mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-07 15:22:29 +00:00
Compare commits
267 Commits
2831
...
base_sessi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2191e3a28 | ||
|
|
72e3e8593d | ||
|
|
67a8b4915d | ||
|
|
468d4a7150 | ||
|
|
2a01e29fec | ||
|
|
90a64d30dc | ||
|
|
31905d4f63 | ||
|
|
7e1d97665a | ||
|
|
b978851a0f | ||
|
|
ef49817eaf | ||
|
|
cac8888b37 | ||
|
|
81853d971a | ||
|
|
b9c5ed3b03 | ||
|
|
0892e0b644 | ||
|
|
b41bf22be7 | ||
|
|
a1cc9bce91 | ||
|
|
8d3cecad86 | ||
|
|
bd8559fad6 | ||
|
|
fb75180632 | ||
|
|
046b06e436 | ||
|
|
af7c69a1aa | ||
|
|
7ad0639f7a | ||
|
|
54a1853e60 | ||
|
|
27021ea271 | ||
|
|
f5a667ad9e | ||
|
|
2b9ce40533 | ||
|
|
d3dd833f21 | ||
|
|
1cc372868b | ||
|
|
a6956c7c34 | ||
|
|
aaaa3e05d1 | ||
|
|
467a5aaae3 | ||
|
|
243dd16285 | ||
|
|
92001f4d37 | ||
|
|
6a31c59081 | ||
|
|
11c5b220a1 | ||
|
|
590ad90cd1 | ||
|
|
ca62e902bc | ||
|
|
34d1eb6768 | ||
|
|
b6b21e5410 | ||
|
|
b8daf0a9f9 | ||
|
|
39f1958300 | ||
|
|
0b1224495b | ||
|
|
ee7d180cbb | ||
|
|
4d3383c620 | ||
|
|
fd78203ff8 | ||
|
|
a36b341865 | ||
|
|
3d6e18394e | ||
|
|
9a6e5c67f5 | ||
|
|
50ea847905 | ||
|
|
b54a9e2bf7 | ||
|
|
918fb1dfc6 | ||
|
|
9f015df61d | ||
|
|
2cd1b7f80b | ||
|
|
afd2aea79c | ||
|
|
c62f761d67 | ||
|
|
acda279111 | ||
|
|
ccf9e2a362 | ||
|
|
3154c6f936 | ||
|
|
8ff3ae0ab2 | ||
|
|
ea22d12581 | ||
|
|
39e236a42c | ||
|
|
01c2786c95 | ||
|
|
9972e88b67 | ||
|
|
cd1c384cc8 | ||
|
|
f97f294d1a | ||
|
|
d3dd54ac3b | ||
|
|
13ee67d15c | ||
|
|
b25caedce7 | ||
|
|
5d4a2e87f8 | ||
|
|
44baca3185 | ||
|
|
b9f28a1beb | ||
|
|
6b7a883331 | ||
|
|
944cf4272d | ||
|
|
cc27e96b22 | ||
|
|
4e4755f91e | ||
|
|
0dcf8ef2f6 | ||
|
|
1ee71be961 | ||
|
|
bfdfb5321c | ||
|
|
dc246960df | ||
|
|
3bfab7718f | ||
|
|
980648df4d | ||
|
|
f2f991e969 | ||
|
|
96a837801e | ||
|
|
9c4da125c8 | ||
|
|
c203215c54 | ||
|
|
155e02bbfb | ||
|
|
d189888902 | ||
|
|
5aee082bf9 | ||
|
|
453c4b99dc | ||
|
|
961e1d611f | ||
|
|
bd449be20d | ||
|
|
9331b0870b | ||
|
|
00d1edcdef | ||
|
|
769e782c6f | ||
|
|
485ff81443 | ||
|
|
d12afc5c07 | ||
|
|
20444adbc9 | ||
|
|
63c017f8e6 | ||
|
|
afe7c4470d | ||
|
|
3f3a957f40 | ||
|
|
26c0164f1e | ||
|
|
b48c897d9b | ||
|
|
e59761baa2 | ||
|
|
baa2c217de | ||
|
|
5dc2aca081 | ||
|
|
84b589719f | ||
|
|
1c6d22b75e | ||
|
|
2547ee3a04 | ||
|
|
7328dd9125 | ||
|
|
0aa2d81844 | ||
|
|
e1f7d4585e | ||
|
|
bdae4b9493 | ||
|
|
8f688c3535 | ||
|
|
60ae8497c0 | ||
|
|
7c1b7a588e | ||
|
|
fd05f120ff | ||
|
|
d9fa05f42e | ||
|
|
12901be6be | ||
|
|
009eeccf3c | ||
|
|
e01acd9cf0 | ||
|
|
ac63a0fa73 | ||
|
|
1d296f9704 | ||
|
|
98e3bcb691 | ||
|
|
880d3525db | ||
|
|
c958c64ba8 | ||
|
|
cc319ad5e9 | ||
|
|
75b2eb0f99 | ||
|
|
acb3bd7ad7 | ||
|
|
7fcc95c9da | ||
|
|
87fa8de815 | ||
|
|
7164cd49e4 | ||
|
|
4ac367d403 | ||
|
|
ffd0827354 | ||
|
|
00f18e9369 | ||
|
|
401c43ee02 | ||
|
|
6ae5adcb3d | ||
|
|
535e5b4f64 | ||
|
|
a5d0bfe1d4 | ||
|
|
67d7d4fc14 | ||
|
|
342a00a6af | ||
|
|
bf14e66142 | ||
|
|
c3f602b7b6 | ||
|
|
9a06d2bf52 | ||
|
|
2951fefef9 | ||
|
|
361e5f0fad | ||
|
|
682169e44c | ||
|
|
c65ccb0af5 | ||
|
|
f035861617 | ||
|
|
045dad3354 | ||
|
|
b5e9c71865 | ||
|
|
7b0a6a2ee5 | ||
|
|
333a0f5f90 | ||
|
|
47aa14e9f6 | ||
|
|
ec8ad6190d | ||
|
|
42b85f73bd | ||
|
|
2bd0f77671 | ||
|
|
11c9d5f0ef | ||
|
|
942d7d7e56 | ||
|
|
e431acda18 | ||
|
|
7ee63a44c5 | ||
|
|
e3bc333115 | ||
|
|
750d6cf534 | ||
|
|
fecd138a3c | ||
|
|
1f8fa3b6d4 | ||
|
|
0acc9187ed | ||
|
|
675eea7b99 | ||
|
|
146ffb0918 | ||
|
|
57b302da3e | ||
|
|
b2f76f3ed6 | ||
|
|
85fbaac9b2 | ||
|
|
022954b40b | ||
|
|
7fa941cb8c | ||
|
|
ad259988b9 | ||
|
|
8c7a3f16b1 | ||
|
|
84c1c3dfd3 | ||
|
|
fccd0d6b29 | ||
|
|
12dda7acb9 | ||
|
|
28861c0054 | ||
|
|
0d3fac7c0f | ||
|
|
dcfebf746f | ||
|
|
4ab8d98cd1 | ||
|
|
fc643f06e4 | ||
|
|
c89791069b | ||
|
|
e06ad6cea9 | ||
|
|
ac834326e7 | ||
|
|
0cf585860b | ||
|
|
259066e2c6 | ||
|
|
fa0dacf7c8 | ||
|
|
ad4e73cc0a | ||
|
|
89eacd6982 | ||
|
|
4a9fdb8a10 | ||
|
|
602c8610bf | ||
|
|
07613aa856 | ||
|
|
db6f2c8868 | ||
|
|
c7fc26d864 | ||
|
|
a5f2cb8bd9 | ||
|
|
78866b0dd7 | ||
|
|
31011b24c2 | ||
|
|
8df54d2cb3 | ||
|
|
0f9c7d8697 | ||
|
|
9d62ecb742 | ||
|
|
bda1d7fdab | ||
|
|
d35a1d60a0 | ||
|
|
90d2340609 | ||
|
|
d70ca48728 | ||
|
|
a82ed4653e | ||
|
|
81be082fe7 | ||
|
|
546651e51f | ||
|
|
79b31543c5 | ||
|
|
7c8fa57bba | ||
|
|
880fb59b2c | ||
|
|
89160e55f0 | ||
|
|
ccf0c8a363 | ||
|
|
84f303dd3c | ||
|
|
3e79926ad4 | ||
|
|
ff0d42a95b | ||
|
|
2c0ef822ae | ||
|
|
83720a4ed5 | ||
|
|
01899b1dfd | ||
|
|
c62809d910 | ||
|
|
de725404ef | ||
|
|
f30bdb3dd9 | ||
|
|
2b20c55bfe | ||
|
|
c6d553738f | ||
|
|
c700261852 | ||
|
|
cbd510bf7d | ||
|
|
0817482b9c | ||
|
|
9ac5aafe10 | ||
|
|
efc9cc9f50 | ||
|
|
2b7976380e | ||
|
|
e93c9dde5d | ||
|
|
9aa12f7d14 | ||
|
|
e367490839 | ||
|
|
91323ebfec | ||
|
|
60c68b624e | ||
|
|
92414a346a | ||
|
|
6f962be322 | ||
|
|
1e3dc20b44 | ||
|
|
ec0439cbb1 | ||
|
|
05801711a7 | ||
|
|
57f7abc6dd | ||
|
|
c4b31435fb | ||
|
|
6a1e9e256d | ||
|
|
a4cfe97b38 | ||
|
|
b4983cfe04 | ||
|
|
4a680e11ac | ||
|
|
ce69ee60ca | ||
|
|
fa9a4480e6 | ||
|
|
f604b1c82d | ||
|
|
701552ec8f | ||
|
|
d26d1ff925 | ||
|
|
bcb955ea72 | ||
|
|
dbd1091364 | ||
|
|
acc41e6d0b | ||
|
|
4d21c28421 | ||
|
|
d15753f719 | ||
|
|
bced38cefc | ||
|
|
5499599720 | ||
|
|
118250750a | ||
|
|
9eb9306e87 | ||
|
|
5579464951 | ||
|
|
693b1c392f | ||
|
|
bbf505c076 | ||
|
|
ead62a5dde | ||
|
|
455660c891 | ||
|
|
7de8b96a07 | ||
|
|
ab83a97fd5 |
26
.flowconfig
26
.flowconfig
@@ -16,18 +16,14 @@
|
||||
; Ignore polyfills
|
||||
.*/Libraries/polyfills/.*
|
||||
|
||||
; Ignore metro
|
||||
.*/node_modules/metro/.*
|
||||
|
||||
; Ignore packages in node_modules which we (i.e. the jitsi-meet project) have
|
||||
; seen to cause errors and we have chosen not to fix.
|
||||
.*/node_modules/@atlaskit/.*/*.js.flow
|
||||
.*/node_modules/@atlassian
|
||||
.*/node_modules/bower/lib/node_modules/bower-json/test/.*
|
||||
.*/node_modules/immutable/dist/immutable.js.flow
|
||||
.*/node_modules/jsonlint/test/.*
|
||||
|
||||
; FIXME Remove once we update past commit
|
||||
; https://github.com/facebook/react-native/commit/df8d0d1db9203cc87ad3682e6138b2a9ed714365
|
||||
.*/node_modules/react-native/local-cli/link/link.js
|
||||
.*/node_modules/react-native-keep-awake/.*
|
||||
.*/node_modules/react-native-permissions/.*
|
||||
.*/node_modules/styled-components/.*
|
||||
|
||||
.*/\.git/.*
|
||||
@@ -37,6 +33,7 @@
|
||||
[libs]
|
||||
node_modules/react-native/Libraries/react-native/react-native-interface.js
|
||||
node_modules/react-native/flow/
|
||||
node_modules/react-native/flow-github/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
@@ -45,25 +42,18 @@ module.system=haste
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
; FIXME Remove once we update past commit
|
||||
; https://github.com/facebook/react-native/commit/df8d0d1db9203cc87ad3682e6138b2a9ed714365
|
||||
module.name_mapper='^./link/link$' -> 'emptyObject'
|
||||
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
; We (i.e. the jitsi-meet project) are using the haste module system on Web as
|
||||
; well, not only on React Native. Unfortunately, Flow does not support .web.js
|
||||
; by default. Override Flow's defaults to include .web.js as well. Technically,
|
||||
@@ -77,4 +67,4 @@ module.file_ext=.jsx
|
||||
module.file_ext=.json
|
||||
|
||||
[version]
|
||||
^0.57.0
|
||||
^0.67.0
|
||||
|
||||
27
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Before posting, please make sure you check https://community.jitsi.org
|
||||
|
||||
---
|
||||
|
||||
*This Issue tracker is only for reporting bugs and tracking code related issues.*
|
||||
|
||||
Before posting, please make sure you check community.jitsi.org to see if the same or similar bugs have already been discussed. General questions, installation help, and feature requests can also be posted to community.jitsi.org.
|
||||
|
||||
## Description
|
||||
---
|
||||
|
||||
## Current behavior
|
||||
---
|
||||
|
||||
## Expected Behavior
|
||||
---
|
||||
|
||||
## Possible Solution
|
||||
---
|
||||
|
||||
## Steps to reproduce
|
||||
---
|
||||
|
||||
# Environment details
|
||||
---
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -68,3 +68,11 @@ buck-out/
|
||||
*/fastlane/report.xml
|
||||
*/fastlane/Preview.html
|
||||
*/fastlane/screenshots
|
||||
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
|
||||
# precommit-hook
|
||||
.jshintignore
|
||||
.jshintrc
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
osx_image: xcode9.3
|
||||
osx_image: xcode9.4
|
||||
language: objective-c
|
||||
script:
|
||||
- "./ios/travis-ci/build-ipa.sh"
|
||||
|
||||
@@ -10,7 +10,7 @@ Jitsi Meet allows for very efficient collaboration. It allows users to stream th
|
||||
|
||||
On the client side, no installation is necessary. You just point your browser to the URL of your deployment. This section is about installing the Jitsi Meet suite on your server and hosting your own conferencing service.
|
||||
|
||||
Installing Jitsi Meet is quite a simple experience. For Debian-based systems, we recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system.
|
||||
Installing Jitsi Meet is quite a simple experience. For Debian-based systems, we recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system. You can also see a demonstration of the process in [this tutorial video](https://jitsi.org/tutorial).
|
||||
|
||||
For other systems, or if you wish to install all components manually, see the [detailed manual installation instructions](https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md).
|
||||
|
||||
@@ -33,7 +33,7 @@ You can get our mobile versions from here:
|
||||
|
||||
## Building the sources
|
||||
|
||||
Node.js >= 8 is required.
|
||||
Node.js >= 8 and npm >= 6 are required.
|
||||
|
||||
On Debian/Ubuntu systems, the required packages can be installed with:
|
||||
```
|
||||
|
||||
@@ -1,24 +1,58 @@
|
||||
# Jitsi Meet SDK for Android
|
||||
|
||||
## Build
|
||||
## Build your own, or use a pre-build SDK artifacts/binaries
|
||||
Jitsi conveniently provides a pre-build SDK artifacts/binaries in its Maven repository. When you do not require any modification to the SDK itself, it's suggested to use the pre-build SDK. This avoids the complexity of building and installing your own SDK artifacts/binaries.
|
||||
|
||||
### Use pre-build SDK artifacts/binaries
|
||||
In your project, add the Maven repository
|
||||
`https://github.com/jitsi/jitsi-maven-repository/raw/master/releases` and the
|
||||
dependency `org.jitsi.react:jitsi-meet-sdk` into your `build.gradle` files.
|
||||
|
||||
The repository typically goes into the `build.gradle` file in the root of your project:
|
||||
|
||||
```gradle
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven {
|
||||
url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dependency definitions belong in the individual module `build.gradle` files:
|
||||
|
||||
```gradle
|
||||
dependencies {
|
||||
// (other dependencies)
|
||||
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
|
||||
}
|
||||
```
|
||||
|
||||
### Build and use your own SDK artifacts/binaries
|
||||
|
||||
1. Install all required [dependencies](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md).
|
||||
|
||||
2. ```bash
|
||||
2. Create the SDK-release assembly, by invoking the following in the jitsi-meet
|
||||
project source:
|
||||
|
||||
```bash
|
||||
cd android/
|
||||
./gradlew :sdk:assembleRelease
|
||||
```
|
||||
When this successfully executes, artifacts/binaries are ready to be published
|
||||
into a Maven repository of your choice.
|
||||
|
||||
3. Configure the Maven repositories in which you are going to publish the
|
||||
artifacts/binaries during step 4. Modify
|
||||
artifacts/binaries during step 4.
|
||||
|
||||
In the file `android/sdk/build.gradle` modify the line that contains
|
||||
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
|
||||
in adroid/sdk/build.gradle for Jitsi Meet SDK for Android and/or
|
||||
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
|
||||
in android/build.gradle for the third-party react-native modules which Jitsi
|
||||
Meet SDK for Android depends on and are not publicly available in Maven
|
||||
repositories. Generally, if you are modifying the JavaScript code of Jitsi
|
||||
Meet SDK for Android only, you will very likely need to consider the former
|
||||
only.
|
||||
|
||||
Change this value (which represents the Maven repository location used internally
|
||||
by the Jitsi Developers) to the location of the repository that you'd like to use.
|
||||
|
||||
4. Publish the Maven artifact/binary of Jitsi Meet SDK for Android in the Maven
|
||||
repository configured in step 3:
|
||||
@@ -27,27 +61,66 @@
|
||||
./gradlew :sdk:publish
|
||||
cd ../
|
||||
```
|
||||
5. In _your_ project, add the Maven repository that you configured in step 3, as well
|
||||
as the dependency `org.jitsi.react:jitsi-meet-sdk` into your `build.gradle`
|
||||
file. Note that it's needed to pull in the transitive dependencies:
|
||||
|
||||
If you would like to publish a third-party react-native module which Jitsi
|
||||
Meet SDK for Android depends on and is not publicly available in Maven
|
||||
repositories, replace `sdk` with the name of the react-native module. For
|
||||
example, to publish react-native-webrtc:
|
||||
```gradle
|
||||
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
|
||||
```
|
||||
|
||||
Generally, if you are modifying the JavaScript code of Jitsi Meet SDK for Android only,
|
||||
the above will suffice. If you would like to publish a third-party react-native module
|
||||
which Jitsi Meet SDK for Android depends on (and is not publicly available in Maven
|
||||
repositories) continue below.
|
||||
|
||||
6. Create the release assembly for _each_ third-party react-native module that you
|
||||
need, replacing it's name in the example below.
|
||||
|
||||
```bash
|
||||
./gradlew :react-native-webrtc:assembleRelease
|
||||
```
|
||||
|
||||
7. Configure the Maven repositories in which you are going to publish the
|
||||
artifacts/binaries during step 8.
|
||||
|
||||
In the file `android/build.gradle` (note that this is a different file than the file
|
||||
that was modified in step 3) modify the line that contains
|
||||
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
|
||||
|
||||
Change this value (which represents the Maven repository location used internally
|
||||
by the Jitsi Developers) to the location of the repository that you'd like to use.
|
||||
You can use the same repository as the one you configured in step 3 if you want.
|
||||
|
||||
8. Publish the Maven artifact/binary of _each_ third-party react-native module that
|
||||
you need, replacing it's name in the example below. For example, to publish
|
||||
react-native-webrtc:
|
||||
|
||||
```bash
|
||||
./gradlew :react-native-webrtc:publish
|
||||
```
|
||||
|
||||
## Install
|
||||
Note that there should not be a need to explicitly add these dependencies in
|
||||
_your_ project, as they will be pulled in as transitive dependencies of
|
||||
`jitsi-meet-sdk`.
|
||||
|
||||
Add the Maven repository
|
||||
`https://github.com/jitsi/jitsi-maven-repository/raw/master/releases` and the
|
||||
dependency `org.jitsi.react:jitsi-meet-sdk:1.9.0` into your `build.gradle`.
|
||||
|
||||
## API
|
||||
## Using the API
|
||||
=======
|
||||
|
||||
Jitsi Meet SDK is an Android library which embodies the whole Jitsi Meet
|
||||
experience and makes it reusable by third-party apps.
|
||||
|
||||
First, add Java 1.8 compatibility support to your project by adding the
|
||||
following lines into your `build.gradle` file:
|
||||
|
||||
```
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
```
|
||||
|
||||
To get started, extends your `android.app.Activity` from
|
||||
`org.jitsi.meet.sdk.JitsiMeetActivity`:
|
||||
|
||||
@@ -132,13 +205,13 @@ which displays a single `JitsiMeetView`.
|
||||
|
||||
See JitsiMeetView.getDefaultURL.
|
||||
|
||||
#### getPictureInPictureEnabled()
|
||||
#### isPictureInPictureEnabled()
|
||||
|
||||
See JitsiMeetView.getPictureInPictureEnabled.
|
||||
See JitsiMeetView.isPictureInPictureEnabled.
|
||||
|
||||
#### getWelcomePageEnabled()
|
||||
#### isWelcomePageEnabled()
|
||||
|
||||
See JitsiMeetView.getWelcomePageEnabled.
|
||||
See JitsiMeetView.isWelcomePageEnabled.
|
||||
|
||||
#### loadURL(URL)
|
||||
|
||||
@@ -177,13 +250,13 @@ if set to `null`, the default built in JavaScript is used: https://meet.jit.si.
|
||||
|
||||
Returns the `JitsiMeetViewListener` instance attached to the view.
|
||||
|
||||
#### getPictureInPictureEnabled()
|
||||
#### isPictureInPictureEnabled()
|
||||
|
||||
Returns `true` if Picture-in-Picture is enabled; `false`, otherwise. If not
|
||||
explicitly set (by a preceding `setPictureInPictureEnabled` call), defaults to
|
||||
`true` if the platform supports Picture-in-Picture natively; `false`, otherwise.
|
||||
|
||||
#### getWelcomePageEnabled()
|
||||
#### isWelcomePageEnabled()
|
||||
|
||||
Returns true if the Welcome page is enabled; otherwise, false. If false, a black
|
||||
empty view will be rendered when not in a conference. Defaults to false.
|
||||
@@ -243,7 +316,7 @@ effect.
|
||||
|
||||
#### setWelcomePageEnabled(boolean)
|
||||
|
||||
Sets whether the Welcome page is enabled. See `getWelcomePageEnabled` for more
|
||||
Sets whether the Welcome page is enabled. See `isWelcomePageEnabled` for more
|
||||
information.
|
||||
|
||||
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
|
||||
|
||||
@@ -25,12 +25,18 @@ android {
|
||||
exclude '/lib/x86_64/**'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
6
android/app/proguard-rules.pro
vendored
6
android/app/proguard-rules.pro
vendored
@@ -68,3 +68,9 @@
|
||||
-dontwarn java.nio.file.*
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-dontwarn okio.**
|
||||
|
||||
# FastImage
|
||||
|
||||
-keep public class com.dylanvann.fastimage.* {*;}
|
||||
-keep public class com.dylanvann.fastimage.** {*;}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url "$rootDir/../node_modules/jsc-android/dist" }
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from
|
||||
// npm.
|
||||
maven { url "$rootDir/../node_modules/react-native/android" }
|
||||
@@ -34,6 +35,12 @@ allprojects {
|
||||
def version = new groovy.json.JsonSlurper().parseText(file.text).version
|
||||
details.useVersion version
|
||||
}
|
||||
if (details.requested.group == 'org.webkit'
|
||||
&& details.requested.name == 'android-jsc') {
|
||||
def file = new File("$rootDir/../node_modules/jsc-android/package.json")
|
||||
def version = new groovy.json.JsonSlurper().parseText(file.text).version
|
||||
details.useVersion "r${version.tokenize('.')[0]}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,7 +153,7 @@ allprojects {
|
||||
ext {
|
||||
buildToolsVersion = "26.0.2"
|
||||
compileSdkVersion = 26
|
||||
minSdkVersion = 16
|
||||
minSdkVersion = 21
|
||||
targetSdkVersion = 26
|
||||
|
||||
// The Maven artifact groupdId of the third-party react-native modules which
|
||||
|
||||
@@ -25,9 +25,10 @@ dependencies {
|
||||
compile 'com.facebook.react:react-native:+'
|
||||
|
||||
compile project(':react-native-background-timer')
|
||||
compile project(':react-native-fetch-blob')
|
||||
compile project(':react-native-fast-image')
|
||||
compile project(':react-native-immersive')
|
||||
compile project(':react-native-keep-awake')
|
||||
compile project(':react-native-linear-gradient')
|
||||
compile project(':react-native-locale-detector')
|
||||
compile project(':react-native-sound')
|
||||
compile project(':react-native-vector-icons')
|
||||
@@ -78,6 +79,8 @@ gradle.projectsEvaluated {
|
||||
from("${projectDir}/../../sounds/left.wav")
|
||||
from("${projectDir}/../../sounds/outgoingRinging.wav")
|
||||
from("${projectDir}/../../sounds/outgoingStart.wav")
|
||||
from("${projectDir}/../../sounds/recordingOn.mp3")
|
||||
from("${projectDir}/../../sounds/recordingOff.mp3")
|
||||
from("${projectDir}/../../sounds/rejected.wav")
|
||||
into("${bundlePath}/assets/sounds")
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
class AndroidSettingsModule extends ReactContextBaseJavaModule {
|
||||
class AndroidSettingsModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public AndroidSettingsModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -11,7 +27,9 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
class AppInfoModule extends ReactContextBaseJavaModule {
|
||||
class AppInfoModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public AppInfoModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ import android.content.pm.PackageManager;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
@@ -41,6 +39,8 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Module implementing a simple API to select the appropriate audio device for a
|
||||
@@ -57,7 +57,10 @@ import java.util.Set;
|
||||
* Before a call has started and after it has ended the
|
||||
* {@code AudioModeModule.DEFAULT} mode should be used.
|
||||
*/
|
||||
class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
class AudioModeModule
|
||||
extends ReactContextBaseJavaModule
|
||||
implements AudioManager.OnAudioFocusChangeListener {
|
||||
|
||||
/**
|
||||
* Constants representing the audio mode.
|
||||
* - DEFAULT: Used before and after every call. It represents the default
|
||||
@@ -97,6 +100,11 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
static final String TAG = MODULE_NAME;
|
||||
|
||||
/**
|
||||
* Indicator that we have lost audio focus.
|
||||
*/
|
||||
private boolean audioFocusLost = false;
|
||||
|
||||
/**
|
||||
* {@link AudioManager} instance used to interact with the Android audio
|
||||
* subsystem.
|
||||
@@ -110,10 +118,11 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
|
||||
|
||||
/**
|
||||
* {@link Handler} for running all operations on the main thread.
|
||||
* {@link ExecutorService} for running all audio operations on a dedicated
|
||||
* thread.
|
||||
*/
|
||||
private final Handler mainThreadHandler
|
||||
= new Handler(Looper.getMainLooper());
|
||||
private static final ExecutorService executor
|
||||
= Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
* {@link Runnable} for running audio device detection the main thread.
|
||||
@@ -220,7 +229,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
|
||||
// Do an initial detection on Android >= M.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
mainThreadHandler.post(onAudioDeviceChangeRunner);
|
||||
runInAudioThread(onAudioDeviceChangeRunner);
|
||||
} else {
|
||||
// On Android < M, detect if we have an earpiece.
|
||||
PackageManager pm = reactContext.getPackageManager();
|
||||
@@ -260,7 +269,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getAudioDevices(final Promise promise) {
|
||||
mainThreadHandler.post(new Runnable() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
WritableMap map = Arguments.createMap();
|
||||
@@ -297,7 +306,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
* Only used on Android >= M.
|
||||
*/
|
||||
void onAudioDeviceChange() {
|
||||
mainThreadHandler.post(onAudioDeviceChangeRunner);
|
||||
runInAudioThread(onAudioDeviceChangeRunner);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,7 +334,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
* Only used on Android < M.
|
||||
*/
|
||||
void onHeadsetDeviceChange() {
|
||||
mainThreadHandler.post(new Runnable() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// XXX: isWiredHeadsetOn is not deprecated when used just for
|
||||
@@ -345,6 +354,44 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
|
||||
* when the audio focus of the system is updated.
|
||||
*
|
||||
* @param focusChange - The type of focus change.
|
||||
*/
|
||||
@Override
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
switch (focusChange) {
|
||||
case AudioManager.AUDIOFOCUS_GAIN: {
|
||||
Log.d(TAG, "Audio focus gained");
|
||||
// Some other application potentially stole our audio focus
|
||||
// temporarily. Restore our mode.
|
||||
if (audioFocusLost) {
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
audioFocusLost = false;
|
||||
break;
|
||||
}
|
||||
case AudioManager.AUDIOFOCUS_LOSS:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
|
||||
Log.d(TAG, "Audio focus lost");
|
||||
audioFocusLost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to run operations on a dedicated thread.
|
||||
* @param runnable
|
||||
*/
|
||||
public void runInAudioThread(Runnable runnable) {
|
||||
executor.execute(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user selected audio device as the active audio device.
|
||||
*
|
||||
@@ -352,7 +399,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setAudioDevice(final String device) {
|
||||
mainThreadHandler.post(new Runnable() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!availableDevices.contains(device)) {
|
||||
@@ -423,7 +470,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
};
|
||||
mainThreadHandler.post(r);
|
||||
runInAudioThread(r);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -495,8 +542,9 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
Log.d(TAG, "Update audio route for mode: " + mode);
|
||||
|
||||
if (mode == DEFAULT) {
|
||||
audioFocusLost = false;
|
||||
audioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
audioManager.abandonAudioFocus(null);
|
||||
audioManager.abandonAudioFocus(this);
|
||||
audioManager.setSpeakerphoneOn(false);
|
||||
setBluetoothAudioRoute(false);
|
||||
selectedDevice = null;
|
||||
@@ -509,7 +557,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
audioManager.setMicrophoneMute(false);
|
||||
|
||||
if (audioManager.requestAudioFocus(
|
||||
null,
|
||||
this,
|
||||
AudioManager.STREAM_VOICE_CALL,
|
||||
AudioManager.AUDIOFOCUS_GAIN)
|
||||
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
|
||||
231
android/sdk/src/main/java/org/jitsi/meet/sdk/BaseReactView.java
Normal file
231
android/sdk/src/main/java/org/jitsi/meet/sdk/BaseReactView.java
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Base class for all views which are backed by a React Native view.
|
||||
*/
|
||||
public abstract class BaseReactView<ListenerT>
|
||||
extends FrameLayout {
|
||||
|
||||
/**
|
||||
* Background color used by {@code BaseReactView} and the React Native root
|
||||
* view.
|
||||
*/
|
||||
protected static int BACKGROUND_COLOR = 0xFF111111;
|
||||
|
||||
/**
|
||||
* The collection of all existing {@code BaseReactView}s. Used to find the
|
||||
* {@code BaseReactView} when delivering events coming from
|
||||
* {@link ExternalAPIModule}.
|
||||
*/
|
||||
static final Set<BaseReactView> views
|
||||
= Collections.newSetFromMap(new WeakHashMap<BaseReactView, Boolean>());
|
||||
|
||||
/**
|
||||
* Finds a {@code BaseReactView} which matches a specific external API
|
||||
* scope.
|
||||
*
|
||||
* @param externalAPIScope - The external API scope associated with the
|
||||
* {@code BaseReactView} to find.
|
||||
* @return The {@code BaseReactView}, if any, associated with the specified
|
||||
* {@code externalAPIScope}; otherwise, {@code null}.
|
||||
*/
|
||||
public static BaseReactView findViewByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
synchronized (views) {
|
||||
for (BaseReactView view : views) {
|
||||
if (view.externalAPIScope.equals(externalAPIScope)) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique identifier of this {@code BaseReactView} within the process
|
||||
* for the purposes of {@link ExternalAPIModule}. The name scope was
|
||||
* inspired by postis which we use on Web for the similar purposes of the
|
||||
* iframe-based external API.
|
||||
*/
|
||||
protected final String externalAPIScope;
|
||||
|
||||
/**
|
||||
* The listener (e.g. {@link JitsiMeetViewListener}) instance for reporting
|
||||
* events occurring in Jitsi Meet.
|
||||
*/
|
||||
private ListenerT listener;
|
||||
|
||||
/**
|
||||
* React Native root view.
|
||||
*/
|
||||
private ReactRootView reactRootView;
|
||||
|
||||
public BaseReactView(@NonNull Context context) {
|
||||
super(context);
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
ReactInstanceManagerHolder.initReactInstanceManager(
|
||||
((Activity) context).getApplication());
|
||||
|
||||
// Hook this BaseReactView into ExternalAPI.
|
||||
externalAPIScope = UUID.randomUUID().toString();
|
||||
synchronized (views) {
|
||||
views.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@code ReactRootView} for the given app name with the given
|
||||
* props. Once created it's set as the view of this {@code FrameLayout}.
|
||||
*
|
||||
* @param appName - The name of the "app" (in React Native terms) to load.
|
||||
* @param props - The React Component props to pass to the app.
|
||||
*/
|
||||
public void createReactRootView(String appName, @Nullable Bundle props) {
|
||||
if (props == null) {
|
||||
props = new Bundle();
|
||||
}
|
||||
|
||||
props.putString("externalAPIScope", externalAPIScope);
|
||||
|
||||
if (reactRootView == null) {
|
||||
reactRootView = new ReactRootView(getContext());
|
||||
reactRootView.startReactApplication(
|
||||
ReactInstanceManagerHolder.getReactInstanceManager(),
|
||||
appName,
|
||||
props);
|
||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
||||
addView(reactRootView);
|
||||
} else {
|
||||
reactRootView.setAppProperties(props);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the React resources (specifically the {@link ReactRootView})
|
||||
* associated with this view.
|
||||
*
|
||||
* MUST be called when the {@link Activity} holding this view is destroyed,
|
||||
* typically in the {@code onDestroy} method.
|
||||
*/
|
||||
public void dispose() {
|
||||
if (reactRootView != null) {
|
||||
removeView(reactRootView);
|
||||
reactRootView.unmountReactApplication();
|
||||
reactRootView = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the listener set on this {@code BaseReactView}.
|
||||
*
|
||||
* @return The listener set on this {@code BaseReactView}.
|
||||
*/
|
||||
public ListenerT getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method called by {@link ExternalAPIModule} when an event is
|
||||
* received for this view.
|
||||
*
|
||||
* @param name - The name of the event.
|
||||
* @param data - The details of the event associated with/specific to the
|
||||
* specified {@code name}.
|
||||
*/
|
||||
public abstract void onExternalAPIEvent(String name, ReadableMap data);
|
||||
|
||||
protected void onExternalAPIEvent(
|
||||
Map<String, Method> listenerMethods,
|
||||
String name, ReadableMap data) {
|
||||
ListenerT listener = getListener();
|
||||
|
||||
if (listener != null) {
|
||||
ListenerUtils.runListenerMethod(
|
||||
listener, listenerMethods, name, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the window containing this view gains or loses focus.
|
||||
*
|
||||
* @param hasFocus If the window of this view now has focus, {@code true};
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
|
||||
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
|
||||
|
||||
// FIXME The singleton pattern employed by RNImmersiveModule is not
|
||||
// advisable because a react-native mobule is consumable only after its
|
||||
// BaseJavaModule#initialize() has completed and here we have no
|
||||
// knowledge of whether the precondition is really met.
|
||||
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
|
||||
|
||||
if (hasFocus && immersive != null) {
|
||||
try {
|
||||
immersive.emitImmersiveStateChangeEvent();
|
||||
} catch (RuntimeException re) {
|
||||
// FIXME I don't know how to check myself whether
|
||||
// BaseJavaModule#initialize() has been invoked and thus
|
||||
// RNImmersiveModule is consumable. A safe workaround is to
|
||||
// swallow the failure because the whole full-screen/immersive
|
||||
// functionality is brittle anyway, akin to the icing on the
|
||||
// cake, and has been working without onWindowFocusChanged for a
|
||||
// very long time.
|
||||
Log.e(
|
||||
"RNImmersiveModule",
|
||||
"emitImmersiveStateChangeEvent() failed!",
|
||||
re);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific listener on this {@code BaseReactView}.
|
||||
*
|
||||
* @param listener The listener to set on this {@code BaseReactView}.
|
||||
*/
|
||||
public void setListener(ListenerT listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
@@ -55,12 +53,6 @@ class BluetoothHeadsetMonitor {
|
||||
*/
|
||||
private boolean headsetAvailable = false;
|
||||
|
||||
/**
|
||||
* {@link Handler} for running all operations on the main thread.
|
||||
*/
|
||||
private final Handler mainThreadHandler
|
||||
= new Handler(Looper.getMainLooper());
|
||||
|
||||
/**
|
||||
* Helper for running Bluetooth operations on the main thread.
|
||||
*/
|
||||
@@ -200,6 +192,6 @@ class BluetoothHeadsetMonitor {
|
||||
* {@link AudioModeModule#onAudioDeviceChange()} callback.
|
||||
*/
|
||||
private void updateDevices() {
|
||||
mainThreadHandler.post(updateDevicesRunnable);
|
||||
audioModeModule.runInAudioThread(updateDevicesRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,81 +16,24 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Module implementing a simple API to enable a proximity sensor-controlled
|
||||
* wake lock. When the lock is held, if the proximity sensor detects a nearby
|
||||
* object it will dim the screen and disable touch controls. The functionality
|
||||
* is used with the conference audio-only mode.
|
||||
* Module implementing an API for sending events from JavaScript to native code.
|
||||
*/
|
||||
class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
/**
|
||||
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
|
||||
* redux action types.
|
||||
*/
|
||||
private static final Map<String, Method> JITSI_MEET_VIEW_LISTENER_METHODS
|
||||
= new HashMap<>();
|
||||
class ExternalAPIModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
static {
|
||||
// Figure out the mapping between the JitsiMeetViewListener methods
|
||||
// and the events i.e. redux action types.
|
||||
Pattern onPattern = Pattern.compile("^on[A-Z]+");
|
||||
Pattern camelcasePattern = Pattern.compile("([a-z0-9]+)([A-Z0-9]+)");
|
||||
|
||||
for (Method method : JitsiMeetViewListener.class.getDeclaredMethods()) {
|
||||
// * The method must be public (because it is declared by an
|
||||
// interface).
|
||||
// * The method must be/return void.
|
||||
if (!Modifier.isPublic(method.getModifiers())
|
||||
|| !Void.TYPE.equals(method.getReturnType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// * The method name must start with "on" followed by a
|
||||
// capital/uppercase letter (in agreement with the camelcase
|
||||
// coding style customary to Java in general and the projects of
|
||||
// the Jitsi community in particular).
|
||||
String name = method.getName();
|
||||
|
||||
if (!onPattern.matcher(name).find()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// * The method must accept/have exactly 1 parameter of a type
|
||||
// assignable from HashMap.
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
if (parameterTypes.length != 1
|
||||
|| !parameterTypes[0].isAssignableFrom(HashMap.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert the method name to an event name.
|
||||
name
|
||||
= camelcasePattern.matcher(name.substring(2))
|
||||
.replaceAll("$1_$2")
|
||||
.toUpperCase(Locale.ROOT);
|
||||
JITSI_MEET_VIEW_LISTENER_METHODS.put(name, method);
|
||||
}
|
||||
}
|
||||
private static final String TAG = ExternalAPIModule.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
* this module throughout the lifetime of the application.
|
||||
* this module throughout the lifetime of the app.
|
||||
*
|
||||
* @param reactContext the {@link ReactApplicationContext} where this module
|
||||
* is created.
|
||||
@@ -109,39 +52,9 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
return "ExternalAPI";
|
||||
}
|
||||
|
||||
/**
|
||||
* The internal processing for the URL of the current conference set on the
|
||||
* associated {@link JitsiMeetView}.
|
||||
*
|
||||
* @param eventName the name of the external API event to be processed
|
||||
* @param eventData the details/specifics of the event to process determined
|
||||
* by/associated with the specified {@code eventName}.
|
||||
* @param view the {@link JitsiMeetView} instance.
|
||||
*/
|
||||
private void maybeSetViewURL(
|
||||
String eventName,
|
||||
ReadableMap eventData,
|
||||
JitsiMeetView view) {
|
||||
switch(eventName) {
|
||||
case "CONFERENCE_WILL_JOIN":
|
||||
view.setURL(eventData.getString("url"));
|
||||
break;
|
||||
|
||||
case "CONFERENCE_FAILED":
|
||||
case "CONFERENCE_WILL_LEAVE":
|
||||
case "LOAD_CONFIG_ERROR":
|
||||
String url = eventData.getString("url");
|
||||
|
||||
if (url != null && url.equals(view.getURL())) {
|
||||
view.setURL(null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event that occurred on the JavaScript side of the SDK to
|
||||
* the specified {@link JitsiMeetView}'s listener.
|
||||
* the specified {@link BaseReactView}'s listener.
|
||||
*
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
@@ -149,106 +62,18 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
* @param scope
|
||||
*/
|
||||
@ReactMethod
|
||||
public void sendEvent(final String name,
|
||||
final ReadableMap data,
|
||||
final String scope) {
|
||||
public void sendEvent(String name, ReadableMap data, String scope) {
|
||||
// The JavaScript App needs to provide uniquely identifying information
|
||||
// to the native ExternalAPI module so that the latter may match the
|
||||
// former to the native JitsiMeetView which hosts it.
|
||||
JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope);
|
||||
// former to the native BaseReactView which hosts it.
|
||||
BaseReactView view = BaseReactView.findViewByExternalAPIScope(scope);
|
||||
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX The JitsiMeetView property URL was introduced in order to address
|
||||
// an exception in the Picture-in-Picture functionality which arose
|
||||
// because of delays related to bridging between JavaScript and Java. To
|
||||
// reduce these delays do not wait for the call to be transfered to the
|
||||
// UI thread.
|
||||
maybeSetViewURL(name, data, view);
|
||||
|
||||
// Make sure JitsiMeetView's listener is invoked on the UI thread. It
|
||||
// was requested by SDK consumers.
|
||||
if (UiThreadUtil.isOnUiThread()) {
|
||||
sendEventOnUiThread(name, data, scope);
|
||||
} else {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendEventOnUiThread(name, data, scope);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event that occurred on the JavaScript side of the SDK to
|
||||
* the specified {@link JitsiMeetView}'s listener on the UI thread.
|
||||
*
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
* by/associated with the specified {@code name}.
|
||||
* @param scope
|
||||
*/
|
||||
private void sendEventOnUiThread(final String name,
|
||||
final ReadableMap data,
|
||||
final String scope) {
|
||||
// The JavaScript App needs to provide uniquely identifying information
|
||||
// to the native ExternalAPI module so that the latter may match the
|
||||
// former to the native JitsiMeetView which hosts it.
|
||||
JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope);
|
||||
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
JitsiMeetViewListener listener = view.getListener();
|
||||
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Method method = JITSI_MEET_VIEW_LISTENER_METHODS.get(name);
|
||||
|
||||
if (method != null) {
|
||||
if (view != null) {
|
||||
try {
|
||||
method.invoke(listener, toHashMap(data));
|
||||
} catch (IllegalAccessException e) {
|
||||
// FIXME There was a multicatch for IllegalAccessException and
|
||||
// InvocationTargetException, but Android Studio complained
|
||||
// with: "Multi-catch with these reflection exceptions requires
|
||||
// API level 19 (current min is 16) because they get compiled to
|
||||
// the common but new super type ReflectiveOperationException.
|
||||
// As a workaround either create individual catch statements, or
|
||||
// catch Exception."
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
view.onExternalAPIEvent(name, data);
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "onExternalAPIEvent: error sending event", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@code HashMap} instance with the key-value
|
||||
* associations of a specific {@code ReadableMap}.
|
||||
*
|
||||
* @param readableMap the {@code ReadableMap} specifying the key-value
|
||||
* associations with which the new {@code HashMap} instance is to be
|
||||
* initialized.
|
||||
* @return a new {@code HashMap} instance initialized with the key-value
|
||||
* associations of the specified {@code readableMap}.
|
||||
*/
|
||||
private HashMap<String, Object> toHashMap(ReadableMap readableMap) {
|
||||
HashMap<String, Object> hashMap = new HashMap<>();
|
||||
|
||||
for (ReadableMapKeySetIterator i = readableMap.keySetIterator();
|
||||
i.hasNextKey();) {
|
||||
String key = i.nextKey();
|
||||
|
||||
hashMap.put(key, readableMap.getString(key));
|
||||
}
|
||||
|
||||
return hashMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,9 @@ import java.net.URL;
|
||||
* hooked to the React Native subsystem via proxy calls through the
|
||||
* {@code JitsiMeetView} static methods.
|
||||
*/
|
||||
public class JitsiMeetActivity extends AppCompatActivity {
|
||||
public class JitsiMeetActivity
|
||||
extends AppCompatActivity {
|
||||
|
||||
/**
|
||||
* The request code identifying requests for the permission to draw on top
|
||||
* of other apps. The value must be 16-bit and is arbitrarily chosen here.
|
||||
@@ -95,25 +97,6 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
return view == null ? defaultURL : view.getDefaultURL();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#getPictureInPictureEnabled()
|
||||
*/
|
||||
public boolean getPictureInPictureEnabled() {
|
||||
return
|
||||
view == null
|
||||
? pictureInPictureEnabled
|
||||
: view.getPictureInPictureEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#getWelcomePageEnabled()
|
||||
*/
|
||||
public boolean getWelcomePageEnabled() {
|
||||
return view == null ? welcomePageEnabled : view.getWelcomePageEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the {@link #view} of this {@code JitsiMeetActivity} with a
|
||||
* new {@link JitsiMeetView} instance.
|
||||
@@ -152,6 +135,25 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#isPictureInPictureEnabled()
|
||||
*/
|
||||
public boolean isPictureInPictureEnabled() {
|
||||
return
|
||||
view == null
|
||||
? pictureInPictureEnabled
|
||||
: view.isPictureInPictureEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#isWelcomePageEnabled()
|
||||
*/
|
||||
public boolean isWelcomePageEnabled() {
|
||||
return view == null ? welcomePageEnabled : view.isWelcomePageEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given URL and displays the conference. If the specified URL is
|
||||
* null, the welcome page is displayed instead.
|
||||
@@ -177,7 +179,7 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!JitsiMeetView.onBackPressed()) {
|
||||
if (!ReactActivityLifecycleCallbacks.onBackPressed()) {
|
||||
// JitsiMeetView didn't handle the invocation of the back button.
|
||||
// Generally, an Activity extender would very likely want to invoke
|
||||
// Activity#onBackPressed(). For the sake of consistency with
|
||||
@@ -220,7 +222,7 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
view = null;
|
||||
}
|
||||
|
||||
JitsiMeetView.onHostDestroy(this);
|
||||
ReactActivityLifecycleCallbacks.onHostDestroy(this);
|
||||
}
|
||||
|
||||
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
|
||||
@@ -242,7 +244,20 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
JitsiMeetView.onNewIntent(intent);
|
||||
// XXX At least twice we received bug reports about malfunctioning
|
||||
// loadURL in the Jitsi Meet SDK while the Jitsi Meet app seemed to
|
||||
// functioning as expected in our testing. But that was to be expected
|
||||
// because the app does not exercise loadURL. In order to increase the
|
||||
// test coverage of loadURL, channel deep linking through loadURL.
|
||||
Uri uri;
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())
|
||||
&& (uri = intent.getData()) != null
|
||||
&& JitsiMeetView.loadURLStringInViews(uri.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactActivityLifecycleCallbacks.onNewIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -250,21 +265,21 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
super.onResume();
|
||||
|
||||
defaultBackButtonImpl = new DefaultHardwareBackBtnHandlerImpl(this);
|
||||
JitsiMeetView.onHostResume(this, defaultBackButtonImpl);
|
||||
ReactActivityLifecycleCallbacks.onHostResume(this, defaultBackButtonImpl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
JitsiMeetView.onHostPause(this);
|
||||
ReactActivityLifecycleCallbacks.onHostPause(this);
|
||||
defaultBackButtonImpl = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUserLeaveHint() {
|
||||
if (view != null) {
|
||||
view.onUserLeaveHint();
|
||||
view.enterPictureInPicture();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,57 +16,35 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import org.jitsi.meet.sdk.invite.InviteController;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class JitsiMeetView
|
||||
extends BaseReactView<JitsiMeetViewListener> {
|
||||
|
||||
public class JitsiMeetView extends FrameLayout {
|
||||
/**
|
||||
* Background color used by {@code JitsiMeetView} and the React Native root
|
||||
* view.
|
||||
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
|
||||
* redux action types.
|
||||
*/
|
||||
private static final int BACKGROUND_COLOR = 0xFF111111;
|
||||
private static final Map<String, Method> LISTENER_METHODS
|
||||
= ListenerUtils.mapListenerMethods(JitsiMeetViewListener.class);
|
||||
|
||||
/**
|
||||
* The {@link Log} tag which identifies the source of the log messages of
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
private final static String TAG = JitsiMeetView.class.getSimpleName();
|
||||
|
||||
private static final Set<JitsiMeetView> views
|
||||
= Collections.newSetFromMap(new WeakHashMap<JitsiMeetView, Boolean>());
|
||||
|
||||
public static JitsiMeetView findViewByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
synchronized (views) {
|
||||
for (JitsiMeetView view : views) {
|
||||
if (view.externalAPIScope.equals(externalAPIScope)) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
private static final String TAG = JitsiMeetView.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Loads a specific URL {@code String} in all existing
|
||||
@@ -78,130 +56,19 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* at least one {@code JitsiMeetView}, then {@code true}; otherwise,
|
||||
* {@code false}.
|
||||
*/
|
||||
private static boolean loadURLStringInViews(String urlString) {
|
||||
synchronized (views) {
|
||||
if (!views.isEmpty()) {
|
||||
for (JitsiMeetView view : views) {
|
||||
view.loadURLString(urlString);
|
||||
}
|
||||
public static boolean loadURLStringInViews(String urlString) {
|
||||
boolean loaded = false;
|
||||
|
||||
return true;
|
||||
synchronized (views) {
|
||||
for (BaseReactView view : views) {
|
||||
if (view instanceof JitsiMeetView) {
|
||||
((JitsiMeetView)view).loadURLString(urlString);
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onBackPressed} so we can do the required internal
|
||||
* processing.
|
||||
*
|
||||
* @return {@code true} if the back-press was processed; {@code false},
|
||||
* otherwise. If {@code false}, the application should call the parent's
|
||||
* implementation.
|
||||
*/
|
||||
public static boolean onBackPressed() {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager == null) {
|
||||
return false;
|
||||
} else {
|
||||
reactInstanceManager.onBackPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onDestroy} so we can do the required internal
|
||||
* processing.
|
||||
*
|
||||
* @param activity {@code Activity} being destroyed.
|
||||
*/
|
||||
public static void onHostDestroy(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostDestroy(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onPause} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being paused.
|
||||
*/
|
||||
public static void onHostPause(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostPause(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onResume} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being resumed.
|
||||
*/
|
||||
public static void onHostResume(Activity activity) {
|
||||
onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onResume} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being resumed.
|
||||
* @param defaultBackButtonImpl a {@code DefaultHardwareBackBtnHandler} to
|
||||
* handle invoking the back button if no {@code JitsiMeetView} handles it.
|
||||
*/
|
||||
public static void onHostResume(
|
||||
Activity activity,
|
||||
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onNewIntent} so we can do the required internal
|
||||
* processing. Note that this is only needed if the activity's "launchMode"
|
||||
* was set to "singleTask". This is required for deep linking to work once
|
||||
* the application is already running.
|
||||
*
|
||||
* @param intent {@code Intent} instance which was received.
|
||||
*/
|
||||
public static void onNewIntent(Intent intent) {
|
||||
// XXX At least twice we received bug reports about malfunctioning
|
||||
// loadURL in the Jitsi Meet SDK while the Jitsi Meet app seemed to
|
||||
// functioning as expected in our testing. But that was to be expected
|
||||
// because the app does not exercise loadURL. In order to increase the
|
||||
// test coverage of loadURL, channel deep linking through loadURL.
|
||||
Uri uri;
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())
|
||||
&& (uri = intent.getData()) != null
|
||||
&& loadURLStringInViews(uri.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onNewIntent(intent);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,26 +78,12 @@ public class JitsiMeetView extends FrameLayout {
|
||||
*/
|
||||
private URL defaultURL;
|
||||
|
||||
/**
|
||||
* The unique identifier of this {@code JitsiMeetView} within the process
|
||||
* for the purposes of {@link ExternalAPI}. The name scope was inspired by
|
||||
* postis which we use on Web for the similar purposes of the iframe-based
|
||||
* external API.
|
||||
*/
|
||||
private final String externalAPIScope;
|
||||
|
||||
/**
|
||||
* The entry point into the invite feature of Jitsi Meet. The Java
|
||||
* counterpart of the JavaScript {@code InviteButton}.
|
||||
*/
|
||||
private final InviteController inviteController;
|
||||
|
||||
/**
|
||||
* {@link JitsiMeetViewListener} instance for reporting events occurring in
|
||||
* Jitsi Meet.
|
||||
*/
|
||||
private JitsiMeetViewListener listener;
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled. If {@code null}, defaults to
|
||||
* {@code true} iff the Android platform supports Picture-in-Picture
|
||||
@@ -238,11 +91,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
*/
|
||||
private Boolean pictureInPictureEnabled;
|
||||
|
||||
/**
|
||||
* React Native root view.
|
||||
*/
|
||||
private ReactRootView reactRootView;
|
||||
|
||||
/**
|
||||
* The URL of the current conference.
|
||||
*/
|
||||
@@ -258,34 +106,33 @@ public class JitsiMeetView extends FrameLayout {
|
||||
public JitsiMeetView(@NonNull Context context) {
|
||||
super(context);
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
ReactInstanceManagerHolder.initReactInstanceManager(
|
||||
((Activity) context).getApplication());
|
||||
|
||||
// Hook this JitsiMeetView into ExternalAPI.
|
||||
externalAPIScope = UUID.randomUUID().toString();
|
||||
synchronized (views) {
|
||||
views.add(this);
|
||||
}
|
||||
|
||||
// The entry point into the invite feature of Jitsi Meet. The Java
|
||||
// counterpart of the JavaScript InviteButton.
|
||||
inviteController = new InviteController(externalAPIScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the React resources (specifically the {@link ReactRootView})
|
||||
* associated with this view.
|
||||
* Enters Picture-In-Picture mode, if possible. This method is designed to
|
||||
* be called from the {@code Activity.onUserLeaveHint} method.
|
||||
*
|
||||
* This method MUST be called when the Activity holding this view is
|
||||
* destroyed, typically in the {@code onDestroy} method.
|
||||
* This is currently not mandatory, but if used will provide automatic
|
||||
* handling of the picture in picture mode when user minimizes the app. It
|
||||
* will be probably the most useful in case the app is using the welcome
|
||||
* page.
|
||||
*/
|
||||
public void dispose() {
|
||||
if (reactRootView != null) {
|
||||
removeView(reactRootView);
|
||||
reactRootView.unmountReactApplication();
|
||||
reactRootView = null;
|
||||
public void enterPictureInPicture() {
|
||||
if (isPictureInPictureEnabled() && getURL() != null) {
|
||||
PictureInPictureModule pipModule
|
||||
= ReactInstanceManagerHolder.getNativeModule(
|
||||
PictureInPictureModule.class);
|
||||
|
||||
if (pipModule != null) {
|
||||
try {
|
||||
pipModule.enterPictureInPicture();
|
||||
} catch (RuntimeException re) {
|
||||
Log.e(TAG, "onUserLeaveHint: failed to enter PiP mode", re);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +141,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* partial URL (e.g. a room name only) is specified to
|
||||
* {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. If not
|
||||
* set or if set to {@code null}, the default built in JavaScript is used:
|
||||
* {@link https://meet.jit.si}
|
||||
* https://meet.jit.si
|
||||
*
|
||||
* @return The default base {@code URL} or {@code null}.
|
||||
*/
|
||||
@@ -315,31 +162,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
return inviteController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link JitsiMeetViewListener} set on this {@code JitsiMeetView}.
|
||||
*
|
||||
* @return The {@code JitsiMeetViewListener} set on this
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
public JitsiMeetViewListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
|
||||
* natively supported on Android API >= 26 (Oreo), so it should not be
|
||||
* enabled on older platform versions.
|
||||
*
|
||||
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean getPictureInPictureEnabled() {
|
||||
return
|
||||
PictureInPictureModule.isPictureInPictureSupported()
|
||||
&& (pictureInPictureEnabled == null
|
||||
|| pictureInPictureEnabled.booleanValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL of the current conference.
|
||||
*
|
||||
@@ -353,6 +175,21 @@ public class JitsiMeetView extends FrameLayout {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
|
||||
* natively supported on Android API >= 26 (Oreo), so it should not be
|
||||
* enabled on older platform versions.
|
||||
*
|
||||
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean isPictureInPictureEnabled() {
|
||||
return
|
||||
PictureInPictureModule.isPictureInPictureSupported()
|
||||
&& (pictureInPictureEnabled == null
|
||||
|| pictureInPictureEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the Welcome page is enabled. If {@code true}, the Welcome
|
||||
* page is rendered when this {@code JitsiMeetView} is not at a URL
|
||||
@@ -361,7 +198,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* @return {@code true} if the Welcome page is enabled; otherwise,
|
||||
* {@code false}.
|
||||
*/
|
||||
public boolean getWelcomePageEnabled() {
|
||||
public boolean isWelcomePageEnabled() {
|
||||
return welcomePageEnabled;
|
||||
}
|
||||
|
||||
@@ -395,9 +232,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
props.putString("defaultURL", defaultURL.toString());
|
||||
}
|
||||
|
||||
// externalAPIScope
|
||||
props.putString("externalAPIScope", externalAPIScope);
|
||||
|
||||
// inviteController
|
||||
InviteController inviteController = getInviteController();
|
||||
|
||||
@@ -413,7 +247,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||
// pictureInPictureEnabled
|
||||
props.putBoolean(
|
||||
"pictureInPictureEnabled",
|
||||
getPictureInPictureEnabled());
|
||||
isPictureInPictureEnabled());
|
||||
|
||||
// url
|
||||
if (urlObject != null) {
|
||||
@@ -434,17 +268,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||
// per loadURLObject: invocation.
|
||||
props.putLong("timestamp", System.currentTimeMillis());
|
||||
|
||||
if (reactRootView == null) {
|
||||
reactRootView = new ReactRootView(getContext());
|
||||
reactRootView.startReactApplication(
|
||||
ReactInstanceManagerHolder.getReactInstanceManager(),
|
||||
"App",
|
||||
props);
|
||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
||||
addView(reactRootView);
|
||||
} else {
|
||||
reactRootView.setAppProperties(props);
|
||||
}
|
||||
createReactRootView("App", props);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,66 +292,48 @@ public class JitsiMeetView extends FrameLayout {
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onUserLeaveHint} so we can do the required internal
|
||||
* processing.
|
||||
* The internal processing for the URL of the current conference set on the
|
||||
* associated {@link JitsiMeetView}.
|
||||
*
|
||||
* This is currently not mandatory, but if used will provide automatic
|
||||
* handling of the picture in picture mode when user minimizes the app. It
|
||||
* will be probably the most useful in case the app is using the welcome
|
||||
* page.
|
||||
* @param eventName the name of the external API event to be processed
|
||||
* @param eventData the details/specifics of the event to process determined
|
||||
* by/associated with the specified {@code eventName}.
|
||||
*/
|
||||
public void onUserLeaveHint() {
|
||||
if (getPictureInPictureEnabled() && getURL() != null) {
|
||||
PictureInPictureModule pipModule
|
||||
= ReactInstanceManagerHolder.getNativeModule(
|
||||
PictureInPictureModule.class);
|
||||
private void maybeSetViewURL(String eventName, ReadableMap eventData) {
|
||||
switch(eventName) {
|
||||
case "CONFERENCE_WILL_JOIN":
|
||||
setURL(eventData.getString("url"));
|
||||
break;
|
||||
|
||||
if (pipModule != null) {
|
||||
try {
|
||||
pipModule.enterPictureInPicture();
|
||||
} catch (RuntimeException re) {
|
||||
Log.e(TAG, "onUserLeaveHint: failed to enter PiP mode", re);
|
||||
}
|
||||
case "CONFERENCE_FAILED":
|
||||
case "CONFERENCE_WILL_LEAVE":
|
||||
case "LOAD_CONFIG_ERROR":
|
||||
String url = eventData.getString("url");
|
||||
|
||||
if (url != null && url.equals(getURL())) {
|
||||
setURL(null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the window containing this view gains or loses focus.
|
||||
* Handler for {@link ExternalAPIModule} events.
|
||||
*
|
||||
* @param hasFocus If the window of this view now has focus, {@code true};
|
||||
* otherwise, {@code false}.
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
* by/associated with the specified {@code name}.
|
||||
*/
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
public void onExternalAPIEvent(String name, ReadableMap data) {
|
||||
// XXX The JitsiMeetView property URL was introduced in order to address
|
||||
// an exception in the Picture-in-Picture functionality which arose
|
||||
// because of delays related to bridging between JavaScript and Java. To
|
||||
// reduce these delays do not wait for the call to be transferred to the
|
||||
// UI thread.
|
||||
maybeSetViewURL(name, data);
|
||||
|
||||
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
|
||||
|
||||
// FIXME The singleton pattern employed by RNImmersiveModule is not
|
||||
// advisable because a react-native mobule is consumable only after its
|
||||
// BaseJavaModule#initialize() has completed and here we have no
|
||||
// knowledge of whether the precondition is really met.
|
||||
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
|
||||
|
||||
if (hasFocus && immersive != null) {
|
||||
try {
|
||||
immersive.emitImmersiveStateChangeEvent();
|
||||
} catch (RuntimeException re) {
|
||||
// FIXME I don't know how to check myself whether
|
||||
// BaseJavaModule#initialize() has been invoked and thus
|
||||
// RNImmersiveModule is consumable. A safe workaround is to
|
||||
// swallow the failure because the whole full-screen/immersive
|
||||
// functionality is brittle anyway, akin to the icing on the
|
||||
// cake, and has been working without onWindowFocusChanged for a
|
||||
// very long time.
|
||||
Log.e(
|
||||
TAG,
|
||||
"RNImmersiveModule#emitImmersiveStateChangeEvent() failed!",
|
||||
re);
|
||||
}
|
||||
}
|
||||
onExternalAPIEvent(LISTENER_METHODS, name, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -543,17 +349,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
this.defaultURL = defaultURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific {@link JitsiMeetViewListener} on this
|
||||
* {@code JitsiMeetView}.
|
||||
*
|
||||
* @param listener The {@code JitsiMeetViewListener} to set on this
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
public void setListener(JitsiMeetViewListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether Picture-in-Picture is enabled. Because Picture-in-Picture is
|
||||
* natively supported only since certain platform versions, specifying
|
||||
@@ -563,7 +358,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* {@code true}; otherwise, {@code false}.
|
||||
*/
|
||||
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
|
||||
this.pictureInPictureEnabled = Boolean.valueOf(pictureInPictureEnabled);
|
||||
this.pictureInPictureEnabled = pictureInPictureEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,9 @@ import java.util.Map;
|
||||
* Implements {@link JitsiMeetViewListener} so apps don't have to add stubs for
|
||||
* all methods in the interface if they are only interested in some.
|
||||
*/
|
||||
public abstract class JitsiMeetViewAdapter implements JitsiMeetViewListener {
|
||||
public abstract class JitsiMeetViewAdapter
|
||||
implements JitsiMeetViewListener {
|
||||
|
||||
@Override
|
||||
public void onConferenceFailed(Map<String, Object> data) {
|
||||
}
|
||||
|
||||
166
android/sdk/src/main/java/org/jitsi/meet/sdk/ListenerUtils.java
Normal file
166
android/sdk/src/main/java/org/jitsi/meet/sdk/ListenerUtils.java
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utility methods for helping with transforming {@link ExternalAPIModule}
|
||||
* events into listener methods. Used with descendants of {@link BaseReactView}.
|
||||
*/
|
||||
public final class ListenerUtils {
|
||||
/**
|
||||
* Extracts the methods defined in a listener and creates a mapping of this
|
||||
* form: event name -> method.
|
||||
*
|
||||
* @param listener - The listener whose methods we want to slurp.
|
||||
* @return A mapping with event names - methods.
|
||||
*/
|
||||
public static Map<String, Method> mapListenerMethods(Class listener) {
|
||||
Map<String, Method> methods = new HashMap<>();
|
||||
|
||||
// Figure out the mapping between the listener methods
|
||||
// and the events i.e. redux action types.
|
||||
Pattern onPattern = Pattern.compile("^on[A-Z]+");
|
||||
Pattern camelcasePattern = Pattern.compile("([a-z0-9]+)([A-Z0-9]+)");
|
||||
|
||||
for (Method method : listener.getDeclaredMethods()) {
|
||||
// * The method must be public (because it is declared by an
|
||||
// interface).
|
||||
// * The method must be/return void.
|
||||
if (!Modifier.isPublic(method.getModifiers())
|
||||
|| !Void.TYPE.equals(method.getReturnType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// * The method name must start with "on" followed by a
|
||||
// capital/uppercase letter (in agreement with the camelcase
|
||||
// coding style customary to Java in general and the projects of
|
||||
// the Jitsi community in particular).
|
||||
String name = method.getName();
|
||||
|
||||
if (!onPattern.matcher(name).find()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// * The method must accept/have exactly 1 parameter of a type
|
||||
// assignable from HashMap.
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
if (parameterTypes.length != 1
|
||||
|| !parameterTypes[0].isAssignableFrom(HashMap.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert the method name to an event name.
|
||||
name
|
||||
= camelcasePattern.matcher(name.substring(2))
|
||||
.replaceAll("$1_$2")
|
||||
.toUpperCase(Locale.ROOT);
|
||||
methods.put(name, method);
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the right listener method for the given event.
|
||||
* NOTE: This function will run asynchronously on the UI thread.
|
||||
*
|
||||
* @param listener - The listener on which the method will be called.
|
||||
* @param listenerMethods - Mapping with event names and the matching
|
||||
* methods.
|
||||
* @param eventName - Name of the event.
|
||||
* @param eventData - Data associated with the event.
|
||||
*/
|
||||
public static void runListenerMethod(
|
||||
final Object listener,
|
||||
final Map<String, Method> listenerMethods,
|
||||
final String eventName,
|
||||
final ReadableMap eventData) {
|
||||
// Make sure listener methods are invoked on the UI thread. It
|
||||
// was requested by SDK consumers.
|
||||
if (UiThreadUtil.isOnUiThread()) {
|
||||
runListenerMethodOnUiThread(
|
||||
listener, listenerMethods, eventName, eventData);
|
||||
} else {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runListenerMethodOnUiThread(
|
||||
listener, listenerMethods, eventName, eventData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper companion for {@link ListenerUtils#runListenerMethod} which runs
|
||||
* in the UI thread.
|
||||
*/
|
||||
private static void runListenerMethodOnUiThread(
|
||||
Object listener,
|
||||
Map<String, Method> listenerMethods,
|
||||
String eventName,
|
||||
ReadableMap eventData) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
Method method = listenerMethods.get(eventName);
|
||||
if (method != null) {
|
||||
try {
|
||||
method.invoke(listener, toHashMap(eventData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@code HashMap} instance with the key-value
|
||||
* associations of a specific {@code ReadableMap}.
|
||||
*
|
||||
* @param readableMap the {@code ReadableMap} specifying the key-value
|
||||
* associations with which the new {@code HashMap} instance is to be
|
||||
* initialized.
|
||||
* @return a new {@code HashMap} instance initialized with the key-value
|
||||
* associations of the specified {@code readableMap}.
|
||||
*/
|
||||
private static HashMap<String, Object> toHashMap(ReadableMap readableMap) {
|
||||
HashMap<String, Object> hashMap = new HashMap<>();
|
||||
|
||||
for (ReadableMapKeySetIterator i = readableMap.keySetIterator();
|
||||
i.hasNextKey();) {
|
||||
String key = i.nextKey();
|
||||
|
||||
hashMap.put(key, readableMap.getString(key));
|
||||
}
|
||||
|
||||
return hashMap;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -11,7 +27,9 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
public class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
class PictureInPictureModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
private final static String TAG = "PictureInPicture";
|
||||
|
||||
static boolean isPictureInPictureSupported() {
|
||||
|
||||
@@ -31,7 +31,9 @@ import com.facebook.react.bridge.UiThreadUtil;
|
||||
* object it will dim the screen and disable touch controls. The functionality
|
||||
* is used with the conference audio-only mode.
|
||||
*/
|
||||
class ProximityModule extends ReactContextBaseJavaModule {
|
||||
class ProximityModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* The name of {@code ProximityModule} to be used in the React Native
|
||||
* bridge.
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
|
||||
/**
|
||||
* Helper class to encapsulate the work which needs to be done on
|
||||
* {@link Activity} lifecycle methods in order for the React side to be aware of
|
||||
* it.
|
||||
*/
|
||||
public class ReactActivityLifecycleCallbacks {
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@link Activity#onBackPressed} so we can do the required internal
|
||||
* processing.
|
||||
*
|
||||
* @return {@code true} if the back-press was processed; {@code false},
|
||||
* otherwise. If {@code false}, the application should call the
|
||||
* {@code super}'s implementation.
|
||||
*/
|
||||
public static boolean onBackPressed() {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager == null) {
|
||||
return false;
|
||||
} else {
|
||||
reactInstanceManager.onBackPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onDestroy} so we can do the required internal
|
||||
* processing.
|
||||
*
|
||||
* @param activity {@code Activity} being destroyed.
|
||||
*/
|
||||
public static void onHostDestroy(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostDestroy(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onPause} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being paused.
|
||||
*/
|
||||
public static void onHostPause(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostPause(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onResume} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being resumed.
|
||||
*/
|
||||
public static void onHostResume(Activity activity) {
|
||||
onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onResume} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being resumed.
|
||||
* @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} to
|
||||
* handle invoking the back button if no {@link BaseReactView} handles it.
|
||||
*/
|
||||
public static void onHostResume(
|
||||
Activity activity,
|
||||
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onNewIntent} so we can do the required internal
|
||||
* processing. Note that this is only needed if the activity's "launchMode"
|
||||
* was set to "singleTask". This is required for deep linking to work once
|
||||
* the application is already running.
|
||||
*
|
||||
* @param intent {@code Intent} instance which was received.
|
||||
*/
|
||||
public static void onNewIntent(Intent intent) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import com.facebook.react.common.LifecycleState;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ReactInstanceManagerHolder {
|
||||
class ReactInstanceManagerHolder {
|
||||
/**
|
||||
* React Native bridge. The instance manager allows embedding applications
|
||||
* to create multiple root views off the same JavaScript bundle.
|
||||
@@ -119,14 +119,15 @@ public class ReactInstanceManagerHolder {
|
||||
.setApplication(application)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.addPackage(new com.BV.LinearGradient.LinearGradientPackage())
|
||||
.addPackage(new com.calendarevents.CalendarEventsPackage())
|
||||
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
|
||||
.addPackage(new com.dylanvann.fastimage.FastImageViewPackage())
|
||||
.addPackage(new com.facebook.react.shell.MainReactPackage())
|
||||
.addPackage(new com.i18n.reactnativei18n.ReactNativeI18n())
|
||||
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
|
||||
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
|
||||
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
|
||||
.addPackage(new com.RNFetchBlob.RNFetchBlobPackage())
|
||||
.addPackage(new com.rnimmersive.RNImmersivePackage())
|
||||
.addPackage(new com.zmxv.RNSound.RNSoundPackage())
|
||||
.addPackage(new ReactPackageAdapter() {
|
||||
|
||||
@@ -25,7 +25,9 @@ import com.facebook.react.uimanager.ViewManager;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ReactPackageAdapter implements ReactPackage {
|
||||
class ReactPackageAdapter
|
||||
implements ReactPackage {
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
|
||||
@@ -19,8 +19,6 @@ package org.jitsi.meet.sdk;
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
@@ -36,14 +34,18 @@ import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Module exposing WiFi statistics.
|
||||
*
|
||||
* Gathers rssi, signal in percentage, timestamp and the addresses
|
||||
* of the wifi device.
|
||||
* Gathers rssi, signal in percentage, timestamp and the addresses of the wifi
|
||||
* device.
|
||||
*/
|
||||
class WiFiStatsModule extends ReactContextBaseJavaModule {
|
||||
class WiFiStatsModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* The name of {@code WiFiStatsModule} to be used in the React Native
|
||||
* bridge.
|
||||
@@ -56,17 +58,16 @@ class WiFiStatsModule extends ReactContextBaseJavaModule {
|
||||
static final String TAG = MODULE_NAME;
|
||||
|
||||
/**
|
||||
* The scale used for the signal value.
|
||||
* A level of the signal, given in the range
|
||||
* of 0 to SIGNAL_LEVEL_SCALE-1 (both inclusive).
|
||||
* The scale used for the signal value. A level of the signal, given in the
|
||||
* range of 0 to SIGNAL_LEVEL_SCALE-1 (both inclusive).
|
||||
*/
|
||||
public final static int SIGNAL_LEVEL_SCALE = 101;
|
||||
|
||||
/**
|
||||
* {@link Handler} for running all operations on the main thread.
|
||||
* {@link ExecutorService} for running all operations on a dedicated thread.
|
||||
*/
|
||||
private final Handler mainThreadHandler
|
||||
= new Handler(Looper.getMainLooper());
|
||||
private static final ExecutorService executor
|
||||
= Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
@@ -119,7 +120,6 @@ class WiFiStatsModule extends ReactContextBaseJavaModule {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
||||
Context context
|
||||
= getReactApplicationContext().getApplicationContext();
|
||||
WifiManager wifiManager
|
||||
@@ -203,6 +203,6 @@ class WiFiStatsModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
};
|
||||
mainThreadHandler.post(r);
|
||||
executor.execute(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk.incoming_call;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public class IncomingCallInfo {
|
||||
/**
|
||||
* URL for the caller avatar.
|
||||
*/
|
||||
private final String callerAvatarURL;
|
||||
|
||||
/**
|
||||
* Caller's name.
|
||||
*/
|
||||
private final String callerName;
|
||||
|
||||
/**
|
||||
* Whether this is a regular call or a video call.
|
||||
*/
|
||||
private final boolean hasVideo;
|
||||
|
||||
public IncomingCallInfo(
|
||||
@NonNull String callerName,
|
||||
@NonNull String callerAvatarURL,
|
||||
boolean hasVideo) {
|
||||
this.callerName = callerName;
|
||||
this.callerAvatarURL = callerAvatarURL;
|
||||
this.hasVideo = hasVideo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the caller's avatar URL.
|
||||
*
|
||||
* @return - The URL as a string.
|
||||
*/
|
||||
public String getCallerAvatarURL() {
|
||||
return callerAvatarURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the caller's name.
|
||||
*
|
||||
* @return - The caller's name.
|
||||
*/
|
||||
public String getCallerName() {
|
||||
return callerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the call is a video call or not.
|
||||
*
|
||||
* @return - {@code true} if this call has video; {@code false}, otherwise.
|
||||
*/
|
||||
public boolean hasVideo() {
|
||||
return hasVideo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk.incoming_call;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import org.jitsi.meet.sdk.BaseReactView;
|
||||
import org.jitsi.meet.sdk.ListenerUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
public class IncomingCallView
|
||||
extends BaseReactView<IncomingCallViewListener> {
|
||||
|
||||
/**
|
||||
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
|
||||
* redux action types.
|
||||
*/
|
||||
private static final Map<String, Method> LISTENER_METHODS
|
||||
= ListenerUtils.mapListenerMethods(IncomingCallViewListener.class);
|
||||
|
||||
public IncomingCallView(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for {@link ExternalAPIModule} events.
|
||||
*
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
* by/associated with the specified {@code name}.
|
||||
*/
|
||||
@Override
|
||||
public void onExternalAPIEvent(String name, ReadableMap data) {
|
||||
onExternalAPIEvent(LISTENER_METHODS, name, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the information for the incoming call this {@code IncomingCallView}
|
||||
* represents.
|
||||
*
|
||||
* @param callInfo - {@link IncomingCallInfo} object representing the caller
|
||||
* information.
|
||||
*/
|
||||
public void setIncomingCallInfo(IncomingCallInfo callInfo) {
|
||||
Bundle props = new Bundle();
|
||||
|
||||
props.putString("callerAvatarURL", callInfo.getCallerAvatarURL());
|
||||
props.putString("callerName", callInfo.getCallerName());
|
||||
props.putBoolean("hasVideo", callInfo.hasVideo());
|
||||
|
||||
createReactRootView("IncomingCallApp", props);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk.incoming_call;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface for listening to events coming from Jitsi Meet, related to
|
||||
* {@link IncomingCallView}.
|
||||
*/
|
||||
public interface IncomingCallViewListener {
|
||||
/**
|
||||
* Called when the user presses the "Answer" button on the
|
||||
* {@link IncomingCallView}.
|
||||
*
|
||||
* @param data - Unused at the moment.
|
||||
*/
|
||||
void onIncomingCallAnswered(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* Called when the user presses the "Decline" button on the
|
||||
* {@link IncomingCallView}.
|
||||
*
|
||||
* @param data - Unused at the moment.
|
||||
*/
|
||||
void onIncomingCallDeclined(Map<String, Object> data);
|
||||
}
|
||||
@@ -33,7 +33,8 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Controller object used by native code to query and submit user selections for the user invitation flow.
|
||||
* Controller object used by native code to query and submit user selections for
|
||||
* the user invitation flow.
|
||||
*/
|
||||
public class AddPeopleController {
|
||||
|
||||
@@ -44,17 +45,18 @@ public class AddPeopleController {
|
||||
private AddPeopleControllerListener listener;
|
||||
|
||||
/**
|
||||
* Local cache of search query results. Used to re-hydrate the list
|
||||
* of selected items based on their ids passed to inviteById
|
||||
* in order to pass the full item maps back to the JitsiMeetView during submission.
|
||||
* Local cache of search query results. Used to re-hydrate the list of
|
||||
* selected items based on their ids passed to inviteById in order to pass
|
||||
* the full item maps back to the JitsiMeetView during submission.
|
||||
*/
|
||||
private final Map<String, ReadableMap> items = new HashMap<>();
|
||||
|
||||
private final WeakReference<InviteController> owner;
|
||||
|
||||
private final WeakReference<ReactApplicationContext> reactContext;
|
||||
|
||||
/**
|
||||
* Randomly generated UUID, used for identification in the InviteModule
|
||||
* Randomly generated UUID, used for identification in the InviteModule.
|
||||
*/
|
||||
private final String uuid = UUID.randomUUID().toString();
|
||||
|
||||
@@ -158,10 +160,10 @@ public class AddPeopleController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches results received by the search into a local map for use
|
||||
* later when the items are submitted. Submission requires the full
|
||||
* map of information, but only the IDs are returned back to the delegate.
|
||||
* Using this map means we don't have to send the whole map back to the delegate.
|
||||
* Caches results received by the search into a local map for use later when
|
||||
* the items are submitted. Submission requires the full map of
|
||||
* information, but only the IDs are returned back to the delegate. Using
|
||||
* this map means we don't have to send the whole map back to the delegate.
|
||||
*
|
||||
* @param results
|
||||
* @param query
|
||||
@@ -179,10 +181,15 @@ public class AddPeopleController {
|
||||
|
||||
if(map.hasKey("id")) {
|
||||
items.put(map.getString("id"), map);
|
||||
} else if(map.hasKey("type") && map.getString("type").equals("phone") && map.hasKey("number")) {
|
||||
} else if(map.hasKey("type")
|
||||
&& map.getString("type").equals("phone")
|
||||
&& map.hasKey("number")) {
|
||||
items.put(map.getString("number"), map);
|
||||
} else {
|
||||
Log.w("AddPeopleController", "Received result without id and that was not a phone number, so not adding it to suggestions: " + map);
|
||||
Log.w(
|
||||
"AddPeopleController",
|
||||
"Received result without id and that was not a phone number, so not adding it to suggestions: "
|
||||
+ map);
|
||||
}
|
||||
|
||||
jvmResults.add(map.toHashMap());
|
||||
|
||||
@@ -202,9 +202,9 @@ public class InviteController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a query for users to invite to the conference. Results will be
|
||||
* returned through the {@link AddPeopleControllerListener#onReceivedResults(AddPeopleController, List, String)}
|
||||
* method.
|
||||
* Starts a query for users to invite to the conference. Results will be
|
||||
* returned through
|
||||
* {@link AddPeopleControllerListener#onReceivedResults(AddPeopleController, List, String)}.
|
||||
*
|
||||
* @param query {@code String} to use for the query
|
||||
*/
|
||||
|
||||
@@ -20,10 +20,10 @@ public interface InviteControllerListener {
|
||||
/**
|
||||
* Called when the add user button is tapped.
|
||||
*
|
||||
* @param addPeopleController {@code AddPeopleController} scoped
|
||||
* for this user invite flow. The {@code AddPeopleController} is used
|
||||
* to start user queries and accepts an {@code AddPeopleControllerListener}
|
||||
* for receiving user query responses.
|
||||
* @param addPeopleController {@code AddPeopleController} scoped for this
|
||||
* user invite flow. The {@code AddPeopleController} is used to start user
|
||||
* queries and accepts an {@code AddPeopleControllerListener} for receiving
|
||||
* user query responses.
|
||||
*/
|
||||
void beginAddPeople(AddPeopleController addPeopleController);
|
||||
}
|
||||
|
||||
@@ -24,12 +24,15 @@ import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import org.jitsi.meet.sdk.BaseReactView;
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
|
||||
/**
|
||||
* Implements the react-native module of the feature invite.
|
||||
*/
|
||||
public class InviteModule extends ReactContextBaseJavaModule {
|
||||
public class InviteModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public InviteModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
@@ -67,7 +70,8 @@ public class InviteModule extends ReactContextBaseJavaModule {
|
||||
private InviteController findInviteControllerByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
JitsiMeetView view
|
||||
= JitsiMeetView.findViewByExternalAPIScope(externalAPIScope);
|
||||
= (JitsiMeetView)
|
||||
BaseReactView.findViewByExternalAPIScope(externalAPIScope);
|
||||
|
||||
return view == null ? null : view.getInviteController();
|
||||
}
|
||||
@@ -81,7 +85,8 @@ public class InviteModule extends ReactContextBaseJavaModule {
|
||||
* Callback for invitation failures
|
||||
*
|
||||
* @param failedInvitees the items for which the invitation failed
|
||||
* @param addPeopleControllerScope a string that represents a connection to a specific AddPeopleController
|
||||
* @param addPeopleControllerScope a string that represents a connection to
|
||||
* a specific AddPeopleController
|
||||
*/
|
||||
@ReactMethod
|
||||
public void inviteSettled(
|
||||
@@ -123,7 +128,8 @@ public class InviteModule extends ReactContextBaseJavaModule {
|
||||
*
|
||||
* @param results the results in a ReadableArray of ReadableMap objects
|
||||
* @param query the query associated with the search
|
||||
* @param addPeopleControllerScope a string that represents a connection to a specific AddPeopleController
|
||||
* @param addPeopleControllerScope a string that represents a connection to
|
||||
* a specific AddPeopleController
|
||||
*/
|
||||
@ReactMethod
|
||||
public void receivedResults(
|
||||
|
||||
@@ -32,7 +32,9 @@ import java.net.UnknownHostException;
|
||||
* [1]: https://tools.ietf.org/html/rfc6146
|
||||
* [2]: https://tools.ietf.org/html/rfc6052
|
||||
*/
|
||||
public class NAT64AddrInfoModule extends ReactContextBaseJavaModule {
|
||||
public class NAT64AddrInfoModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* The host for which the module wil try to resolve both IPv4 and IPv6
|
||||
* addresses in order to figure out the NAT64 prefix.
|
||||
|
||||
@@ -3,12 +3,14 @@ rootProject.name = 'jitsi-meet'
|
||||
include ':app', ':sdk'
|
||||
include ':react-native-background-timer'
|
||||
project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android')
|
||||
include ':react-native-fetch-blob'
|
||||
project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android')
|
||||
include ':react-native-fast-image'
|
||||
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android')
|
||||
include ':react-native-immersive'
|
||||
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
|
||||
include ':react-native-keep-awake'
|
||||
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
|
||||
include ':react-native-linear-gradient'
|
||||
project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
|
||||
include ':react-native-locale-detector'
|
||||
project(':react-native-locale-detector').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-locale-detector/android')
|
||||
include ':react-native-sound'
|
||||
|
||||
346
conference.js
346
conference.js
@@ -7,8 +7,6 @@ import Recorder from './modules/recorder/Recorder';
|
||||
|
||||
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
|
||||
|
||||
import { reportError } from './modules/util/helpers';
|
||||
|
||||
import * as RemoteControlEvents
|
||||
from './service/remotecontrol/RemoteControlEvents';
|
||||
import UIEvents from './service/UI/UIEvents';
|
||||
@@ -18,7 +16,6 @@ import * as JitsiMeetConferenceEvents from './ConferenceEvents';
|
||||
import {
|
||||
createDeviceChangedEvent,
|
||||
createScreenSharingEvent,
|
||||
createSelectParticipantFailedEvent,
|
||||
createStreamSwitchDelayEvent,
|
||||
createTrackMutedEvent,
|
||||
sendAnalytics
|
||||
@@ -27,17 +24,18 @@ import {
|
||||
redirectWithStoredParams,
|
||||
reloadWithStoredParams
|
||||
} from './react/features/app';
|
||||
import { updateRecordingSessionData } from './react/features/recording';
|
||||
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
AVATAR_URL_COMMAND,
|
||||
authStatusChanged,
|
||||
conferenceFailed,
|
||||
conferenceJoined,
|
||||
conferenceLeft,
|
||||
conferenceWillJoin,
|
||||
conferenceWillLeave,
|
||||
dataChannelOpened,
|
||||
EMAIL_COMMAND,
|
||||
lockStateChanged,
|
||||
@@ -47,6 +45,7 @@ import {
|
||||
setDesktopSharingEnabled
|
||||
} from './react/features/base/conference';
|
||||
import {
|
||||
getAvailableDevices,
|
||||
setAudioOutputDeviceId,
|
||||
updateDeviceList
|
||||
} from './react/features/base/devices';
|
||||
@@ -75,6 +74,8 @@ import {
|
||||
getAvatarURLByParticipantId,
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
hiddenParticipantJoined,
|
||||
hiddenParticipantLeft,
|
||||
localParticipantConnectionStatusChanged,
|
||||
localParticipantRoleChanged,
|
||||
MAX_DISPLAY_NAME_LENGTH,
|
||||
@@ -109,6 +110,7 @@ import {
|
||||
} from './react/features/overlay';
|
||||
import { setSharedVideoStatus } from './react/features/shared-video';
|
||||
import { isButtonEnabled } from './react/features/toolbox';
|
||||
import { endpointMessageReceived } from './react/features/subtitles';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
@@ -372,6 +374,8 @@ class ConferenceConnector {
|
||||
|
||||
case JitsiConferenceErrors.FOCUS_LEFT:
|
||||
case JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE:
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
|
||||
// FIXME the conference should be stopped by the library and not by
|
||||
// the app. Both the errors above are unrecoverable from the library
|
||||
// perspective.
|
||||
@@ -468,6 +472,7 @@ function _connectionFailedHandler(error) {
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_connectionFailedHandler);
|
||||
if (room) {
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
room.leave();
|
||||
}
|
||||
}
|
||||
@@ -485,8 +490,7 @@ export default {
|
||||
|
||||
/**
|
||||
* Indicates if the desktop sharing functionality has been enabled.
|
||||
* It takes into consideration {@link isDesktopSharingDisabledByConfig}
|
||||
* as well as the status returned by
|
||||
* It takes into consideration the status returned by
|
||||
* {@link JitsiMeetJS.isDesktopSharingEnabled()}. The latter can be false
|
||||
* either if the desktop sharing is not supported by the current browser
|
||||
* or if it was disabled through lib-jitsi-meet specific options (check
|
||||
@@ -494,19 +498,6 @@ export default {
|
||||
*/
|
||||
isDesktopSharingEnabled: false,
|
||||
|
||||
/**
|
||||
* Set to <tt>true</tt> if the desktop sharing functionality has been
|
||||
* explicitly disabled in the config.
|
||||
*/
|
||||
isDesktopSharingDisabledByConfig: false,
|
||||
|
||||
/**
|
||||
* The text displayed when the desktop sharing button is disabled through
|
||||
* the config. The value is set through
|
||||
* {@link interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP}.
|
||||
*/
|
||||
desktopSharingDisabledTooltip: null,
|
||||
|
||||
/**
|
||||
* The local audio track (if any).
|
||||
* FIXME tracks from redux store should be the single source of truth
|
||||
@@ -721,13 +712,8 @@ export default {
|
||||
APP.connection = connection = con;
|
||||
|
||||
// Desktop sharing related stuff:
|
||||
this.isDesktopSharingDisabledByConfig
|
||||
= config.disableDesktopSharing;
|
||||
this.isDesktopSharingEnabled
|
||||
= !this.isDesktopSharingDisabledByConfig
|
||||
&& JitsiMeetJS.isDesktopSharingEnabled();
|
||||
this.desktopSharingDisabledTooltip
|
||||
= interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
|
||||
= JitsiMeetJS.isDesktopSharingEnabled();
|
||||
eventEmitter.emit(
|
||||
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
||||
this.isDesktopSharingEnabled);
|
||||
@@ -1256,13 +1242,6 @@ export default {
|
||||
_getConferenceOptions() {
|
||||
const options = config;
|
||||
|
||||
if (config.enableRecording && !config.recordingType) {
|
||||
options.recordingType
|
||||
= config.hosts && (typeof config.hosts.jirecon !== 'undefined')
|
||||
? 'jirecon'
|
||||
: 'colibri';
|
||||
}
|
||||
|
||||
const nick = APP.store.getState()['features/base/settings'].displayName;
|
||||
|
||||
if (nick) {
|
||||
@@ -1677,17 +1656,21 @@ export default {
|
||||
room.on(
|
||||
JitsiConferenceEvents.AUTH_STATUS_CHANGED,
|
||||
(authEnabled, authLogin) =>
|
||||
APP.UI.updateAuthInfo(authEnabled, authLogin));
|
||||
APP.store.dispatch(authStatusChanged(authEnabled, authLogin)));
|
||||
|
||||
room.on(JitsiConferenceEvents.PARTCIPANT_FEATURES_CHANGED,
|
||||
user => APP.UI.onUserFeaturesChanged(user));
|
||||
room.on(JitsiConferenceEvents.USER_JOINED, (id, user) => {
|
||||
if (user.isHidden()) {
|
||||
return;
|
||||
}
|
||||
const displayName = user.getDisplayName();
|
||||
|
||||
if (user.isHidden()) {
|
||||
APP.store.dispatch(hiddenParticipantJoined(id, displayName));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(participantJoined({
|
||||
botType: user.getBotType(),
|
||||
conference: room,
|
||||
id,
|
||||
name: displayName,
|
||||
@@ -1709,12 +1692,19 @@ export default {
|
||||
|
||||
room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
|
||||
if (user.isHidden()) {
|
||||
APP.store.dispatch(hiddenParticipantLeft(id));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(participantLeft(id, room));
|
||||
logger.log('USER %s LEFT', id, user);
|
||||
APP.API.notifyUserLeft(id);
|
||||
APP.UI.removeUser(id, user.getDisplayName());
|
||||
APP.UI.messageHandler.participantNotification(
|
||||
user.getDisplayName(),
|
||||
'notify.somebody',
|
||||
'disconnected',
|
||||
'notify.disconnected');
|
||||
APP.UI.onSharedVideoStop(id);
|
||||
});
|
||||
|
||||
@@ -1799,24 +1789,14 @@ export default {
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
|
||||
(id, connectionStatus) => {
|
||||
APP.store.dispatch(participantConnectionStatusChanged(
|
||||
id, connectionStatus));
|
||||
(id, connectionStatus) => APP.store.dispatch(
|
||||
participantConnectionStatusChanged(id, connectionStatus)));
|
||||
|
||||
APP.UI.participantConnectionStatusChanged(id);
|
||||
});
|
||||
room.on(
|
||||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
id => APP.store.dispatch(dominantSpeakerChanged(id, room)));
|
||||
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||
APP.UI.markVideoInterrupted(true);
|
||||
});
|
||||
room.on(JitsiConferenceEvents.CONNECTION_RESTORED, () => {
|
||||
APP.UI.markVideoInterrupted(false);
|
||||
});
|
||||
|
||||
if (isButtonEnabled('chat')) {
|
||||
room.on(
|
||||
JitsiConferenceEvents.MESSAGE_RECEIVED,
|
||||
@@ -1843,38 +1823,16 @@ export default {
|
||||
room.sendTextMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, id => {
|
||||
APP.API.notifyOnStageParticipantChanged(id);
|
||||
try {
|
||||
// do not try to select participant if there is none (we
|
||||
// are alone in the room), otherwise an error will be
|
||||
// thrown cause reporting mechanism is not available
|
||||
// (datachannels currently)
|
||||
if (room.getParticipants().length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
room.selectParticipant(id);
|
||||
} catch (e) {
|
||||
sendAnalytics(createSelectParticipantFailedEvent(e));
|
||||
reportError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||
APP.store.dispatch(localParticipantConnectionStatusChanged(
|
||||
JitsiParticipantConnectionStatus.INTERRUPTED));
|
||||
|
||||
APP.UI.showLocalConnectionInterrupted(true);
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.CONNECTION_RESTORED, () => {
|
||||
APP.store.dispatch(localParticipantConnectionStatusChanged(
|
||||
JitsiParticipantConnectionStatus.ACTIVE));
|
||||
|
||||
APP.UI.showLocalConnectionInterrupted(false);
|
||||
});
|
||||
|
||||
room.on(
|
||||
@@ -1898,6 +1856,21 @@ export default {
|
||||
APP.UI.changeDisplayName(id, formattedDisplayName);
|
||||
}
|
||||
);
|
||||
room.on(
|
||||
JitsiConferenceEvents.BOT_TYPE_CHANGED,
|
||||
(id, botType) => {
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id,
|
||||
botType
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||
(...args) => APP.store.dispatch(endpointMessageReceived(...args)));
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.LOCK_STATE_CHANGED,
|
||||
@@ -1916,6 +1889,14 @@ export default {
|
||||
JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
|
||||
(participant, name, oldValue, newValue) => {
|
||||
switch (name) {
|
||||
case 'features_screen-sharing': {
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: participant.getId(),
|
||||
features: { 'screen-sharing': true }
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case 'raisedHand':
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
@@ -1934,38 +1915,6 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
/* eslint-enable max-params */
|
||||
room.on(
|
||||
JitsiConferenceEvents.RECORDER_STATE_CHANGED,
|
||||
recorderSession => {
|
||||
if (!recorderSession) {
|
||||
logger.error(
|
||||
'Received invalid recorder status update',
|
||||
recorderSession);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (recorderSession.getID()) {
|
||||
APP.store.dispatch(
|
||||
updateRecordingSessionData(recorderSession));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// These errors fire when the local participant has requested a
|
||||
// recording but the request itself failed, hence the missing
|
||||
// session ID because the recorder never started.
|
||||
if (recorderSession.getError()) {
|
||||
this._showRecordingErrorNotification(recorderSession);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error(
|
||||
'Received a recorder status update with no ID or error');
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.KICKED, () => {
|
||||
APP.UI.hideStats();
|
||||
APP.UI.notifyKicked();
|
||||
@@ -2015,14 +1964,14 @@ export default {
|
||||
}
|
||||
);
|
||||
|
||||
APP.UI.addListener(UIEvents.EMAIL_CHANGED, this.changeLocalEmail);
|
||||
APP.UI.addListener(UIEvents.EMAIL_CHANGED,
|
||||
this.changeLocalEmail.bind(this));
|
||||
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: from,
|
||||
email: data.value
|
||||
}));
|
||||
APP.UI.setUserEmail(from, data.value);
|
||||
});
|
||||
|
||||
room.addCommandListener(
|
||||
@@ -2181,20 +2130,6 @@ export default {
|
||||
}
|
||||
);
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
|
||||
audioOutputDeviceId => {
|
||||
sendAnalytics(createDeviceChangedEvent('audio', 'output'));
|
||||
setAudioOutputDeviceId(audioOutputDeviceId)
|
||||
.then(() => logger.log('changed audio output device'))
|
||||
.catch(err => {
|
||||
logger.warn('Failed to change audio output device. '
|
||||
+ 'Default or previously set audio output device '
|
||||
+ 'will be used instead.', err);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly => {
|
||||
|
||||
// FIXME On web video track is stored both in redux and in
|
||||
@@ -2326,10 +2261,10 @@ export default {
|
||||
|
||||
APP.store.dispatch(conferenceJoined(room));
|
||||
|
||||
APP.UI.mucJoined();
|
||||
const displayName
|
||||
= APP.store.getState()['features/base/settings'].displayName;
|
||||
|
||||
APP.UI.changeDisplayName('localVideoContainer', displayName);
|
||||
APP.API.notifyConferenceJoined(
|
||||
this.roomName,
|
||||
this._room.myUserId(),
|
||||
@@ -2342,7 +2277,6 @@ export default {
|
||||
APP.store.getState(), this._room.myUserId())
|
||||
}
|
||||
);
|
||||
APP.UI.markVideoInterrupted(false);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2367,47 +2301,43 @@ export default {
|
||||
/**
|
||||
* Inits list of current devices and event listener for device change.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_initDeviceList() {
|
||||
JitsiMeetJS.mediaDevices.isDeviceListAvailable()
|
||||
.then(isDeviceListAvailable => {
|
||||
if (isDeviceListAvailable
|
||||
&& JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
|
||||
JitsiMeetJS.mediaDevices.enumerateDevices(devices => {
|
||||
// Ugly way to synchronize real device IDs with local
|
||||
// storage and settings menu. This is a workaround until
|
||||
// getConstraints() method will be implemented
|
||||
// in browsers.
|
||||
const { dispatch } = APP.store;
|
||||
const { mediaDevices } = JitsiMeetJS;
|
||||
|
||||
if (this.localAudio) {
|
||||
dispatch(updateSettings({
|
||||
micDeviceId: this.localAudio.getDeviceId()
|
||||
}));
|
||||
}
|
||||
if (mediaDevices.isDeviceListAvailable()
|
||||
&& mediaDevices.isDeviceChangeAvailable()) {
|
||||
this.deviceChangeListener = devices =>
|
||||
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
|
||||
mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
|
||||
if (this.localVideo) {
|
||||
dispatch(updateSettings({
|
||||
cameraDeviceId: this.localVideo.getDeviceId()
|
||||
}));
|
||||
}
|
||||
const { dispatch } = APP.store;
|
||||
|
||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||
APP.UI.onAvailableDevicesChanged(devices);
|
||||
APP.store.dispatch(updateDeviceList(devices));
|
||||
});
|
||||
return dispatch(getAvailableDevices())
|
||||
.then(devices => {
|
||||
// Ugly way to synchronize real device IDs with local
|
||||
// storage and settings menu. This is a workaround until
|
||||
// getConstraints() method will be implemented in browsers.
|
||||
if (this.localAudio) {
|
||||
dispatch(updateSettings({
|
||||
micDeviceId: this.localAudio.getDeviceId()
|
||||
}));
|
||||
}
|
||||
|
||||
this.deviceChangeListener = devices =>
|
||||
window.setTimeout(
|
||||
() => this._onDeviceListChanged(devices), 0);
|
||||
JitsiMeetJS.mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logger.warn(`Error getting device list: ${error}`);
|
||||
});
|
||||
if (this.localVideo) {
|
||||
dispatch(updateSettings({
|
||||
cameraDeviceId: this.localVideo.getDeviceId()
|
||||
}));
|
||||
}
|
||||
|
||||
APP.UI.onAvailableDevicesChanged(devices);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2418,16 +2348,7 @@ export default {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_onDeviceListChanged(devices) {
|
||||
let currentDevices = mediaDeviceHelper.getCurrentMediaDevices();
|
||||
|
||||
// Event handler can be fired before direct
|
||||
// enumerateDevices() call, so handle this situation here.
|
||||
if (!currentDevices.audioinput
|
||||
&& !currentDevices.videoinput
|
||||
&& !currentDevices.audiooutput) {
|
||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||
currentDevices = mediaDeviceHelper.getCurrentMediaDevices();
|
||||
}
|
||||
APP.store.dispatch(updateDeviceList(devices));
|
||||
|
||||
const newDevices
|
||||
= mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged(
|
||||
@@ -2440,9 +2361,13 @@ export default {
|
||||
const videoWasMuted = this.isLocalVideoMuted();
|
||||
|
||||
if (typeof newDevices.audiooutput !== 'undefined') {
|
||||
// Just ignore any errors in catch block.
|
||||
promises.push(setAudioOutputDeviceId(newDevices.audiooutput)
|
||||
.catch());
|
||||
const { dispatch } = APP.store;
|
||||
const setAudioOutputPromise
|
||||
= setAudioOutputDeviceId(newDevices.audiooutput, dispatch)
|
||||
.catch(); // Just ignore any errors in catch block.
|
||||
|
||||
|
||||
promises.push(setAudioOutputPromise);
|
||||
}
|
||||
|
||||
promises.push(
|
||||
@@ -2476,7 +2401,6 @@ export default {
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(() => {
|
||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||
APP.UI.onAvailableDevicesChanged(devices);
|
||||
});
|
||||
},
|
||||
@@ -2486,7 +2410,7 @@ export default {
|
||||
*/
|
||||
updateAudioIconEnabled() {
|
||||
const audioMediaDevices
|
||||
= mediaDeviceHelper.getCurrentMediaDevices().audioinput;
|
||||
= APP.store.getState()['features/base/devices'].audioInput;
|
||||
const audioDeviceCount
|
||||
= audioMediaDevices ? audioMediaDevices.length : 0;
|
||||
|
||||
@@ -2509,7 +2433,7 @@ export default {
|
||||
*/
|
||||
updateVideoIconEnabled() {
|
||||
const videoMediaDevices
|
||||
= mediaDeviceHelper.getCurrentMediaDevices().videoinput;
|
||||
= APP.store.getState()['features/base/devices'].videoInput;
|
||||
const videoDeviceCount
|
||||
= videoMediaDevices ? videoMediaDevices.length : 0;
|
||||
|
||||
@@ -2540,9 +2464,11 @@ export default {
|
||||
APP.UI.removeLocalMedia();
|
||||
|
||||
// Remove unnecessary event listeners from firing callbacks.
|
||||
JitsiMeetJS.mediaDevices.removeEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
if (this.deviceChangeListener) {
|
||||
JitsiMeetJS.mediaDevices.removeEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
}
|
||||
|
||||
let requestFeedbackPromise;
|
||||
|
||||
@@ -2562,13 +2488,24 @@ export default {
|
||||
// before all operations are done.
|
||||
Promise.all([
|
||||
requestFeedbackPromise,
|
||||
room.leave().then(disconnect, disconnect)
|
||||
this.leaveRoomAndDisconnect()
|
||||
]).then(values => {
|
||||
APP.API.notifyReadyToClose();
|
||||
maybeRedirectToWelcomePage(values[0]);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Leaves the room and calls JitsiConnection.disconnect.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
leaveRoomAndDisconnect() {
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
|
||||
return room.leave().then(disconnect, disconnect);
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the email for the local user
|
||||
* @param email {string} the new email
|
||||
@@ -2599,8 +2536,9 @@ export default {
|
||||
APP.store.dispatch(updateSettings({
|
||||
email: formattedEmail
|
||||
}));
|
||||
|
||||
APP.UI.setUserEmail(localId, formattedEmail);
|
||||
APP.API.notifyEmailChanged(localId, {
|
||||
email: formattedEmail
|
||||
});
|
||||
sendData(commands.EMAIL, formattedEmail);
|
||||
},
|
||||
|
||||
@@ -2766,57 +2704,5 @@ export default {
|
||||
if (score === -1 || (score >= 1 && score <= 5)) {
|
||||
APP.store.dispatch(submitFeedback(score, message, room));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a notification about an error in the recording session. A
|
||||
* default notification will display if no error is specified in the passed
|
||||
* in recording session.
|
||||
*
|
||||
* @param {Object} recorderSession - The recorder session model from the
|
||||
* lib.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_showRecordingErrorNotification(recorderSession) {
|
||||
const isStreamMode
|
||||
= recorderSession.getMode()
|
||||
=== JitsiMeetJS.constants.recording.mode.STREAM;
|
||||
|
||||
switch (recorderSession.getError()) {
|
||||
case JitsiMeetJS.constants.recording.error.SERVICE_UNAVAILABLE:
|
||||
APP.UI.messageHandler.showError({
|
||||
descriptionKey: 'recording.unavailable',
|
||||
descriptionArguments: {
|
||||
serviceName: isStreamMode
|
||||
? 'Live Streaming service'
|
||||
: 'Recording service'
|
||||
},
|
||||
titleKey: isStreamMode
|
||||
? 'liveStreaming.unavailableTitle'
|
||||
: 'recording.unavailableTitle'
|
||||
});
|
||||
break;
|
||||
case JitsiMeetJS.constants.recording.error.RESOURCE_CONSTRAINT:
|
||||
APP.UI.messageHandler.showError({
|
||||
descriptionKey: isStreamMode
|
||||
? 'liveStreaming.busy'
|
||||
: 'recording.busy',
|
||||
titleKey: isStreamMode
|
||||
? 'liveStreaming.busyTitle'
|
||||
: 'recording.busyTitle'
|
||||
});
|
||||
break;
|
||||
default:
|
||||
APP.UI.messageHandler.showError({
|
||||
descriptionKey: isStreamMode
|
||||
? 'liveStreaming.error'
|
||||
: 'recording.error',
|
||||
titleKey: isStreamMode
|
||||
? 'liveStreaming.failedToStart'
|
||||
: 'recording.failedToStart'
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
32
config.js
32
config.js
@@ -99,13 +99,13 @@ var config = {
|
||||
// used by browsers that return true from lib-jitsi-meet's
|
||||
// util#browser#usesNewGumFlow. The constraints are independency from
|
||||
// this config's resolution value. Defaults to requesting an ideal aspect
|
||||
// ratio of 16:9 with an ideal resolution of 1080p.
|
||||
// ratio of 16:9 with an ideal resolution of 720.
|
||||
// constraints: {
|
||||
// video: {
|
||||
// aspectRatio: 16 / 9,
|
||||
// height: {
|
||||
// ideal: 1080,
|
||||
// max: 1080,
|
||||
// ideal: 720,
|
||||
// max: 720,
|
||||
// min: 240
|
||||
// }
|
||||
// }
|
||||
@@ -114,6 +114,11 @@ var config = {
|
||||
// Enable / disable simulcast support.
|
||||
// disableSimulcast: false,
|
||||
|
||||
// Enable / disable layer suspension. If enabled, endpoints whose HD
|
||||
// layers are not in use will be suspended (no longer sent) until they
|
||||
// are requested again.
|
||||
// enableLayerSuspension: false,
|
||||
|
||||
// Suspend sending video if bandwidth estimation is too low. This may cause
|
||||
// problems with audio playback. Disabled until these are fixed.
|
||||
disableSuspendVideo: true,
|
||||
@@ -137,9 +142,6 @@ var config = {
|
||||
|
||||
// Desktop sharing
|
||||
|
||||
// Enable / disable desktop sharing
|
||||
// disableDesktopSharing: false,
|
||||
|
||||
// The ID of the jidesha extension for Chrome.
|
||||
desktopSharingChromeExtId: null,
|
||||
|
||||
@@ -167,11 +169,15 @@ var config = {
|
||||
|
||||
// Recording
|
||||
|
||||
// Whether to enable recording or not.
|
||||
// enableRecording: false,
|
||||
// Whether to enable file recording or not.
|
||||
// fileRecordingsEnabled: false,
|
||||
|
||||
// Type for recording: one of jibri or jirecon.
|
||||
// recordingType: 'jibri',
|
||||
// Whether to enable live streaming or not.
|
||||
// liveStreamingEnabled: false,
|
||||
|
||||
// Transcription (in interface_config,
|
||||
// subtitles and buttons can be configured)
|
||||
// transcribingEnabled: false,
|
||||
|
||||
// Misc
|
||||
|
||||
@@ -243,11 +249,13 @@ var config = {
|
||||
// edit their profile.
|
||||
enableUserRolesBasedOnToken: false,
|
||||
|
||||
// Whether or not some features are checked based on token.
|
||||
// enableFeaturesBasedOnToken: false,
|
||||
|
||||
// Message to show the users. Example: 'The service will be down for
|
||||
// maintenance at 01:00 AM GMT,
|
||||
// noticeMessage: '',
|
||||
|
||||
|
||||
// Stats
|
||||
//
|
||||
|
||||
@@ -341,7 +349,7 @@ var config = {
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
alwaysVisibleToolbar
|
||||
_immediateReloadThreshold
|
||||
autoRecord
|
||||
autoRecordToken
|
||||
debug
|
||||
|
||||
@@ -132,7 +132,7 @@ function connect(id, password, roomName) {
|
||||
*
|
||||
*/
|
||||
function handleConnectionEstablished() {
|
||||
APP.store.dispatch(connectionEstablished(connection));
|
||||
APP.store.dispatch(connectionEstablished(connection, Date.now()));
|
||||
unsubscribe();
|
||||
resolve(connection);
|
||||
}
|
||||
|
||||
12
css/_atlaskit_overrides.scss
Normal file
12
css/_atlaskit_overrides.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Move Atlaskit Flag up a little bit so it does not cover the toolbar with the
|
||||
* first notification.
|
||||
*/
|
||||
.cxGWJB{
|
||||
bottom: calc(#{$newToolbarSizeWithPadding}) !important;
|
||||
}
|
||||
.gXSEsl:nth-child(n+2) {
|
||||
transform: translateX(0) translateY(100%) translateY(16px) !important;
|
||||
-ms-transform: translateX(0) translateY(100%) translateY(16px) !important;
|
||||
-webkit-transform: translateX(0) translateY(100%) translateY(16px) !important;
|
||||
}
|
||||
@@ -14,6 +14,11 @@ textarea {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
@@ -103,14 +108,15 @@ form {
|
||||
}
|
||||
|
||||
.leftwatermark {
|
||||
left: $defaultToolbarSize;
|
||||
margin-left: 10px;
|
||||
left: 32px;
|
||||
top: 32px;
|
||||
background-image: url($defaultWatermarkLink);
|
||||
background-position: center left;
|
||||
}
|
||||
|
||||
.rightwatermark {
|
||||
right: 15;
|
||||
right: 32px;
|
||||
top: 32px;
|
||||
background-position: center right;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#chatconversation {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
top: 5px;
|
||||
top: 15px;
|
||||
padding: 5px;
|
||||
text-align: left;
|
||||
line-height: 20px;
|
||||
@@ -60,37 +60,7 @@
|
||||
}
|
||||
|
||||
.localuser {
|
||||
color: #087dba;
|
||||
}
|
||||
.use-new-toolbox {
|
||||
.chatmessage {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.localuser {
|
||||
color: #4C9AFF;
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chatmessage,
|
||||
#smileysarea,
|
||||
#smileysContainer,
|
||||
#usermsg {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
}
|
||||
|
||||
.smileyContainer:hover {
|
||||
background-color: $newToolbarButtonToggleColor;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
color: #4C9AFF
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
@@ -98,10 +68,11 @@
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
color: white;
|
||||
color: #B8C7E0;
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
visibility:hidden;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
@@ -112,8 +83,7 @@
|
||||
max-height:150px;
|
||||
min-height:35px;
|
||||
border: 0px none;
|
||||
background: #3a3a3a;
|
||||
color: #a7a7a7;
|
||||
color: white;
|
||||
box-shadow: none;
|
||||
border-radius:0;
|
||||
font-size: 10pt;
|
||||
@@ -183,7 +153,7 @@
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background: #3a3a3a;
|
||||
background-color: $newToolbarBackgroundColor;;
|
||||
width: 93%;
|
||||
margin-left: 9px;
|
||||
margin-right: auto;
|
||||
@@ -191,7 +161,7 @@
|
||||
border-top-left-radius: 0px;
|
||||
margin-top: 3px;
|
||||
left: 5px;
|
||||
color: #a7a7a7;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
@@ -227,7 +197,7 @@
|
||||
max-height:150px;
|
||||
min-height:35px;
|
||||
border: 0px none;
|
||||
background: #3a3a3a;
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -239,7 +209,7 @@
|
||||
#smileysContainer {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background: #3a3a3a;
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border-bottom: 1px solid;
|
||||
border-top: 1px solid;
|
||||
width: 100%;
|
||||
@@ -257,7 +227,9 @@
|
||||
}
|
||||
|
||||
.smileyContainer:hover {
|
||||
background: #3e3e3e;
|
||||
background-color: $newToolbarButtonToggleColor;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#usermsg::-webkit-input-placeholder {
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
%align-right {
|
||||
@include flex();
|
||||
flex-direction: row-reverse;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.use-new-toolbox {
|
||||
.filmstrip.reduce-height {
|
||||
bottom: $newToolbarSizeWithPadding;
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
transition: bottom .3s;
|
||||
}
|
||||
|
||||
.filmstrip__videos.hidden {
|
||||
bottom: calc(-196px - #{$newToolbarSizeWithPadding});
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 10px 5px;
|
||||
@extend %align-right;
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
&__toolbar {
|
||||
@include flex();
|
||||
flex-direction: column-reverse;
|
||||
flex-wrap: nowrap;
|
||||
position: relative;
|
||||
width: $filmstripToggleButtonWidth;
|
||||
|
||||
button {
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
opacity: 0.7;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
-webkit-appearance: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__videos {
|
||||
@extend %align-right;
|
||||
position:relative;
|
||||
padding: 0;
|
||||
/* The filmstrip should not be covered by the left toolbar. */
|
||||
bottom: 0;
|
||||
width:auto;
|
||||
overflow: visible !important;
|
||||
|
||||
&#remoteVideos {
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
padding-left: $defaultToolbarSize + 5;
|
||||
transition: bottom 2s;
|
||||
}
|
||||
|
||||
/**
|
||||
* The local video identifier.
|
||||
*/
|
||||
&#filmstripLocalVideo {
|
||||
align-self: flex-end;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
bottom: -196px;
|
||||
}
|
||||
|
||||
.remote-videos-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.videocontainer {
|
||||
display: none;
|
||||
position: relative;
|
||||
background-size: contain;
|
||||
border: $thumbnailVideoBorder solid transparent;
|
||||
border-radius: $borderRadius;
|
||||
margin: 0 $thumbnailVideoMargin;
|
||||
|
||||
&.videoContainerFocused, &:hover {
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Focused video thumbnail.
|
||||
*/
|
||||
&.videoContainerFocused {
|
||||
transition-duration: 0.5s;
|
||||
-webkit-transition-duration: 0.5s;
|
||||
-webkit-animation-name: greyPulse;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: 1;
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailSelected !important;
|
||||
box-shadow: inset 0 0 3px $videoThumbnailSelected,
|
||||
0 0 3px $videoThumbnailSelected !important;
|
||||
}
|
||||
|
||||
.remotevideomenu > .icon-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.presence-label {
|
||||
color: $participantNameColor;
|
||||
font-size: 12px;
|
||||
font-weight: 100;
|
||||
left: 0;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
top: calc(50% + 30px);
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
z-index: $zindex3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hovered video thumbnail.
|
||||
*/
|
||||
&:hover {
|
||||
cursor: hand;
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailHovered;
|
||||
box-shadow: inset 0 0 3px $videoThumbnailHovered,
|
||||
0 0 3px $videoThumbnailHovered;
|
||||
|
||||
.remotevideomenu > .icon-menu {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
/* With the TemasysWebRTC plugin <object/> element is used
|
||||
instead of <video/> */
|
||||
& > video,
|
||||
& > object {
|
||||
cursor: hand;
|
||||
border-radius: $borderRadius;
|
||||
object-fit: cover;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Style the filmstrip videos in filmstrip-only mode.
|
||||
*/
|
||||
&__videos-filmstripOnly {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
|
||||
.filmstrip__videos {
|
||||
&#filmstripLocalVideo {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remote-videos-container {
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
&.hide-videos {
|
||||
.remote-videos-container {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,13 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-arrow_back:before {
|
||||
content: "\e5c4";
|
||||
}
|
||||
.icon-close:before {
|
||||
content: "\e5cd";
|
||||
}
|
||||
.icon-event_note:before {
|
||||
content: "\e616";
|
||||
}
|
||||
@@ -204,3 +208,15 @@
|
||||
.icon-live:before {
|
||||
content: "\e92c";
|
||||
}
|
||||
.icon-speaker:before {
|
||||
content: "\e92d";
|
||||
}
|
||||
.icon-tiles-many:before {
|
||||
content: "\e92e";
|
||||
}
|
||||
.icon-tiles-one:before {
|
||||
content: "\e92f";
|
||||
}
|
||||
.icon-closed_caption:before {
|
||||
content: "\e930";
|
||||
}
|
||||
|
||||
43
css/_navigate_section_list.scss
Normal file
43
css/_navigate_section_list.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
%navigate-section-list-text {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: $welcomePageTitleColor;
|
||||
text-align: left;
|
||||
font-family: 'open_sanslight', Helvetica, sans-serif;
|
||||
}
|
||||
%navigate-section-list-tile-text {
|
||||
@extend %navigate-section-list-text;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
float: left;
|
||||
}
|
||||
.navigate-section-list-tile {
|
||||
height: 90px;
|
||||
width: 260px;
|
||||
border-radius: 4px;
|
||||
background-color: #1754A9;
|
||||
margin-right: 8px;
|
||||
padding: 16px;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
.navigate-section-tile-body {
|
||||
@extend %navigate-section-list-tile-text;
|
||||
font-weight: normal;
|
||||
}
|
||||
.navigate-section-tile-title {
|
||||
@extend %navigate-section-list-tile-text;
|
||||
font-weight: bold;
|
||||
}
|
||||
.navigate-section-section-header {
|
||||
@extend %navigate-section-list-text;
|
||||
font-weight: bold;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.navigate-section-list {
|
||||
position: relative;
|
||||
margin-top: 36px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
@@ -10,14 +10,24 @@
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.popover-mousemove-padding-right {
|
||||
|
||||
%vertical-popover-padding {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: -20;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.popover-mousemove-padding-left {
|
||||
@extend %vertical-popover-padding;
|
||||
left: -20px;
|
||||
}
|
||||
|
||||
.popover-mousemove-padding-right {
|
||||
@extend %vertical-popover-padding;
|
||||
right: -20px;
|
||||
}
|
||||
|
||||
/**
|
||||
* An invisible element is added to the top of the popover to ensure the mouse
|
||||
* stays over the popover when the popover's height is shrunk, which would then
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.google-error {
|
||||
color: $errorColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Google sign in button must follow Google's design guidelines.
|
||||
* See: https://developers.google.com/identity/branding-guidelines
|
||||
|
||||
@@ -1,41 +1,14 @@
|
||||
/**
|
||||
* Toolbar side panel main container element.
|
||||
*/
|
||||
.use-new-toolbox #sideToolbarContainer {
|
||||
#sideToolbarContainer {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
|
||||
/**
|
||||
* Make the sidebar flush with the top of the toolbar. Take the size of
|
||||
* the toolbar and subtract from 100%.
|
||||
*/
|
||||
height: calc(100% - #{$newToolbarSizeWithPadding});
|
||||
left: 0;
|
||||
|
||||
.side-toolbar-close {
|
||||
background: gray;
|
||||
border: 3px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 100%;
|
||||
color: white;
|
||||
cursor:pointer;
|
||||
height: 10px;
|
||||
line-height: 10px;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
text-align: center;
|
||||
top: 5px;
|
||||
width: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#chatconversation {
|
||||
top: 15px;
|
||||
}
|
||||
}
|
||||
#sideToolbarContainer {
|
||||
background-color: $sideToolbarContainerBg;
|
||||
height: 100%;
|
||||
left: $defaultToolbarSize;
|
||||
max-width: $sidebarWidth;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
@@ -92,7 +65,7 @@
|
||||
/**
|
||||
* Titles and subtitles of inner containers.
|
||||
*/
|
||||
div.title, div.subTitle {
|
||||
div.title {
|
||||
margin: 24px 0 11px;
|
||||
}
|
||||
|
||||
@@ -113,52 +86,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
.settings-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 10%;
|
||||
padding-right: 10%;
|
||||
|
||||
.moderator-checkbox {
|
||||
display: inline-block;
|
||||
margin: 0 5px 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.moderator-option {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
color: $defaultSideBarFontColor;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile
|
||||
*/
|
||||
.auth_container {
|
||||
ul {
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
|
||||
a.authButton {
|
||||
width: 160px;
|
||||
margin: 10px 20px;
|
||||
padding: 3px 29px;
|
||||
box-sizing: border-box;
|
||||
background-color: #06a5df;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: $defaultColor;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.side-toolbar-close {
|
||||
background: gray;
|
||||
border: 3px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 100%;
|
||||
color: white;
|
||||
cursor:pointer;
|
||||
height: 10px;
|
||||
line-height: 10px;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
text-align: center;
|
||||
top: 5px;
|
||||
width: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,17 +19,6 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.use-new-toolbox {
|
||||
.cxGWJB{
|
||||
bottom: calc(#{$newToolbarSizeWithPadding});
|
||||
}
|
||||
.gXSEsl:nth-child(n+2) {
|
||||
transform: translateX(0) translateY(100%) translateY(16px);
|
||||
-ms-transform: translateX(0) translateY(100%) translateY(16px);
|
||||
-webkit-transform: translateX(0) translateY(100%) translateY(16px);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: when the old filmstrip has been removed, remove the "new-" prefix.
|
||||
*/
|
||||
@@ -39,8 +28,12 @@
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
left: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 12px 8px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transition: bottom .3s ease-in;
|
||||
width: 100%;
|
||||
z-index: $toolbarZ;
|
||||
@@ -91,7 +84,13 @@
|
||||
}
|
||||
|
||||
i.disabled {
|
||||
cursor: initial
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
|
||||
.disabled i {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
|
||||
i.disabled:hover {
|
||||
@@ -121,6 +120,12 @@
|
||||
height: 22px;
|
||||
padding: 5px 12px;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #313D52;
|
||||
}
|
||||
@@ -131,13 +136,16 @@
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
}
|
||||
|
||||
.beta-tag {
|
||||
background: #B8C7E0;
|
||||
border-radius: 2px;
|
||||
color: $newToolbarBackgroundColor;
|
||||
font-family: -apple-system, BlinkMacSystemFont, $baseFontFamily;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
|
||||
13
css/_transcription-subtitles.scss
Normal file
13
css/_transcription-subtitles.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
.transcription-subtitles{
|
||||
bottom: 10%;
|
||||
font-size: 16px;
|
||||
font-weight: 1000;
|
||||
opacity: 0.80;
|
||||
position: absolute;
|
||||
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 1px 1px rgba(0,0,0,0.3),
|
||||
1px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 0px 1px rgba(0,0,0,0.3);
|
||||
width: 100%;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Style variables
|
||||
*/
|
||||
$baseFontFamily: 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
$baseFontFamily: -apple-system, BlinkMacSystemFont, 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
$hangupColor: #bf2117;
|
||||
$hangupFontSize: 2em;
|
||||
|
||||
@@ -144,7 +144,7 @@ $watermarkHeight: 74px;
|
||||
/**
|
||||
* Welcome page variables.
|
||||
*/
|
||||
$welcomePageDescriptionColor: #fff;
|
||||
$welcomePageDescriptionColor: #E6EDFA;
|
||||
$welcomePageFontFamily: inherit;
|
||||
$welcomePageHeaderBackground: linear-gradient(#165ecc, #44A5FF);
|
||||
$welcomePageHeaderBackground: #1D69D4;
|
||||
$welcomePageTitleColor: #fff;
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
/**
|
||||
* Override other styles to support vertical filmstrip mode.
|
||||
*/
|
||||
.vertical-filmstrip {
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
#etherpad,
|
||||
#sharedvideo {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&.use-new-toolbox {
|
||||
/**
|
||||
* Adjust the height of the filmstrip as the toolbar is displayed.
|
||||
*/
|
||||
.filmstrip {
|
||||
top: 0;
|
||||
transition: height .3s ease-in;
|
||||
|
||||
&.reduce-height {
|
||||
height: calc(100% - #{$newToolbarSizeWithPadding});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
align-items: flex-end;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: 100%;
|
||||
/**
|
||||
* fixed positioning is necessary for remote menus and tooltips to pop
|
||||
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
|
||||
* a library called popper which will position its elements fixed if
|
||||
* any parent is also fixed.
|
||||
*/
|
||||
position: fixed;
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
/**
|
||||
* Hide videos by making them slight to the right.
|
||||
*/
|
||||
.filmstrip__videos {
|
||||
right: 0;
|
||||
|
||||
&.hidden {
|
||||
bottom: auto;
|
||||
right: -196px;
|
||||
}
|
||||
|
||||
/**
|
||||
* An id selector is used to match id specificity with existing
|
||||
* filmstrip styles.
|
||||
*/
|
||||
&#remoteVideos {
|
||||
/**
|
||||
* Remove horizontal filmstrip padding used to prevent videos
|
||||
* from overlapping getting near the left-side of the screen.
|
||||
* An id selector is used to match id specificity with filmstrip
|
||||
* styles.
|
||||
*/
|
||||
padding-left: 0;
|
||||
transition: right 2s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styles the local Video to better fit vertical filmstrip layout.
|
||||
*/
|
||||
#filmstripLocalVideo {
|
||||
align-self: initial;
|
||||
bottom: 5px;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unnecssary padding that is normally used to prevent horizontal
|
||||
* filmstrip from overlapping the left edge of the screen.
|
||||
*/
|
||||
#filmstripLocalVideo,
|
||||
#filmstripRemoteVideos {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#filmstripRemoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
justify-content: flex-end;
|
||||
|
||||
#filmstripRemoteVideosContainer {
|
||||
flex-direction: column-reverse;
|
||||
/**
|
||||
* Add padding as a hack for Firefox not to show scrollbars when
|
||||
* unnecessary.
|
||||
*/
|
||||
padding: 1px 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the hide filmstrip icon so it points towards the right edge
|
||||
* of the screen.
|
||||
*/
|
||||
&__toolbar {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the remote video menu trigger to the bottom left of the
|
||||
* video thumbnail.
|
||||
*/
|
||||
.remotevideomenu,
|
||||
.remote-video-menu-trigger {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: auto;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.remote-video-menu-trigger {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
#remoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.videocontainer {
|
||||
/**
|
||||
* Move status icons to the bottom right of the thumbnail.
|
||||
*/
|
||||
&__toolbar {
|
||||
text-align: right;
|
||||
|
||||
.right {
|
||||
float: none;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.filmstrip-only {
|
||||
.filmstrip {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.filmstrip__videos-filmstripOnly {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These styles are for the video labels that display on the top right. The
|
||||
* styles adjust the labels' positioning as the filmstrip itself or
|
||||
* filmstrip's remote videos appear and disappear.
|
||||
*
|
||||
* The class with-filmstrip is for when the filmstrip is visible.
|
||||
* The class without-filmstrip is for when the filmstrip has been toggled to
|
||||
* be hidden.
|
||||
* The class opening is for when the filmstrip is transitioning from hidden
|
||||
* to visible.
|
||||
*/
|
||||
.large-video-labels {
|
||||
&.with-filmstrip {
|
||||
right: 150px;
|
||||
}
|
||||
|
||||
&.with-filmstrip.opening {
|
||||
transition: 0.9s;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
&.without-filmstrip {
|
||||
transition: 1.2s ease-in-out;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply hardware acceleration to prevent flickering on scroll. The
|
||||
* selectors are specific to icon wrappers to prevent fixed position dialogs
|
||||
* and tooltips from getting a new location context due to translate3d.
|
||||
*/
|
||||
.connection-indicator,
|
||||
.remote-video-menu-trigger,
|
||||
.indicator-icon-container {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.indicator-container {
|
||||
float: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME: disable pointer to allow any elements moved below to still be
|
||||
* clickable. The real fix would to make sure those moved elements are
|
||||
* actually part of the toolbar instead of positioning being faked.
|
||||
*/
|
||||
.videocontainer__toolbar {
|
||||
pointer-events: none;
|
||||
|
||||
> div {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toolbar-icon {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Workarounds for Edge, IE11, and Firefox not handling scrolling properly
|
||||
* with flex-direction: column-reverse. The remove videos in filmstrip should
|
||||
* start scrolling from the bottom of the filmstrip, but in those browsers the
|
||||
* scrolling won't happen. Per W3C spec, scrolling should happen from the
|
||||
* bottom. As such, use css hacks to get around the css issue, with the intent
|
||||
* being to remove the hacks as the spec is supported.
|
||||
*/
|
||||
@mixin undoColumnReverseVideos() {
|
||||
.vertical-filmstrip {
|
||||
#remoteVideos #filmstripRemoteVideos #filmstripRemoteVideosContainer {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Firefox detection hack **/
|
||||
@-moz-document url-prefix() {
|
||||
@include undoColumnReverseVideos();
|
||||
}
|
||||
|
||||
/** IE11 detection hack **/
|
||||
@media screen and (-ms-high-contrast: active),
|
||||
screen and (-ms-high-contrast: none) {
|
||||
@include undoColumnReverseVideos();
|
||||
}
|
||||
|
||||
/** Edge detection hack **/
|
||||
@supports (-ms-ime-align:auto) {
|
||||
@include undoColumnReverseVideos();
|
||||
}
|
||||
@@ -627,7 +627,6 @@
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
#remotePresenceMessage .no-presence,
|
||||
#remoteConnectionMessage {
|
||||
display: none;
|
||||
}
|
||||
@@ -682,7 +681,8 @@
|
||||
}
|
||||
|
||||
video {
|
||||
visibility: hidden;
|
||||
opacity: 0.2;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -739,3 +739,19 @@
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.presence-label {
|
||||
color: $participantNameColor;
|
||||
font-size: 12px;
|
||||
font-weight: 100;
|
||||
left: 0;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
top: calc(50% + 30px);
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -4,15 +4,19 @@ body.welcome-page {
|
||||
}
|
||||
|
||||
.welcome {
|
||||
background-color: $welcomePageHeaderBackground;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: $welcomePageFontFamily;
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
background: $welcomePageHeaderBackground;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: fit-content;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
@@ -20,49 +24,42 @@ body.welcome-page {
|
||||
.header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
margin-top: 120px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: $watermarkHeight + 80;
|
||||
margin-bottom: 36px;
|
||||
max-width: calc(100% - 40px);
|
||||
min-height: 286px;
|
||||
width: 645px;
|
||||
width: 650px;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
.header-text-title {
|
||||
color: $welcomePageTitleColor;
|
||||
font-size: 48px;
|
||||
letter-spacing: -1px;
|
||||
line-height: 58px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.18;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.header-text-description {
|
||||
color: $welcomePageDescriptionColor;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
opacity: 0.8;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.header-image {
|
||||
background-image: url(../images/welcome_page/curves.png);
|
||||
background-size: contain;
|
||||
height: 209px;
|
||||
position: absolute;
|
||||
width: 1070px;
|
||||
}
|
||||
|
||||
#new_enter_room {
|
||||
#enter_room {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
max-width: calc(100% - 40px);
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 650px;
|
||||
z-index: $zindex2;
|
||||
|
||||
.enter-room-input {
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
width: 350px;
|
||||
margin-right: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,24 +67,10 @@ body.welcome-page {
|
||||
.welcome-page-button {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome.with-content {
|
||||
.header {
|
||||
min-height: 552px;
|
||||
}
|
||||
.header-image {
|
||||
left: -61px;
|
||||
top: 401px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome.without-content {
|
||||
.header {
|
||||
.welcome-watermark {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.header-image {
|
||||
bottom: -20px;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
31
css/filmstrip/_filmstrip_toolbar.scss
Normal file
31
css/filmstrip/_filmstrip_toolbar.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
.filmstrip__toolbar {
|
||||
@include flex();
|
||||
flex-direction: column-reverse;
|
||||
flex-wrap: nowrap;
|
||||
position: relative;
|
||||
width: $filmstripToggleButtonWidth;
|
||||
|
||||
button {
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
opacity: 0.7;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
-webkit-appearance: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
css/filmstrip/_horizontal_filmstrip.scss
Normal file
77
css/filmstrip/_horizontal_filmstrip.scss
Normal file
@@ -0,0 +1,77 @@
|
||||
%align-right {
|
||||
@include flex();
|
||||
flex-direction: row-reverse;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.horizontal-filmstrip .filmstrip {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 10px 5px;
|
||||
@extend %align-right;
|
||||
transition: bottom .3s;
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
&.reduce-height {
|
||||
bottom: $newToolbarSizeWithPadding;
|
||||
}
|
||||
|
||||
&__videos {
|
||||
@extend %align-right;
|
||||
position:relative;
|
||||
padding: 0;
|
||||
/* The filmstrip should not be covered by the left toolbar. */
|
||||
bottom: 0;
|
||||
width:auto;
|
||||
overflow: visible !important;
|
||||
|
||||
&#remoteVideos {
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
padding-left: $defaultToolbarSize + 5;
|
||||
transition: bottom 2s;
|
||||
}
|
||||
|
||||
/**
|
||||
* The local video identifier.
|
||||
*/
|
||||
&#filmstripLocalVideo {
|
||||
align-self: flex-end;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
bottom: calc(-196px - #{$newToolbarSizeWithPadding});
|
||||
}
|
||||
|
||||
.remote-videos-container {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Style the filmstrip videos in filmstrip-only mode.
|
||||
*/
|
||||
&__videos-filmstripOnly {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
|
||||
.filmstrip__videos {
|
||||
&#filmstripLocalVideo {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remote-videos-container {
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
&.hide-videos {
|
||||
.remote-videos-container {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
css/filmstrip/_small_video.scss
Normal file
56
css/filmstrip/_small_video.scss
Normal file
@@ -0,0 +1,56 @@
|
||||
.filmstrip__videos .videocontainer {
|
||||
display: none;
|
||||
position: relative;
|
||||
background-size: contain;
|
||||
border: $thumbnailVideoBorder solid transparent;
|
||||
border-radius: $borderRadius;
|
||||
margin: 0 $thumbnailVideoMargin;
|
||||
|
||||
&.videoContainerFocused, &:hover {
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Focused video thumbnail.
|
||||
*/
|
||||
&.videoContainerFocused {
|
||||
transition-duration: 0.5s;
|
||||
-webkit-transition-duration: 0.5s;
|
||||
-webkit-animation-name: greyPulse;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: 1;
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailSelected !important;
|
||||
box-shadow: inset 0 0 3px $videoThumbnailSelected,
|
||||
0 0 3px $videoThumbnailSelected !important;
|
||||
}
|
||||
|
||||
.remotevideomenu > .icon-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hovered video thumbnail.
|
||||
*/
|
||||
&:hover {
|
||||
cursor: hand;
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailHovered;
|
||||
box-shadow: inset 0 0 3px $videoThumbnailHovered,
|
||||
0 0 3px $videoThumbnailHovered;
|
||||
|
||||
.remotevideomenu > .icon-menu {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
& > video {
|
||||
cursor: hand;
|
||||
border-radius: $borderRadius;
|
||||
object-fit: cover;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.presence-label {
|
||||
position: absolute;
|
||||
z-index: $zindex3;
|
||||
}
|
||||
}
|
||||
171
css/filmstrip/_vertical_filmstrip.scss
Normal file
171
css/filmstrip/_vertical_filmstrip.scss
Normal file
@@ -0,0 +1,171 @@
|
||||
.vertical-filmstrip .filmstrip {
|
||||
&.hide-videos {
|
||||
.remote-videos-container {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
@extend %align-right;
|
||||
align-items: flex-end;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: 100%;
|
||||
padding: 10px 5px;
|
||||
/**
|
||||
* fixed positioning is necessary for remote menus and tooltips to pop
|
||||
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
|
||||
* a library called popper which will position its elements fixed if
|
||||
* any parent is also fixed.
|
||||
*/
|
||||
position: fixed;
|
||||
top: 0;
|
||||
transition: height .3s ease-in;
|
||||
right: 0;
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
&.reduce-height {
|
||||
height: calc(100% - #{$newToolbarSizeWithPadding});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide videos by making them slight to the right.
|
||||
*/
|
||||
.filmstrip__videos {
|
||||
@extend %align-right;
|
||||
bottom: 0;
|
||||
overflow: visible !important;
|
||||
padding: 0;
|
||||
position:relative;
|
||||
right: 0;
|
||||
width: auto;
|
||||
|
||||
&.hidden {
|
||||
bottom: auto;
|
||||
right: -196px;
|
||||
}
|
||||
|
||||
/**
|
||||
* An id selector is used to match id specificity with existing
|
||||
* filmstrip styles.
|
||||
*/
|
||||
&#remoteVideos {
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
padding-left: 0;
|
||||
transition: right 2s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styles the local Video to better fit vertical filmstrip layout.
|
||||
*/
|
||||
#filmstripLocalVideo {
|
||||
align-self: initial;
|
||||
bottom: 5px;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unnecssary padding that is normally used to prevent horizontal
|
||||
* filmstrip from overlapping the left edge of the screen.
|
||||
*/
|
||||
#filmstripLocalVideo,
|
||||
#filmstripRemoteVideos {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#filmstripRemoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
justify-content: flex-end;
|
||||
|
||||
#filmstripRemoteVideosContainer {
|
||||
flex-direction: column-reverse;
|
||||
/**
|
||||
* Add padding as a hack for Firefox not to show scrollbars when
|
||||
* unnecessary.
|
||||
*/
|
||||
padding: 1px 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
#remoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.remote-videos-container {
|
||||
display: flex;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override other styles to support vertical filmstrip mode.
|
||||
*/
|
||||
.filmstrip-only .vertical-filmstrip {
|
||||
.filmstrip {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.filmstrip__videos-filmstripOnly {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.filmstrip__videos {
|
||||
&#filmstripLocalVideo {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Workarounds for Edge and Firefox not handling scrolling properly with
|
||||
* flex-direction: column-reverse. The remove videos in filmstrip should
|
||||
* start scrolling from the bottom of the filmstrip, but in those browsers the
|
||||
* scrolling won't happen. Per W3C spec, scrolling should happen from the
|
||||
* bottom. As such, use css hacks to get around the css issue, with the intent
|
||||
* being to remove the hacks as the spec is supported.
|
||||
*/
|
||||
@mixin undoColumnReverseVideos() {
|
||||
.vertical-filmstrip {
|
||||
#remoteVideos #filmstripRemoteVideos #filmstripRemoteVideosContainer {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Firefox detection hack **/
|
||||
@-moz-document url-prefix() {
|
||||
@include undoColumnReverseVideos();
|
||||
}
|
||||
|
||||
/** Edge detection hack **/
|
||||
@supports (-ms-ime-align:auto) {
|
||||
@include undoColumnReverseVideos();
|
||||
}
|
||||
105
css/filmstrip/_vertical_filmstrip_overrides.scss
Normal file
105
css/filmstrip/_vertical_filmstrip_overrides.scss
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Rotate the hide filmstrip icon so it points towards the right edge
|
||||
* of the screen.
|
||||
*/
|
||||
.vertical-filmstrip .filmstrip__toolbar {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides for video containers that should not be centered aligned to avoid=
|
||||
* clashing with the filmstrip.
|
||||
*/
|
||||
.vertical-filmstrip #etherpad,
|
||||
.vertical-filmstrip #sharedvideo {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides for small videos in vertical filmstrip mode.
|
||||
*/
|
||||
.vertical-filmstrip .filmstrip__videos .videocontainer {
|
||||
/**
|
||||
* Move status icons to the bottom right of the thumbnail.
|
||||
*/
|
||||
.videocontainer__toolbar {
|
||||
/**
|
||||
* FIXME: disable pointer to allow any elements moved below to still
|
||||
* be clickable. The real fix would to make sure those moved elements
|
||||
* are actually part of the toolbar instead of positioning being faked.
|
||||
*/
|
||||
pointer-events: none;
|
||||
text-align: right;
|
||||
|
||||
> div {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: none;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.toolbar-icon {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply hardware acceleration to prevent flickering on scroll. The
|
||||
* selectors are specific to icon wrappers to prevent fixed position dialogs
|
||||
* and tooltips from getting a new location context due to translate3d.
|
||||
*/
|
||||
.connection-indicator,
|
||||
.remote-video-menu-trigger,
|
||||
.indicator-icon-container {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.indicator-container {
|
||||
float: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the remote video menu trigger to the bottom left of the video
|
||||
* thumbnail.
|
||||
*/
|
||||
.remotevideomenu,
|
||||
.remote-video-menu-trigger {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: auto;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.remote-video-menu-trigger {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides for quality labels in filmstrip only mode. The styles adjust the
|
||||
* labels' positioning as the filmstrip itself or filmstrip's remote videos
|
||||
* appear and disappear.
|
||||
*
|
||||
* The class with-filmstrip is for when the filmstrip is visible.
|
||||
* The class without-filmstrip is for when the filmstrip has been toggled to
|
||||
* be hidden.
|
||||
* The class opening is for when the filmstrip is transitioning from hidden
|
||||
* to visible.
|
||||
*/
|
||||
.vertical-filmstrip .large-video-labels {
|
||||
&.with-filmstrip {
|
||||
right: 150px;
|
||||
}
|
||||
|
||||
&.with-filmstrip.opening {
|
||||
transition: 0.9s;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
&.without-filmstrip {
|
||||
transition: 1.2s ease-in-out;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
/* Modules BEGIN */
|
||||
|
||||
@import 'aui_reset';
|
||||
@import 'atlaskit_overrides';
|
||||
@import 'base';
|
||||
@import 'utils';
|
||||
@import 'overlay/overlay';
|
||||
@@ -41,6 +42,7 @@
|
||||
@import 'modals/dialog';
|
||||
@import 'modals/feedback/feedback';
|
||||
@import 'modals/invite/info';
|
||||
@import 'modals/settings/settings';
|
||||
@import 'modals/speaker_stats/speaker_stats';
|
||||
@import 'modals/video-quality/video-quality';
|
||||
@import 'videolayout_default';
|
||||
@@ -67,10 +69,14 @@
|
||||
@import '404';
|
||||
@import 'policy';
|
||||
@import 'popover';
|
||||
@import 'filmstrip';
|
||||
@import 'filmstrip/filmstrip_toolbar';
|
||||
@import 'filmstrip/horizontal_filmstrip';
|
||||
@import 'filmstrip/small_video';
|
||||
@import 'filmstrip/vertical_filmstrip';
|
||||
@import 'filmstrip/vertical_filmstrip_overrides';
|
||||
@import 'unsupported-browser/main';
|
||||
@import 'modals/invite/add-people';
|
||||
@import 'vertical_filmstrip_overrides';
|
||||
@import 'deep-linking/main';
|
||||
|
||||
@import 'transcription-subtitles';
|
||||
@import 'navigate_section_list';
|
||||
/* Modules END */
|
||||
|
||||
@@ -103,10 +103,14 @@
|
||||
font-size: 14px;
|
||||
|
||||
a {
|
||||
color: 4C9AFF;
|
||||
color: #2684FF;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #B3D4FF;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-input-preview {
|
||||
|
||||
@@ -23,6 +23,21 @@
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-text-wrap {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.footer-telephone-icon {
|
||||
display: flex;
|
||||
transform: scaleX(-1);
|
||||
padding-left: 10px;
|
||||
|
||||
i {
|
||||
line-height: 20px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
.use-new-toolbox {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-dialog {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
|
||||
.info-dialog-action-link {
|
||||
display: inline-block;
|
||||
@@ -129,11 +126,16 @@
|
||||
|
||||
.dial-in-page {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 24px;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
padding: 25px;
|
||||
position: absolute;
|
||||
transform: translateY(-50%);
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
|
||||
.dial-in-numbers-list {
|
||||
@@ -143,6 +145,7 @@
|
||||
|
||||
.dial-in-conference-id {
|
||||
text-align: center;
|
||||
min-width: 200px;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
43
css/modals/settings/_settings.scss
Normal file
43
css/modals/settings/_settings.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
.settings-pane {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
&.profile-pane {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.auth-name {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.device-selection {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.mock-atlaskit-label {
|
||||
color: #56637A;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1.33;
|
||||
padding: 20px 0px 4px 0px;
|
||||
}
|
||||
|
||||
.more-tab,
|
||||
.profile-edit {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profile-edit-field,
|
||||
.settings-sub-pane {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.profile-edit-field {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.language-settings {
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,6 @@
|
||||
|
||||
.circular-label {
|
||||
color: white;
|
||||
font-family: -apple-system, BlinkMacSystemFont, $baseFontFamily;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
opacity: 0.8;
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
z-index: $ringingZ;
|
||||
background: linear-gradient(transparent, #000);
|
||||
opacity: 0.8;
|
||||
@include transparentBg(#283447, 0.95);
|
||||
|
||||
&.solidBG {
|
||||
background: $defaultBackground;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&__content {
|
||||
@@ -22,20 +20,26 @@
|
||||
top: 50%;
|
||||
margin-left: -200px;
|
||||
margin-top: -125px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #1B2638;
|
||||
}
|
||||
|
||||
&__caller-info {
|
||||
.mention {
|
||||
color: #333;
|
||||
}
|
||||
&__status{
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
14
doc/api.md
14
doc/api.md
@@ -194,6 +194,15 @@ changes. The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **emailChange** - event notifications about email
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id, // the id of the participant that changed his email
|
||||
"email": email // the new email
|
||||
}
|
||||
```
|
||||
|
||||
* **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
@@ -290,6 +299,11 @@ You can get the display name of a participant in the conference with the followi
|
||||
var displayName = api.getDisplayName(participantId);
|
||||
```
|
||||
|
||||
You can get the email of a participant in the conference with the following API function:
|
||||
```javascript
|
||||
var email = api.getEmail(participantId);
|
||||
```
|
||||
|
||||
You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
|
||||
```javascript
|
||||
var iframe = api.getIFrame();
|
||||
|
||||
@@ -13,7 +13,7 @@ There are also some complete [example config files](https://github.com/jitsi/jit
|
||||
|
||||
## Network description
|
||||
|
||||
This how the network look like:
|
||||
This is how the network looks:
|
||||
```
|
||||
+ +
|
||||
| |
|
||||
|
||||
@@ -4,7 +4,11 @@ This document describes the required steps for a quick Jitsi Meet installation o
|
||||
|
||||
Debian Wheezy and other older systems may require additional things to be done. Specifically for Wheezy, [libc needs to be updated](http://lists.jitsi.org/pipermail/users/2015-September/010064.html).
|
||||
|
||||
N.B.: 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.
|
||||
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.
|
||||
|
||||
b.) You only need to do this if you want to ___host your own Jitsi server___. If you just want to have a video conference with someone, use https://meet.jit.si instead.
|
||||
|
||||
## Basic Jitsi Meet install
|
||||
|
||||
@@ -59,6 +63,8 @@ org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=<Public.IP.Address>
|
||||
See [the documenation of ice4j](https://github.com/jitsi/ice4j/blob/master/doc/configuration.md)
|
||||
for details.
|
||||
|
||||
By default, anyone who has access to your jitsi instance will be able to start a conferencee: if your server is open to the world, anyone can have a chat with anyone else. If you want to limit the ability to start a conference to registered users, set up a "secure domain". Follow the instructions at https://github.com/jitsi/jicofo#secure-domain.
|
||||
|
||||
### Open a conference
|
||||
|
||||
Launch a web browser (Chrome, Chromium or latest Opera) and enter in the URL bar the hostname (or IP address) you used in the previous step.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
One-to-one calls should avoid going throught the JVB for optimal performance and for optimal resource usage. This is why we've added the peer-to-peer mode where the two participants connect directly to each other. Unfortunately, a direct connection is not always possible between the participants. In those cases you can use a TURN server to relay the traffic (n.b. the JVB does much more than just relay the traffic, so this is not the same as using the JVB to "relay" the traffic).
|
||||
|
||||
This document describes how to enable TURN server support in one-to-one calls in Jitsi Meet. Even tho it gives some hints how to configure [prosody](prosody.im) and [coTURN](https://github.com/coturn/coturn), it assumes a properly configured TURN server and a proprely configured XMPP server.
|
||||
This document describes how to enable TURN server support in one-to-one calls in Jitsi Meet, even though it gives some hints how to configure [prosody](prosody.im) and [coTURN](https://github.com/coturn/coturn), it assumes a properly configured TURN server and a proprely configured XMPP server.
|
||||
|
||||
One way to configure TURN support in meet with a static configuration. You can simply fill out the `p2p.stunServers` option with appropriate values, e.g.:
|
||||
|
||||
|
||||
3686
flow-typed/npm/jquery_v3.x.x.js
vendored
Normal file
3686
flow-typed/npm/jquery_v3.x.x.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2568
flow-typed/npm/lodash_v4.x.x.js
vendored
2568
flow-typed/npm/lodash_v4.x.x.js
vendored
File diff suppressed because it is too large
Load Diff
329
flow-typed/npm/moment_v2.3.x.js
vendored
Normal file
329
flow-typed/npm/moment_v2.3.x.js
vendored
Normal file
@@ -0,0 +1,329 @@
|
||||
// flow-typed signature: c30aa20539f52183d4d30dd36d8ab9c2
|
||||
// flow-typed version: 886cf7c002/moment_v2.3.x/flow_>=v0.63.x
|
||||
|
||||
type moment$MomentOptions = {
|
||||
y?: number | string,
|
||||
year?: number | string,
|
||||
years?: number | string,
|
||||
M?: number | string,
|
||||
month?: number | string,
|
||||
months?: number | string,
|
||||
d?: number | string,
|
||||
day?: number | string,
|
||||
days?: number | string,
|
||||
date?: number | string,
|
||||
h?: number | string,
|
||||
hour?: number | string,
|
||||
hours?: number | string,
|
||||
m?: number | string,
|
||||
minute?: number | string,
|
||||
minutes?: number | string,
|
||||
s?: number | string,
|
||||
second?: number | string,
|
||||
seconds?: number | string,
|
||||
ms?: number | string,
|
||||
millisecond?: number | string,
|
||||
milliseconds?: number | string
|
||||
};
|
||||
|
||||
type moment$MomentObject = {
|
||||
years: number,
|
||||
months: number,
|
||||
date: number,
|
||||
hours: number,
|
||||
minutes: number,
|
||||
seconds: number,
|
||||
milliseconds: number
|
||||
};
|
||||
|
||||
type moment$MomentCreationData = {
|
||||
input: string,
|
||||
format: string,
|
||||
locale: Object,
|
||||
isUTC: boolean,
|
||||
strict: boolean
|
||||
};
|
||||
|
||||
type moment$CalendarFormat = string | ((moment: moment$Moment) => string);
|
||||
|
||||
type moment$CalendarFormats = {
|
||||
sameDay?: moment$CalendarFormat,
|
||||
nextDay?: moment$CalendarFormat,
|
||||
nextWeek?: moment$CalendarFormat,
|
||||
lastDay?: moment$CalendarFormat,
|
||||
lastWeek?: moment$CalendarFormat,
|
||||
sameElse?: moment$CalendarFormat
|
||||
};
|
||||
|
||||
declare class moment$LocaleData {
|
||||
months(moment: moment$Moment): string,
|
||||
monthsShort(moment: moment$Moment): string,
|
||||
monthsParse(month: string): number,
|
||||
weekdays(moment: moment$Moment): string,
|
||||
weekdaysShort(moment: moment$Moment): string,
|
||||
weekdaysMin(moment: moment$Moment): string,
|
||||
weekdaysParse(weekDay: string): number,
|
||||
longDateFormat(dateFormat: string): string,
|
||||
isPM(date: string): boolean,
|
||||
meridiem(hours: number, minutes: number, isLower: boolean): string,
|
||||
calendar(
|
||||
key:
|
||||
| "sameDay"
|
||||
| "nextDay"
|
||||
| "lastDay"
|
||||
| "nextWeek"
|
||||
| "prevWeek"
|
||||
| "sameElse",
|
||||
moment: moment$Moment
|
||||
): string,
|
||||
relativeTime(
|
||||
number: number,
|
||||
withoutSuffix: boolean,
|
||||
key: "s" | "m" | "mm" | "h" | "hh" | "d" | "dd" | "M" | "MM" | "y" | "yy",
|
||||
isFuture: boolean
|
||||
): string,
|
||||
pastFuture(diff: any, relTime: string): string,
|
||||
ordinal(number: number): string,
|
||||
preparse(str: string): any,
|
||||
postformat(str: string): any,
|
||||
week(moment: moment$Moment): string,
|
||||
invalidDate(): string,
|
||||
firstDayOfWeek(): number,
|
||||
firstDayOfYear(): number
|
||||
}
|
||||
declare class moment$MomentDuration {
|
||||
humanize(suffix?: boolean): string,
|
||||
milliseconds(): number,
|
||||
asMilliseconds(): number,
|
||||
seconds(): number,
|
||||
asSeconds(): number,
|
||||
minutes(): number,
|
||||
asMinutes(): number,
|
||||
hours(): number,
|
||||
asHours(): number,
|
||||
days(): number,
|
||||
asDays(): number,
|
||||
months(): number,
|
||||
asMonths(): number,
|
||||
years(): number,
|
||||
asYears(): number,
|
||||
add(value: number | moment$MomentDuration | Object, unit?: string): this,
|
||||
subtract(value: number | moment$MomentDuration | Object, unit?: string): this,
|
||||
as(unit: string): number,
|
||||
get(unit: string): number,
|
||||
toJSON(): string,
|
||||
toISOString(): string,
|
||||
isValid(): boolean
|
||||
}
|
||||
declare class moment$Moment {
|
||||
static ISO_8601: string,
|
||||
static (
|
||||
string?: string,
|
||||
format?: string | Array<string>,
|
||||
strict?: boolean
|
||||
): moment$Moment,
|
||||
static (
|
||||
string?: string,
|
||||
format?: string | Array<string>,
|
||||
locale?: string,
|
||||
strict?: boolean
|
||||
): moment$Moment,
|
||||
static (
|
||||
initDate: ?Object | number | Date | Array<number> | moment$Moment | string
|
||||
): moment$Moment,
|
||||
static unix(seconds: number): moment$Moment,
|
||||
static utc(): moment$Moment,
|
||||
static utc(number: number | Array<number>): moment$Moment,
|
||||
static utc(
|
||||
str: string,
|
||||
str2?: string | Array<string>,
|
||||
str3?: string
|
||||
): moment$Moment,
|
||||
static utc(moment: moment$Moment): moment$Moment,
|
||||
static utc(date: Date): moment$Moment,
|
||||
static parseZone(): moment$Moment,
|
||||
static parseZone(rawDate: string): moment$Moment,
|
||||
static parseZone(
|
||||
rawDate: string,
|
||||
format: string | Array<string>
|
||||
): moment$Moment,
|
||||
static parseZone(
|
||||
rawDate: string,
|
||||
format: string,
|
||||
strict: boolean
|
||||
): moment$Moment,
|
||||
static parseZone(
|
||||
rawDate: string,
|
||||
format: string,
|
||||
locale: string,
|
||||
strict: boolean
|
||||
): moment$Moment,
|
||||
isValid(): boolean,
|
||||
invalidAt(): 0 | 1 | 2 | 3 | 4 | 5 | 6,
|
||||
creationData(): moment$MomentCreationData,
|
||||
millisecond(number: number): this,
|
||||
milliseconds(number: number): this,
|
||||
millisecond(): number,
|
||||
milliseconds(): number,
|
||||
second(number: number): this,
|
||||
seconds(number: number): this,
|
||||
second(): number,
|
||||
seconds(): number,
|
||||
minute(number: number): this,
|
||||
minutes(number: number): this,
|
||||
minute(): number,
|
||||
minutes(): number,
|
||||
hour(number: number): this,
|
||||
hours(number: number): this,
|
||||
hour(): number,
|
||||
hours(): number,
|
||||
date(number: number): this,
|
||||
dates(number: number): this,
|
||||
date(): number,
|
||||
dates(): number,
|
||||
day(day: number | string): this,
|
||||
days(day: number | string): this,
|
||||
day(): number,
|
||||
days(): number,
|
||||
weekday(number: number): this,
|
||||
weekday(): number,
|
||||
isoWeekday(number: number): this,
|
||||
isoWeekday(): number,
|
||||
dayOfYear(number: number): this,
|
||||
dayOfYear(): number,
|
||||
week(number: number): this,
|
||||
weeks(number: number): this,
|
||||
week(): number,
|
||||
weeks(): number,
|
||||
isoWeek(number: number): this,
|
||||
isoWeeks(number: number): this,
|
||||
isoWeek(): number,
|
||||
isoWeeks(): number,
|
||||
month(number: number): this,
|
||||
months(number: number): this,
|
||||
month(): number,
|
||||
months(): number,
|
||||
quarter(number: number): this,
|
||||
quarter(): number,
|
||||
year(number: number): this,
|
||||
years(number: number): this,
|
||||
year(): number,
|
||||
years(): number,
|
||||
weekYear(number: number): this,
|
||||
weekYear(): number,
|
||||
isoWeekYear(number: number): this,
|
||||
isoWeekYear(): number,
|
||||
weeksInYear(): number,
|
||||
isoWeeksInYear(): number,
|
||||
get(string: string): number,
|
||||
set(unit: string, value: number): this,
|
||||
set(options: { [unit: string]: number }): this,
|
||||
static max(...dates: Array<moment$Moment>): moment$Moment,
|
||||
static max(dates: Array<moment$Moment>): moment$Moment,
|
||||
static min(...dates: Array<moment$Moment>): moment$Moment,
|
||||
static min(dates: Array<moment$Moment>): moment$Moment,
|
||||
add(
|
||||
value: number | moment$MomentDuration | moment$Moment | Object,
|
||||
unit?: string
|
||||
): this,
|
||||
subtract(
|
||||
value: number | moment$MomentDuration | moment$Moment | string | Object,
|
||||
unit?: string
|
||||
): this,
|
||||
startOf(unit: string): this,
|
||||
endOf(unit: string): this,
|
||||
local(): this,
|
||||
utc(): this,
|
||||
utcOffset(
|
||||
offset: number | string,
|
||||
keepLocalTime?: boolean,
|
||||
keepMinutes?: boolean
|
||||
): this,
|
||||
utcOffset(): number,
|
||||
format(format?: string): string,
|
||||
fromNow(removeSuffix?: boolean): string,
|
||||
from(
|
||||
value: moment$Moment | string | number | Date | Array<number>,
|
||||
removePrefix?: boolean
|
||||
): string,
|
||||
toNow(removePrefix?: boolean): string,
|
||||
to(
|
||||
value: moment$Moment | string | number | Date | Array<number>,
|
||||
removePrefix?: boolean
|
||||
): string,
|
||||
calendar(refTime?: any, formats?: moment$CalendarFormats): string,
|
||||
diff(
|
||||
date: moment$Moment | string | number | Date | Array<number>,
|
||||
format?: string,
|
||||
floating?: boolean
|
||||
): number,
|
||||
valueOf(): number,
|
||||
unix(): number,
|
||||
daysInMonth(): number,
|
||||
toDate(): Date,
|
||||
toArray(): Array<number>,
|
||||
toJSON(): string,
|
||||
toISOString(
|
||||
keepOffset?: boolean
|
||||
): string,
|
||||
toObject(): moment$MomentObject,
|
||||
isBefore(
|
||||
date?: moment$Moment | string | number | Date | Array<number>,
|
||||
units?: ?string
|
||||
): boolean,
|
||||
isSame(
|
||||
date?: moment$Moment | string | number | Date | Array<number>,
|
||||
units?: ?string
|
||||
): boolean,
|
||||
isAfter(
|
||||
date?: moment$Moment | string | number | Date | Array<number>,
|
||||
units?: ?string
|
||||
): boolean,
|
||||
isSameOrBefore(
|
||||
date?: moment$Moment | string | number | Date | Array<number>,
|
||||
units?: ?string
|
||||
): boolean,
|
||||
isSameOrAfter(
|
||||
date?: moment$Moment | string | number | Date | Array<number>,
|
||||
units?: ?string
|
||||
): boolean,
|
||||
isBetween(
|
||||
fromDate: moment$Moment | string | number | Date | Array<number>,
|
||||
toDate?: ?moment$Moment | string | number | Date | Array<number>,
|
||||
granularity?: ?string,
|
||||
inclusion?: ?string
|
||||
): boolean,
|
||||
isDST(): boolean,
|
||||
isDSTShifted(): boolean,
|
||||
isLeapYear(): boolean,
|
||||
clone(): moment$Moment,
|
||||
static isMoment(obj: any): boolean,
|
||||
static isDate(obj: any): boolean,
|
||||
static locale(locale: string, localeData?: Object): string,
|
||||
static updateLocale(locale: string, localeData?: ?Object): void,
|
||||
static locale(locales: Array<string>): string,
|
||||
locale(locale: string, customization?: Object | null): moment$Moment,
|
||||
locale(): string,
|
||||
static months(): Array<string>,
|
||||
static monthsShort(): Array<string>,
|
||||
static weekdays(): Array<string>,
|
||||
static weekdaysShort(): Array<string>,
|
||||
static weekdaysMin(): Array<string>,
|
||||
static months(): string,
|
||||
static monthsShort(): string,
|
||||
static weekdays(): string,
|
||||
static weekdaysShort(): string,
|
||||
static weekdaysMin(): string,
|
||||
static localeData(key?: string): moment$LocaleData,
|
||||
static duration(
|
||||
value: number | Object | string,
|
||||
unit?: string
|
||||
): moment$MomentDuration,
|
||||
static isDuration(obj: any): boolean,
|
||||
static normalizeUnits(unit: string): string,
|
||||
static invalid(object: any): moment$Moment
|
||||
}
|
||||
|
||||
declare module "moment" {
|
||||
declare module.exports: Class<moment$Moment>;
|
||||
}
|
||||
5
flow-typed/npm/prop-types_v15.x.x.js
vendored
5
flow-typed/npm/prop-types_v15.x.x.js
vendored
@@ -1,5 +1,5 @@
|
||||
// flow-typed signature: 3eaa1f24c7397b78a7481992d2cddcb2
|
||||
// flow-typed version: a1a20d4928/prop-types_v15.x.x/flow_>=v0.41.x
|
||||
// flow-typed signature: d9a983bb1ac458a256c31c139047bdbb
|
||||
// flow-typed version: 927687984d/prop-types_v15.x.x/flow_>=v0.41.x
|
||||
|
||||
type $npm$propTypes$ReactPropsCheckType = (
|
||||
props: any,
|
||||
@@ -14,6 +14,7 @@ declare module 'prop-types' {
|
||||
declare var number: React$PropType$Primitive<number>;
|
||||
declare var object: React$PropType$Primitive<Object>;
|
||||
declare var string: React$PropType$Primitive<string>;
|
||||
declare var symbol: React$PropType$Primitive<Symbol>;
|
||||
declare var any: React$PropType$Primitive<any>;
|
||||
declare var arrayOf: React$PropType$ArrayOf;
|
||||
declare var element: React$PropType$Primitive<any>; /* TODO */
|
||||
|
||||
292
flow-typed/npm/react-redux_v5.x.x.js
vendored
292
flow-typed/npm/react-redux_v5.x.x.js
vendored
@@ -1,132 +1,192 @@
|
||||
// flow-typed signature: 59b0c4be0e1408f21e2446be96c79804
|
||||
// flow-typed version: 9092387fd2/react-redux_v5.x.x/flow_>=v0.54.x
|
||||
// flow-typed signature: d4e793bc07ef1dc9906a244b12960f7b
|
||||
// flow-typed version: cf33ff8762/react-redux_v5.x.x/flow_>=v0.63.0
|
||||
|
||||
import type { Dispatch, Store } from "redux";
|
||||
|
||||
declare module "react-redux" {
|
||||
/*
|
||||
import type { ComponentType, ElementConfig } from 'react';
|
||||
|
||||
S = State
|
||||
A = Action
|
||||
OP = OwnProps
|
||||
SP = StateProps
|
||||
DP = DispatchProps
|
||||
|
||||
*/
|
||||
|
||||
declare type MapStateToProps<S, OP: Object, SP: Object> = (
|
||||
state: S,
|
||||
ownProps: OP
|
||||
) => ((state: S, ownProps: OP) => SP) | SP;
|
||||
|
||||
declare type MapDispatchToProps<A, OP: Object, DP: Object> =
|
||||
| ((dispatch: Dispatch<A>, ownProps: OP) => DP)
|
||||
| DP;
|
||||
|
||||
declare type MergeProps<SP, DP: Object, OP: Object, P: Object> = (
|
||||
stateProps: SP,
|
||||
dispatchProps: DP,
|
||||
ownProps: OP
|
||||
) => P;
|
||||
|
||||
declare type Context = { store: Store<*, *> };
|
||||
|
||||
declare type ComponentWithDefaultProps<DP: {}, P: {}, CP: P> = Class<
|
||||
React$Component<CP>
|
||||
> & { defaultProps: DP };
|
||||
|
||||
declare class ConnectedComponentWithDefaultProps<
|
||||
OP,
|
||||
DP,
|
||||
CP
|
||||
> extends React$Component<OP> {
|
||||
static defaultProps: DP, // <= workaround for https://github.com/facebook/flow/issues/4644
|
||||
static WrappedComponent: Class<React$Component<CP>>,
|
||||
getWrappedInstance(): React$Component<CP>,
|
||||
props: OP,
|
||||
state: void
|
||||
}
|
||||
|
||||
declare class ConnectedComponent<OP, P> extends React$Component<OP> {
|
||||
static WrappedComponent: Class<React$Component<P>>,
|
||||
getWrappedInstance(): React$Component<P>,
|
||||
props: OP,
|
||||
state: void
|
||||
}
|
||||
|
||||
declare type ConnectedComponentWithDefaultPropsClass<OP, DP, CP> = Class<
|
||||
ConnectedComponentWithDefaultProps<OP, DP, CP>
|
||||
>;
|
||||
|
||||
declare type ConnectedComponentClass<OP, P> = Class<
|
||||
ConnectedComponent<OP, P>
|
||||
>;
|
||||
|
||||
declare type Connector<OP, P> = (<DP: {}, CP: {}>(
|
||||
component: ComponentWithDefaultProps<DP, P, CP>
|
||||
) => ConnectedComponentWithDefaultPropsClass<OP, DP, CP>) &
|
||||
((component: React$ComponentType<P>) => ConnectedComponentClass<OP, P>);
|
||||
|
||||
declare class Provider<S, A> extends React$Component<{
|
||||
declare export class Provider<S, A> extends React$Component<{
|
||||
store: Store<S, A>,
|
||||
children?: any
|
||||
}> {}
|
||||
|
||||
declare function createProvider(
|
||||
declare export function createProvider(
|
||||
storeKey?: string,
|
||||
subKey?: string
|
||||
): Provider<*, *>;
|
||||
|
||||
declare type ConnectOptions = {
|
||||
/*
|
||||
|
||||
S = State
|
||||
A = Action
|
||||
OP = OwnProps
|
||||
SP = StateProps
|
||||
DP = DispatchProps
|
||||
MP = Merge props
|
||||
MDP = Map dispatch to props object
|
||||
RSP = Returned state props
|
||||
RDP = Returned dispatch props
|
||||
RMP = Returned merge props
|
||||
CP = Props for returned component
|
||||
Com = React Component
|
||||
*/
|
||||
|
||||
declare type MapStateToProps<S: Object, SP: Object, RSP: Object> = (state: S, props: SP) => RSP;
|
||||
|
||||
declare type MapDispatchToProps<A, OP: Object, RDP: Object> = (dispatch: Dispatch<A>, ownProps: OP) => RDP;
|
||||
|
||||
declare type MergeProps<SP: Object, DP: Object, MP: Object, RMP: Object> = (
|
||||
stateProps: SP,
|
||||
dispatchProps: DP,
|
||||
ownProps: MP
|
||||
) => RMP;
|
||||
|
||||
declare type ConnectOptions<S: Object, OP: Object, RSP: Object, RMP: Object> = {|
|
||||
pure?: boolean,
|
||||
withRef?: boolean
|
||||
withRef?: boolean,
|
||||
areStatesEqual?: (next: S, prev: S) => boolean,
|
||||
areOwnPropsEqual?: (next: OP, prev: OP) => boolean,
|
||||
areStatePropsEqual?: (next: RSP, prev: RSP) => boolean,
|
||||
areMergedPropsEqual?: (next: RMP, prev: RMP) => boolean,
|
||||
storeKey?: string
|
||||
|};
|
||||
|
||||
declare type OmitDispatch<Component> = $Diff<Component, {dispatch: Dispatch<*>}>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
RSP: Object,
|
||||
CP: $Diff<OmitDispatch<ElementConfig<Com>>, RSP>
|
||||
>(
|
||||
mapStateToProps: MapStateToProps<S, DP, RSP>,
|
||||
mapDispatchToProps?: null
|
||||
): (component: Com) => ComponentType<CP & DP>;
|
||||
|
||||
declare export function connect<Com: ComponentType<*>>(
|
||||
mapStateToProps?: null,
|
||||
mapDispatchToProps?: null
|
||||
): (component: Com) => ComponentType<OmitDispatch<ElementConfig<Com>>>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
A,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
RDP: Object,
|
||||
CP: $Diff<$Diff<ElementConfig<Com>, RSP>, RDP>
|
||||
>(
|
||||
mapStateToProps: MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToProps: MapDispatchToProps<A, DP, RDP>
|
||||
): (component: Com) => ComponentType<CP & SP & DP>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
A,
|
||||
OP: Object,
|
||||
DP: Object,
|
||||
PR: Object,
|
||||
CP: $Diff<ElementConfig<Com>, DP>
|
||||
>(
|
||||
mapStateToProps?: null,
|
||||
mapDispatchToProps: MapDispatchToProps<A, OP, DP>
|
||||
): (Com) => ComponentType<CP & OP>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
MDP: Object
|
||||
>(
|
||||
mapStateToProps?: null,
|
||||
mapDispatchToProps: MDP
|
||||
): (component: Com) => ComponentType<$Diff<ElementConfig<Com>, MDP>>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
S: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
MDP: Object,
|
||||
CP: $Diff<ElementConfig<Com>, RSP>
|
||||
>(
|
||||
mapStateToProps: MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToPRops: MDP
|
||||
): (component: Com) => ComponentType<$Diff<CP, MDP> & SP>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
A,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
RDP: Object,
|
||||
MP: Object,
|
||||
RMP: Object,
|
||||
CP: $Diff<ElementConfig<Com>, RMP>
|
||||
>(
|
||||
mapStateToProps: MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToProps: ?MapDispatchToProps<A, DP, RDP>,
|
||||
mergeProps: MergeProps<RSP, RDP, MP, RMP>
|
||||
): (component: Com) => ComponentType<CP & SP & DP & MP>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
A,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
RDP: Object,
|
||||
MDP: Object,
|
||||
MP: Object,
|
||||
RMP: Object,
|
||||
CP: $Diff<ElementConfig<Com>, RMP>
|
||||
>(
|
||||
mapStateToProps: MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToProps: MDP,
|
||||
mergeProps: MergeProps<RSP, RDP, MP, RMP>
|
||||
): (component: Com) => ComponentType<CP & SP & DP & MP>;
|
||||
|
||||
declare export function connect<Com: ComponentType<*>,
|
||||
A,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
RDP: Object,
|
||||
MP: Object,
|
||||
RMP: Object
|
||||
>(
|
||||
mapStateToProps: ?MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToProps: ?MapDispatchToProps<A, DP, RDP>,
|
||||
mergeProps: ?MergeProps<RSP, RDP, MP, RMP>,
|
||||
options: ConnectOptions<S, SP & DP & MP, RSP, RMP>
|
||||
): (component: Com) => ComponentType<$Diff<ElementConfig<Com>, RMP> & SP & DP & MP>;
|
||||
|
||||
declare export function connect<Com: ComponentType<*>,
|
||||
A,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
RDP: Object,
|
||||
MDP: Object,
|
||||
MP: Object,
|
||||
RMP: Object
|
||||
>(
|
||||
mapStateToProps: ?MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToProps: ?MapDispatchToProps<A, DP, RDP>,
|
||||
mergeProps: MDP,
|
||||
options: ConnectOptions<S, SP & DP & MP, RSP, RMP>
|
||||
): (component: Com) => ComponentType<$Diff<ElementConfig<Com>, RMP> & SP & DP & MP>;
|
||||
|
||||
declare export default {
|
||||
Provider: typeof Provider,
|
||||
createProvider: typeof createProvider,
|
||||
connect: typeof connect,
|
||||
};
|
||||
|
||||
declare type Null = null | void;
|
||||
|
||||
declare function connect<A, OP>(
|
||||
...rest: Array<void> // <= workaround for https://github.com/facebook/flow/issues/2360
|
||||
): Connector<OP, $Supertype<{ dispatch: Dispatch<A> } & OP>>;
|
||||
|
||||
declare function connect<A, OP>(
|
||||
mapStateToProps: Null,
|
||||
mapDispatchToProps: Null,
|
||||
mergeProps: Null,
|
||||
options: ConnectOptions
|
||||
): Connector<OP, $Supertype<{ dispatch: Dispatch<A> } & OP>>;
|
||||
|
||||
// declare function connect<S, A, OP, SP>(
|
||||
// mapStateToProps: MapStateToProps<S, OP, SP>,
|
||||
// mapDispatchToProps: Null,
|
||||
// mergeProps: Null,
|
||||
// options?: ConnectOptions
|
||||
// ): Connector<OP, $Supertype<SP & { dispatch: Dispatch<A> } & OP>>;
|
||||
|
||||
declare function connect<A, OP, DP>(
|
||||
mapStateToProps: Null,
|
||||
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
|
||||
mergeProps: Null,
|
||||
options?: ConnectOptions
|
||||
): Connector<OP, $Supertype<DP & OP>>;
|
||||
|
||||
declare function connect<S, A, OP, SP, DP>(
|
||||
mapStateToProps: MapStateToProps<S, OP, SP>,
|
||||
mapDispatchToProps: MapDispatchToProps<A, OP, DP> | Null,
|
||||
mergeProps: Null,
|
||||
options?: ConnectOptions
|
||||
): Connector<OP, $Supertype<SP & DP & OP>>;
|
||||
|
||||
declare function connect<S, A, OP, SP, DP, P>(
|
||||
mapStateToProps: MapStateToProps<S, OP, SP>,
|
||||
mapDispatchToProps: Null,
|
||||
mergeProps: MergeProps<SP, DP, OP, P>,
|
||||
options?: ConnectOptions
|
||||
): Connector<OP, P>;
|
||||
|
||||
declare function connect<S, A, OP, SP, DP, P>(
|
||||
mapStateToProps: MapStateToProps<S, OP, SP>,
|
||||
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
|
||||
mergeProps: MergeProps<SP, DP, OP, P>,
|
||||
options?: ConnectOptions
|
||||
): Connector<OP, P>;
|
||||
}
|
||||
|
||||
9
flow-typed/npm/redux_v3.x.x.js
vendored
9
flow-typed/npm/redux_v3.x.x.js
vendored
@@ -1,6 +1,3 @@
|
||||
// flow-typed signature: ec7daead5cb4fec5ab25fedbedef29e8
|
||||
// flow-typed version: 2c04631d20/redux_v3.x.x/flow_>=v0.55.x
|
||||
|
||||
declare module 'redux' {
|
||||
|
||||
/*
|
||||
@@ -27,7 +24,7 @@ declare module 'redux' {
|
||||
replaceReducer(nextReducer: Reducer<S, A>): void
|
||||
};
|
||||
|
||||
declare export type Reducer<S, A> = (state: S, action: A) => S;
|
||||
declare export type Reducer<S, A> = (state: S | void, action: A) => S;
|
||||
|
||||
declare export type CombinedReducer<S, A> = (state: $Shape<S> & {} | void, action: A) => S;
|
||||
|
||||
@@ -43,7 +40,7 @@ declare module 'redux' {
|
||||
declare export type StoreEnhancer<S, A, D = Dispatch<A>> = (next: StoreCreator<S, A, D>) => StoreCreator<S, A, D>;
|
||||
|
||||
declare export function createStore<S, A, D>(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
|
||||
declare export function createStore<S, A, D>(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
|
||||
declare export function createStore<S, A, D>(reducer: Reducer<S, A>, preloadedState?: S, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
|
||||
|
||||
declare export function applyMiddleware<S, A, D>(...middlewares: Array<Middleware<S, A, D>>): StoreEnhancer<S, A, D>;
|
||||
|
||||
@@ -56,4 +53,4 @@ declare module 'redux' {
|
||||
declare export function combineReducers<O: Object, A>(reducers: O): CombinedReducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
|
||||
|
||||
declare export var compose: $Compose;
|
||||
}
|
||||
}
|
||||
|
||||
27
flow-typed/npm/uuid_v3.x.x.js
vendored
27
flow-typed/npm/uuid_v3.x.x.js
vendored
@@ -1,5 +1,5 @@
|
||||
// flow-typed signature: 615e568e95029d58f116dd157e320137
|
||||
// flow-typed version: 2b95c0dfc1/uuid_v3.x.x/flow_>=v0.32.x
|
||||
// flow-typed signature: 3cf668e64747095cab0bb360cf2fb34f
|
||||
// flow-typed version: d659bd0cb8/uuid_v3.x.x/flow_>=v0.32.x
|
||||
|
||||
declare module "uuid" {
|
||||
declare class uuid {
|
||||
@@ -52,6 +52,23 @@ declare module "uuid/v1" {
|
||||
declare module.exports: Class<v1>;
|
||||
}
|
||||
|
||||
declare module "uuid/v3" {
|
||||
declare class v3 {
|
||||
static (
|
||||
name?: string | number[],
|
||||
namespace?: string | number[],
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string,
|
||||
|
||||
static name: string,
|
||||
static DNS: string,
|
||||
static URL: string
|
||||
}
|
||||
|
||||
declare module.exports: Class<v3>;
|
||||
}
|
||||
|
||||
declare module "uuid/v4" {
|
||||
declare class v4 {
|
||||
static (
|
||||
@@ -74,7 +91,11 @@ declare module "uuid/v5" {
|
||||
namespace?: string | number[],
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string
|
||||
): string,
|
||||
|
||||
static name: string,
|
||||
static DNS: string,
|
||||
static URL: string
|
||||
}
|
||||
|
||||
declare module.exports: Class<v5>;
|
||||
|
||||
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
@@ -15,6 +15,7 @@
|
||||
<glyph unicode="" glyph-name="navigate_next" d="M426 768l256-256-256-256-60 60 196 196-196 196z" />
|
||||
<glyph unicode="" glyph-name="timer" d="M512 170c166 0 298 134 298 300s-132 298-298 298-298-132-298-298 132-300 298-300zM812 708c52-66 84-148 84-238 0-212-172-384-384-384s-384 172-384 384 172 384 384 384c90 0 174-34 240-86l60 62c22-18 42-38 60-60zM470 426v256h84v-256h-84zM640 982v-86h-256v86h256z" />
|
||||
<glyph unicode="" glyph-name="arrow_back" d="M854 554v-84h-520l238-240-60-60-342 342 342 342 60-60-238-240h520z" />
|
||||
<glyph unicode="" glyph-name="close" d="M810 750l-238-238 238-238-60-60-238 238-238-238-60 60 238 238-238 238 60 60 238-238 238 238z" />
|
||||
<glyph unicode="" glyph-name="menu" d="M128 768h768v-86h-768v86zM128 470v84h768v-84h-768zM128 256v86h768v-86h-768z" />
|
||||
<glyph unicode="" glyph-name="thumb-menu" d="M512 342c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 598c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 682c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86z" />
|
||||
<glyph unicode="" glyph-name="presentation" horiz-adv-x="1088" d="M952.495 1019.065h-818.689c-72.81 0-132.183-60.63-132.183-135.162v-750.719c0-74.473 59.372-135.101 132.183-135.101h818.686c72.936 0 132.314 60.625 132.314 135.101v750.722c0.003 74.532-59.378 135.159-132.311 135.159zM946.346 139.651h-806.14v737.822h806.015l0.126-737.822zM685.753 738.544h216.911v-566.758h-216.911v566.758zM428.672 610.002h216.911v-438.216h-216.911v438.216zM172.339 481.46h216.161v-309.677h-216.161v309.677z" />
|
||||
@@ -70,4 +71,8 @@
|
||||
<glyph unicode="" glyph-name="camera-take-picture" d="M725.333 512c0-117.821-95.513-213.333-213.333-213.333s-213.333 95.513-213.333 213.333c0 117.821 95.513 213.333 213.333 213.333s213.333-95.513 213.333-213.333zM512 256c141.385 0 256 114.615 256 256s-114.615 256-256 256v0c-141.385 0-256-114.615-256-256s114.615-256 256-256v0zM512 213.333c-164.949 0-298.667 133.718-298.667 298.667s133.718 298.667 298.667 298.667v0c164.949 0 298.667-133.718 298.667-298.667s-133.718-298.667-298.667-298.667v0z" />
|
||||
<glyph unicode="" glyph-name="rec" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM581.333 433.782h-110.595v59.233h104.338v40.332h-104.338v56.87h110.595v43.539h-161.665v-243.512h161.665v43.539zM738.771 384c58.849 0 101.802 36.282 106.029 88.933h-49.717c-4.904-26.832-26.888-44.045-56.143-44.045-38.556 0-62.4 31.895-62.4 83.196s23.844 83.027 62.231 83.027c29.086 0 51.239-18.394 56.143-46.407h49.717c-3.72 52.989-48.026 91.296-105.86 91.296-70.855 0-114.485-48.77-114.485-127.916 0-79.314 43.798-128.084 114.485-128.084zM230.27 478.502h41.769l45.489-88.258h57.834l-51.408 96.19c28.072 11.138 44.306 38.138 44.306 69.189 0 48.432-32.976 78.133-86.582 78.133h-102.478v-243.512h51.070v88.258zM230.27 592.58v-74.927h44.813c25.704 0 40.754 13.838 40.754 37.295 0 23.119-15.896 37.632-41.262 37.632h-44.306z" />
|
||||
<glyph unicode="" glyph-name="live" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM298.225 430.025h-112.35v206.5h-52.85v-252.525h165.2v46.025zM399.025 384v252.525h-52.85v-252.525h52.85zM591.525 384l84.175 252.525h-56.875l-56.35-193.025h-3.15l-57.4 193.025h-59.675l85.4-252.525h63.875zM886.050 429.15h-114.45v61.425h107.975v41.825h-107.975v58.975h114.45v45.15h-167.3v-252.525h167.3v45.15z" />
|
||||
<glyph unicode="" glyph-name="speaker" d="M0 512c0-282.795 229.205-512 512-512s512 229.205 512 512c0 282.795-229.205 512-512 512s-512-229.205-512-512zM525.005 759.362c-20.475 24.944-16.326 61.342 9.268 81.297s62.94 15.911 83.416-9.033c16.036-19.536 38.593-52.97 60.894-97.797 81.621-164.065 89.461-340.992-26.857-506.352-8.384-11.919-17.386-23.69-27.012-35.307-20.593-24.851-57.959-28.727-83.458-8.657s-29.476 56.487-8.882 81.338c7.686 9.275 14.833 18.621 21.455 28.035 88.66 126.041 82.71 260.306 17.953 390.475-10.599 21.305-21.94 40.51-33.198 57.196-6.515 9.657-11.322 16.057-13.578 18.805zM353.479 647.46c-19.353 24.679-15.129 60.448 9.434 79.893s60.164 15.2 79.517-9.479c9.635-12.287 22.577-32.644 35.209-60.034 50.35-109.176 50.35-231.689-33.639-349.612-18.198-25.551-53.566-31.441-78.997-13.157s-31.294 53.819-13.096 79.37c57.564 80.822 57.564 160.581 22.983 235.565-8.601 18.65-16.892 31.691-21.412 37.455z" />
|
||||
<glyph unicode="" glyph-name="tiles-many" d="M113.778 1024h227.556c62.838 0 113.778-50.94 113.778-113.778v-227.556c0-62.838-50.94-113.778-113.778-113.778h-227.556c-62.838 0-113.778 50.94-113.778 113.778v227.556c0 62.838 50.94 113.778 113.778 113.778zM170.667 910.222c-31.419 0-56.889-25.47-56.889-56.889v-113.778c0-31.419 25.47-56.889 56.889-56.889h113.778c31.419 0 56.889 25.47 56.889 56.889v113.778c0 31.419-25.47 56.889-56.889 56.889h-113.778zM113.778 455.111h227.556c62.838 0 113.778-50.94 113.778-113.778v-227.556c0-62.838-50.94-113.778-113.778-113.778h-227.556c-62.838 0-113.778 50.94-113.778 113.778v227.556c0 62.838 50.94 113.778 113.778 113.778zM170.667 341.333c-31.419 0-56.889-25.47-56.889-56.889v-113.778c0-31.419 25.47-56.889 56.889-56.889h113.778c31.419 0 56.889 25.47 56.889 56.889v113.778c0 31.419-25.47 56.889-56.889 56.889h-113.778zM682.667 1024h227.556c62.838 0 113.778-50.94 113.778-113.778v-227.556c0-62.838-50.94-113.778-113.778-113.778h-227.556c-62.838 0-113.778 50.94-113.778 113.778v227.556c0 62.838 50.94 113.778 113.778 113.778zM739.556 910.222c-31.419 0-56.889-25.47-56.889-56.889v-113.778c0-31.419 25.47-56.889 56.889-56.889h113.778c31.419 0 56.889 25.47 56.889 56.889v113.778c0 31.419-25.47 56.889-56.889 56.889h-113.778zM682.667 455.111h227.556c62.838 0 113.778-50.94 113.778-113.778v-227.556c0-62.838-50.94-113.778-113.778-113.778h-227.556c-62.838 0-113.778 50.94-113.778 113.778v227.556c0 62.838 50.94 113.778 113.778 113.778zM739.556 341.333c-31.419 0-56.889-25.47-56.889-56.889v-113.778c0-31.419 25.47-56.889 56.889-56.889h113.778c31.419 0 56.889 25.47 56.889 56.889v113.778c0 31.419-25.47 56.889-56.889 56.889h-113.778z" />
|
||||
<glyph unicode="" glyph-name="tiles-one" d="M170.667 810.667h682.667c47.128 0 85.333-38.205 85.333-85.333v-426.667c0-47.128-38.205-85.333-85.333-85.333h-682.667c-47.128 0-85.333 38.205-85.333 85.333v426.667c0 47.128 38.205 85.333 85.333 85.333zM213.333 725.333c-23.564 0-42.667-19.103-42.667-42.667v-341.333c0-23.564 19.103-42.667 42.667-42.667h597.333c23.564 0 42.667 19.103 42.667 42.667v341.333c0 23.564-19.103 42.667-42.667 42.667h-597.333z" />
|
||||
<glyph unicode="" glyph-name="closed_caption" d="M768 554v44c0 24-18 42-42 42h-128c-24 0-44-18-44-42v-172c0-24 20-42 44-42h128c24 0 42 18 42 42v44h-64v-22h-86v128h86v-22h64zM470 554v44c0 24-20 42-44 42h-128c-24 0-42-18-42-42v-172c0-24 18-42 42-42h128c24 0 44 18 44 42v44h-64v-22h-86v128h86v-22h64zM810 854c46 0 86-40 86-86v-512c0-46-40-86-86-86h-596c-48 0-86 40-86 86v512c0 46 38 86 86 86h596z" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 28 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
1293
fonts/selection.json
1293
fonts/selection.json
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 98 KiB |
@@ -1,26 +1,2 @@
|
||||
// The type field of react-native application loader's React Element is created
|
||||
// as number and not Symbol, because it's not been defined by the polyfill yet.
|
||||
// We import the application renderer, before Symbol is defined, in order to use
|
||||
// number types as well. Otherwise this will result in the invariant exception,
|
||||
// because fiber thingy will not recognise root react-native component as React
|
||||
// Element, but as an Object.
|
||||
//
|
||||
// See node_modules/react-native/Libraries/polyfills/babelHelpers.js
|
||||
// :babelHelpers.createRawReactElement - that's where first react-native element
|
||||
// is created (super early - it's the app loader).
|
||||
//
|
||||
// See node_modules/react-native/Libraries/Renderer/ReactNativeFiber-dev.js
|
||||
// and look for REACT_ELEMENT_TYPE definition - it's defined later when Symbol
|
||||
// has been defined and type will not match.
|
||||
//
|
||||
// As an alternative solution we could stop using/polyfilling Symbols and
|
||||
// replace with classpath string constants or some kind of a wrapper around
|
||||
// that.
|
||||
|
||||
import 'react-native/Libraries/ReactNative/renderApplication';
|
||||
|
||||
// Android doesn't provide Symbol
|
||||
import 'es6-symbol/implement';
|
||||
|
||||
import './react/index.native';
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@ var interfaceConfig = {
|
||||
DEFAULT_BACKGROUND: '#474747',
|
||||
|
||||
/**
|
||||
* In case the desktop sharing is disabled through the config the button
|
||||
* will not be hidden, but displayed as disabled with this text us as
|
||||
* a tooltip.
|
||||
* Whether or not the blurred video background for large video should be
|
||||
* displayed on browsers that can support it.
|
||||
*/
|
||||
DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP: null,
|
||||
DISABLE_VIDEO_BACKGROUND: false,
|
||||
|
||||
INITIAL_TOOLBAR_TIMEOUT: 20000,
|
||||
TOOLBAR_TIMEOUT: 4000,
|
||||
TOOLBAR_ALWAYS_VISIBLE: false,
|
||||
DEFAULT_REMOTE_DISPLAY_NAME: 'Fellow Jitster',
|
||||
DEFAULT_LOCAL_DISPLAY_NAME: 'me',
|
||||
SHOW_JITSI_WATERMARK: true,
|
||||
@@ -37,21 +38,20 @@ var interfaceConfig = {
|
||||
AUTHENTICATION_ENABLE: true,
|
||||
|
||||
/**
|
||||
* the toolbar buttons line is intentionally left in one line, to be able
|
||||
* to easily override values or remove them using regex
|
||||
* The name of the toolbar buttons to display in the toolbar. If present,
|
||||
* the button will display. Exceptions are "livestreaming" and "recording"
|
||||
* which also require being a moderator and some values in config.js to be
|
||||
* enabled. Also, the "profile" button will not display for user's with a
|
||||
* jwt.
|
||||
*/
|
||||
TOOLBAR_BUTTONS: [
|
||||
|
||||
// main toolbar
|
||||
'microphone', 'camera', 'desktop', 'fullscreen', 'fodeviceselection', 'hangup',
|
||||
|
||||
// extended toolbar
|
||||
'profile', 'info', 'chat', 'recording', 'livestreaming', 'etherpad',
|
||||
'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip',
|
||||
'invite', 'feedback', 'stats', 'shortcuts'
|
||||
'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen',
|
||||
'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
|
||||
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts'
|
||||
],
|
||||
|
||||
SETTINGS_SECTIONS: [ 'language', 'devices', 'moderator' ],
|
||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile' ],
|
||||
|
||||
// Determines how the video would fit the screen. 'both' would fit the whole
|
||||
// screen, 'height' would fit the original video height to the height of the
|
||||
@@ -80,6 +80,14 @@ var interfaceConfig = {
|
||||
DISABLE_FOCUS_INDICATOR: false,
|
||||
DISABLE_DOMINANT_SPEAKER_INDICATOR: false,
|
||||
|
||||
/**
|
||||
* Whether the speech to text transcription subtitles panel is disabled.
|
||||
* If {@code undefined}, defaults to {@code false}.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
DISABLE_TRANSCRIPTION_SUBTITLES: false,
|
||||
|
||||
/**
|
||||
* Whether the ringing sound in the call/ring overlay is disabled. If
|
||||
* {@code undefined}, defaults to {@code false}.
|
||||
@@ -137,6 +145,13 @@ var interfaceConfig = {
|
||||
*/
|
||||
CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT: 5000,
|
||||
|
||||
/**
|
||||
* If true, hides the connection indicators completely.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
CONNECTION_INDICATOR_DISABLED: false,
|
||||
|
||||
/**
|
||||
* The name of the application connected to the "Add people" search service.
|
||||
*/
|
||||
@@ -151,14 +166,11 @@ var interfaceConfig = {
|
||||
VIDEO_QUALITY_LABEL_DISABLED: false,
|
||||
|
||||
/**
|
||||
* Temporary feature flag to debug performance with the large video
|
||||
* background blur. On initial implementation, blur was always enabled so a
|
||||
* falsy value here will be used to keep blur enabled, as will the value
|
||||
* "video", and will render the blur over a video element. The value
|
||||
* "canvas" will display the blur over a canvas element, while the value
|
||||
* "off" will prevent the background from rendering.
|
||||
* If true, will display recent list
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
_BACKGROUND_BLUR: 'canvas'
|
||||
RECENT_LIST_ENABLED: true
|
||||
|
||||
/**
|
||||
* Specify custom URL for downloading android mobile app.
|
||||
|
||||
15
ios/Podfile
15
ios/Podfile
@@ -7,7 +7,7 @@ target 'JitsiMeet' do
|
||||
|
||||
pod 'React', :path => '../node_modules/react-native', :subspecs => [
|
||||
'Core',
|
||||
'BatchedBridge',
|
||||
'CxxBridge',
|
||||
'DevSupport',
|
||||
'RCTActionSheet',
|
||||
'RCTAnimation',
|
||||
@@ -19,15 +19,24 @@ target 'JitsiMeet' do
|
||||
]
|
||||
pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
|
||||
|
||||
pod 'DoubleConversion',
|
||||
:podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
|
||||
pod 'glog',
|
||||
:podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
|
||||
pod 'Folly',
|
||||
:podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
|
||||
|
||||
pod 'react-native-background-timer',
|
||||
:path => '../node_modules/react-native-background-timer'
|
||||
pod 'react-native-fetch-blob',
|
||||
:path => '../node_modules/react-native-fetch-blob'
|
||||
pod 'react-native-fast-image',
|
||||
:path => '../node_modules/react-native-fast-image'
|
||||
pod 'react-native-keep-awake',
|
||||
:path => '../node_modules/react-native-keep-awake'
|
||||
pod 'react-native-locale-detector',
|
||||
:path => '../node_modules/react-native-locale-detector'
|
||||
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
|
||||
pod 'ReactNativePermissions',
|
||||
:path => '../node_modules/react-native-permissions'
|
||||
pod 'RNSound', :path => '../node_modules/react-native-sound'
|
||||
pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
|
||||
pod 'react-native-calendar-events',
|
||||
|
||||
132
ios/Podfile.lock
132
ios/Podfile.lock
@@ -1,49 +1,70 @@
|
||||
PODS:
|
||||
- React (0.51.0):
|
||||
- React/Core (= 0.51.0)
|
||||
- boost-for-react-native (1.63.0)
|
||||
- DoubleConversion (1.1.5)
|
||||
- FLAnimatedImage (1.0.12)
|
||||
- Folly (2016.09.26.00):
|
||||
- boost-for-react-native
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- glog (0.3.4)
|
||||
- React (0.55.4):
|
||||
- React/Core (= 0.55.4)
|
||||
- react-native-background-timer (2.0.0):
|
||||
- React
|
||||
- react-native-calendar-events (1.5.0):
|
||||
- react-native-calendar-events (1.6.0):
|
||||
- React
|
||||
- react-native-fetch-blob (0.10.6):
|
||||
- React/Core
|
||||
- react-native-fast-image (4.0.14):
|
||||
- FLAnimatedImage
|
||||
- React
|
||||
- SDWebImage/Core
|
||||
- SDWebImage/GIF
|
||||
- react-native-keep-awake (2.0.6):
|
||||
- React
|
||||
- react-native-locale-detector (1.0.0):
|
||||
- React
|
||||
- react-native-webrtc (1.58.2):
|
||||
- React
|
||||
- React/BatchedBridge (0.51.0):
|
||||
- React/Core (0.55.4):
|
||||
- yoga (= 0.55.4.React)
|
||||
- React/CxxBridge (0.55.4):
|
||||
- Folly (= 2016.09.26.00)
|
||||
- React/Core
|
||||
- React/cxxreact_legacy
|
||||
- React/Core (0.51.0):
|
||||
- yoga (= 0.51.0.React)
|
||||
- React/cxxreact_legacy (0.51.0):
|
||||
- React/jschelpers_legacy
|
||||
- React/DevSupport (0.51.0):
|
||||
- React/cxxreact
|
||||
- React/cxxreact (0.55.4):
|
||||
- boost-for-react-native (= 1.63.0)
|
||||
- Folly (= 2016.09.26.00)
|
||||
- React/jschelpers
|
||||
- React/jsinspector
|
||||
- React/DevSupport (0.55.4):
|
||||
- React/Core
|
||||
- React/RCTWebSocket
|
||||
- React/fishhook (0.51.0)
|
||||
- React/jschelpers_legacy (0.51.0)
|
||||
- React/RCTActionSheet (0.51.0):
|
||||
- React/fishhook (0.55.4)
|
||||
- React/jschelpers (0.55.4):
|
||||
- Folly (= 2016.09.26.00)
|
||||
- React/PrivateDatabase
|
||||
- React/jsinspector (0.55.4)
|
||||
- React/PrivateDatabase (0.55.4)
|
||||
- React/RCTActionSheet (0.55.4):
|
||||
- React/Core
|
||||
- React/RCTAnimation (0.51.0):
|
||||
- React/RCTAnimation (0.55.4):
|
||||
- React/Core
|
||||
- React/RCTBlob (0.51.0):
|
||||
- React/RCTBlob (0.55.4):
|
||||
- React/Core
|
||||
- React/RCTImage (0.51.0):
|
||||
- React/RCTImage (0.55.4):
|
||||
- React/Core
|
||||
- React/RCTNetwork
|
||||
- React/RCTLinkingIOS (0.51.0):
|
||||
- React/RCTLinkingIOS (0.55.4):
|
||||
- React/Core
|
||||
- React/RCTNetwork (0.51.0):
|
||||
- React/RCTNetwork (0.55.4):
|
||||
- React/Core
|
||||
- React/RCTText (0.51.0):
|
||||
- React/RCTText (0.55.4):
|
||||
- React/Core
|
||||
- React/RCTWebSocket (0.51.0):
|
||||
- React/RCTWebSocket (0.55.4):
|
||||
- React/Core
|
||||
- React/fishhook
|
||||
- React/RCTBlob
|
||||
- ReactNativePermissions (1.1.1):
|
||||
- React
|
||||
- RNSound (0.10.9):
|
||||
- React/Core
|
||||
- RNSound/Core (= 0.10.9)
|
||||
@@ -51,17 +72,24 @@ PODS:
|
||||
- React/Core
|
||||
- RNVectorIcons (4.4.2):
|
||||
- React
|
||||
- yoga (0.51.0.React)
|
||||
- SDWebImage/Core (4.4.2)
|
||||
- SDWebImage/GIF (4.4.2):
|
||||
- FLAnimatedImage (~> 1.0)
|
||||
- SDWebImage/Core
|
||||
- yoga (0.55.4.React)
|
||||
|
||||
DEPENDENCIES:
|
||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- react-native-background-timer (from `../node_modules/react-native-background-timer`)
|
||||
- react-native-calendar-events (from `../node_modules/react-native-calendar-events`)
|
||||
- react-native-fetch-blob (from `../node_modules/react-native-fetch-blob`)
|
||||
- react-native-fast-image (from `../node_modules/react-native-fast-image`)
|
||||
- react-native-keep-awake (from `../node_modules/react-native-keep-awake`)
|
||||
- react-native-locale-detector (from `../node_modules/react-native-locale-detector`)
|
||||
- react-native-webrtc (from `../node_modules/react-native-webrtc`)
|
||||
- React/BatchedBridge (from `../node_modules/react-native`)
|
||||
- React/Core (from `../node_modules/react-native`)
|
||||
- React/CxxBridge (from `../node_modules/react-native`)
|
||||
- React/DevSupport (from `../node_modules/react-native`)
|
||||
- React/RCTActionSheet (from `../node_modules/react-native`)
|
||||
- React/RCTAnimation (from `../node_modules/react-native`)
|
||||
@@ -70,44 +98,66 @@ DEPENDENCIES:
|
||||
- React/RCTNetwork (from `../node_modules/react-native`)
|
||||
- React/RCTText (from `../node_modules/react-native`)
|
||||
- React/RCTWebSocket (from `../node_modules/react-native`)
|
||||
- ReactNativePermissions (from `../node_modules/react-native-permissions`)
|
||||
- RNSound (from `../node_modules/react-native-sound`)
|
||||
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
|
||||
- yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
||||
|
||||
SPEC REPOS:
|
||||
https://github.com/cocoapods/specs.git:
|
||||
- boost-for-react-native
|
||||
- FLAnimatedImage
|
||||
- SDWebImage
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
DoubleConversion:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
|
||||
Folly:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/Folly.podspec"
|
||||
glog:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
|
||||
React:
|
||||
:path: ../node_modules/react-native
|
||||
:path: "../node_modules/react-native"
|
||||
react-native-background-timer:
|
||||
:path: ../node_modules/react-native-background-timer
|
||||
:path: "../node_modules/react-native-background-timer"
|
||||
react-native-calendar-events:
|
||||
:path: ../node_modules/react-native-calendar-events
|
||||
react-native-fetch-blob:
|
||||
:path: ../node_modules/react-native-fetch-blob
|
||||
:path: "../node_modules/react-native-calendar-events"
|
||||
react-native-fast-image:
|
||||
:path: "../node_modules/react-native-fast-image"
|
||||
react-native-keep-awake:
|
||||
:path: ../node_modules/react-native-keep-awake
|
||||
:path: "../node_modules/react-native-keep-awake"
|
||||
react-native-locale-detector:
|
||||
:path: ../node_modules/react-native-locale-detector
|
||||
:path: "../node_modules/react-native-locale-detector"
|
||||
react-native-webrtc:
|
||||
:path: ../node_modules/react-native-webrtc
|
||||
:path: "../node_modules/react-native-webrtc"
|
||||
ReactNativePermissions:
|
||||
:path: "../node_modules/react-native-permissions"
|
||||
RNSound:
|
||||
:path: ../node_modules/react-native-sound
|
||||
:path: "../node_modules/react-native-sound"
|
||||
RNVectorIcons:
|
||||
:path: ../node_modules/react-native-vector-icons
|
||||
:path: "../node_modules/react-native-vector-icons"
|
||||
yoga:
|
||||
:path: ../node_modules/react-native/ReactCommon/yoga
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
React: 541ba768b9855e10cdc76f55427a5cd0653ca806
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
DoubleConversion: e22e0762848812a87afd67ffda3998d9ef29170c
|
||||
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
|
||||
Folly: 211775e49d8da0ca658aebc8eab89d642935755c
|
||||
glog: 1de0bb937dccdc981596d3b5825ebfb765017ded
|
||||
React: aa2040dbb6f317b95314968021bd2888816e03d5
|
||||
react-native-background-timer: 63dcbf37dbcf294b5c6c071afcdc661fa06a7594
|
||||
react-native-calendar-events: fe6fbc8ed337a7423c98f2c9012b25f20444de09
|
||||
react-native-fetch-blob: 63394b1d7b0781547b3e4463b3195790177b1222
|
||||
react-native-fast-image: cba3d9bf9c2cf8ddb643d887a686c53a5dd90a2c
|
||||
react-native-keep-awake: 0de4bd66de0c23178107dce0c2fcc3354b2a8e94
|
||||
react-native-locale-detector: d1b2c6fe5abb56e3a1efb6c2d6f308c05c4251f1
|
||||
react-native-webrtc: 31b6d3f1e3e2ce373aa43fd682b04367250f807d
|
||||
ReactNativePermissions: 9f2d9c45c98800795e6c2ed330e25d11a66a8169
|
||||
RNSound: b360b3862d3118ed1c74bb9825696b5957686ac4
|
||||
RNVectorIcons: c0dbfbf6068fefa240c37b0f71bd03b45dddac44
|
||||
yoga: 17521bbb0dd54a47c0b3ac43253e78cdac7488e0
|
||||
SDWebImage: 624d6e296c69b244bcede364c72ae0430ac14681
|
||||
yoga: a23273df0088bf7f2bb7e5d7b00044ea57a2a54a
|
||||
|
||||
PODFILE CHECKSUM: 4a5a310403b99b9c2d619e0b18da89bf0fe5858c
|
||||
PODFILE CHECKSUM: 1d5c8382f73d9540fac68d93b32e1d3b58d069ee
|
||||
|
||||
COCOAPODS: 1.4.0
|
||||
COCOAPODS: 1.5.3
|
||||
|
||||
@@ -56,13 +56,13 @@
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>See your scheduled conferences in the app.</string>
|
||||
<string>See your scheduled meetings in the app.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Participate in conferences with video.</string>
|
||||
<string>Participate in meetings with video.</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Participate in conferences with voice.</string>
|
||||
<string>Participate in meetings with voice.</string>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
0BCA496C1EC4BBF900B793EE /* jitsi.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0BCA496B1EC4BBF900B793EE /* jitsi.ttf */; };
|
||||
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */; };
|
||||
6C31EDC820C06D490089C899 /* recordingOn.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 6C31EDC720C06D490089C899 /* recordingOn.mp3 */; };
|
||||
6C31EDCA20C06D530089C899 /* recordingOff.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 6C31EDC920C06D530089C899 /* recordingOff.mp3 */; };
|
||||
75635B0A20751D6D00F29C9F /* joined.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0820751D6D00F29C9F /* joined.wav */; };
|
||||
75635B0B20751D6D00F29C9F /* left.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0920751D6D00F29C9F /* left.wav */; };
|
||||
A4414AE020B37F1A003546E6 /* rejected.wav in Resources */ = {isa = PBXBuildFile; fileRef = A4414ADF20B37F1A003546E6 /* rejected.wav */; };
|
||||
@@ -78,6 +80,8 @@
|
||||
0BD906E51EC0C00300C8C18E /* JitsiMeet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JitsiMeet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeet.h; sourceTree = "<group>"; };
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
6C31EDC720C06D490089C899 /* recordingOn.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = recordingOn.mp3; path = ../../sounds/recordingOn.mp3; sourceTree = "<group>"; };
|
||||
6C31EDC920C06D530089C899 /* recordingOff.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = recordingOff.mp3; path = ../../sounds/recordingOff.mp3; sourceTree = "<group>"; };
|
||||
75635B0820751D6D00F29C9F /* joined.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = joined.wav; path = ../../sounds/joined.wav; sourceTree = "<group>"; };
|
||||
75635B0920751D6D00F29C9F /* left.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = left.wav; path = ../../sounds/left.wav; sourceTree = "<group>"; };
|
||||
98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -118,6 +122,8 @@
|
||||
0BCA49681EC4BBE500B793EE /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6C31EDC720C06D490089C899 /* recordingOn.mp3 */,
|
||||
6C31EDC920C06D530089C899 /* recordingOff.mp3 */,
|
||||
A4414ADF20B37F1A003546E6 /* rejected.wav */,
|
||||
0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */,
|
||||
C6245F5B2053091D0040BE68 /* image-resize@2x.png */,
|
||||
@@ -315,10 +321,12 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0B49424520AD8DBD00BD2DE0 /* outgoingStart.wav in Resources */,
|
||||
6C31EDCA20C06D530089C899 /* recordingOff.mp3 in Resources */,
|
||||
A4414AE020B37F1A003546E6 /* rejected.wav in Resources */,
|
||||
0B49424620AD8DBD00BD2DE0 /* outgoingRinging.wav in Resources */,
|
||||
0BCA496C1EC4BBF900B793EE /* jitsi.ttf in Resources */,
|
||||
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */,
|
||||
6C31EDC820C06D490089C899 /* recordingOn.mp3 in Resources */,
|
||||
0BC4B8691F8C03A700CE8B21 /* CallKitIcon.png in Resources */,
|
||||
75635B0B20751D6D00F29C9F /* left.wav in Resources */,
|
||||
75635B0A20751D6D00F29C9F /* joined.wav in Resources */,
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSDictionary *)constantsToExport {
|
||||
NSDictionary<NSString *, id> *infoDictionary
|
||||
= [[NSBundle mainBundle] infoDictionary];
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
@interface AudioMode : NSObject<RCTBridgeModule>
|
||||
|
||||
@property(nonatomic, strong) dispatch_queue_t workerQueue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AudioMode {
|
||||
@@ -35,6 +38,10 @@ typedef enum {
|
||||
kAudioModeVideoCall
|
||||
} JitsiMeetAudioMode;
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSDictionary *)constantsToExport {
|
||||
return @{
|
||||
@"AUDIO_CALL" : [NSNumber numberWithInt: kAudioModeAudioCall],
|
||||
@@ -48,15 +55,18 @@ typedef enum {
|
||||
if (self) {
|
||||
_category = nil;
|
||||
_mode = nil;
|
||||
|
||||
dispatch_queue_attr_t attributes =
|
||||
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL,
|
||||
QOS_CLASS_USER_INITIATED, -1);
|
||||
_workerQueue = dispatch_queue_create("WebRTCModule.queue", attributes);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue {
|
||||
// Make sure all our methods run in the main thread. The route change
|
||||
// notification runs there so this will make sure it will only be fired
|
||||
// after our changes have been applied (when we cause them, that is).
|
||||
return dispatch_get_main_queue();
|
||||
// Use a dedicated queue for audio mode operations.
|
||||
return _workerQueue;
|
||||
}
|
||||
|
||||
- (void)routeChanged:(NSNotification*)notification {
|
||||
@@ -66,12 +76,15 @@ typedef enum {
|
||||
integerValue];
|
||||
|
||||
switch (reason) {
|
||||
case AVAudioSessionRouteChangeReasonCategoryChange:
|
||||
case AVAudioSessionRouteChangeReasonCategoryChange: {
|
||||
// The category has changed. Check if it's the one we want and adjust as
|
||||
// needed.
|
||||
[self setCategory:_category mode:_mode error:nil];
|
||||
// needed. This notification is posted on a secondary thread, so make
|
||||
// sure we switch to our worker thread.
|
||||
dispatch_async(_workerQueue, ^{
|
||||
[self setCategory:_category mode:_mode error:nil];
|
||||
});
|
||||
break;
|
||||
|
||||
}
|
||||
default:
|
||||
// Do nothing.
|
||||
break;
|
||||
|
||||
@@ -411,10 +411,9 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||
externalAPIScope = [NSUUID UUID].UUIDString;
|
||||
[views setObject:self forKey:externalAPIScope];
|
||||
|
||||
Invite *inviteModule = [bridgeWrapper.bridge moduleForName:@"Invite"];
|
||||
_inviteController
|
||||
= [[JMInviteController alloc] initWithExternalAPIScope:externalAPIScope
|
||||
andInviteModule:inviteModule];
|
||||
bridgeWrapper:bridgeWrapper];
|
||||
|
||||
// Set a background color which is in accord with the JavaScript and Android
|
||||
// parts of the application and causes less perceived visual flicker than
|
||||
|
||||
@@ -72,6 +72,11 @@ RCT_EXTERN void RCTRegisterModule(Class);
|
||||
[JMCallKitProxy removeListener:self];
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue {
|
||||
// Make sure all our methods run in the main thread.
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
// End call
|
||||
RCT_EXPORT_METHOD(endCall:(NSString *)callUUID
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
|
||||
@@ -18,9 +18,11 @@ import AVKit
|
||||
import CallKit
|
||||
import Foundation
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
|
||||
private var listeners = Set<JMCallKitEventListenerWrapper>()
|
||||
private var pendingMuteActions = Set<UUID>()
|
||||
|
||||
internal override init() {}
|
||||
|
||||
@@ -28,13 +30,10 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
|
||||
func addListener(_ listener: JMCallKitListener) {
|
||||
let wrapper = JMCallKitEventListenerWrapper(listener: listener)
|
||||
objc_sync_enter(listeners)
|
||||
listeners.insert(wrapper)
|
||||
objc_sync_exit(listeners)
|
||||
}
|
||||
|
||||
func removeListener(_ listener: JMCallKitListener) {
|
||||
objc_sync_enter(listeners)
|
||||
// XXX Constructing a new JMCallKitEventListenerWrapper instance in
|
||||
// order to remove the specified listener from listeners is (1) a bit
|
||||
// funny (though may make a statement about performance) and (2) not
|
||||
@@ -57,79 +56,80 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
listeners.remove($0)
|
||||
}
|
||||
}
|
||||
objc_sync_exit(listeners)
|
||||
}
|
||||
|
||||
// MARK: - Add mute action
|
||||
|
||||
func addMuteAction(_ actionUUID: UUID) {
|
||||
pendingMuteActions.insert(actionUUID)
|
||||
}
|
||||
|
||||
// MARK: - CXProviderDelegate
|
||||
|
||||
func providerDidReset(_ provider: CXProvider) {
|
||||
objc_sync_enter(listeners)
|
||||
listeners.forEach { $0.listener?.providerDidReset?() }
|
||||
objc_sync_exit(listeners)
|
||||
pendingMuteActions.removeAll()
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
|
||||
objc_sync_enter(listeners)
|
||||
listeners.forEach {
|
||||
$0.listener?.performAnswerCall?(UUID: action.callUUID)
|
||||
}
|
||||
objc_sync_exit(listeners)
|
||||
|
||||
action.fulfill()
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
||||
objc_sync_enter(listeners)
|
||||
listeners.forEach {
|
||||
$0.listener?.performEndCall?(UUID: action.callUUID)
|
||||
}
|
||||
objc_sync_exit(listeners)
|
||||
|
||||
action.fulfill()
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
|
||||
objc_sync_enter(listeners)
|
||||
listeners.forEach {
|
||||
$0.listener?.performSetMutedCall?(UUID: action.callUUID,
|
||||
isMuted: action.isMuted)
|
||||
let uuid = pendingMuteActions.remove(action.uuid)
|
||||
|
||||
// XXX avoid mute actions ping-pong: if the mute action was caused by
|
||||
// the JS side (we requested a transaction) don't call the delegate
|
||||
// method. If it was called by the provder itself (when the user presses
|
||||
// the mute button in the CallKit view) then call the delegate method.
|
||||
if (uuid == nil) {
|
||||
listeners.forEach {
|
||||
$0.listener?.performSetMutedCall?(UUID: action.callUUID,
|
||||
isMuted: action.isMuted)
|
||||
}
|
||||
}
|
||||
objc_sync_exit(listeners)
|
||||
|
||||
action.fulfill()
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
|
||||
objc_sync_enter(listeners)
|
||||
listeners.forEach {
|
||||
$0.listener?.performStartCall?(UUID: action.callUUID,
|
||||
isVideo: action.isVideo)
|
||||
}
|
||||
objc_sync_exit(listeners)
|
||||
|
||||
action.fulfill()
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider,
|
||||
didActivate audioSession: AVAudioSession) {
|
||||
objc_sync_enter(listeners)
|
||||
listeners.forEach {
|
||||
$0.listener?.providerDidActivateAudioSession?(session: audioSession)
|
||||
}
|
||||
objc_sync_exit(listeners)
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider,
|
||||
didDeactivate audioSession: AVAudioSession) {
|
||||
objc_sync_enter(listeners)
|
||||
listeners.forEach {
|
||||
$0.listener?.providerDidDeactivateAudioSession?(
|
||||
session: audioSession)
|
||||
}
|
||||
objc_sync_exit(listeners)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
fileprivate struct JMCallKitEventListenerWrapper: Hashable {
|
||||
|
||||
public var hashValue: Int
|
||||
|
||||
@@ -18,6 +18,7 @@ import AVKit
|
||||
import CallKit
|
||||
import Foundation
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
@objc public protocol JMCallKitListener: NSObjectProtocol {
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
|
||||
@@ -18,6 +18,8 @@ import CallKit
|
||||
import Foundation
|
||||
|
||||
/// JitsiMeet CallKit proxy
|
||||
// NOTE: The methods this class exposes are meant to be called in the UI thread.
|
||||
// All delegate methods called by JMCallKitEmitter will be called in the UI thread.
|
||||
@available(iOS 10.0, *)
|
||||
@objc public final class JMCallKitProxy: NSObject {
|
||||
|
||||
@@ -151,6 +153,14 @@ import Foundation
|
||||
completion: @escaping (Error?) -> Swift.Void) {
|
||||
guard enabled else { return }
|
||||
|
||||
// XXX keep track of muted actions to avoid "ping-pong"ing. See
|
||||
// JMCallKitEmitter for details on the CXSetMutedCallAction handling.
|
||||
for action in transaction.actions {
|
||||
if (action as? CXSetMutedCallAction) != nil {
|
||||
emitter.addMuteAction(action.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
callController.request(transaction, completion: completion)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#import "AddPeopleController.h"
|
||||
#import "Invite+Private.h"
|
||||
#import "RCTBridgeWrapper.h"
|
||||
|
||||
@interface JMInviteController ()
|
||||
|
||||
@@ -25,10 +26,12 @@
|
||||
|
||||
@property (nonatomic) NSString * _Nonnull externalAPIScope;
|
||||
|
||||
@property (nonatomic, nullable, weak) Invite *inviteModule;
|
||||
@property (nonatomic, nullable, weak) RCTBridgeWrapper *bridgeWrapper;
|
||||
|
||||
@property (nonatomic, readonly) Invite * _Nullable inviteModule;
|
||||
|
||||
- (instancetype _Nonnull)initWithExternalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
andInviteModule:(Invite * _Nonnull)inviteModule;
|
||||
bridgeWrapper:(RCTBridgeWrapper * _Nullable)bridgeWrapper;
|
||||
|
||||
- (void)beginAddPeople;
|
||||
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
#pragma mark Constructor
|
||||
|
||||
-(instancetype)initWithExternalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
andInviteModule:(Invite * _Nonnull)inviteModule {
|
||||
bridgeWrapper:(RCTBridgeWrapper * _Nullable)bridgeWrapper {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.externalAPIScope = externalAPIScope;
|
||||
self.inviteModule = inviteModule;
|
||||
self.bridgeWrapper = bridgeWrapper;
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -40,6 +40,10 @@
|
||||
|
||||
#pragma mark Public API
|
||||
|
||||
-(Invite * _Nullable)inviteModule {
|
||||
return [self.bridgeWrapper.bridge moduleForName:@"Invite"];
|
||||
}
|
||||
|
||||
-(void)beginAddPeople {
|
||||
if (_delegate == nil) {
|
||||
return;
|
||||
|
||||
@@ -130,6 +130,7 @@ cp "${CERT_DIR}/dev-profile.mobileprovision" ~/Library/MobileDevice/Provisionin
|
||||
npm install
|
||||
|
||||
cd ios
|
||||
pod update
|
||||
pod install
|
||||
cd ..
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"bg": "Bulgarian",
|
||||
"cs": "Czech",
|
||||
"de": "German",
|
||||
"el": "Greek",
|
||||
"eo": "Esperanto",
|
||||
"es": "Spanish",
|
||||
"fr": "French",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"contactlist": "__count__ Teilnehmer",
|
||||
"contactlist_plural": "",
|
||||
"contactlist_plural": "__count__ Teilnehmer",
|
||||
"passwordSetRemotely": "von einem anderen Teilnehmer gesetzt",
|
||||
"poweredby": "Betrieben von",
|
||||
"inviteUrlDefaultMsg": "Die Konferenz wird erstellt...",
|
||||
@@ -23,6 +22,7 @@
|
||||
"react-nativeGrantPermissions": "Wählen Sie <b><i>Erlauben</i></b> wenn der Browser um Berechtigungen bittet.",
|
||||
"chromeGrantPermissions": "Wählen Sie <b><i>Erlauben</i></b> wenn der Browser um Berechtigungen bittet.",
|
||||
"androidGrantPermissions": "Wählen Sie <b><i>Erlauben</i></b> wenn der Browser um Berechtigungen bittet.",
|
||||
"electronGrantPermissions": "Bitte Berechtigungen zur Verwendung der Kamera und des Mikrofons erteilen",
|
||||
"firefoxGrantPermissions": "Wählen Sie <b><i>Markiertes Gerät teilen</i></b> wenn der Browser um Berechtigungen bittet.",
|
||||
"operaGrantPermissions": "Wählen Sie <b><i>Erlauben</i></b> wenn der Browser um Berechtigungen bittet.",
|
||||
"iexplorerGrantPermissions": "Wählen Sie <b><i>OK</i></b> wenn der Browser um Berechtigungen bittet.",
|
||||
@@ -46,46 +46,20 @@
|
||||
"showSpeakerStats": "Statistiken für Sprecher anzeigen"
|
||||
},
|
||||
"welcomepage": {
|
||||
"disable": "Diesen Hinweis nicht mehr anzeigen",
|
||||
"feature1": {
|
||||
"content": "Kein Download nötig. __app__ läuft direkt im Browser. Einfach die Konferenzadresse teilen und los geht's.",
|
||||
"title": "Einfach zu benutzen"
|
||||
},
|
||||
"feature2": {
|
||||
"content": "Videokonferenzen mit mehreren Teilnehmen mit weniger als 128Kpbs. Bildschirmfreigaben und Telefonkonferenzen kommen sogar mit noch weniger Bandbreite aus.",
|
||||
"title": "Niedrige Bandbreite"
|
||||
},
|
||||
"feature3": {
|
||||
"content": "__app__ steht unter der Apache Lizenz. Es steht ihnen frei __app__ gemäss dieser Lizenz herunterzuladen, zu verändern oder zu verbreiten.",
|
||||
"title": "Open Source"
|
||||
},
|
||||
"feature4": {
|
||||
"content": "Es gibt keine künstlichen Begrenzungen der Anzahl der Konferenz-Teilnehmer. Die Bandbreite und Rechenleistung des Server sind die einzigen Limitierungen.",
|
||||
"title": "Unbegrenzte Anzahl Benutzer"
|
||||
},
|
||||
"feature5": {
|
||||
"content": "Es ist ganz einfach den Bildschirm zu teilen. __app__ ist ideal für Online-Präsentationen, Vorlesungen und Fernwartungsanfragen.",
|
||||
"title": "Bildschirmfreigabe"
|
||||
},
|
||||
"feature6": {
|
||||
"content": "Privatsphäre gewünscht? __app__ Konferenzen können mit einem Passwort geschützt werden um ungebetene Gäste fernzuhalten und Unterbrechungen zu vermeiden.",
|
||||
"title": "Sichere Konferenzen"
|
||||
},
|
||||
"feature7": {
|
||||
"content": "__app__ verwendet Etherpad, ein Editor zur kollaborativen Bearbeitung von Texten.",
|
||||
"title": "Freigegebene Notizen"
|
||||
},
|
||||
"feature8": {
|
||||
"content": "Die Verwendung kann durch die Integration mit Piwik, Google Analytics und anderen Überwachungs- und Statistikprogrammen protokolliert werden.",
|
||||
"title": "Benutzungsstatistiken"
|
||||
"appDescription": "Auf geht's! Beginne eine Videokonferenz mit dem ganzen Team. Oder eigentlich, lade alle ein die du kennst. __app__ ist eine vollständig verschlüsselte, aus 100% Open-Source-Software bestehende Videokonferenzlösung die du den ganzen Tag kostenlos verwenden kannst — ohne Registrierung.",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "Sprache",
|
||||
"video": "Video"
|
||||
},
|
||||
"calendar": "Kalender",
|
||||
"go": "Los",
|
||||
"join": "Beitreten",
|
||||
"privacy": "Privatsphäre",
|
||||
"roomname": "Konferenzname eingeben",
|
||||
"roomnamePlaceHolder": "Konferenzname",
|
||||
"roomnameHint": "Name oder URL der Konferenz der Sie beitreten möchten. Sie können einen Namen erfinden, er muss nur den anderen Teilnehmern übermittelt werden damit sie der gleichen Konferenz beitreten.",
|
||||
"sendFeedback": "Senden Sie uns Ihr Feedback",
|
||||
"terms": "Bedingungen"
|
||||
"terms": "Bedingungen",
|
||||
"title": "Sichere, flexible und vollständig freie Videokonferenzen"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
@@ -99,22 +73,28 @@
|
||||
"toolbar": {
|
||||
"addPeople": "Teilnehmer zur Konferenz hinzufügen",
|
||||
"audioonly": "Nur-Audio-Modus aktivieren/deaktivieren (spart Bandbreite)",
|
||||
"callQuality": "Qualitätseinstellungen",
|
||||
"enterFullScreen": "Vollbildmodus",
|
||||
"exitFullScreen": "Vollbildmodus verlassen",
|
||||
"feedback": "Feedback hinterlasen",
|
||||
"moreActions": "Weitere Einstellungen",
|
||||
"mute": "Stummschaltung aktivieren / deaktivieren",
|
||||
"videomute": "Kamera starten / stoppen",
|
||||
"authenticate": "Anmelden",
|
||||
"lock": "Konferenz schützen / Schutz aufheben",
|
||||
"invite": "Link teilen",
|
||||
"chat": "Chat öffnen / schliessen",
|
||||
"etherpad": "Geteiltes Dokument öffnen / schliessen",
|
||||
"documentOpen": "Geteiltes Dokument öffnen",
|
||||
"documentClose": "Geteiltes Dokument schliessen",
|
||||
"sharedvideo": "YouTube-Video teilen",
|
||||
"sharescreen": "Bildschirmfreigabe starten / stoppen",
|
||||
"sharescreen": "Bildschirmfreigabe",
|
||||
"stopSharedVideo": "YouTube Video stoppen",
|
||||
"fullscreen": "Vollbildmodus aktivieren / deaktivieren",
|
||||
"sip": "SIP Nummer anrufen",
|
||||
"Settings": "Einstellungen",
|
||||
"hangup": "Verlassen",
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden",
|
||||
"dialpad": "Wähltastatur öffnen / schliessen",
|
||||
"sharedVideoMutedPopup": "Das freigegebene Video wurde stumm geschaltet um mit den anderen Teilnehmern zu sprechen.",
|
||||
"micMutedPopup": "Das Mikrofon wurde stumm geschaltet um das freigegebene Video geniessen zu können.",
|
||||
"talkWhileMutedPopup": "Versuchen sie zu sprechen? Ihr Mikrofon ist stummgeschaltet.",
|
||||
@@ -123,17 +103,9 @@
|
||||
"micDisabled": "Kein Mikrofon verfügbar",
|
||||
"filmstrip": "Videos anzeigen / verbergen",
|
||||
"profile": "Profil bearbeiten",
|
||||
"raiseHand": "Hand erheben"
|
||||
},
|
||||
"unsupportedBrowser": {
|
||||
"appNotInstalled": "Mit __app__ auf dem Mobiltelefon teilnehmen.",
|
||||
"downloadApp": "App herunterladen",
|
||||
"openApp": "In __app__ fortfahren"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Chat öffnen / schliessen",
|
||||
"filmstrip": "Videos anzeigen / verbergen",
|
||||
"contactlist": "Teilnehmerliste und neue Teilnehmer einladen"
|
||||
"raiseHand": "Hand erheben",
|
||||
"shortcuts": "Tastenkürzel anzeigen",
|
||||
"speakerStats": "Sprecher-Statistiken"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
@@ -218,10 +190,11 @@
|
||||
"grantedToUnknown": "Moderatorenrechte an $t(notify.somebody) vergeben.",
|
||||
"muted": "Der Konferenz wurde stumm beigetreten.",
|
||||
"mutedTitle": "Stummschaltung aktiv!",
|
||||
"raisedHand": "Möchte sprechen."
|
||||
"raisedHand": "Möchte sprechen.",
|
||||
"suboptimalExperienceTitle": "Browserwarnung",
|
||||
"suboptimalExperienceDescription": "Tut uns leid, aber die Konferenz wird mit __appName__ kein grossartiges Erlebnis. Wir versuchen immer die Situation zu verbessern, bis dahin empfehlen wir aber die Verwendung einer der <a href=\"static/recommendedBrowsers.html\" target=\"_blank\">vollständig unterstützen Browser</a>."
|
||||
},
|
||||
"dialog": {
|
||||
"add": "Hinzufügen",
|
||||
"allow": "Erlauben",
|
||||
"kickMessage": "Oh! Sie wurden aus der Konferenz ausgeschlossen.",
|
||||
"popupErrorTitle": "Popup blockiert",
|
||||
@@ -236,7 +209,6 @@
|
||||
"copy": "Kopieren",
|
||||
"contactSupport": "Support kontaktieren",
|
||||
"error": "Fehler",
|
||||
"createPassword": "Passwort erstellen",
|
||||
"detectext": "Fehler bei der Erkennung der Bildschirmfreigabeerweiterung.",
|
||||
"failedpermissions": "Die Zugriffsberechtigungen auf das Mikrofon und/oder die Kamera konnten nicht eingeholt werden.",
|
||||
"conferenceReloadTitle": "Leider ist etwas schiefgegangen.",
|
||||
@@ -287,10 +259,6 @@
|
||||
"Save": "Speichern",
|
||||
"recording": "Aufnahme",
|
||||
"recordingToken": "Aufnahme-Token eingeben",
|
||||
"passwordCheck": "Sind Sie sicher, dass Sie das Passwort entfernen möchten?",
|
||||
"passwordMsg": "Passwort setzen um die Konferenz zu schützen",
|
||||
"shareLink": "Link zu dieser Konferenz teilen",
|
||||
"yourPassword": "Neues Passwort eingeben",
|
||||
"Back": "Zurück",
|
||||
"serviceUnavailable": "Dienst nicht verfügbar",
|
||||
"gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",
|
||||
@@ -304,15 +272,14 @@
|
||||
"tokenAuthFailed": "Sie sind nicht berechtigt dieser Konferenz beizutreten.",
|
||||
"displayNameRequired": "Anzeigename ist erforderlich",
|
||||
"enterDisplayName": "Geben Sie Ihren Anzeigenamen ein",
|
||||
"extensionRequired": "Erweiterung erforderlich:",
|
||||
"firefoxExtensionPrompt": "Um die Bildschirmfreigabe nutzen zu können, muss eine Firefox-Erweiterung installiert werden. Bitte versuchen Sie es erneut nachdem die <a href='__url__'>Erweiterung installiert</a> wurde.",
|
||||
"feedbackHelp": "Ihr Feedback hilft uns die Qualität der Konferenzen zu verbessern.",
|
||||
"feedbackQuestion": "Anmerkungen zur Konferenz.",
|
||||
"thankYou": "Danke für die Verwendung von __appName__!",
|
||||
"sorryFeedback": "Tut uns leid. Möchten Sie uns mehr mitteilen?",
|
||||
"liveStreaming": "Live-Streaming",
|
||||
"streamKey": "Streamname/-schlüssel",
|
||||
"streamKey": "Name/Schlüssel für den Stream",
|
||||
"startLiveStreaming": "Live-Streaming starten",
|
||||
"startRecording": "Aufnahme starten",
|
||||
"stopStreamingWarning": "Sind Sie sicher dass Sie das Live-Streaming stoppen möchten?",
|
||||
"stopRecordingWarning": "Sind Sie sicher dass Sie die Aufnahme stoppen möchten?",
|
||||
"stopLiveStreaming": "Live-Streaming stoppen",
|
||||
@@ -321,6 +288,8 @@
|
||||
"permissionDenied": "Zugriff verweigert",
|
||||
"screenSharingFailedToInstall": "Oh! Die Erweiterung für die Bildschirmfreigabe konnte nicht installiert werden.",
|
||||
"screenSharingFailedToInstallTitle": "Bildschirmfreigabe-Erweiterung konnte nicht installiert werden",
|
||||
"screenSharingFirefoxPermissionDeniedError": "Die Bildschirmfreigabe ist leider fehlgeschlagen. Bitte stellen Sie sicher, dass die Berechtigung für die Bildschirmfreigabe im Browser erteilt wurde.",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "Die Bildschirmfreigabe konnte nicht gestartet werden.",
|
||||
"screenSharingPermissionDeniedError": "Oh! Beim Anfordern der Bildschirmfreigabe-Berechtigungen hat etwas nicht funktioniert. Bitte aktualisieren und erneut versuchen.",
|
||||
"cameraUnsupportedResolutionError": "Die Kamera unterstützt die erforderliche Auflösung nicht.",
|
||||
"cameraUnknownError": "Die Kamera kann aus einem unbekannten Grund nicht verwendet werden.",
|
||||
@@ -417,14 +386,21 @@
|
||||
"busy": "Es werden Resourcen zum Streamen bereitgestellt. Bitte in ein paar Minuten erneut versuchen.",
|
||||
"busyTitle": "Alle Streaming-Instanzen sind in Gebrauch",
|
||||
"buttonTooltip": "Live-Stream starten / stoppen",
|
||||
"changeSignIn": "Konten wechseln.",
|
||||
"choose": "Live stream auswählen",
|
||||
"chooseCTA": "Streaming-Option auswählen. Sie sind aktuell als __email__ angemeldet.",
|
||||
"enterStreamKey": "Name/Schlüssel für den YouTube Livestream hier eingeben.",
|
||||
"error": "Das Live-Streaming ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"errorAPI": "Beim abrufen der YouTube Livestreams ist ein Fehler aufgetreten. Bitte versuchen Sie sich erneut anzumelden.",
|
||||
"failedToStart": "Live-Streaming konnte nicht gestartet werden",
|
||||
"off": "Live-Streaming gestoppt",
|
||||
"on": "Live-Streaming",
|
||||
"pending": "Live-Stream wird gestartet...",
|
||||
"serviceName": "Live Streaming-Dienst",
|
||||
"streamIdRequired": "Bitte Stream-ID eingeben um das Live-Streaming zu starten.",
|
||||
"streamIdHelp": "Wo ist die Stream-ID zu finden?",
|
||||
"signIn": "Mit Google anmelden",
|
||||
"signInCTA": "Anmelden oder den Name/Schlüssel des YouTube Livestreams eingeben.",
|
||||
"start": "Einen Livestream starten",
|
||||
"streamIdHelp": "Was ist das?",
|
||||
"unavailableTitle": "Live-Streaming nicht verfügbar"
|
||||
},
|
||||
"videoSIPGW": {
|
||||
@@ -454,26 +430,16 @@
|
||||
"selectADevice": "Ein Gerät wählen",
|
||||
"testAudio": "Audio testen"
|
||||
},
|
||||
"invite": {
|
||||
"addPassword": "Passwort hinzufügen",
|
||||
"callNumber": "__number__ anrufen",
|
||||
"enterID": "Um mit einem Telefon teilzunehmen, geben Sie die Konferenz ID (__conferenceID__) gefolgt von # ein",
|
||||
"howToDialIn": "Wählen Sie eine der folgenden Nummern um via Telefon teilzunehmen und die Konferenz ID",
|
||||
"hidePassword": "Passwort verstecken",
|
||||
"inviteTo": "Teilnehmer zu __conferenceName__ einladen",
|
||||
"invitedYouTo": "Sie wurden von __userName__ zur Konferenz __inviteURL__ eingeladen",
|
||||
"invitePeople": "Einladen",
|
||||
"locked": "Diese Konferenz ist gesperrt. Neue Teilnehmer müssen das Passwort eingeben um beizutreten.",
|
||||
"showPassword": "Passwort anzeigen",
|
||||
"unlocked": "Die Konferenz ist nicht geschützt. Jeder mit dem Link kann der Konferenz beitreten."
|
||||
},
|
||||
"videoStatus": {
|
||||
"callQuality": "Konferenzqualität",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Video wird in HD angezeigt",
|
||||
"highDefinition": "Hohe Auflösung",
|
||||
"labelTooltipVideo": "Aktuelle Videoqualität",
|
||||
"labelTooltipAudioOnly": "Nur-Audio Modus aktiv",
|
||||
"labelTooiltipNoVideo": "Kein Video",
|
||||
"labelTooltipVideo": "Aktuelle Videoqualität",
|
||||
"ld": "LD",
|
||||
"ldTooltip": "Video wird in niedriger Auflösung angezeigt",
|
||||
"lowDefinition": "Niedrige Auflösung",
|
||||
"onlyAudioAvailable": "Nur Ton",
|
||||
"onlyAudioSupported": "In diesem Browser wird nur Audio unterstützt.",
|
||||
@@ -481,21 +447,30 @@
|
||||
"p2pVideoQualityDescription": "Im Ende-zu-Ende Modus kann die Konferenzqualität nur zwischen hoch und nur-Audio gewählt werden. Andere Einstellungen werden nicht beachtet.",
|
||||
"recHighDefinitionOnly": "Hohe Qualität wird bevorzugt.",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Video wird in Standardauflösung angezeigt",
|
||||
"standardDefinition": "Standardauflösung",
|
||||
"qualityButtonTip": "Empfangene Videoqualität ändern"
|
||||
},
|
||||
"dialOut": {
|
||||
"dial": "Wählen",
|
||||
"dialOut": "Nummer anrufen",
|
||||
"statusMessage": "ist jetzt __status__",
|
||||
"enterPhone": "Telefonnummer eingeben",
|
||||
"phoneNotAllowed": "Diese Telefonnummer wird leider noch nicht unterstützt!"
|
||||
"statusMessage": "ist jetzt __status__"
|
||||
},
|
||||
"addPeople": {
|
||||
"add": "Hinzufügen",
|
||||
"add": "Einladen",
|
||||
"countryNotSupported": "Wir unterstützen dieses Land noch nicht.",
|
||||
"countryReminder": "Telefonnummer nicht in den USA? Bitte sicherstellen, dass die Telefonnummer mit dem Ländercode beginnt.",
|
||||
"disabled": "",
|
||||
"invite": "Einladen",
|
||||
"loading": "Suche nach Teilnehmern und Telefonnummern",
|
||||
"loadingNumber": "Telefonnummer wird überprüft",
|
||||
"loadingPeople": "Suche nach einzuladenden Teilnehmern",
|
||||
"noResults": "Keine passenden Ergebnisse",
|
||||
"searchPlaceholder": "Nach Teilnehmern und Konferenzen suchen",
|
||||
"title": "Teilnehmer zur Konferenz hinzufügen",
|
||||
"noValidNumbers": "Telefonnummer eingeben",
|
||||
"notAvailable": "Sie können keine Teilnehmer einladen.",
|
||||
"searchNumbers": "Telefonnummern hinzufügen",
|
||||
"searchPeople": "Nach Teilnehmern suchen",
|
||||
"searchPeopleAndNumbers": "Nach Teilnehmen suchen oder deren Telefonnummern hinzufügen",
|
||||
"telephone": "Telefon: __number__",
|
||||
"title": "Teilnehmer zu dieser Konferenz einladen",
|
||||
"failedToAdd": "Teilnehmer konnte nicht hinzugefügt werden"
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
@@ -514,14 +489,71 @@
|
||||
"average": "Durschnittlich",
|
||||
"bad": "Schlecht",
|
||||
"good": "Gut",
|
||||
"rateExperience": "Bitte bewerten Sie diese Konferenz.",
|
||||
"detailsLabel": "Sagen Sie uns mehr dazu.",
|
||||
"rateExperience": "Bitte bewerten Sie diese Konferenz",
|
||||
"veryBad": "Sehr schlecht",
|
||||
"veryGood": "Sehr gut"
|
||||
},
|
||||
"info": {
|
||||
"copy": "Link kopieren",
|
||||
"invite": "In __app__ einladen",
|
||||
"title": "Konferenz-Zugriffsinformationen",
|
||||
"addPassword": "Passwort hinzufügen",
|
||||
"cancelPassword": "Password abbrechen",
|
||||
"conferenceURL": "Link:",
|
||||
"country": "Land",
|
||||
"dialANumber": "Um dieser Konferenz beizutreten, rufen Sie eine dieser Telefonnummern an und geben die PIN __conferenceID__# ein.",
|
||||
"dialInNumber": "Einwählen:",
|
||||
"dialInConferenceID": "PIN:",
|
||||
"dialInNotSupported": "Tut uns leid, einwählen ist momentan nicht unterstützt.",
|
||||
"genericError": "Es ist leider etwas schiefgegangen.",
|
||||
"inviteLiveStream": "Klicken Sie auf __url__ um den Livestream dieser Konferenz zu öffnen",
|
||||
"invitePhone": "Um der Konferenz telefonisch beizutreten, rufen Sie __number__ and und geben die PIN __conferenceID__# ein",
|
||||
"invitePhoneAlternatives": "Klicken sie auf __url__ um mehr Telefonnummern anzuzeigen",
|
||||
"inviteURL": "Klicken Sie auf __url__ um der Konferenz beizutreten",
|
||||
"liveStreamURL": "Livestream:",
|
||||
"moreNumbers": "Weitere Telefonnummern",
|
||||
"noNumbers": "Keine Telefonnummern verfügbar.",
|
||||
"noPassword": "Kein",
|
||||
"noRoom": "Keine Konferenz für die Einwähl-Informationen angegeben.",
|
||||
"numbers": "Einwählnummern",
|
||||
"password": "Passwort:",
|
||||
"title": "Teilen",
|
||||
"tooltip": "Zugriffsinformationen über die Konferenz abrufen"
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "OK",
|
||||
"alertTitle": "Warnung",
|
||||
"alertURLText": "Die angegebene Server URL ist ungültig",
|
||||
"conferenceSection": "Konferenz",
|
||||
"displayName": "Anzeigename",
|
||||
"email": "E-Mail",
|
||||
"header": "Einstellungen",
|
||||
"profileSection": "Profil",
|
||||
"serverURL": "Server URL",
|
||||
"startWithAudioMuted": "Stumm beitreten",
|
||||
"startWithVideoMuted": "Ohne Video beitreten"
|
||||
},
|
||||
"calendarSync": {
|
||||
"later": "Später",
|
||||
"next": "Folgend",
|
||||
"nextMeeting": "Nächste Konferenz",
|
||||
"now": "Jetzt",
|
||||
"permissionButton": "Einstellungen öffnen",
|
||||
"permissionMessage": "Die App benötigt Zugriff auf den Kalender um die Termine und Konferenzen anzuzeigen."
|
||||
},
|
||||
"recentList": {
|
||||
"today": "Heute",
|
||||
"yesterday": "Gestern",
|
||||
"earlier": "Früher"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Ziehen um zu aktualisieren"
|
||||
},
|
||||
"deepLinking": {
|
||||
"title": "Die Konferenz wird in __app__ geöffnet...",
|
||||
"description": "Nichts passiert? Wir haben versucht die Konferenz in __app__ zu öffnen. Versuchen Sie es erneut oder treten Sie der Konferenz in __app__ im Web bei.",
|
||||
"tryAgainButton": "Erneut mit der nativen Applikation versuchen",
|
||||
"launchWebButton": "Im Web öffnen",
|
||||
"appNotInstalled": "Sie benötigen die __app__ App um der Konferenz auf dem Smartphone beizutreten.",
|
||||
"downloadApp": "App herunterladen",
|
||||
"openApp": "In der App fortfahren"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user