mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-03 21:32:28 +00:00
Compare commits
409 Commits
2762
...
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 | ||
|
|
ca4c7129f1 | ||
|
|
ad7728a599 | ||
|
|
5c7b7cd625 | ||
|
|
7186a9c79c | ||
|
|
c672ffd435 | ||
|
|
8cfc83f18c | ||
|
|
fa9549582f | ||
|
|
11b7144ad0 | ||
|
|
92fee790f2 | ||
|
|
72487fa7c7 | ||
|
|
380909371f | ||
|
|
d4d5ef202a | ||
|
|
c95cb0e9cf | ||
|
|
ab83a97fd5 | ||
|
|
9e2a101089 | ||
|
|
c8c198b0b1 | ||
|
|
4bb6e5aefd | ||
|
|
3dbb663922 | ||
|
|
592305df74 | ||
|
|
357b206831 | ||
|
|
d557a6505d | ||
|
|
f12ba37cf3 | ||
|
|
d4d2c25499 | ||
|
|
8b91afd80d | ||
|
|
d89227829f | ||
|
|
2b1c875b91 | ||
|
|
5b4a16cf6b | ||
|
|
480fe53001 | ||
|
|
2ecacf6c3e | ||
|
|
ee9fcbb735 | ||
|
|
db21e97c19 | ||
|
|
8cd2bd272b | ||
|
|
771d60f954 | ||
|
|
37cd5bb5b9 | ||
|
|
983e62ffe9 | ||
|
|
fcca15c827 | ||
|
|
6b3da4a4a4 | ||
|
|
8ecd6e6ced | ||
|
|
f7c4133fb7 | ||
|
|
bbb1dce42a | ||
|
|
31864b95ab | ||
|
|
33a748b569 | ||
|
|
83ede66037 | ||
|
|
2128c84212 | ||
|
|
a72463a302 | ||
|
|
62cc44f791 | ||
|
|
b4c95bec39 | ||
|
|
7012c77fe9 | ||
|
|
a8b1ca38dc | ||
|
|
99b5a41269 | ||
|
|
f608ad4e5e | ||
|
|
0d81e677bf | ||
|
|
22ce001f14 | ||
|
|
9650404099 | ||
|
|
7704809c4c | ||
|
|
75c7cfd9e1 | ||
|
|
ef7fb1a7b0 | ||
|
|
ccbf3efa38 | ||
|
|
39e46bacf6 | ||
|
|
12d7ab9026 | ||
|
|
9131d2448d | ||
|
|
44c498a566 | ||
|
|
192f1d44f5 | ||
|
|
b57dad576a | ||
|
|
494e8eb8d9 | ||
|
|
21c1e4abc4 | ||
|
|
5bd975e3f3 | ||
|
|
8f75c2e279 | ||
|
|
add89e2488 | ||
|
|
284d09eae7 | ||
|
|
180141cefb | ||
|
|
df8eb36d0e | ||
|
|
f54f5df428 | ||
|
|
c344a83376 | ||
|
|
ee74f11c3d | ||
|
|
5fd0f95a89 | ||
|
|
0d1d0d06a4 | ||
|
|
b037e1d736 | ||
|
|
bce2438471 | ||
|
|
8a160fd9ab | ||
|
|
cd57477b68 | ||
|
|
f1123a8cdd | ||
|
|
447035c8b2 | ||
|
|
7fe421aeba | ||
|
|
3aff4967f1 | ||
|
|
a42496ba53 | ||
|
|
9ca7ca9515 | ||
|
|
9a3effe97a | ||
|
|
a2834a2495 | ||
|
|
450400b768 | ||
|
|
c7d72ee3f6 | ||
|
|
01cb4ac7c8 | ||
|
|
b634f6b200 | ||
|
|
8d94cc5cb2 | ||
|
|
52da5010cc | ||
|
|
362486cbea | ||
|
|
eea6af51db | ||
|
|
df7b8e51fc | ||
|
|
f64b511682 | ||
|
|
c6f99f3dda | ||
|
|
75fe3e3b9d | ||
|
|
631f51d627 | ||
|
|
d4dea22576 | ||
|
|
5e8ecc5fee | ||
|
|
46bc63b79e | ||
|
|
ee94d79ee6 | ||
|
|
1ba564f49c | ||
|
|
e649ae9016 | ||
|
|
f60559fb67 | ||
|
|
39ba83131b | ||
|
|
b2ad8a95d4 | ||
|
|
b797b0b201 | ||
|
|
bd7c9473e7 | ||
|
|
53971d0b50 | ||
|
|
44a65eb329 | ||
|
|
cc7e15ab8f | ||
|
|
959db3a665 | ||
|
|
ab7e572162 | ||
|
|
4fdd71d1bd | ||
|
|
8f5ec20da8 | ||
|
|
740817fdcf | ||
|
|
8ca836c922 | ||
|
|
6a0de0ddde | ||
|
|
b777322fdc | ||
|
|
0826ffa974 | ||
|
|
6aae56527f | ||
|
|
7ffdaf59c7 | ||
|
|
85612b9ae1 | ||
|
|
529e5e8938 | ||
|
|
bcd9163284 | ||
|
|
d62ac72cfa | ||
|
|
8656c7e760 | ||
|
|
a9ee5944e1 | ||
|
|
36ecc99b5b | ||
|
|
82f5eb894b | ||
|
|
91a65735f9 | ||
|
|
c2e40f975e | ||
|
|
5e79bbecef | ||
|
|
e5309a6482 | ||
|
|
520bb8bd22 | ||
|
|
69eefc82a5 | ||
|
|
d1af11c67e | ||
|
|
3091d9e6dd |
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
|
||||
|
||||
|
||||
4
.travis.yml
Normal file
4
.travis.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
osx_image: xcode9.4
|
||||
language: objective-c
|
||||
script:
|
||||
- "./ios/travis-ci/build-ipa.sh"
|
||||
@@ -10,12 +10,15 @@ 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).
|
||||
|
||||
## Download
|
||||
|
||||
| Latest stable release | [](https://github.com/jitsi/jitsi-meet/releases/latest) |
|
||||
|---|---|
|
||||
|
||||
You can download Debian/Ubuntu binaries:
|
||||
* [stable](https://download.jitsi.org/stable/) ([instructions](https://jitsi.org/downloads/ubuntu-debian-installations-instructions/))
|
||||
* [testing](https://download.jitsi.org/testing/) ([instructions](https://jitsi.org/downloads/ubuntu-debian-installations-instructions-for-testing/))
|
||||
@@ -30,11 +33,11 @@ You can get our mobile versions from here:
|
||||
|
||||
## Building the sources
|
||||
|
||||
Node.js >= 6 is required.
|
||||
Node.js >= 8 and npm >= 6 are required.
|
||||
|
||||
On Debian/Ubuntu systems, the required packages can be installed with:
|
||||
```
|
||||
sudo apt-get install npm nodejs-legacy
|
||||
sudo apt-get install npm nodejs
|
||||
cd jitsi-meet
|
||||
npm install
|
||||
```
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', options.googleAnalyticsTrackingId, 'jit.si');
|
||||
ga('create', options.googleAnalyticsTrackingId, 'auto');
|
||||
ga('send', 'pageview');
|
||||
|
||||
/* eslint-enable */
|
||||
|
||||
@@ -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.** {*;}
|
||||
|
||||
|
||||
@@ -23,11 +23,16 @@ import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
import org.jitsi.meet.sdk.JitsiMeetViewListener;
|
||||
import org.jitsi.meet.sdk.invite.AddPeopleController;
|
||||
import org.jitsi.meet.sdk.invite.AddPeopleControllerListener;
|
||||
import org.jitsi.meet.sdk.invite.InviteController;
|
||||
import org.jitsi.meet.sdk.invite.InviteControllerListener;
|
||||
|
||||
import com.calendarevents.CalendarEventsPackage;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -43,6 +48,14 @@ import java.util.Map;
|
||||
* {@code react-native run-android}.
|
||||
*/
|
||||
public class MainActivity extends JitsiMeetActivity {
|
||||
/**
|
||||
* The query to perform through {@link AddPeopleController} when the
|
||||
* {@code InviteButton} is tapped in order to exercise the public API of the
|
||||
* feature invite. If {@code null}, the {@code InviteButton} will not be
|
||||
* rendered.
|
||||
*/
|
||||
private static final String ADD_PEOPLE_CONTROLLER_QUERY = null;
|
||||
|
||||
@Override
|
||||
protected JitsiMeetView initializeView() {
|
||||
JitsiMeetView view = super.initializeView();
|
||||
@@ -53,6 +66,8 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
if (BuildConfig.DEBUG && view != null) {
|
||||
view.setListener(new JitsiMeetViewListener() {
|
||||
private void on(String name, Map<String, Object> data) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// Log with the tag "ReactNative" in order to have the log
|
||||
// visible in react-native log-android as well.
|
||||
Log.d(
|
||||
@@ -93,23 +108,72 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
}
|
||||
});
|
||||
|
||||
view.getInviteController().setListener(
|
||||
new InviteControllerListener() {
|
||||
public void beginAddPeople(
|
||||
AddPeopleController addPeopleController) {
|
||||
// Log with the tag "ReactNative" in order to have the
|
||||
// log visible in react-native log-android as well.
|
||||
Log.d(
|
||||
"ReactNative",
|
||||
InviteControllerListener.class.getSimpleName()
|
||||
+ ".beginAddPeople");
|
||||
}
|
||||
});
|
||||
// inviteController
|
||||
final InviteController inviteController
|
||||
= view.getInviteController();
|
||||
|
||||
inviteController.setListener(new InviteControllerListener() {
|
||||
public void beginAddPeople(
|
||||
AddPeopleController addPeopleController) {
|
||||
onInviteControllerBeginAddPeople(
|
||||
inviteController,
|
||||
addPeopleController);
|
||||
}
|
||||
});
|
||||
inviteController.setAddPeopleEnabled(
|
||||
ADD_PEOPLE_CONTROLLER_QUERY != null);
|
||||
inviteController.setDialOutEnabled(
|
||||
inviteController.isAddPeopleEnabled());
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void onAddPeopleControllerInviteSettled(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> failedInvitees) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
|
||||
// it is going to be memory-leaked in the associated InviteController
|
||||
// and no subsequent InviteButton clicks/taps will be delivered.
|
||||
// Technically, endAddPeople will automatically be invoked if there are
|
||||
// no failedInviteees i.e. the invite succeeeded for all specified
|
||||
// invitees.
|
||||
addPeopleController.endAddPeople();
|
||||
}
|
||||
|
||||
private void onAddPeopleControllerReceivedResults(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> results,
|
||||
String query) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
int size = results.size();
|
||||
|
||||
if (size > 0) {
|
||||
// Exercise AddPeopleController's inviteById implementation.
|
||||
List<String> ids = new ArrayList<>(size);
|
||||
|
||||
for (Map<String, Object> result : results) {
|
||||
Object id = result.get("id");
|
||||
|
||||
if (id != null) {
|
||||
ids.add(id.toString());
|
||||
}
|
||||
}
|
||||
|
||||
addPeopleController.inviteById(ids);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
|
||||
// it is going to be memory-leaked in the associated InviteController
|
||||
// and no subsequent InviteButton clicks/taps will be delivered.
|
||||
addPeopleController.endAddPeople();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
|
||||
@@ -122,9 +186,63 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
private void onInviteControllerBeginAddPeople(
|
||||
InviteController inviteController,
|
||||
AddPeopleController addPeopleController) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// Log with the tag "ReactNative" in order to have the log visible in
|
||||
// react-native log-android as well.
|
||||
Log.d(
|
||||
"ReactNative",
|
||||
InviteControllerListener.class.getSimpleName() + ".beginAddPeople");
|
||||
|
||||
String query = ADD_PEOPLE_CONTROLLER_QUERY;
|
||||
|
||||
if (query != null
|
||||
&& (inviteController.isAddPeopleEnabled()
|
||||
|| inviteController.isDialOutEnabled())) {
|
||||
addPeopleController.setListener(new AddPeopleControllerListener() {
|
||||
public void onInviteSettled(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> failedInvitees) {
|
||||
onAddPeopleControllerInviteSettled(
|
||||
addPeopleController,
|
||||
failedInvitees);
|
||||
}
|
||||
|
||||
public void onReceivedResults(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> results,
|
||||
String query) {
|
||||
onAddPeopleControllerReceivedResults(
|
||||
addPeopleController,
|
||||
results, query);
|
||||
}
|
||||
});
|
||||
addPeopleController.performQuery(query);
|
||||
} else {
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController;
|
||||
// otherwise, it is going to be memory-leaked in the associated
|
||||
// InviteController and no subsequent InviteButton clicks/taps will
|
||||
// be delivered.
|
||||
addPeopleController.endAddPeople();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
CalendarEventsPackage.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode,
|
||||
String[] permissions,
|
||||
int[] grantResults) {
|
||||
CalendarEventsPackage.onRequestPermissionsResult(
|
||||
requestCode,
|
||||
permissions,
|
||||
grantResults);
|
||||
|
||||
super.onRequestPermissionsResult(
|
||||
requestCode,
|
||||
permissions,
|
||||
grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,33 @@ 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" }
|
||||
}
|
||||
|
||||
// Make sure we use the react-native version in node_modules and not the one
|
||||
// published in jcenter / elsewhere.
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
eachDependency { DependencyResolveDetails details ->
|
||||
if (details.requested.group == 'com.facebook.react'
|
||||
&& details.requested.name == 'react-native') {
|
||||
def file = new File("$rootDir/../node_modules/react-native/package.json")
|
||||
def version = new 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]}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Third-party react-native modules which Jitsi Meet SDK for Android depends
|
||||
// on and which are not available in third-party Maven repositories need to
|
||||
// be deployed in a Maven repository of ours.
|
||||
@@ -131,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')
|
||||
@@ -76,6 +77,11 @@ gradle.projectsEvaluated {
|
||||
type: Copy) {
|
||||
from("${projectDir}/../../sounds/joined.wav")
|
||||
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
|
||||
@@ -72,13 +75,20 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
private static final int VIDEO_CALL = 2;
|
||||
|
||||
/**
|
||||
*
|
||||
* Constant defining the action for plugging in a headset. This is used on
|
||||
* our device detection system for API < 23.
|
||||
*/
|
||||
private static final String ACTION_HEADSET_PLUG
|
||||
= (Build.VERSION.SDK_INT >= 21)
|
||||
? AudioManager.ACTION_HEADSET_PLUG
|
||||
: Intent.ACTION_HEADSET_PLUG;
|
||||
|
||||
/**
|
||||
* Constant defining a USB headset. Only available on API level >= 26.
|
||||
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
|
||||
*/
|
||||
private static final int TYPE_USB_HEADSET = 22;
|
||||
|
||||
/**
|
||||
* The name of {@code AudioModeModule} to be used in the React Native
|
||||
* bridge.
|
||||
@@ -90,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.
|
||||
@@ -103,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.
|
||||
@@ -133,6 +149,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
break;
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
|
||||
case TYPE_USB_HEADSET:
|
||||
devices.add(DEVICE_HEADPHONES);
|
||||
break;
|
||||
}
|
||||
@@ -212,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();
|
||||
@@ -252,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();
|
||||
@@ -289,7 +306,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
* Only used on Android >= M.
|
||||
*/
|
||||
void onAudioDeviceChange() {
|
||||
mainThreadHandler.post(onAudioDeviceChangeRunner);
|
||||
runInAudioThread(onAudioDeviceChangeRunner);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,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
|
||||
@@ -337,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.
|
||||
*
|
||||
@@ -344,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)) {
|
||||
@@ -415,7 +470,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
};
|
||||
mainThreadHandler.post(r);
|
||||
runInAudioThread(r);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,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;
|
||||
@@ -501,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,83 +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 org.jitsi.meet.sdk.JitsiMeetView;
|
||||
import org.jitsi.meet.sdk.JitsiMeetViewListener;
|
||||
|
||||
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.
|
||||
@@ -112,35 +53,8 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* The internal processing for the conference URL set on
|
||||
* a {@link JitsiMeetView} instance.
|
||||
*
|
||||
* @param eventName the name of the external API event to be processed.
|
||||
* @param view the {@link JitsiMeetView} instance.
|
||||
* @param url the "url" attribute value retrieved from the "data" carried by
|
||||
* the event.
|
||||
*/
|
||||
private void maybeSetConferenceUrlOnTheView(
|
||||
String eventName, JitsiMeetView view, String url)
|
||||
{
|
||||
switch(eventName) {
|
||||
case "CONFERENCE_WILL_JOIN":
|
||||
view.setCurrentConferenceUrl(url);
|
||||
break;
|
||||
|
||||
case "CONFERENCE_FAILED":
|
||||
case "CONFERENCE_WILL_LEAVE":
|
||||
case "LOAD_CONFIG_ERROR":
|
||||
// Abandon the conference only if it's for the current URL
|
||||
if (url != null && url.equals(view.getCurrentConferenceUrl())) {
|
||||
view.setCurrentConferenceUrl(null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event that occurred on JavaScript to the view's listener.
|
||||
* Dispatches an event that occurred on the JavaScript side of the SDK to
|
||||
* the specified {@link BaseReactView}'s listener.
|
||||
*
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
@@ -151,62 +65,15 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
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;
|
||||
}
|
||||
|
||||
maybeSetConferenceUrlOnTheView(name, view, data.getString("url"));
|
||||
|
||||
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.
|
||||
@@ -122,6 +105,11 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
JitsiMeetView view = initializeView();
|
||||
|
||||
if (view != null) {
|
||||
// XXX Allow extenders who override initializeView() to configure
|
||||
// the view before the first loadURL(). Probably works around a
|
||||
// problem related to ReactRootView#setAppProperties().
|
||||
view.loadURL(null);
|
||||
|
||||
this.view = view;
|
||||
setContentView(this.view);
|
||||
}
|
||||
@@ -144,11 +132,28 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
}
|
||||
view.setWelcomePageEnabled(welcomePageEnabled);
|
||||
|
||||
view.loadURL(null);
|
||||
|
||||
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.
|
||||
@@ -174,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
|
||||
@@ -217,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
|
||||
@@ -239,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
|
||||
@@ -247,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,141 +56,21 @@ 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;
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the current conference URL. Will have a value when the app is in
|
||||
* a conference.
|
||||
*
|
||||
* Currently one thread writes and one thread reads, so it should be fine to
|
||||
* have this field volatile without additional synchronization.
|
||||
*/
|
||||
private volatile String conferenceUrl;
|
||||
|
||||
/**
|
||||
* The default base {@code URL} used to join a conference when a partial URL
|
||||
* (e.g. a room name only) is specified to {@link #loadURLString(String)} or
|
||||
@@ -220,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
|
||||
@@ -248,9 +92,11 @@ public class JitsiMeetView extends FrameLayout {
|
||||
private Boolean pictureInPictureEnabled;
|
||||
|
||||
/**
|
||||
* React Native root view.
|
||||
* The URL of the current conference.
|
||||
*/
|
||||
private ReactRootView reactRootView;
|
||||
// XXX Currently, one thread writes and one thread reads, so it should be
|
||||
// fine to have this field volatile without additional synchronization.
|
||||
private volatile String url;
|
||||
|
||||
/**
|
||||
* Whether the Welcome page is enabled.
|
||||
@@ -260,45 +106,34 @@ 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);
|
||||
|
||||
/**
|
||||
* Retrieves the current conferences URL.
|
||||
*
|
||||
* @return a string with conference URL if the view is currently in
|
||||
* a conference or {@code null} otherwise.
|
||||
*/
|
||||
public String getCurrentConferenceUrl() {
|
||||
return conferenceUrl;
|
||||
if (pipModule != null) {
|
||||
try {
|
||||
pipModule.enterPictureInPicture();
|
||||
} catch (RuntimeException re) {
|
||||
Log.e(TAG, "onUserLeaveHint: failed to enter PiP mode", re);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,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}.
|
||||
*/
|
||||
@@ -328,13 +163,16 @@ public class JitsiMeetView extends FrameLayout {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link JitsiMeetViewListener} set on this {@code JitsiMeetView}.
|
||||
* Gets the URL of the current conference.
|
||||
*
|
||||
* @return The {@code JitsiMeetViewListener} set on this
|
||||
* {@code JitsiMeetView}.
|
||||
* XXX The method is meant for internal purposes only at the time of this
|
||||
* writing because there is no equivalent API on iOS.
|
||||
*
|
||||
* @return the URL {@code String} of the current conference if any;
|
||||
* otherwise, {@code null}.
|
||||
*/
|
||||
public JitsiMeetViewListener getListener() {
|
||||
return listener;
|
||||
String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,11 +183,11 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean getPictureInPictureEnabled() {
|
||||
public boolean isPictureInPictureEnabled() {
|
||||
return
|
||||
PictureInPictureModule.isPictureInPictureSupported()
|
||||
&& (pictureInPictureEnabled == null
|
||||
|| pictureInPictureEnabled.booleanValue());
|
||||
|| pictureInPictureEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -360,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;
|
||||
}
|
||||
|
||||
@@ -394,9 +232,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
props.putString("defaultURL", defaultURL.toString());
|
||||
}
|
||||
|
||||
// externalAPIScope
|
||||
props.putString("externalAPIScope", externalAPIScope);
|
||||
|
||||
// inviteController
|
||||
InviteController inviteController = getInviteController();
|
||||
|
||||
@@ -412,7 +247,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||
// pictureInPictureEnabled
|
||||
props.putBoolean(
|
||||
"pictureInPictureEnabled",
|
||||
getPictureInPictureEnabled());
|
||||
isPictureInPictureEnabled());
|
||||
|
||||
// url
|
||||
if (urlObject != null) {
|
||||
@@ -433,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -467,78 +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() && conferenceUrl != 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 exc) {
|
||||
Log.e(
|
||||
TAG, "onUserLeaveHint: failed to enter PiP mode", exc);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current conference URL.
|
||||
*
|
||||
* @param conferenceUrl a string with new conference URL to set if the view
|
||||
* is entering the conference or {@code null} if the view is no longer in
|
||||
* the conference.
|
||||
*/
|
||||
void setCurrentConferenceUrl(String conferenceUrl) {
|
||||
this.conferenceUrl = conferenceUrl;
|
||||
onExternalAPIEvent(LISTENER_METHODS, name, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,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
|
||||
@@ -574,7 +358,20 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* {@code true}; otherwise, {@code false}.
|
||||
*/
|
||||
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
|
||||
this.pictureInPictureEnabled = Boolean.valueOf(pictureInPictureEnabled);
|
||||
this.pictureInPictureEnabled = pictureInPictureEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of the current conference.
|
||||
*
|
||||
* XXX The method is meant for internal purposes only. It does not
|
||||
* {@code loadURL}, it merely remembers the specified URL.
|
||||
*
|
||||
* @param url the URL {@code String} which to be set as the URL of the
|
||||
* current conference.
|
||||
*/
|
||||
void setURL(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -110,20 +112,20 @@ public class AddPeopleController {
|
||||
|
||||
if (owner != null) {
|
||||
WritableArray invitees = new WritableNativeArray();
|
||||
|
||||
|
||||
for(int i = 0, size = ids.size(); i < size; i++) {
|
||||
String id = ids.get(i);
|
||||
|
||||
if(items.containsKey(id)) {
|
||||
WritableNativeMap map = new WritableNativeMap();
|
||||
map.merge(items.get(ids));
|
||||
map.merge(items.get(id));
|
||||
invitees.pushMap(map);
|
||||
} else {
|
||||
// If the id doesn't exist in the map, we can't do anything,
|
||||
// so just skip it.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
owner.invite(this, invitees);
|
||||
}
|
||||
}
|
||||
@@ -138,14 +140,14 @@ public class AddPeopleController {
|
||||
jFailedInvitees.add(failedInvitees.getMap(i).toHashMap());
|
||||
}
|
||||
|
||||
listener.inviteSettled(this, jFailedInvitees);
|
||||
listener.onInviteSettled(this, jFailedInvitees);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a search for entities to invite with the given query. Results will
|
||||
* be returned through the associated AddPeopleControllerListener's
|
||||
* onReceiveResults method.
|
||||
* onReceivedResults method.
|
||||
*
|
||||
* @param query
|
||||
*/
|
||||
@@ -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,16 +181,21 @@ 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());
|
||||
}
|
||||
|
||||
listener.onReceiveResults(this, jvmResults, query);
|
||||
listener.onReceivedResults(this, jvmResults, query);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,29 +21,36 @@ import java.util.Map;
|
||||
|
||||
public interface AddPeopleControllerListener {
|
||||
/**
|
||||
* Called when results are received for a query called through AddPeopleController.query()
|
||||
* Called when the call to {@link AddPeopleController#inviteById(List)}
|
||||
* completes.
|
||||
*
|
||||
* @param addPeopleController
|
||||
* @param results a List of Map<String, Object> objects that represent items returned by the query.
|
||||
* The object at key "type" describes the type of item: "user", "videosipgw" (conference room), or "phone".
|
||||
* "user" types have properties at "id", "name", and "avatar"
|
||||
* "videosipgw" types have properties at "id" and "name"
|
||||
* "phone" types have properties at "number", "title", "and "subtitle"
|
||||
* @param query the query that generated the given results
|
||||
* @param addPeopleController the active {@link AddPeopleController} for
|
||||
* this invite flow. This object should be cleaned up by calling
|
||||
* {@link AddPeopleController#endAddPeople()} if the user exits the invite
|
||||
* flow. Otherwise, it can stay active if the user will attempt to invite
|
||||
* @param failedInvitees a {@code List} of {@code Map<String, Object>}
|
||||
* dictionaries that represent the invitations that failed. The data type of
|
||||
* the objects is identical to the results returned in onReceivedResuls.
|
||||
*/
|
||||
void onReceiveResults(AddPeopleController addPeopleController, List<Map<String, Object>> results, String query);
|
||||
void onInviteSettled(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> failedInvitees);
|
||||
|
||||
/**
|
||||
* Called when the call to {@link AddPeopleController#inviteById(List)} completes, but the
|
||||
* invitation fails for one or more of the selected items.
|
||||
* Called when results are received for a query called through
|
||||
* AddPeopleController.query().
|
||||
*
|
||||
* @param addPeopleController the active {@link AddPeopleController} for this invite flow. This object
|
||||
* should be cleaned up by calling {@link AddPeopleController#endAddPeople()} if
|
||||
* the user exits the invite flow. Otherwise, it can stay active if the user
|
||||
* will attempt to invite
|
||||
* @param failedInvitees a {@code List} of {@code Map<String, Object>} dictionaries that represent the
|
||||
* invitations that failed. The data type of the objects is identical to the results
|
||||
* returned in onReceiveResuls.
|
||||
* @param addPeopleController
|
||||
* @param results a List of Map<String, Object> objects that represent items
|
||||
* returned by the query. The object at key "type" describes the type of
|
||||
* item: "user", "videosipgw" (conference room), or "phone". "user" types
|
||||
* have properties at "id", "name", and "avatar". "videosipgw" types have
|
||||
* properties at "id" and "name". "phone" types have properties at "number",
|
||||
* "title", "and "subtitle"
|
||||
* @param query the query that generated the given results
|
||||
*/
|
||||
void inviteSettled(AddPeopleController addPeopleController, List<Map<String, Object>> failedInvitees);
|
||||
void onReceivedResults(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> results,
|
||||
String query);
|
||||
}
|
||||
|
||||
@@ -61,10 +61,6 @@ public class InviteController {
|
||||
this.externalAPIScope = externalAPIScope;
|
||||
}
|
||||
|
||||
public InviteControllerListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
void beginAddPeople(ReactApplicationContext reactContext) {
|
||||
InviteControllerListener listener = getListener();
|
||||
|
||||
@@ -102,6 +98,10 @@ public class InviteController {
|
||||
}
|
||||
}
|
||||
|
||||
public InviteControllerListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends JavaScript event to submit invitations to the given item ids
|
||||
*
|
||||
@@ -202,17 +202,17 @@ public class InviteController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a query for users to invite to the conference. Results will be
|
||||
* returned through the {@link AddPeopleControllerListener#onReceiveResults(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
|
||||
*/
|
||||
void performQuery(AddPeopleController addPeopleController, String query) {
|
||||
WritableNativeMap params = new WritableNativeMap();
|
||||
|
||||
params.putString("externalAPIScope", externalAPIScope);
|
||||
params.putString("addPeopleControllerScope", addPeopleController.getUuid());
|
||||
params.putString("externalAPIScope", externalAPIScope);
|
||||
params.putString("query", query);
|
||||
ReactContextUtils.emitEvent(
|
||||
addPeopleController.getReactApplicationContext(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -22,13 +22,17 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
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);
|
||||
}
|
||||
@@ -42,7 +46,19 @@ public class InviteModule extends ReactContextBaseJavaModule {
|
||||
* {@code JitsiMeetView} whose {@code InviteButton} was clicked/tapped.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void beginAddPeople(String externalAPIScope) {
|
||||
public void beginAddPeople(final String externalAPIScope) {
|
||||
// Make sure InviteControllerListener (like all other listeners of the
|
||||
// SDK) is invoked on the UI thread. It was requested by SDK consumers.
|
||||
if (!UiThreadUtil.isOnUiThread()) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
beginAddPeople(externalAPIScope);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
InviteController inviteController
|
||||
= findInviteControllerByExternalAPIScope(externalAPIScope);
|
||||
|
||||
@@ -54,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();
|
||||
}
|
||||
@@ -68,13 +85,30 @@ 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(
|
||||
String externalAPIScope,
|
||||
String addPeopleControllerScope,
|
||||
ReadableArray failedInvitees) {
|
||||
final String externalAPIScope,
|
||||
final String addPeopleControllerScope,
|
||||
final ReadableArray failedInvitees) {
|
||||
// Make sure AddPeopleControllerListener (like all other listeners of
|
||||
// the SDK) is invoked on the UI thread. It was requested by SDK
|
||||
// consumers.
|
||||
if (!UiThreadUtil.isOnUiThread()) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
inviteSettled(
|
||||
externalAPIScope,
|
||||
addPeopleControllerScope,
|
||||
failedInvitees);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
InviteController inviteController
|
||||
= findInviteControllerByExternalAPIScope(externalAPIScope);
|
||||
|
||||
@@ -94,14 +128,32 @@ 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(
|
||||
String externalAPIScope,
|
||||
String addPeopleControllerScope,
|
||||
String query,
|
||||
ReadableArray results) {
|
||||
final String externalAPIScope,
|
||||
final String addPeopleControllerScope,
|
||||
final String query,
|
||||
final ReadableArray results) {
|
||||
// Make sure AddPeopleControllerListener (like all other listeners of
|
||||
// the SDK) is invoked on the UI thread. It was requested by SDK
|
||||
// consumers.
|
||||
if (!UiThreadUtil.isOnUiThread()) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
receivedResults(
|
||||
externalAPIScope,
|
||||
addPeopleControllerScope,
|
||||
query,
|
||||
results);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
InviteController inviteController
|
||||
= findInviteControllerByExternalAPIScope(externalAPIScope);
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
2
app.js
2
app.js
@@ -15,7 +15,6 @@ import conference from './conference';
|
||||
import API from './modules/API';
|
||||
import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
|
||||
import remoteControl from './modules/remotecontrol/RemoteControl';
|
||||
import settings from './modules/settings/Settings';
|
||||
import translation from './modules/translation/translation';
|
||||
import UI from './modules/UI/UI';
|
||||
|
||||
@@ -41,7 +40,6 @@ window.APP = {
|
||||
|
||||
keyboardshortcut,
|
||||
remoteControl,
|
||||
settings,
|
||||
translation,
|
||||
UI
|
||||
};
|
||||
|
||||
362
conference.js
362
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 { updateRecordingState } 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,
|
||||
@@ -46,7 +44,11 @@ import {
|
||||
sendLocalParticipant,
|
||||
setDesktopSharingEnabled
|
||||
} from './react/features/base/conference';
|
||||
import { updateDeviceList } from './react/features/base/devices';
|
||||
import {
|
||||
getAvailableDevices,
|
||||
setAudioOutputDeviceId,
|
||||
updateDeviceList
|
||||
} from './react/features/base/devices';
|
||||
import {
|
||||
isFatalJitsiConnectionError,
|
||||
JitsiConferenceErrors,
|
||||
@@ -72,6 +74,8 @@ import {
|
||||
getAvatarURLByParticipantId,
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
hiddenParticipantJoined,
|
||||
hiddenParticipantLeft,
|
||||
localParticipantConnectionStatusChanged,
|
||||
localParticipantRoleChanged,
|
||||
MAX_DISPLAY_NAME_LENGTH,
|
||||
@@ -82,6 +86,7 @@ import {
|
||||
participantRoleChanged,
|
||||
participantUpdated
|
||||
} from './react/features/base/participants';
|
||||
import { updateSettings } from './react/features/base/settings';
|
||||
import {
|
||||
createLocalTracksF,
|
||||
isLocalTrackMuted,
|
||||
@@ -105,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);
|
||||
|
||||
@@ -368,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.
|
||||
@@ -464,6 +472,7 @@ function _connectionFailedHandler(error) {
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_connectionFailedHandler);
|
||||
if (room) {
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
room.leave();
|
||||
}
|
||||
}
|
||||
@@ -481,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
|
||||
@@ -490,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
|
||||
@@ -717,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);
|
||||
@@ -1096,20 +1086,6 @@ export default {
|
||||
return this._room && this._room.myUserId();
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates if recording is supported in this conference.
|
||||
*/
|
||||
isRecordingSupported() {
|
||||
return this._room && this._room.isRecordingSupported();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the recording state or undefined if the room is not defined.
|
||||
*/
|
||||
getRecordingState() {
|
||||
return this._room ? this._room.getRecordingState() : undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Will be filled with values only when config.debug is enabled.
|
||||
* Its used by torture to check audio levels.
|
||||
@@ -1266,14 +1242,7 @@ 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.settings.getDisplayName();
|
||||
const nick = APP.store.getState()['features/base/settings'].displayName;
|
||||
|
||||
if (nick) {
|
||||
options.displayName = nick;
|
||||
@@ -1687,19 +1656,25 @@ 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,
|
||||
presence: user.getStatus(),
|
||||
role: user.getRole()
|
||||
}));
|
||||
|
||||
@@ -1717,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, user));
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -1807,30 +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.on(JitsiConferenceEvents.LIVE_STREAM_URL_CHANGED,
|
||||
(from, liveStreamViewURL) =>
|
||||
APP.store.dispatch(updateRecordingState({
|
||||
liveStreamViewURL
|
||||
})));
|
||||
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,
|
||||
@@ -1857,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 +1842,7 @@ export default {
|
||||
= displayName.substr(0, MAX_DISPLAY_NAME_LENGTH);
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id,
|
||||
name: formattedDisplayName
|
||||
}));
|
||||
@@ -1911,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,
|
||||
@@ -1929,8 +1889,17 @@ 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,
|
||||
id: participant.getId(),
|
||||
raisedHand: newValue === 'true'
|
||||
}));
|
||||
@@ -1946,16 +1915,6 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
/* eslint-enable max-params */
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.RECORDER_STATE_CHANGED,
|
||||
(status, error) => {
|
||||
logger.log('Received recorder status change: ', status, error);
|
||||
APP.UI.updateRecordingState(status);
|
||||
}
|
||||
);
|
||||
|
||||
room.on(JitsiConferenceEvents.KICKED, () => {
|
||||
APP.UI.hideStats();
|
||||
APP.UI.notifyKicked();
|
||||
@@ -2005,13 +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(
|
||||
@@ -2019,6 +1979,7 @@ export default {
|
||||
(data, from) => {
|
||||
APP.store.dispatch(
|
||||
participantUpdated({
|
||||
conference: room,
|
||||
id: from,
|
||||
avatarURL: data.value
|
||||
}));
|
||||
@@ -2028,6 +1989,7 @@ export default {
|
||||
(data, from) => {
|
||||
APP.store.dispatch(
|
||||
participantUpdated({
|
||||
conference: room,
|
||||
id: from,
|
||||
avatarID: data.value
|
||||
}));
|
||||
@@ -2089,13 +2051,6 @@ export default {
|
||||
}));
|
||||
});
|
||||
|
||||
/* eslint-enable max-params */
|
||||
|
||||
// Starts or stops the recording for the conference.
|
||||
APP.UI.addListener(UIEvents.RECORDING_TOGGLED, options => {
|
||||
room.toggleRecording(options);
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
|
||||
AuthHandler.authenticate(room);
|
||||
});
|
||||
@@ -2131,7 +2086,9 @@ export default {
|
||||
})
|
||||
.then(() => {
|
||||
logger.log('switched local video device');
|
||||
APP.settings.setCameraDeviceId(cameraDeviceId, true);
|
||||
APP.store.dispatch(updateSettings({
|
||||
cameraDeviceId
|
||||
}));
|
||||
})
|
||||
.catch(err => {
|
||||
APP.UI.showCameraErrorNotification(err);
|
||||
@@ -2163,7 +2120,9 @@ export default {
|
||||
.then(stream => {
|
||||
this.useAudioStream(stream);
|
||||
logger.log('switched local audio device');
|
||||
APP.settings.setMicDeviceId(micDeviceId, true);
|
||||
APP.store.dispatch(updateSettings({
|
||||
micDeviceId
|
||||
}));
|
||||
})
|
||||
.catch(err => {
|
||||
APP.UI.showMicErrorNotification(err);
|
||||
@@ -2171,20 +2130,6 @@ export default {
|
||||
}
|
||||
);
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
|
||||
audioOutputDeviceId => {
|
||||
sendAnalytics(createDeviceChangedEvent('audio', 'output'));
|
||||
APP.settings.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
|
||||
@@ -2316,9 +2261,10 @@ export default {
|
||||
|
||||
APP.store.dispatch(conferenceJoined(room));
|
||||
|
||||
APP.UI.mucJoined();
|
||||
const displayName = APP.settings.getDisplayName();
|
||||
const displayName
|
||||
= APP.store.getState()['features/base/settings'].displayName;
|
||||
|
||||
APP.UI.changeDisplayName('localVideoContainer', displayName);
|
||||
APP.API.notifyConferenceJoined(
|
||||
this.roomName,
|
||||
this._room.myUserId(),
|
||||
@@ -2331,7 +2277,6 @@ export default {
|
||||
APP.store.getState(), this._room.myUserId())
|
||||
}
|
||||
);
|
||||
APP.UI.markVideoInterrupted(false);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2356,43 +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.
|
||||
if (this.localAudio) {
|
||||
APP.settings.setMicDeviceId(
|
||||
this.localAudio.getDeviceId(), false);
|
||||
}
|
||||
const { mediaDevices } = JitsiMeetJS;
|
||||
|
||||
if (this.localVideo) {
|
||||
APP.settings.setCameraDeviceId(
|
||||
this.localVideo.getDeviceId(), false);
|
||||
}
|
||||
if (mediaDevices.isDeviceListAvailable()
|
||||
&& mediaDevices.isDeviceChangeAvailable()) {
|
||||
this.deviceChangeListener = devices =>
|
||||
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
|
||||
mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
|
||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||
APP.UI.onAvailableDevicesChanged(devices);
|
||||
APP.store.dispatch(updateDeviceList(devices));
|
||||
});
|
||||
const { dispatch } = APP.store;
|
||||
|
||||
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}`);
|
||||
});
|
||||
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()
|
||||
}));
|
||||
}
|
||||
|
||||
if (this.localVideo) {
|
||||
dispatch(updateSettings({
|
||||
cameraDeviceId: this.localVideo.getDeviceId()
|
||||
}));
|
||||
}
|
||||
|
||||
APP.UI.onAvailableDevicesChanged(devices);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2403,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(
|
||||
@@ -2425,10 +2361,13 @@ export default {
|
||||
const videoWasMuted = this.isLocalVideoMuted();
|
||||
|
||||
if (typeof newDevices.audiooutput !== 'undefined') {
|
||||
// Just ignore any errors in catch block.
|
||||
promises.push(APP.settings
|
||||
.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(
|
||||
@@ -2462,7 +2401,6 @@ export default {
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(() => {
|
||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||
APP.UI.onAvailableDevicesChanged(devices);
|
||||
});
|
||||
},
|
||||
@@ -2472,7 +2410,7 @@ export default {
|
||||
*/
|
||||
updateAudioIconEnabled() {
|
||||
const audioMediaDevices
|
||||
= mediaDeviceHelper.getCurrentMediaDevices().audioinput;
|
||||
= APP.store.getState()['features/base/devices'].audioInput;
|
||||
const audioDeviceCount
|
||||
= audioMediaDevices ? audioMediaDevices.length : 0;
|
||||
|
||||
@@ -2495,7 +2433,7 @@ export default {
|
||||
*/
|
||||
updateVideoIconEnabled() {
|
||||
const videoMediaDevices
|
||||
= mediaDeviceHelper.getCurrentMediaDevices().videoinput;
|
||||
= APP.store.getState()['features/base/devices'].videoInput;
|
||||
const videoDeviceCount
|
||||
= videoMediaDevices ? videoMediaDevices.length : 0;
|
||||
|
||||
@@ -2526,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;
|
||||
|
||||
@@ -2548,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
|
||||
@@ -2571,13 +2522,23 @@ export default {
|
||||
const localId = localParticipant.id;
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id: localId,
|
||||
local: true,
|
||||
email: formattedEmail
|
||||
}));
|
||||
|
||||
APP.settings.setEmail(formattedEmail);
|
||||
APP.UI.setUserEmail(localId, formattedEmail);
|
||||
APP.store.dispatch(updateSettings({
|
||||
email: formattedEmail
|
||||
}));
|
||||
APP.API.notifyEmailChanged(localId, {
|
||||
email: formattedEmail
|
||||
});
|
||||
sendData(commands.EMAIL, formattedEmail);
|
||||
},
|
||||
|
||||
@@ -2595,12 +2556,20 @@ export default {
|
||||
}
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id,
|
||||
local: true,
|
||||
avatarURL: formattedUrl
|
||||
}));
|
||||
|
||||
APP.settings.setAvatarUrl(url);
|
||||
APP.store.dispatch(updateSettings({
|
||||
avatarURL: formattedUrl
|
||||
}));
|
||||
sendData(commands.AVATAR_URL, url);
|
||||
},
|
||||
|
||||
@@ -2649,12 +2618,21 @@ export default {
|
||||
}
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id,
|
||||
local: true,
|
||||
name: formattedNickname
|
||||
}));
|
||||
|
||||
APP.settings.setDisplayName(formattedNickname);
|
||||
APP.store.dispatch(updateSettings({
|
||||
displayName: formattedNickname
|
||||
}));
|
||||
|
||||
APP.API.notifyDisplayNameChanged(id, {
|
||||
displayName: formattedNickname,
|
||||
formattedDisplayName:
|
||||
|
||||
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
|
||||
|
||||
@@ -102,7 +102,12 @@ function connect(id, password, roomName) {
|
||||
/* eslint-enable max-params */
|
||||
APP.store.dispatch(
|
||||
connectionFailed(
|
||||
connection, error, message, credentials, details));
|
||||
connection, {
|
||||
credentials,
|
||||
details,
|
||||
message,
|
||||
name: error
|
||||
}));
|
||||
|
||||
if (isFatalJitsiConnectionError(error)) {
|
||||
connection.removeEventListener(
|
||||
@@ -127,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%;
|
||||
@@ -66,7 +71,6 @@ input[type="reset"], input[type="submit"] {
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
resize: none;
|
||||
line-height: 1.5em;
|
||||
@@ -104,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;
|
||||
padding: 16px 8px;
|
||||
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 {
|
||||
@@ -100,6 +99,7 @@
|
||||
|
||||
.icon-hangup {
|
||||
color: $hangupColor;
|
||||
font-size: $newToolbarHangupFontSize;
|
||||
}
|
||||
|
||||
.overflow-menu {
|
||||
@@ -120,6 +120,12 @@
|
||||
height: 22px;
|
||||
padding: 5px 12px;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #313D52;
|
||||
}
|
||||
@@ -130,6 +136,20 @@
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
}
|
||||
|
||||
.beta-tag {
|
||||
background: #B8C7E0;
|
||||
border-radius: 2px;
|
||||
color: $newToolbarBackgroundColor;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.overflow-menu-item-icon {
|
||||
@@ -239,10 +259,14 @@
|
||||
z-index: $toolbarZ;
|
||||
|
||||
i {
|
||||
font-size: $alwaysOnTopToolbarFontSize;
|
||||
height: $alwaysOnTopToolbarSize;
|
||||
line-height: $alwaysOnTopToolbarSize;
|
||||
width: $alwaysOnTopToolbarSize;
|
||||
font-size: $newToolbarFontSize;
|
||||
height: $newToolbarSize;
|
||||
line-height: $newToolbarSize;
|
||||
width: $newToolbarSize;
|
||||
}
|
||||
|
||||
.icon-hangup {
|
||||
font-size: $newToolbarHangupFontSize;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
|
||||
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;
|
||||
|
||||
@@ -32,15 +32,14 @@ $defaultDarkColor: #2b3d5c;
|
||||
/**
|
||||
* Toolbar
|
||||
*/
|
||||
$alwaysOnTopToolbarFontSize: 1em;
|
||||
$alwaysOnTopToolbarSize: 30px;
|
||||
$defaultToolbarSize: 50px;
|
||||
$newToolbarBackgroundColor: rgba(22, 38, 55, 0.8);
|
||||
$newToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);
|
||||
$newToolbarButtonToggleColor: rgba(14, 20, 35, 1);
|
||||
$newToolbarFontSize: 1.9em;
|
||||
$newToolbarSize: 32px;
|
||||
$newToolbarSizeWithPadding: calc(32px + 32px);
|
||||
$newToolbarFontSize: 24px;
|
||||
$newToolbarHangupFontSize: 32px;
|
||||
$newToolbarSize: 40px;
|
||||
$newToolbarSizeWithPadding: calc(#{$newToolbarSize} + 24px);
|
||||
$toolbarTitleFontSize: 19px;
|
||||
|
||||
/**
|
||||
@@ -145,7 +144,7 @@ $watermarkHeight: 74px;
|
||||
/**
|
||||
* Welcome page variables.
|
||||
*/
|
||||
$welcomePageDescriptionColor: #fff;
|
||||
$welcomePageDescriptionColor: #E6EDFA;
|
||||
$welcomePageFontFamily: inherit;
|
||||
$welcomePageHeaderBackground: linear-gradient(#165ecc, #44A5FF);
|
||||
$welcomePageHeaderBackground: #1D69D4;
|
||||
$welcomePageTitleColor: #fff;
|
||||
|
||||
@@ -1,272 +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.
|
||||
*/
|
||||
.video-state-indicator.moveToCorner {
|
||||
transition: right 0.5s;
|
||||
|
||||
&.with-filmstrip {
|
||||
&#recordingLabel {
|
||||
right: 200px;
|
||||
}
|
||||
|
||||
&#videoResolutionLabel {
|
||||
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();
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#videospace {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
@@ -224,6 +226,11 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#largeVideoContainer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#largeVideoWrapper {
|
||||
box-shadow: 0 0 20px -2px #444;
|
||||
}
|
||||
@@ -620,7 +627,6 @@
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
#remotePresenceMessage .no-presence,
|
||||
#remoteConnectionMessage {
|
||||
display: none;
|
||||
}
|
||||
@@ -642,3 +648,110 @@
|
||||
1px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 0px 1px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.display-avatar-with-name {
|
||||
.avatar-container {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.displayNameContainer {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.videocontainer__hoverOverlay {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
video {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.display-name-on-black {
|
||||
.avatar-container {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.displayNameContainer {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.videocontainer__hoverOverlay {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
video {
|
||||
opacity: 0.2;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.display-video {
|
||||
.avatar-container {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.displayNameContainer {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.videocontainer__hoverOverlay {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
video {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.display-name-on-video {
|
||||
.avatar-container {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.displayNameContainer {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.videocontainer__hoverOverlay {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
video {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.display-avatar-only {
|
||||
.avatar-container {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.displayNameContainer {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.videocontainer__hoverOverlay {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
video {
|
||||
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%;
|
||||
}
|
||||
}
|
||||
@@ -141,98 +141,57 @@
|
||||
}
|
||||
}
|
||||
|
||||
.video-state-indicator {
|
||||
background: $videoStateIndicatorBackground;
|
||||
cursor: default;
|
||||
font-size: 13px;
|
||||
height: $videoStateIndicatorSize;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
min-width: $videoStateIndicatorSize;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.is-recording {
|
||||
background: none;
|
||||
opacity: 0.9;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
i {
|
||||
line-height: $videoStateIndicatorSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the label padding so it has more volume and can be easily clicked.
|
||||
*/
|
||||
.video-quality-label-status {
|
||||
line-height: $videoStateIndicatorSize;
|
||||
min-width: $videoStateIndicatorSize;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.recording-icon,
|
||||
.recording-icon i {
|
||||
line-height: $videoStateIndicatorSize;
|
||||
font-size: $videoStateIndicatorSize;
|
||||
opacity: 0.9;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon-rec {
|
||||
color: #FF5630;
|
||||
}
|
||||
|
||||
.icon-live {
|
||||
color: #0065FF;
|
||||
}
|
||||
|
||||
.recording-icon-background {
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
height: calc(#{$videoStateIndicatorSize} - 1px);
|
||||
left: 50%;
|
||||
opacity: 0.9;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: calc(#{$videoStateIndicatorSize} - 1px);
|
||||
}
|
||||
|
||||
#recordingLabelText {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.centeredVideoLabel.moveToCorner {
|
||||
z-index: $zindex3;
|
||||
}
|
||||
|
||||
#videoResolutionLabel {
|
||||
z-index: $zindex3 + 1;
|
||||
}
|
||||
|
||||
.centeredVideoLabel {
|
||||
bottom: 45%;
|
||||
border-radius: 2px;
|
||||
display: none;
|
||||
padding: 10px;
|
||||
transform: translate(-50%, 0);
|
||||
z-index: $centeredVideoLabelZ;
|
||||
|
||||
&.moveToCorner {
|
||||
bottom: auto;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.moveToCorner {
|
||||
.large-video-labels {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
right: 30px;
|
||||
transition: right 0.5s;
|
||||
z-index: $zindex3;
|
||||
|
||||
.circular-label {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.circular-label {
|
||||
background: #B8C7E0;
|
||||
}
|
||||
|
||||
.circular-label.file {
|
||||
background: #FF5630;
|
||||
}
|
||||
|
||||
.circular-label.stream {
|
||||
background: #0065FF;
|
||||
}
|
||||
|
||||
.recording-label.center-message {
|
||||
background: $videoStateIndicatorBackground;
|
||||
bottom: 50%;
|
||||
display: block;
|
||||
left: 50%;
|
||||
padding: 10px;
|
||||
position: fixed;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: $centeredVideoLabelZ;
|
||||
}
|
||||
}
|
||||
|
||||
.moveToCorner + .moveToCorner {
|
||||
right: 80px;
|
||||
}
|
||||
.circular-label {
|
||||
background: $videoStateIndicatorBackground;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
cursor: default;
|
||||
font-size: 13px;
|
||||
height: $videoStateIndicatorSize;
|
||||
line-height: $videoStateIndicatorSize;
|
||||
text-align: center;
|
||||
min-width: $videoStateIndicatorSize;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
33
doc/api.md
33
doc/api.md
@@ -28,6 +28,7 @@ Its constructor gets a number of options:
|
||||
* **noSSL**: (optional, defaults to true) Boolean indicating if the server should be contacted using HTTP or HTTPS.
|
||||
* **jwt**: (optional) [JWT](https://jwt.io/) token.
|
||||
* **onload**: (optional) handler for the iframe onload event.
|
||||
* **invitees**: (optional) Array of objects containing information about new participants that will be invited in the call.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -193,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
|
||||
{
|
||||
@@ -289,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();
|
||||
@@ -296,32 +311,42 @@ var iframe = api.getIFrame();
|
||||
|
||||
You can check whether the audio is muted with the following API function:
|
||||
```javascript
|
||||
isAudioMuted().then(function(muted) {
|
||||
api.isAudioMuted().then(function(muted) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can check whether the video is muted with the following API function:
|
||||
```javascript
|
||||
isVideoMuted().then(function(muted) {
|
||||
api.isVideoMuted().then(function(muted) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can check whether the audio is available with the following API function:
|
||||
```javascript
|
||||
isAudioAvailable().then(function(available) {
|
||||
api.isAudioAvailable().then(function(available) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can check whether the video is available with the following API function:
|
||||
```javascript
|
||||
isVideoAvailable().then(function(available) {
|
||||
api.isVideoAvailable().then(function(available) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can invite new participants to the call with the following API function:
|
||||
```javascript
|
||||
api.invite([{...}, {...}, {...}]).then(function() {
|
||||
// success
|
||||
}).catch(function() {
|
||||
// failure
|
||||
});
|
||||
```
|
||||
**NOTE: The format of the invitees in the array depends on the invite service used for the deployment.**
|
||||
|
||||
You can remove the embedded Jitsi Meet Conference with the following API function:
|
||||
```javascript
|
||||
api.dispose()
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
|
||||
// Call extra handler if defined.
|
||||
if (typeof postLoadErrorHandler === "function") {
|
||||
postLoadErrorHandler();
|
||||
postLoadErrorHandler(fileRef);
|
||||
}
|
||||
};
|
||||
window.removeEventListener(
|
||||
|
||||
@@ -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', '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>
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
|
||||
#import <JitsiMeet/JitsiMeet.h>
|
||||
|
||||
@interface ViewController : UIViewController<JitsiMeetViewDelegate, InviteControllerDelegate>
|
||||
@interface ViewController
|
||||
: UIViewController<
|
||||
JitsiMeetViewDelegate,
|
||||
JMAddPeopleControllerDelegate,
|
||||
JMInviteControllerDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
|
||||
#import "ViewController.h"
|
||||
|
||||
/**
|
||||
* The query to perform through JMAddPeopleController when the InviteButton is
|
||||
* tapped in order to exercise the public API of the feature invite. If nil, the
|
||||
* InviteButton will not be rendered.
|
||||
*/
|
||||
static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
|
||||
|
||||
@interface ViewController ()
|
||||
|
||||
@end
|
||||
@@ -32,8 +39,11 @@
|
||||
view.delegate = self;
|
||||
|
||||
// inviteController
|
||||
InviteController *inviteController = view.inviteController;
|
||||
JMInviteController *inviteController = view.inviteController;
|
||||
inviteController.delegate = self;
|
||||
inviteController.addPeopleEnabled
|
||||
= inviteController.dialOutEnabled
|
||||
= ADD_PEOPLE_CONTROLLER_QUERY != nil;
|
||||
|
||||
#endif // #ifdef DEBUG
|
||||
|
||||
@@ -48,44 +58,122 @@
|
||||
|
||||
#if DEBUG
|
||||
|
||||
void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) {
|
||||
// JitsiMeetViewDelegate
|
||||
|
||||
- (void)_onJitsiMeetViewDelegateEvent:(NSString *)name
|
||||
withData:(NSDictionary *)data {
|
||||
NSLog(
|
||||
@"[%s:%d] JitsiMeetViewDelegate %@ %@",
|
||||
__FILE__, __LINE__, name, data);
|
||||
|
||||
NSAssert(
|
||||
[NSThread isMainThread],
|
||||
@"JitsiMeetViewDelegate %@ method invoked on a non-main thread",
|
||||
name);
|
||||
}
|
||||
|
||||
- (void)conferenceFailed:(NSDictionary *)data {
|
||||
_onJitsiMeetViewDelegateEvent(@"CONFERENCE_FAILED", data);
|
||||
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_FAILED" withData:data];
|
||||
}
|
||||
|
||||
- (void)conferenceJoined:(NSDictionary *)data {
|
||||
_onJitsiMeetViewDelegateEvent(@"CONFERENCE_JOINED", data);
|
||||
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_JOINED" withData:data];
|
||||
}
|
||||
|
||||
- (void)conferenceLeft:(NSDictionary *)data {
|
||||
_onJitsiMeetViewDelegateEvent(@"CONFERENCE_LEFT", data);
|
||||
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_LEFT" withData:data];
|
||||
}
|
||||
|
||||
- (void)conferenceWillJoin:(NSDictionary *)data {
|
||||
_onJitsiMeetViewDelegateEvent(@"CONFERENCE_WILL_JOIN", data);
|
||||
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_WILL_JOIN" withData:data];
|
||||
}
|
||||
|
||||
- (void)conferenceWillLeave:(NSDictionary *)data {
|
||||
_onJitsiMeetViewDelegateEvent(@"CONFERENCE_WILL_LEAVE", data);
|
||||
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_WILL_LEAVE" withData:data];
|
||||
}
|
||||
|
||||
- (void)loadConfigError:(NSDictionary *)data {
|
||||
_onJitsiMeetViewDelegateEvent(@"LOAD_CONFIG_ERROR", data);
|
||||
[self _onJitsiMeetViewDelegateEvent:@"LOAD_CONFIG_ERROR" withData:data];
|
||||
}
|
||||
|
||||
- (void)beginAddPeople:(AddPeopleController *)addPeopleController {
|
||||
// JMInviteControllerDelegate
|
||||
|
||||
- (void)beginAddPeople:(JMAddPeopleController *)addPeopleController {
|
||||
NSLog(
|
||||
@"[%s:%d] InviteControllerDelegate %s",
|
||||
@"[%s:%d] JMInviteControllerDelegate %s",
|
||||
__FILE__, __LINE__, __FUNCTION__);
|
||||
|
||||
NSAssert(
|
||||
[NSThread isMainThread],
|
||||
@"JMInviteControllerDelegate beginAddPeople: invoked on a non-main thread");
|
||||
|
||||
NSString *query = ADD_PEOPLE_CONTROLLER_QUERY;
|
||||
JitsiMeetView *view = (JitsiMeetView *) self.view;
|
||||
JMInviteController *inviteController = view.inviteController;
|
||||
|
||||
if (query
|
||||
&& (inviteController.addPeopleEnabled
|
||||
|| inviteController.dialOutEnabled)) {
|
||||
addPeopleController.delegate = self;
|
||||
[addPeopleController performQuery:query];
|
||||
} else {
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
|
||||
// it is going to be memory-leaked in the associated JMInviteController
|
||||
// and no subsequent InviteButton clicks/taps will be delivered.
|
||||
[addPeopleController endAddPeople];
|
||||
}
|
||||
}
|
||||
|
||||
// JMAddPeopleControllerDelegate
|
||||
|
||||
- (void)addPeopleController:(JMAddPeopleController * _Nonnull)controller
|
||||
didReceiveResults:(NSArray<NSDictionary *> * _Nonnull)results
|
||||
forQuery:(NSString * _Nonnull)query {
|
||||
NSAssert(
|
||||
[NSThread isMainThread],
|
||||
@"JMAddPeopleControllerDelegate addPeopleController:didReceiveResults:forQuery: invoked on a non-main thread");
|
||||
|
||||
NSUInteger count = results.count;
|
||||
|
||||
if (count) {
|
||||
// Exercise JMAddPeopleController's inviteById: implementation.
|
||||
NSMutableArray *ids = [NSMutableArray arrayWithCapacity:count];
|
||||
|
||||
for (NSUInteger i = 0; i < count; ++i) {
|
||||
ids[i] = results[i][@"id"];
|
||||
}
|
||||
|
||||
[controller inviteById:ids];
|
||||
|
||||
// Exercise JMInviteController's invite:withCompletion: implementation.
|
||||
//
|
||||
// XXX Technically, only at most one of the two exercises will result in
|
||||
// an actual invitation eventually.
|
||||
JitsiMeetView *view = (JitsiMeetView *) self.view;
|
||||
JMInviteController *inviteController = view.inviteController;
|
||||
|
||||
[inviteController invite:results withCompletion:nil];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it
|
||||
// is going to be memory-leaked in the associated InviteController and no
|
||||
// is going to be memory-leaked in the associated JMInviteController and no
|
||||
// subsequent InviteButton clicks/taps will be delivered.
|
||||
[controller endAddPeople];
|
||||
}
|
||||
|
||||
- (void) inviteSettled:(NSArray<NSDictionary *> * _Nonnull)failedInvitees
|
||||
fromSearchController:(JMAddPeopleController * _Nonnull)addPeopleController {
|
||||
NSAssert(
|
||||
[NSThread isMainThread],
|
||||
@"JMAddPeopleControllerDelegate inviteSettled:fromSearchController: invoked on a non-main thread");
|
||||
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it
|
||||
// is going to be memory-leaked in the associated JMInviteController and no
|
||||
// subsequent InviteButton clicks/taps will be delivered. Technically,
|
||||
// endAddPeople will automatically be invoked if there are no
|
||||
// failedInviteees i.e. the invite succeeeded for all specified invitees.
|
||||
[addPeopleController endAddPeople];
|
||||
}
|
||||
|
||||
|
||||
@@ -79,11 +79,11 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C6F99C3A204DE6BE0001F710 /* AppDelegate.swift */,
|
||||
C6F99C3C204DE6BE0001F710 /* ViewController.swift */,
|
||||
C6F99C3E204DE6BE0001F710 /* Main.storyboard */,
|
||||
C6F99C41204DE6BE0001F710 /* Assets.xcassets */,
|
||||
C6F99C43204DE6BE0001F710 /* LaunchScreen.storyboard */,
|
||||
C6F99C46204DE6BE0001F710 /* Info.plist */,
|
||||
C6F99C43204DE6BE0001F710 /* LaunchScreen.storyboard */,
|
||||
C6F99C3E204DE6BE0001F710 /* Main.storyboard */,
|
||||
C6F99C3C204DE6BE0001F710 /* ViewController.swift */,
|
||||
);
|
||||
path = src;
|
||||
sourceTree = "<group>";
|
||||
|
||||
@@ -20,21 +20,21 @@ import JitsiMeet
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
guard let launchOptions = launchOptions else { return false }
|
||||
return JitsiMeetView.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Linking delegate methods
|
||||
|
||||
|
||||
func application(_ application: UIApplication,
|
||||
continue userActivity: NSUserActivity,
|
||||
restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
|
||||
return JitsiMeetView.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
||||
}
|
||||
|
||||
|
||||
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
|
||||
return JitsiMeetView.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation)
|
||||
}
|
||||
|
||||
@@ -20,27 +20,27 @@ import JitsiMeet
|
||||
class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var videoButton: UIButton?
|
||||
|
||||
|
||||
fileprivate var pipViewCoordinator: PiPViewCoordinator?
|
||||
fileprivate var jitsiMeetView: JitsiMeetView?
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
|
||||
override func viewWillTransition(to size: CGSize,
|
||||
with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
|
||||
let rect = CGRect(origin: CGPoint.zero, size: size)
|
||||
pipViewCoordinator?.resetBounds(bounds: rect)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
|
||||
@IBAction func openJitsiMeet(sender: Any?) {
|
||||
cleanUp()
|
||||
|
||||
|
||||
// create and configure jitsimeet view
|
||||
let jitsiMeetView = JitsiMeetView()
|
||||
jitsiMeetView.welcomePageEnabled = true
|
||||
@@ -48,18 +48,18 @@ class ViewController: UIViewController {
|
||||
jitsiMeetView.load(nil)
|
||||
jitsiMeetView.delegate = self
|
||||
self.jitsiMeetView = jitsiMeetView
|
||||
|
||||
|
||||
// Enable jitsimeet view to be a view that can be displayed
|
||||
// on top of all the things, and let the coordinator to manage
|
||||
// the view state and interactions
|
||||
pipViewCoordinator = PiPViewCoordinator(withView: jitsiMeetView)
|
||||
pipViewCoordinator?.configureAsStickyView(withParentView: view)
|
||||
|
||||
|
||||
// animate in
|
||||
jitsiMeetView.alpha = 0
|
||||
pipViewCoordinator?.show()
|
||||
}
|
||||
|
||||
|
||||
fileprivate func cleanUp() {
|
||||
jitsiMeetView?.removeFromSuperview()
|
||||
jitsiMeetView = nil
|
||||
@@ -68,21 +68,21 @@ class ViewController: UIViewController {
|
||||
}
|
||||
|
||||
extension ViewController: JitsiMeetViewDelegate {
|
||||
|
||||
|
||||
func conferenceFailed(_ data: [AnyHashable : Any]!) {
|
||||
hideJitsiMeetViewAndCleanUp()
|
||||
}
|
||||
|
||||
|
||||
func conferenceLeft(_ data: [AnyHashable : Any]!) {
|
||||
hideJitsiMeetViewAndCleanUp()
|
||||
}
|
||||
|
||||
|
||||
func enterPicture(inPicture data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.pipViewCoordinator?.enterPictureInPicture()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func hideJitsiMeetViewAndCleanUp() {
|
||||
DispatchQueue.main.async {
|
||||
self.pipViewCoordinator?.hide() { _ in
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */; };
|
||||
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */; };
|
||||
0B49424520AD8DBD00BD2DE0 /* outgoingStart.wav in Resources */ = {isa = PBXBuildFile; fileRef = 0B49424320AD8DBD00BD2DE0 /* outgoingStart.wav */; };
|
||||
0B49424620AD8DBD00BD2DE0 /* outgoingRinging.wav in Resources */ = {isa = PBXBuildFile; fileRef = 0B49424420AD8DBD00BD2DE0 /* outgoingRinging.wav */; };
|
||||
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B7C2CFC200F51D60060D076 /* LaunchOptions.m */; };
|
||||
0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; };
|
||||
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
|
||||
@@ -27,8 +29,11 @@
|
||||
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 */; };
|
||||
B386B85720981A75000DEF7A /* InviteController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85020981A74000DEF7A /* InviteController.m */; };
|
||||
B386B85820981A75000DEF7A /* AddPeopleController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85120981A74000DEF7A /* AddPeopleController.m */; };
|
||||
B386B85920981A75000DEF7A /* AddPeopleControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@@ -38,6 +43,9 @@
|
||||
B386B85D20981A75000DEF7A /* Invite.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85620981A75000DEF7A /* Invite.m */; };
|
||||
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
|
||||
C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5C2053091D0040BE68 /* image-resize@3x.png */; };
|
||||
C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA09209A0F650027712B /* JMCallKitEmitter.swift */; };
|
||||
C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */; };
|
||||
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0B209A0F660027712B /* JMCallKitListener.swift */; };
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
|
||||
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */; };
|
||||
@@ -49,6 +57,8 @@
|
||||
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = "<group>"; };
|
||||
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = "<group>"; };
|
||||
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
|
||||
0B49424320AD8DBD00BD2DE0 /* outgoingStart.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = outgoingStart.wav; path = ../../sounds/outgoingStart.wav; sourceTree = "<group>"; };
|
||||
0B49424420AD8DBD00BD2DE0 /* outgoingRinging.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = outgoingRinging.wav; path = ../../sounds/outgoingRinging.wav; sourceTree = "<group>"; };
|
||||
0B6F414F20987DE600FF6789 /* Invite+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Invite+Private.h"; sourceTree = "<group>"; };
|
||||
0B6F41502098840600FF6789 /* InviteController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "InviteController+Private.h"; sourceTree = "<group>"; };
|
||||
0B6F4151209884E500FF6789 /* AddPeopleController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AddPeopleController+Private.h"; sourceTree = "<group>"; };
|
||||
@@ -70,10 +80,13 @@
|
||||
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>"; };
|
||||
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
|
||||
A4414ADF20B37F1A003546E6 /* rejected.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = rejected.wav; path = ../../sounds/rejected.wav; sourceTree = "<group>"; };
|
||||
B386B85020981A74000DEF7A /* InviteController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InviteController.m; sourceTree = "<group>"; };
|
||||
B386B85120981A74000DEF7A /* AddPeopleController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddPeopleController.m; sourceTree = "<group>"; };
|
||||
B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddPeopleControllerDelegate.h; sourceTree = "<group>"; };
|
||||
@@ -83,6 +96,9 @@
|
||||
B386B85620981A75000DEF7A /* Invite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Invite.m; sourceTree = "<group>"; };
|
||||
C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@2x.png"; path = "src/picture-in-picture/image-resize@2x.png"; sourceTree = "<group>"; };
|
||||
C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@3x.png"; path = "src/picture-in-picture/image-resize@3x.png"; sourceTree = "<group>"; };
|
||||
C69EFA09209A0F650027712B /* JMCallKitEmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitEmitter.swift; sourceTree = "<group>"; };
|
||||
C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitProxy.swift; sourceTree = "<group>"; };
|
||||
C69EFA0B209A0F660027712B /* JMCallKitListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitListener.swift; sourceTree = "<group>"; };
|
||||
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
|
||||
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
|
||||
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
|
||||
@@ -106,12 +122,17 @@
|
||||
0BCA49681EC4BBE500B793EE /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
75635B0820751D6D00F29C9F /* joined.wav */,
|
||||
75635B0920751D6D00F29C9F /* left.wav */,
|
||||
6C31EDC720C06D490089C899 /* recordingOn.mp3 */,
|
||||
6C31EDC920C06D530089C899 /* recordingOff.mp3 */,
|
||||
A4414ADF20B37F1A003546E6 /* rejected.wav */,
|
||||
0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */,
|
||||
C6245F5B2053091D0040BE68 /* image-resize@2x.png */,
|
||||
C6245F5C2053091D0040BE68 /* image-resize@3x.png */,
|
||||
0BCA496B1EC4BBF900B793EE /* jitsi.ttf */,
|
||||
75635B0820751D6D00F29C9F /* joined.wav */,
|
||||
75635B0920751D6D00F29C9F /* left.wav */,
|
||||
0B49424420AD8DBD00BD2DE0 /* outgoingRinging.wav */,
|
||||
0B49424320AD8DBD00BD2DE0 /* outgoingStart.wav */,
|
||||
);
|
||||
name = Resources;
|
||||
sourceTree = "<group>";
|
||||
@@ -138,24 +159,24 @@
|
||||
0BD906E71EC0C00300C8C18E /* src */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B386B84F20981A11000DEF7A /* invite */,
|
||||
C6A3426B204F127900E062DD /* picture-in-picture */,
|
||||
0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
|
||||
0BB9AD7C1F60356D001C08DB /* AppInfo.m */,
|
||||
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
|
||||
0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
|
||||
C69EFA02209A0EFD0027712B /* callkit */,
|
||||
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */,
|
||||
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
|
||||
B386B84F20981A11000DEF7A /* invite */,
|
||||
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
|
||||
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
|
||||
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
|
||||
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */,
|
||||
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */,
|
||||
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
|
||||
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */,
|
||||
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */,
|
||||
0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */,
|
||||
C6A3426B204F127900E062DD /* picture-in-picture */,
|
||||
0BCA495D1EC4B6C600B793EE /* POSIX.m */,
|
||||
0BCA495E1EC4B6C600B793EE /* Proximity.m */,
|
||||
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */,
|
||||
0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */,
|
||||
);
|
||||
path = src;
|
||||
sourceTree = "<group>";
|
||||
@@ -163,11 +184,11 @@
|
||||
9C3C6FA2341729836589B856 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0BB9AD781F5EC6D7001C08DB /* Intents.framework */,
|
||||
0BB9AD761F5EC6CE001C08DB /* CallKit.framework */,
|
||||
0B93EF7A1EC608550030D24D /* CoreText.framework */,
|
||||
0BCA49631EC4B76D00B793EE /* WebRTC.framework */,
|
||||
0BB9AD781F5EC6D7001C08DB /* Intents.framework */,
|
||||
03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */,
|
||||
0BCA49631EC4B76D00B793EE /* WebRTC.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -198,6 +219,17 @@
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C69EFA02209A0EFD0027712B /* callkit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
|
||||
C69EFA09209A0F650027712B /* JMCallKitEmitter.swift */,
|
||||
C69EFA0B209A0F660027712B /* JMCallKitListener.swift */,
|
||||
C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */,
|
||||
);
|
||||
path = callkit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C6A3426B204F127900E062DD /* picture-in-picture */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -288,8 +320,13 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
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 */,
|
||||
@@ -312,7 +349,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "export NODE_BINARY=node\n../../node_modules/react-native/scripts/react-native-xcode.sh";
|
||||
shellScript = "if [[ \"$CONFIGURATION\" == *Debug* && ! \"$PLATFORM_NAME\" == *simulator ]]; then\n IP=$(ipconfig getifaddr en0)\n if [ -z \"$IP\" ]; then\n IP=$(ifconfig | grep 'inet ' | grep -v ' 127.' | cut -d\\ -f2 | awk 'NR==1{print $1}')\n fi\n DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH\n echo \"$IP\" > \"$DEST/ip.txt\"\n exit 0\nfi\nexport NODE_BINARY=node\n../../node_modules/react-native/scripts/react-native-xcode.sh";
|
||||
};
|
||||
26796D8589142D80C8AFDA51 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@@ -390,7 +427,10 @@
|
||||
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
|
||||
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
|
||||
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
|
||||
C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */,
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
|
||||
C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */,
|
||||
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */,
|
||||
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user