mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-05 14:22:28 +00:00
Compare commits
651 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7186a9c79c | ||
|
|
c672ffd435 | ||
|
|
8cfc83f18c | ||
|
|
fa9549582f | ||
|
|
11b7144ad0 | ||
|
|
92fee790f2 | ||
|
|
72487fa7c7 | ||
|
|
380909371f | ||
|
|
d4d5ef202a | ||
|
|
c95cb0e9cf | ||
|
|
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 | ||
|
|
19ce472d4d | ||
|
|
a793742e3a | ||
|
|
3d8d5e3107 | ||
|
|
0bad2dffb4 | ||
|
|
552fadc4a4 | ||
|
|
ed7d8ac57e | ||
|
|
cf6dd98b02 | ||
|
|
6c602accae | ||
|
|
17ae89a56c | ||
|
|
565fd37f28 | ||
|
|
effd3728b6 | ||
|
|
f450756337 | ||
|
|
f015f4edc3 | ||
|
|
0831c16d7e | ||
|
|
d94093a01e | ||
|
|
1eac4c51ca | ||
|
|
eaea0e5f49 | ||
|
|
b2971aa9be | ||
|
|
f64fdb4a8f | ||
|
|
31dec2cf99 | ||
|
|
54b6db02d8 | ||
|
|
68608478f6 | ||
|
|
2ee8f1ef58 | ||
|
|
627b9d319c | ||
|
|
aaf2f428e6 | ||
|
|
f4060975d1 | ||
|
|
0a0256501c | ||
|
|
81fc961998 | ||
|
|
e098ad87f4 | ||
|
|
ed395036b7 | ||
|
|
4f8fd1019b | ||
|
|
f14095ecfc | ||
|
|
6947926494 | ||
|
|
784d94b30f | ||
|
|
1d7e0845aa | ||
|
|
f64c13d4b7 | ||
|
|
4e36127dc7 | ||
|
|
8b1aff5512 | ||
|
|
d7103c1c4c | ||
|
|
9cbcbf6e26 | ||
|
|
2c4a3b0f60 | ||
|
|
d82f172db8 | ||
|
|
169c47ac7f | ||
|
|
2af76ebcf9 | ||
|
|
6931b8f2fb | ||
|
|
adec8e6438 | ||
|
|
382c548cf9 | ||
|
|
ff9820a61b | ||
|
|
1ab107b238 | ||
|
|
2861d8d24e | ||
|
|
172342cac3 | ||
|
|
f8941c846a | ||
|
|
8daa13cd99 | ||
|
|
a86ca3f41c | ||
|
|
c029663b77 | ||
|
|
66bf5cf966 | ||
|
|
63c165ee8b | ||
|
|
374e3ccf2c | ||
|
|
09482f053b | ||
|
|
1e69dc93d6 | ||
|
|
008645568c | ||
|
|
13e0e18f37 | ||
|
|
8c0bb377ba | ||
|
|
fc25125667 | ||
|
|
5b7b373e21 | ||
|
|
8e42a7b034 | ||
|
|
4bd94fc94c | ||
|
|
41e1c3a2e2 | ||
|
|
6586be9a8e | ||
|
|
56d8210e35 | ||
|
|
f1ab160c62 | ||
|
|
eac74aa0b7 | ||
|
|
e30d141cec | ||
|
|
1513e1f3b3 | ||
|
|
0539e8f2df | ||
|
|
eb19f94598 | ||
|
|
fd44721bac | ||
|
|
219b93a3c9 | ||
|
|
2a55548b84 | ||
|
|
8f142d5ec4 | ||
|
|
5cf16a20d3 | ||
|
|
be78ab5317 | ||
|
|
907cb013a8 | ||
|
|
8828525511 | ||
|
|
c03e66954d | ||
|
|
157800c494 | ||
|
|
3285d647e6 | ||
|
|
78ff0f7864 | ||
|
|
76c56339c4 | ||
|
|
95927c4804 | ||
|
|
6c5482fb9d | ||
|
|
4f157b71f3 | ||
|
|
5270da4c14 | ||
|
|
fe473bf426 | ||
|
|
abee3331aa | ||
|
|
a5e4fb000f | ||
|
|
27a1be1e1c | ||
|
|
4d942440db | ||
|
|
461540d874 | ||
|
|
3f99e80358 | ||
|
|
2ce3c2d459 | ||
|
|
8363f3cfeb | ||
|
|
f8537dde6b | ||
|
|
f34686afee | ||
|
|
ea1aef0703 | ||
|
|
8b2ce21e1a | ||
|
|
c377219013 | ||
|
|
10c8d380b7 | ||
|
|
7e9a64d7c1 | ||
|
|
a783939f12 | ||
|
|
6883ee0141 | ||
|
|
1eee20dd5a | ||
|
|
7d86e3f8e7 | ||
|
|
60fde5efb8 | ||
|
|
79b7e1641d | ||
|
|
decbcefbd4 | ||
|
|
cb70c084b3 | ||
|
|
0afe72d42b | ||
|
|
84e5e657a0 | ||
|
|
d8ad39ed3f | ||
|
|
16c2bc2d15 | ||
|
|
01e0dfe58a | ||
|
|
38c8a41634 | ||
|
|
b2efcadeb8 | ||
|
|
b73b51f1f4 | ||
|
|
0cd32c8155 | ||
|
|
45adf3e26a | ||
|
|
58d1b69148 | ||
|
|
1f0dc6fcd8 | ||
|
|
95e00405b6 | ||
|
|
968b279b37 | ||
|
|
0456df239f | ||
|
|
2f23f8e400 | ||
|
|
2412239206 | ||
|
|
cc6e04ddf8 | ||
|
|
6f11bbc400 | ||
|
|
7c08116dc2 | ||
|
|
f8717a7135 | ||
|
|
1b85442dba | ||
|
|
ea46cbc479 | ||
|
|
b76ab305e3 | ||
|
|
358ce0799e | ||
|
|
e7223c49ef | ||
|
|
02a31746fb | ||
|
|
40154b1feb | ||
|
|
4c49e3bec0 | ||
|
|
0a086fa3f7 | ||
|
|
b353b8fffb | ||
|
|
a3c00021de | ||
|
|
1e0a3ceb74 | ||
|
|
8492aad7d6 | ||
|
|
7ad9fa8392 | ||
|
|
6916252ce1 | ||
|
|
b4eae56eed | ||
|
|
eb69fb69cb | ||
|
|
2b7cdbc6a8 | ||
|
|
f3a90f048a | ||
|
|
8bf69d30b7 | ||
|
|
45078fe6b2 | ||
|
|
4783b22018 | ||
|
|
d93782af8a | ||
|
|
962df14382 | ||
|
|
01db70fd3d | ||
|
|
6cc8800016 | ||
|
|
e5596c3cd5 | ||
|
|
1b91e0bc2f | ||
|
|
b258e0d397 | ||
|
|
83f47c2df1 | ||
|
|
a39da15c94 | ||
|
|
fd787abf85 | ||
|
|
7822155e5e | ||
|
|
15e1633d86 | ||
|
|
de0a7bfcd3 | ||
|
|
5858859838 | ||
|
|
8428dd95c2 | ||
|
|
a26ccf195e | ||
|
|
0a5f60c637 | ||
|
|
f8163de765 | ||
|
|
181580871f | ||
|
|
ea431ca90c | ||
|
|
a902540167 | ||
|
|
823481dc1d | ||
|
|
b5b99301ca | ||
|
|
ef52f09910 | ||
|
|
6a066c3079 | ||
|
|
71e368f70e | ||
|
|
134ff71c78 | ||
|
|
fef1d8b520 | ||
|
|
309fcf9672 | ||
|
|
2eafbacdd4 | ||
|
|
26ea667170 | ||
|
|
5f43e8f8c7 | ||
|
|
9ef83702cf | ||
|
|
fda2548a38 | ||
|
|
eb53944a4d | ||
|
|
2334eb9967 | ||
|
|
04bd4a9038 | ||
|
|
eb8f34cee8 | ||
|
|
b9379f5996 | ||
|
|
40d7d0c9cb | ||
|
|
357f173e85 | ||
|
|
7da26042b3 | ||
|
|
c86c7beb24 | ||
|
|
1020a54a33 | ||
|
|
c84abd543e | ||
|
|
4b17c6f015 | ||
|
|
cb973b61aa | ||
|
|
b096622995 | ||
|
|
ae0bf876a8 | ||
|
|
bba480f329 | ||
|
|
4dbcaf851f | ||
|
|
04dff9059b | ||
|
|
26cd2f17f6 | ||
|
|
60e03e3dec | ||
|
|
bfb45ed0e8 | ||
|
|
e325199075 | ||
|
|
4e4713c3e2 | ||
|
|
ff8386e931 | ||
|
|
8f520086e5 | ||
|
|
5cde674eff | ||
|
|
c018252eee | ||
|
|
c8cab1560c | ||
|
|
d218abfd97 | ||
|
|
9e0fee6c7d | ||
|
|
5dca9e08f4 | ||
|
|
d3a1f7d4f7 | ||
|
|
80bdf908ca | ||
|
|
0d3b4eedf8 | ||
|
|
824a8a8864 | ||
|
|
45c1438fe6 | ||
|
|
8caeabf6b4 | ||
|
|
466561f99f | ||
|
|
8dc866fab3 | ||
|
|
1c8b8e031b | ||
|
|
796489dc77 | ||
|
|
a30412ba65 | ||
|
|
8b35ea8ad5 | ||
|
|
e05f2a9027 | ||
|
|
0d5cc8898d | ||
|
|
7672a88990 | ||
|
|
5a45b52881 | ||
|
|
ce6e8472f0 | ||
|
|
b00aaf1de7 | ||
|
|
e622829c1c | ||
|
|
037e7f59b0 | ||
|
|
df754f4f41 | ||
|
|
fd0749000e | ||
|
|
d727ee80b2 | ||
|
|
e4da0e988e | ||
|
|
1474304cc5 | ||
|
|
d02ab2c641 | ||
|
|
0e07020d09 | ||
|
|
e0deb6d64b | ||
|
|
88325aeef2 | ||
|
|
9f69c4d730 | ||
|
|
e23d4317eb | ||
|
|
547ddee3a5 | ||
|
|
3cf9fd439b | ||
|
|
7cd40353e7 | ||
|
|
04690dfc8f | ||
|
|
9a9890f86c | ||
|
|
9b04a7852a | ||
|
|
df9d17ba18 | ||
|
|
5d0ac7653d | ||
|
|
1ef2e2ee7e | ||
|
|
b3431ab3e7 | ||
|
|
6fbe78eb34 | ||
|
|
b8de5bbfc3 | ||
|
|
b3683068d4 | ||
|
|
94473e5660 | ||
|
|
7f78050513 | ||
|
|
2d9b906a3b | ||
|
|
1f82ce3d19 | ||
|
|
68b710a222 | ||
|
|
9fea5e89b3 | ||
|
|
e1d849e3a0 | ||
|
|
74a92f83c7 | ||
|
|
e47802538e | ||
|
|
e2cf7a788d | ||
|
|
4757c1ebca | ||
|
|
59d046dca9 | ||
|
|
0bbcd3181c | ||
|
|
7a9ff9975a | ||
|
|
10f72f8e40 | ||
|
|
417e1e83e7 | ||
|
|
cacc4bd769 | ||
|
|
1419247801 | ||
|
|
4fb37c38eb | ||
|
|
f3b5ed2ef4 | ||
|
|
7341c7bf84 | ||
|
|
5d31532cbb | ||
|
|
423c8d3f53 | ||
|
|
a1ba7beff9 | ||
|
|
03fc711e81 | ||
|
|
a370a88d19 | ||
|
|
7153d94dad | ||
|
|
240fff74c7 | ||
|
|
7bd8b7948f | ||
|
|
a505c01e9e | ||
|
|
990b1eddf2 | ||
|
|
abbfd3de9a | ||
|
|
bd301403c4 | ||
|
|
d481c6f736 | ||
|
|
ba94ba30c5 | ||
|
|
5305557ce5 | ||
|
|
c9d8b5c827 | ||
|
|
0ad1c88cd2 | ||
|
|
78fbfba573 | ||
|
|
9e53d40b9c | ||
|
|
aa314c10ac | ||
|
|
62c9762793 | ||
|
|
d7dddb2509 | ||
|
|
83243d5980 | ||
|
|
6e05cab46e | ||
|
|
7954d5fd39 | ||
|
|
158cadf4f9 | ||
|
|
f35578c803 | ||
|
|
c087e90099 | ||
|
|
da0ae73d10 | ||
|
|
b4d44f367d | ||
|
|
083f6b400b | ||
|
|
dd5ae49217 | ||
|
|
6a9e6db3be | ||
|
|
c4468cb7b8 | ||
|
|
80c4205fb8 | ||
|
|
aa9efd6f69 | ||
|
|
6f8f64ba48 | ||
|
|
1c3cef1eed | ||
|
|
2720c76e4d | ||
|
|
4ab34589c8 | ||
|
|
ed36132e94 | ||
|
|
f43687944c | ||
|
|
dda0ea0ba9 | ||
|
|
e1f967869a | ||
|
|
8673083829 | ||
|
|
b52e584327 | ||
|
|
4c65262a87 | ||
|
|
7ce670df0c | ||
|
|
f5f341ca9e | ||
|
|
f29fbb6757 | ||
|
|
e5e3c6c6c4 | ||
|
|
dc92fb5073 | ||
|
|
2478176f23 | ||
|
|
12ec982067 | ||
|
|
c9e3e5052d | ||
|
|
22401614a7 | ||
|
|
762f529f1d | ||
|
|
1f6b743bec | ||
|
|
48f4317adb | ||
|
|
75f6786588 | ||
|
|
3ec4d67a99 | ||
|
|
a23eec55e8 | ||
|
|
bdf2ecfe4b | ||
|
|
fc36759114 | ||
|
|
9c2849a663 | ||
|
|
98ff20a026 | ||
|
|
b04661b40b | ||
|
|
112c856850 | ||
|
|
410dc132e1 | ||
|
|
b7f950f5f7 | ||
|
|
7ad875e735 | ||
|
|
41aa704e1f | ||
|
|
e00ea353e8 | ||
|
|
6f93424d7c | ||
|
|
292f3ab1bd | ||
|
|
a3cb081609 | ||
|
|
64c5ae1c48 | ||
|
|
259004b8bf | ||
|
|
4fea22676b | ||
|
|
05a492241f | ||
|
|
8ec4697a27 | ||
|
|
a357b0cf14 | ||
|
|
9eff669b0b | ||
|
|
c7714959e6 | ||
|
|
e898527294 | ||
|
|
bfcd34358b | ||
|
|
871ef9ff0e | ||
|
|
178c8e02ff | ||
|
|
43ac039fd6 | ||
|
|
090f2f9ccb | ||
|
|
d08bbae770 | ||
|
|
30b51ff384 | ||
|
|
c109199e06 | ||
|
|
0ed31f0ae8 | ||
|
|
006e6cc851 | ||
|
|
0680c086df | ||
|
|
698ec1e2d7 | ||
|
|
e217c172f8 | ||
|
|
6146c12533 | ||
|
|
eb64b74493 | ||
|
|
1255b3349b | ||
|
|
2ea5ad68a5 | ||
|
|
fb6f1bdba0 | ||
|
|
25b130f8e8 | ||
|
|
9591cb54a2 | ||
|
|
ca78309427 | ||
|
|
65abd5efd4 | ||
|
|
05de599739 | ||
|
|
1e0550c746 | ||
|
|
45c405de0e | ||
|
|
3aedce11f2 | ||
|
|
f0a180cf0c | ||
|
|
28013f6ffa | ||
|
|
5640524647 | ||
|
|
ac09233558 | ||
|
|
9a3f98a4a0 | ||
|
|
2f3ea1b458 | ||
|
|
ceeefb33c1 | ||
|
|
d6c3ab64fa | ||
|
|
eee87bc546 | ||
|
|
9a42b866ba | ||
|
|
b920140488 | ||
|
|
49acd6bf6a | ||
|
|
7001208d87 | ||
|
|
4a0e55b1f4 | ||
|
|
73cc9a68c7 | ||
|
|
e9c91d194c | ||
|
|
3460fe03e5 | ||
|
|
e65566ad03 | ||
|
|
57206cc36a | ||
|
|
c05c8e0f1e | ||
|
|
87a87eebb9 | ||
|
|
ad497fed7c | ||
|
|
0f6243ee88 | ||
|
|
b39b6640b4 | ||
|
|
870e6bbddc | ||
|
|
b93bac5aa9 | ||
|
|
c86895ae13 | ||
|
|
e28b847fb0 | ||
|
|
f6ace61674 | ||
|
|
63d661ad5e | ||
|
|
e0e2104723 | ||
|
|
6f0b828512 | ||
|
|
1984f8d0c0 | ||
|
|
9b67e796bd | ||
|
|
ee1ec42463 | ||
|
|
76fb3b3c63 | ||
|
|
b49e600267 | ||
|
|
5a3f952a2f | ||
|
|
3ac41bb0c3 | ||
|
|
94813bc0fd | ||
|
|
457b4255b9 | ||
|
|
bed9bd1d5a | ||
|
|
0d4dcffbac | ||
|
|
a5538adf8a | ||
|
|
38b645bc27 | ||
|
|
15bf6b9e30 | ||
|
|
67ac0e8b8a | ||
|
|
b74b29e8a0 | ||
|
|
b258a9fc5e | ||
|
|
a653816f90 | ||
|
|
9ddc5a0e42 | ||
|
|
d79995e14c | ||
|
|
5ffcaca649 | ||
|
|
40df5f97d4 | ||
|
|
aa93a78372 | ||
|
|
6b8b929d92 | ||
|
|
c9b54845d9 | ||
|
|
0eafee2a95 | ||
|
|
de0d69a20e | ||
|
|
eae9ddabad | ||
|
|
569b3547c8 | ||
|
|
d8bc26a8ea | ||
|
|
61e653a510 | ||
|
|
ac3a74c47e | ||
|
|
bf7b723891 | ||
|
|
4829b86352 | ||
|
|
dda4d7a99e | ||
|
|
19702671f6 | ||
|
|
d88b57d35c | ||
|
|
a9b8f49995 | ||
|
|
2306e26287 | ||
|
|
3633f2aac5 | ||
|
|
c320540fa3 | ||
|
|
75bf7638b3 | ||
|
|
379bad0ce6 | ||
|
|
2becfd026b | ||
|
|
cd48ee3dbf | ||
|
|
e4ed02815f | ||
|
|
d1e5e6b93b | ||
|
|
38629b437d | ||
|
|
d0859b3ce1 | ||
|
|
345fcefa7d | ||
|
|
8e8edb0793 | ||
|
|
4ead402388 | ||
|
|
c51d351694 | ||
|
|
83b6dc1518 | ||
|
|
09a952c390 | ||
|
|
d5752afd96 | ||
|
|
bb45f76a7a | ||
|
|
045a922482 | ||
|
|
03e68b0e4b | ||
|
|
980aa9b39a | ||
|
|
f0d3abffc5 | ||
|
|
7b1b873b6e | ||
|
|
28b153facf | ||
|
|
c2f5afe9c2 | ||
|
|
fcb3ca836f | ||
|
|
ef813fbf71 | ||
|
|
fe411398e3 | ||
|
|
0aa377fcfc | ||
|
|
90e4291751 | ||
|
|
8f59b6f215 | ||
|
|
81ac1bf4a5 | ||
|
|
81094ba7fd | ||
|
|
3acf0c7f64 | ||
|
|
917fdcaa10 | ||
|
|
3f350be805 | ||
|
|
78bc8121ff | ||
|
|
18d677e2f5 | ||
|
|
8fd91573fc | ||
|
|
30be46326a |
22
.flowconfig
22
.flowconfig
@@ -12,21 +12,21 @@
|
||||
; For RN Apps installed via npm, "Libraries" folder is inside
|
||||
; "node_modules/react-native" but in the source repo it is in the root
|
||||
.*/Libraries/react-native/React.js
|
||||
.*/Libraries/react-native/ReactNative.js
|
||||
|
||||
; Ignore polyfills
|
||||
.*/Libraries/polyfills/.*
|
||||
|
||||
; 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 this commit:
|
||||
; 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/styled-components/.*
|
||||
|
||||
@@ -36,21 +36,16 @@
|
||||
|
||||
[libs]
|
||||
node_modules/react-native/Libraries/react-native/react-native-interface.js
|
||||
node_modules/react-native/flow
|
||||
node_modules/react-native/flow/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
|
||||
module.system=haste
|
||||
|
||||
; FIXME: munge_underscores should be false but right now there are some errors
|
||||
; if we change the value to false
|
||||
; Treats class properties with underscore as private. Disabled because currently
|
||||
; for us "_" can mean protected too.
|
||||
; munge_underscores=false
|
||||
munge_underscores=true
|
||||
|
||||
; FIXME Remove once we update past this commit:
|
||||
; FIXME Remove once we update past commit
|
||||
; https://github.com/facebook/react-native/commit/df8d0d1db9203cc87ad3682e6138b2a9ed714365
|
||||
module.name_mapper='^./link/link$' -> 'emptyObject'
|
||||
|
||||
@@ -62,11 +57,10 @@ suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
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\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowDisableNextLine
|
||||
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
@@ -83,4 +77,4 @@ module.file_ext=.jsx
|
||||
module.file_ext=.json
|
||||
|
||||
[version]
|
||||
^0.53.0
|
||||
^0.57.0
|
||||
|
||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -7,6 +7,9 @@ all.css
|
||||
.remote-sync.json
|
||||
.sync-config.cson
|
||||
|
||||
# CocoaPods
|
||||
Pods/
|
||||
|
||||
# The following are automatically generated by the react-native command line
|
||||
# utility (either with the init or upgrade option which pull in the latest
|
||||
# template files recommended by Facebook for React Native).
|
||||
@@ -57,14 +60,11 @@ buck-out/
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use
|
||||
# fastlane to re-generate the screenshots whenever they are needed. For more
|
||||
# information about the recommended setup visit:
|
||||
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
|
||||
#
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/
|
||||
|
||||
# CocoaPods
|
||||
Pods/
|
||||
*/fastlane/report.xml
|
||||
*/fastlane/Preview.html
|
||||
*/fastlane/screenshots
|
||||
|
||||
4
.travis.yml
Normal file
4
.travis.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
osx_image: xcode9.3
|
||||
language: objective-c
|
||||
script:
|
||||
- "./ios/travis-ci/build-ipa.sh"
|
||||
@@ -21,7 +21,7 @@ license agreement as either a [corporation](https://jitsi.org/ccla) or an
|
||||
in the agreement, unfortunately, we cannot accept your contribution.
|
||||
|
||||
## Creating Pull Requests
|
||||
- Make sure your code passes the linter rules beforehand. The linter is exeuted
|
||||
- Make sure your code passes the linter rules beforehand. The linter is executed
|
||||
automatically when committing code.
|
||||
- Perform **one** logical change per pull request.
|
||||
- Maintain a clean list of commits, squash them if necessary.
|
||||
|
||||
10
Makefile
10
Makefile
@@ -9,6 +9,7 @@ STYLES_BUNDLE = css/all.bundle.css
|
||||
STYLES_DESTINATION = css/all.css
|
||||
STYLES_MAIN = css/main.scss
|
||||
WEBPACK = ./node_modules/.bin/webpack
|
||||
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack-dev-server
|
||||
|
||||
all: compile deploy clean
|
||||
|
||||
@@ -21,6 +22,7 @@ clean:
|
||||
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-css deploy-local
|
||||
|
||||
deploy-init:
|
||||
rm -fr $(DEPLOY_DIR)
|
||||
mkdir -p $(DEPLOY_DIR)
|
||||
|
||||
deploy-appbundle:
|
||||
@@ -33,9 +35,11 @@ deploy-appbundle:
|
||||
$(BUILD_DIR)/external_api.min.map \
|
||||
$(BUILD_DIR)/device_selection_popup_bundle.min.js \
|
||||
$(BUILD_DIR)/device_selection_popup_bundle.min.map \
|
||||
$(BUILD_DIR)/dial_in_info_bundle.min.js \
|
||||
$(BUILD_DIR)/dial_in_info_bundle.min.map \
|
||||
$(BUILD_DIR)/alwaysontop.min.js \
|
||||
$(BUILD_DIR)/alwaysontop.min.map \
|
||||
$(OUTPUT_DIR)/analytics.js \
|
||||
$(OUTPUT_DIR)/analytics-ga.js \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-lib-jitsi-meet:
|
||||
@@ -43,6 +47,7 @@ deploy-lib-jitsi-meet:
|
||||
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.js \
|
||||
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.map \
|
||||
$(LIBJITSIMEET_DIR)/connection_optimization/external_connect.js \
|
||||
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-css:
|
||||
@@ -53,6 +58,9 @@ deploy-css:
|
||||
deploy-local:
|
||||
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
|
||||
|
||||
dev: deploy-init deploy-css deploy-lib-jitsi-meet
|
||||
$(WEBPACK_DEV_SERVER)
|
||||
|
||||
source-package:
|
||||
mkdir -p source_package/jitsi-meet/css && \
|
||||
cp -r *.js *.html connection_optimization favicon.ico fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
|
||||
|
||||
48
README.md
48
README.md
@@ -1,19 +1,24 @@
|
||||
# Jitsi Meet - Secure, Simple and Scalable Video Conferences
|
||||
|
||||
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, scalable video conferences. You can see [Jitsi Meet in action](http://youtu.be/7vFUVClsNh0) here at the session #482 of the VoIP Users Conference.
|
||||
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, [secure](#security) and scalable video conferences. You can see Jitsi Meet in action [here at the session #482 of the VoIP Users Conference](http://youtu.be/7vFUVClsNh0).
|
||||
|
||||
You can also try it out yourself at https://meet.jit.si .
|
||||
The Jitsi Meet client runs in your browser, without the need for installing anything on your computer. You can also try it out yourself at https://meet.jit.si .
|
||||
|
||||
Jitsi Meet allows for very efficient collaboration. It allows users to stream their desktop or only some windows. It also supports shared document editing with Etherpad.
|
||||
|
||||
## Installation
|
||||
|
||||
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.
|
||||
|
||||
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/))
|
||||
@@ -28,11 +33,11 @@ You can get our mobile versions from here:
|
||||
|
||||
## Building the sources
|
||||
|
||||
Node.js >= 6 is required.
|
||||
Node.js >= 8 is 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
|
||||
```
|
||||
@@ -42,7 +47,7 @@ To build the Jitsi Meet application, just type
|
||||
make
|
||||
```
|
||||
|
||||
## Working with the library sources (lib-jitsi-meet)
|
||||
### Working with the library sources (lib-jitsi-meet)
|
||||
|
||||
By default the library is build from its git repository sources. The default dependency path in package.json is :
|
||||
```json
|
||||
@@ -70,12 +75,12 @@ It allows to link `lib-jitsi-meet` dependency to local source in few steps:
|
||||
```bash
|
||||
cd lib-jitsi-meet
|
||||
|
||||
# create global symlink for lib-jitsi-meet package
|
||||
#### create global symlink for lib-jitsi-meet package
|
||||
npm link
|
||||
|
||||
cd ../jitsi-meet
|
||||
|
||||
# create symlink from the local node_modules folder to the global lib-jitsi-meet symlink
|
||||
#### create symlink from the local node_modules folder to the global lib-jitsi-meet symlink
|
||||
npm link lib-jitsi-meet
|
||||
```
|
||||
|
||||
@@ -88,6 +93,20 @@ cd jitsi-meet
|
||||
npm unlink lib-jitsi-meet
|
||||
npm install
|
||||
```
|
||||
### Running with webpack-dev-server for development
|
||||
|
||||
Use it at the CLI, type
|
||||
```
|
||||
make dev
|
||||
```
|
||||
|
||||
By default the backend deployment used is `beta.meet.jit.si`, you can point the Jitsi-Meet app at a different backend by using a proxy server. To do this set the WEBPACK_DEV_SERVER_PROXY_TARGET variable:
|
||||
```
|
||||
export WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com
|
||||
make dev
|
||||
```
|
||||
|
||||
The app should be running at https://localhost:8080/
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -98,6 +117,21 @@ see our [guidelines for contributing](CONTRIBUTING.md).
|
||||
|
||||
Jitsi Meet provides a very flexible way of embedding it in external applications by using the [Jitsi Meet API](doc/api.md).
|
||||
|
||||
## Security
|
||||
WebRTC today does not provide a way of conducting multiparty conversations with
|
||||
end-to-end encryption. As a matter of fact, unless you consistently vocally
|
||||
compare DTLS fingerprints with your peers, the same goes for one-to-one calls.
|
||||
As a result when using a Jitsi Meet instance, your stream is encrypted on the
|
||||
network but decrypted on the machine that hosts the bridge.
|
||||
|
||||
The Jitsi Meet architecture allows you to deploy your own version, including
|
||||
all server components, and in that case your security guarantees will be roughly
|
||||
equivalent to these of a direct one-to-one WebRTC call. This is what's unique to
|
||||
Jitsi Meet in terms of security.
|
||||
|
||||
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
|
||||
at [Atlassian](https://atlassian.com).
|
||||
|
||||
## Mobile app
|
||||
Jitsi Meet is also available as a React Native app for Android and iOS.
|
||||
Instructions on how to build it can be found [here](doc/mobile.md).
|
||||
|
||||
154
analytics-ga.js
Normal file
154
analytics-ga.js
Normal file
@@ -0,0 +1,154 @@
|
||||
/* global ga */
|
||||
|
||||
(function(ctx) {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function Analytics(options) {
|
||||
/* eslint-disable */
|
||||
|
||||
if (!options.googleAnalyticsTrackingId) {
|
||||
console.log(
|
||||
'Failed to initialize Google Analytics handler, no tracking ID');
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Analytics
|
||||
* TODO: Keep this local, there's no need to add it to window.
|
||||
*/
|
||||
(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, 'auto');
|
||||
ga('send', 'pageview');
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the integer to use for a Google Analytics event's value field
|
||||
* from a lib-jitsi-meet analytics event.
|
||||
* @param {Object} event - The lib-jitsi-meet analytics event.
|
||||
* @returns {Object} - The integer to use for the 'value' of a Google
|
||||
* Analytics event.
|
||||
* @private
|
||||
*/
|
||||
Analytics.prototype._extractAction = function(event) {
|
||||
// Page events have a single 'name' field.
|
||||
if (event.type === 'page') {
|
||||
return event.name;
|
||||
}
|
||||
|
||||
// All other events have action, actionSubject, and source fields. All
|
||||
// three fields are required, and the often jitsi-meet and
|
||||
// lib-jitsi-meet use the same value when separate values are not
|
||||
// necessary (i.e. event.action == event.actionSubject).
|
||||
// Here we concatenate these three fields, but avoid adding the same
|
||||
// value twice, because it would only make the GA event's action harder
|
||||
// to read.
|
||||
let action = event.action;
|
||||
|
||||
if (event.actionSubject && event.actionSubject !== event.action) {
|
||||
// Intentionally use string concatenation as analytics needs to
|
||||
// work on IE but this file does not go through babel. For some
|
||||
// reason disabling this globally for the file does not have an
|
||||
// effect.
|
||||
// eslint-disable-next-line prefer-template
|
||||
action = event.actionSubject + '.' + action;
|
||||
}
|
||||
if (event.source && event.source !== event.action
|
||||
&& event.source !== event.action) {
|
||||
// eslint-disable-next-line prefer-template
|
||||
action = event.source + '.' + action;
|
||||
}
|
||||
|
||||
return action;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the integer to use for a Google Analytics event's value field
|
||||
* from a lib-jitsi-meet analytics event.
|
||||
* @param {Object} event - The lib-jitsi-meet analytics event.
|
||||
* @returns {Object} - The integer to use for the 'value' of a Google
|
||||
* Analytics event, or NaN if the lib-jitsi-meet event doesn't contain a
|
||||
* suitable value.
|
||||
* @private
|
||||
*/
|
||||
Analytics.prototype._extractValue = function(event) {
|
||||
let value = event && event.attributes && event.attributes.value;
|
||||
|
||||
// Try to extract an integer from the "value" attribute.
|
||||
value = Math.round(parseFloat(value));
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the string to use for a Google Analytics event's label field
|
||||
* from a lib-jitsi-meet analytics event.
|
||||
* @param {Object} event - The lib-jitsi-meet analytics event.
|
||||
* @returns {string} - The string to use for the 'label' of a Google
|
||||
* Analytics event.
|
||||
* @private
|
||||
*/
|
||||
Analytics.prototype._extractLabel = function(event) {
|
||||
let label = '';
|
||||
|
||||
// The label field is limited to 500B. We will concatenate all
|
||||
// attributes of the event, except the user agent because it may be
|
||||
// lengthy and is probably included from elsewhere.
|
||||
for (const property in event.attributes) {
|
||||
if (property !== 'permanent_user_agent'
|
||||
&& property !== 'permanent_callstats_name'
|
||||
&& event.attributes.hasOwnProperty(property)) {
|
||||
// eslint-disable-next-line prefer-template
|
||||
label += property + '=' + event.attributes[property] + '&';
|
||||
}
|
||||
}
|
||||
|
||||
if (label.length > 0) {
|
||||
label = label.slice(0, -1);
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the entry point of the API. The function sends an event to
|
||||
* google analytics. The format of the event is described in
|
||||
* AnalyticsAdapter in lib-jitsi-meet.
|
||||
* @param {Object} event - the event in the format specified by
|
||||
* lib-jitsi-meet.
|
||||
*/
|
||||
Analytics.prototype.sendEvent = function(event) {
|
||||
if (!event || !ga) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gaEvent = {
|
||||
'eventCategory': 'jitsi-meet',
|
||||
'eventAction': this._extractAction(event),
|
||||
'eventLabel': this._extractLabel(event)
|
||||
};
|
||||
const value = this._extractValue(event);
|
||||
|
||||
if (!isNaN(value)) {
|
||||
gaEvent.eventValue = value;
|
||||
}
|
||||
|
||||
ga('send', 'event', gaEvent);
|
||||
};
|
||||
|
||||
if (typeof ctx.JitsiMeetJS === 'undefined') {
|
||||
ctx.JitsiMeetJS = {};
|
||||
}
|
||||
if (typeof ctx.JitsiMeetJS.app === 'undefined') {
|
||||
ctx.JitsiMeetJS.app = {};
|
||||
}
|
||||
if (typeof ctx.JitsiMeetJS.app.analyticsHandlers === 'undefined') {
|
||||
ctx.JitsiMeetJS.app.analyticsHandlers = [];
|
||||
}
|
||||
ctx.JitsiMeetJS.app.analyticsHandlers.push(Analytics);
|
||||
})(window);
|
||||
/* eslint-enable prefer-template */
|
||||
47
analytics.js
47
analytics.js
@@ -1,47 +0,0 @@
|
||||
/* global ga */
|
||||
|
||||
(function(ctx) {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function Analytics() {
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Google Analytics
|
||||
*/
|
||||
(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', 'UA-319188-14', 'jit.si');
|
||||
ga('send', 'pageview');
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
Analytics.prototype.sendEvent = function(action, data) {
|
||||
// empty label if missing value for it and add the value,
|
||||
// the value should be integer or null
|
||||
let value = data.value;
|
||||
|
||||
value = value ? Math.round(parseFloat(value)) : null;
|
||||
const label = data.label || '';
|
||||
|
||||
// Intentionally use string concatenation as analytics needs to work on
|
||||
// IE but this file does not go through babel.
|
||||
// eslint-disable-next-line prefer-template
|
||||
ga('send', 'event', 'jit.si', action + '.' + data.browserName,
|
||||
label, value);
|
||||
};
|
||||
|
||||
if (typeof ctx.JitsiMeetJS === 'undefined') {
|
||||
ctx.JitsiMeetJS = {};
|
||||
}
|
||||
if (typeof ctx.JitsiMeetJS.app === 'undefined') {
|
||||
ctx.JitsiMeetJS.app = {};
|
||||
}
|
||||
if (typeof ctx.JitsiMeetJS.app.analyticsHandlers === 'undefined') {
|
||||
ctx.JitsiMeetJS.app.analyticsHandlers = [];
|
||||
}
|
||||
ctx.JitsiMeetJS.app.analyticsHandlers.push(Analytics);
|
||||
})(window);
|
||||
@@ -16,7 +16,7 @@
|
||||
`"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 JavaSource code of Jitsi
|
||||
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.
|
||||
|
||||
@@ -107,19 +107,19 @@ public class MainActivity extends AppCompatActivity {
|
||||
JitsiMeetView.onNewIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
JitsiMeetView.onHostPause(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
JitsiMeetView.onHostResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
JitsiMeetView.onHostPause(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -132,6 +132,10 @@ which displays a single `JitsiMeetView`.
|
||||
|
||||
See JitsiMeetView.getDefaultURL.
|
||||
|
||||
#### getPictureInPictureEnabled()
|
||||
|
||||
See JitsiMeetView.getPictureInPictureEnabled.
|
||||
|
||||
#### getWelcomePageEnabled()
|
||||
|
||||
See JitsiMeetView.getWelcomePageEnabled.
|
||||
@@ -144,6 +148,10 @@ See JitsiMeetView.loadURL.
|
||||
|
||||
See JitsiMeetView.setDefaultURL.
|
||||
|
||||
#### setPictureInPictureEnabled(boolean)
|
||||
|
||||
See JitsiMeetView.setPictureInPictureEnabled.
|
||||
|
||||
#### setWelcomePageEnabled(boolean)
|
||||
|
||||
See JitsiMeetView.setWelcomePageEnabled.
|
||||
@@ -169,6 +177,12 @@ if set to `null`, the default built in JavaScript is used: https://meet.jit.si.
|
||||
|
||||
Returns the `JitsiMeetViewListener` instance attached to the view.
|
||||
|
||||
#### getPictureInPictureEnabled()
|
||||
|
||||
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()
|
||||
|
||||
Returns true if the Welcome page is enabled; otherwise, false. If false, a black
|
||||
@@ -210,19 +224,30 @@ view.loadURLObject(urlObject);
|
||||
|
||||
Sets the default URL. See `getDefaultURL` for more information.
|
||||
|
||||
NOTE: Must be called before `loadURL`/`loadURLString` for it to take effect.
|
||||
NOTE: Must be called before (if at all) `loadURL`/`loadURLString` for it to take
|
||||
effect.
|
||||
|
||||
#### setListener(listener)
|
||||
|
||||
Sets the given listener (class implementing the `JitsiMeetViewListener`
|
||||
interface) on the view.
|
||||
|
||||
#### setPictureInPictureEnabled(boolean)
|
||||
|
||||
Sets whether Picture-in-Picture is enabled. If not set, Jitsi Meet SDK
|
||||
automatically enables/disables Picture-in-Picture based on native platform
|
||||
support.
|
||||
|
||||
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
|
||||
effect.
|
||||
|
||||
#### setWelcomePageEnabled(boolean)
|
||||
|
||||
Sets whether the Welcome page is enabled. See `getWelcomePageEnabled` for more
|
||||
information.
|
||||
|
||||
NOTE: Must be called before `loadURL`/`loadURLString` for it to take effect.
|
||||
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
|
||||
effect.
|
||||
|
||||
#### onBackPressed()
|
||||
|
||||
@@ -247,7 +272,8 @@ This is a static method.
|
||||
|
||||
#### onHostResume(activity)
|
||||
|
||||
Helper method which should be called from the activity's `onResume` method.
|
||||
Helper method which should be called from the activity's `onResume` or `onStop`
|
||||
method.
|
||||
|
||||
This is a static method.
|
||||
|
||||
@@ -259,6 +285,13 @@ activity's `onNewIntent` method.
|
||||
|
||||
This is a static method.
|
||||
|
||||
#### onUserLeaveHint()
|
||||
|
||||
Helper method for integrating automatic Picture-in-Picture. It should be called
|
||||
from the activity's `onUserLeaveHint` method.
|
||||
|
||||
This is a static method.
|
||||
|
||||
#### JitsiMeetViewListener
|
||||
|
||||
`JitsiMeetViewListener` provides an interface apps can implement to listen to
|
||||
@@ -309,3 +342,79 @@ fails.
|
||||
|
||||
The `data` `Map` contains an "error" key with the error and a "url" key with the
|
||||
conference URL which necessitated the loading of the configuration file.
|
||||
|
||||
## ProGuard rules
|
||||
|
||||
When using the SDK on a project some proguard rules have to be added in order
|
||||
to avoid necessary code being stripped. Add the following to your project's
|
||||
rules file:
|
||||
|
||||
```
|
||||
# React Native
|
||||
|
||||
# Keep our interfaces so they can be used by other ProGuard rules.
|
||||
# See http://sourceforge.net/p/proguard/bugs/466/
|
||||
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
|
||||
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
|
||||
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
|
||||
|
||||
# Do not strip any method/class that is annotated with @DoNotStrip
|
||||
-keep @com.facebook.proguard.annotations.DoNotStrip class *
|
||||
-keep @com.facebook.common.internal.DoNotStrip class *
|
||||
-keepclassmembers class * {
|
||||
@com.facebook.proguard.annotations.DoNotStrip *;
|
||||
@com.facebook.common.internal.DoNotStrip *;
|
||||
}
|
||||
|
||||
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
|
||||
-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
|
||||
-keep class * extends com.facebook.react.bridge.NativeModule { *; }
|
||||
-keepclassmembers,includedescriptorclasses class * { native <methods>; }
|
||||
-keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; }
|
||||
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
|
||||
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
|
||||
|
||||
-dontwarn com.facebook.react.**
|
||||
|
||||
# TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
|
||||
# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
|
||||
-dontwarn android.text.StaticLayout
|
||||
|
||||
# okhttp
|
||||
|
||||
-keepattributes Signature
|
||||
-keepattributes *Annotation*
|
||||
-keep class okhttp3.** { *; }
|
||||
-keep interface okhttp3.** { *; }
|
||||
-dontwarn okhttp3.**
|
||||
|
||||
# okio
|
||||
|
||||
-keep class sun.misc.Unsafe { *; }
|
||||
-dontwarn java.nio.file.*
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-dontwarn okio.**
|
||||
|
||||
# WebRTC
|
||||
|
||||
-keep class org.webrtc.** { *; }
|
||||
-dontwarn org.chromium.build.BuildHooksAndroid
|
||||
|
||||
# Jisti Meet SDK
|
||||
|
||||
-keep class org.jitsi.meet.sdk.** { *; }
|
||||
```
|
||||
|
||||
## Picture-in-Picture
|
||||
|
||||
`JitsiMeetView` will automatically adjust its UI when presented in a
|
||||
Picture-in-Picture style scenario, in a rectangle too small to accommodate its
|
||||
"full" UI.
|
||||
|
||||
Jitsi Meet SDK automatically enables (unless explicitly disabled by a
|
||||
`setPictureInPictureEnabled(false)` call) Android's native Picture-in-Picture
|
||||
mode iff the platform is supported i.e. Android >= Oreo.
|
||||
|
||||
4
android/app/proguard-rules.pro
vendored
4
android/app/proguard-rules.pro
vendored
@@ -50,6 +50,10 @@
|
||||
|
||||
-dontwarn com.facebook.react.**
|
||||
|
||||
# TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
|
||||
# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
|
||||
-dontwarn android.text.StaticLayout
|
||||
|
||||
# okhttp
|
||||
|
||||
-keepattributes Signature
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:name=".MainActivity"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -22,7 +22,17 @@ import android.util.Log;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -38,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();
|
||||
@@ -48,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(
|
||||
@@ -87,19 +107,142 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
on("LOAD_CONFIG_ERROR", data);
|
||||
}
|
||||
});
|
||||
|
||||
// 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
|
||||
// want the Welcome page to be enabled. It defaults to disabled in the
|
||||
// SDK at the time of this writing but it is clearer to be explicit
|
||||
// about what we want anyway.
|
||||
// want to enable some options.
|
||||
|
||||
// The welcome page defaults to disabled in the SDK at the time of this
|
||||
// writing but it is clearer to be explicit about what we want anyway.
|
||||
setWelcomePageEnabled(true);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,5 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://maven.google.com' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.0'
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files.
|
||||
@@ -16,14 +16,28 @@ buildscript {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
google()
|
||||
jcenter()
|
||||
maven { url "https://maven.google.com" } // Required for appcompat.
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -133,7 +147,7 @@ ext {
|
||||
buildToolsVersion = "26.0.2"
|
||||
compileSdkVersion = 26
|
||||
minSdkVersion = 16
|
||||
targetSdkVersion = 25
|
||||
targetSdkVersion = 26
|
||||
|
||||
// The Maven artifact groupdId of the third-party react-native modules which
|
||||
// Jitsi Meet SDK for Android depends on and which are not available in
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
keystore(
|
||||
name = 'debug',
|
||||
store = 'debug.keystore',
|
||||
properties = 'debug.keystore.properties',
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
name = "debug",
|
||||
properties = "debug.keystore.properties",
|
||||
store = "debug.keystore",
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -21,15 +21,20 @@ android {
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
compile 'com.android.support:appcompat-v7:25.4.0'
|
||||
compile 'com.android.support:appcompat-v7:27.0.2'
|
||||
compile 'com.facebook.react:react-native:+'
|
||||
|
||||
compile project(':react-native-background-timer')
|
||||
compile project(':react-native-fetch-blob')
|
||||
compile project(':react-native-immersive')
|
||||
compile project(':react-native-keep-awake')
|
||||
compile project(':react-native-locale-detector')
|
||||
compile project(':react-native-sound')
|
||||
compile project(':react-native-vector-icons')
|
||||
compile project(':react-native-webrtc')
|
||||
compile project(':react-native-calendar-events')
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
// Build process helpers
|
||||
@@ -66,6 +71,25 @@ gradle.projectsEvaluated {
|
||||
runBefore("processUniversal${buildNameCapitalized}Resources", currentFontTask)
|
||||
runBefore("process${buildNameCapitalized}Resources", currentFontTask)
|
||||
|
||||
def currentSoundsTask = tasks.create(
|
||||
name: "copy${buildNameCapitalized}Sounds",
|
||||
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/rejected.wav")
|
||||
into("${bundlePath}/assets/sounds")
|
||||
}
|
||||
|
||||
currentSoundsTask.dependsOn("merge${buildNameCapitalized}Resources")
|
||||
currentSoundsTask.dependsOn("merge${buildNameCapitalized}Assets")
|
||||
|
||||
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
runBefore("processX86${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
runBefore("processUniversal${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
runBefore("process${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
|
||||
// Bundle JavaScript and React resources.
|
||||
// (adapted from react-native/react.gradle)
|
||||
//
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
|
||||
<uses-feature
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
@@ -25,7 +27,7 @@ class AndroidSettingsModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void open() {
|
||||
public void open(Promise promise) {
|
||||
Context context = getReactApplicationContext();
|
||||
Intent intent = new Intent();
|
||||
|
||||
@@ -34,6 +36,15 @@ class AndroidSettingsModule extends ReactContextBaseJavaModule {
|
||||
intent.setData(
|
||||
Uri.fromParts("package", context.getPackageName(), null));
|
||||
|
||||
context.startActivity(intent);
|
||||
try {
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// Some devices may give an error here.
|
||||
// https://developer.android.com/reference/android/provider/Settings.html#ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
promise.reject(e);
|
||||
return;
|
||||
}
|
||||
|
||||
promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@@ -73,13 +72,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.
|
||||
@@ -134,6 +140,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;
|
||||
}
|
||||
@@ -181,7 +188,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
/**
|
||||
* List of currently available audio devices.
|
||||
*/
|
||||
private Set<String> availableDevices = Collections.emptySet();
|
||||
private Set<String> availableDevices = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Currently selected device.
|
||||
@@ -300,7 +307,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
* Only used on Android < M. Runs on the main thread.
|
||||
*/
|
||||
void onBluetoothDeviceChange() {
|
||||
if (bluetoothHeadsetMonitor.isHeadsetAvailable()) {
|
||||
if (bluetoothHeadsetMonitor != null && bluetoothHeadsetMonitor.isHeadsetAvailable()) {
|
||||
availableDevices.add(DEVICE_BLUETOOTH);
|
||||
} else {
|
||||
availableDevices.remove(DEVICE_BLUETOOTH);
|
||||
|
||||
@@ -21,9 +21,7 @@ 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 com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -112,7 +110,38 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event that occurred on JavaScript to the view's listener.
|
||||
* The internal processing for the URL of the current conference set on the
|
||||
* associated {@link JitsiMeetView}.
|
||||
*
|
||||
* @param eventName the name of the external API event to be processed
|
||||
* @param eventData the details/specifics of the event to process determined
|
||||
* by/associated with the specified {@code eventName}.
|
||||
* @param view the {@link JitsiMeetView} instance.
|
||||
*/
|
||||
private void maybeSetViewURL(
|
||||
String eventName,
|
||||
ReadableMap eventData,
|
||||
JitsiMeetView view) {
|
||||
switch(eventName) {
|
||||
case "CONFERENCE_WILL_JOIN":
|
||||
view.setURL(eventData.getString("url"));
|
||||
break;
|
||||
|
||||
case "CONFERENCE_FAILED":
|
||||
case "CONFERENCE_WILL_LEAVE":
|
||||
case "LOAD_CONFIG_ERROR":
|
||||
String url = eventData.getString("url");
|
||||
|
||||
if (url != null && url.equals(view.getURL())) {
|
||||
view.setURL(null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event that occurred on the JavaScript side of the SDK to
|
||||
* the specified {@link JitsiMeetView}'s listener.
|
||||
*
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
@@ -120,7 +149,51 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
* @param scope
|
||||
*/
|
||||
@ReactMethod
|
||||
public void sendEvent(String name, ReadableMap data, String scope) {
|
||||
public void sendEvent(final String name,
|
||||
final ReadableMap data,
|
||||
final String scope) {
|
||||
// The JavaScript App needs to provide uniquely identifying information
|
||||
// to the native ExternalAPI module so that the latter may match the
|
||||
// former to the native JitsiMeetView which hosts it.
|
||||
JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope);
|
||||
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX The JitsiMeetView property URL was introduced in order to address
|
||||
// an exception in the Picture-in-Picture functionality which arose
|
||||
// because of delays related to bridging between JavaScript and Java. To
|
||||
// reduce these delays do not wait for the call to be transfered to the
|
||||
// UI thread.
|
||||
maybeSetViewURL(name, data, view);
|
||||
|
||||
// Make sure JitsiMeetView's listener is invoked on the UI thread. It
|
||||
// was requested by SDK consumers.
|
||||
if (UiThreadUtil.isOnUiThread()) {
|
||||
sendEventOnUiThread(name, data, scope);
|
||||
} else {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendEventOnUiThread(name, data, scope);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event that occurred on the JavaScript side of the SDK to
|
||||
* the specified {@link JitsiMeetView}'s listener on the UI thread.
|
||||
*
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
* by/associated with the specified {@code name}.
|
||||
* @param scope
|
||||
*/
|
||||
private void sendEventOnUiThread(final String name,
|
||||
final ReadableMap data,
|
||||
final String scope) {
|
||||
// The JavaScript App needs to provide uniquely identifying information
|
||||
// to the native ExternalAPI module so that the latter may match the
|
||||
// former to the native JitsiMeetView which hosts it.
|
||||
@@ -141,7 +214,16 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
if (method != null) {
|
||||
try {
|
||||
method.invoke(listener, toHashMap(data));
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,25 +23,25 @@ import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
|
||||
/**
|
||||
* Base Activity for applications integrating Jitsi Meet at a higher level. It
|
||||
* contains all the required wiring between the {@code JKConferenceView} and
|
||||
* contains all the required wiring between the {@code JitsiMeetView} and
|
||||
* the Activity lifecycle methods already implemented.
|
||||
*
|
||||
* In this activity we use a single {@code JKConferenceView} instance. This
|
||||
* In this activity we use a single {@code JitsiMeetView} instance. This
|
||||
* instance gives us access to a view which displays the welcome page and the
|
||||
* conference itself. All lifetime methods associated with this Activity are
|
||||
* hooked to the React Native subsystem via proxy calls through the
|
||||
* {@code JKConferenceView} static methods.
|
||||
* {@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.
|
||||
@@ -67,6 +67,12 @@ public class JitsiMeetActivity
|
||||
*/
|
||||
private JitsiMeetView view;
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled. The value is used only while
|
||||
* {@link #view} equals {@code null}.
|
||||
*/
|
||||
private Boolean pictureInPictureEnabled;
|
||||
|
||||
/**
|
||||
* Whether the Welcome page is enabled. The value is used only while
|
||||
* {@link #view} equals {@code null}.
|
||||
@@ -89,6 +95,17 @@ public class JitsiMeetActivity
|
||||
return view == null ? defaultURL : view.getDefaultURL();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#getPictureInPictureEnabled()
|
||||
*/
|
||||
public boolean getPictureInPictureEnabled() {
|
||||
return
|
||||
view == null
|
||||
? pictureInPictureEnabled
|
||||
: view.getPictureInPictureEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#getWelcomePageEnabled()
|
||||
@@ -105,6 +122,11 @@ public class JitsiMeetActivity
|
||||
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);
|
||||
}
|
||||
@@ -121,10 +143,12 @@ public class JitsiMeetActivity
|
||||
// XXX Before calling JitsiMeetView#loadURL, make sure to call whatever
|
||||
// is documented to need such an order in order to take effect:
|
||||
view.setDefaultURL(defaultURL);
|
||||
if (pictureInPictureEnabled != null) {
|
||||
view.setPictureInPictureEnabled(
|
||||
pictureInPictureEnabled.booleanValue());
|
||||
}
|
||||
view.setWelcomePageEnabled(welcomePageEnabled);
|
||||
|
||||
view.loadURL(null);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -199,17 +223,26 @@ public class JitsiMeetActivity
|
||||
JitsiMeetView.onHostDestroy(this);
|
||||
}
|
||||
|
||||
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
JitsiMeetView.onNewIntent(intent);
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
ReactInstanceManager reactInstanceManager;
|
||||
|
||||
if (!super.onKeyUp(keyCode, event)
|
||||
&& BuildConfig.DEBUG
|
||||
&& (reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager())
|
||||
!= null
|
||||
&& keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
reactInstanceManager.showDevOptionsDialog();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
JitsiMeetView.onHostPause(this);
|
||||
defaultBackButtonImpl = null;
|
||||
public void onNewIntent(Intent intent) {
|
||||
JitsiMeetView.onNewIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,6 +253,21 @@ public class JitsiMeetActivity
|
||||
JitsiMeetView.onHostResume(this, defaultBackButtonImpl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
JitsiMeetView.onHostPause(this);
|
||||
defaultBackButtonImpl = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUserLeaveHint() {
|
||||
if (view != null) {
|
||||
view.onUserLeaveHint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setDefaultURL(URL)
|
||||
@@ -232,6 +280,19 @@ public class JitsiMeetActivity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setPictureInPictureEnabled(boolean)
|
||||
*/
|
||||
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
|
||||
if (view == null) {
|
||||
this.pictureInPictureEnabled
|
||||
= Boolean.valueOf(pictureInPictureEnabled);
|
||||
} else {
|
||||
view.setPictureInPictureEnabled(pictureInPictureEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setWelcomePageEnabled(boolean)
|
||||
|
||||
@@ -17,26 +17,24 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
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.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
|
||||
import org.jitsi.meet.sdk.invite.InviteController;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
@@ -49,25 +47,14 @@ public class JitsiMeetView extends FrameLayout {
|
||||
private static final int BACKGROUND_COLOR = 0xFF111111;
|
||||
|
||||
/**
|
||||
* React Native bridge. The instance manager allows embedding applications
|
||||
* to create multiple root views off the same JavaScript bundle.
|
||||
* The {@link Log} tag which identifies the source of the log messages of
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
private static ReactInstanceManager reactInstanceManager;
|
||||
private final static String TAG = JitsiMeetView.class.getSimpleName();
|
||||
|
||||
private static final Set<JitsiMeetView> views
|
||||
= Collections.newSetFromMap(new WeakHashMap<JitsiMeetView, Boolean>());
|
||||
|
||||
private static List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
return Arrays.<NativeModule>asList(
|
||||
new AndroidSettingsModule(reactContext),
|
||||
new AppInfoModule(reactContext),
|
||||
new AudioModeModule(reactContext),
|
||||
new ExternalAPIModule(reactContext),
|
||||
new ProximityModule(reactContext)
|
||||
);
|
||||
}
|
||||
|
||||
public static JitsiMeetView findViewByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
synchronized (views) {
|
||||
@@ -81,39 +68,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to initialize the React Native instance manager. We
|
||||
* create a single instance in order to load the JavaScript bundle a single
|
||||
* time. All {@code ReactRootView} instances will be tied to the one and
|
||||
* only {@code ReactInstanceManager}.
|
||||
*
|
||||
* @param application {@code Application} instance which is running.
|
||||
*/
|
||||
private static void initReactInstanceManager(Application application) {
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
.setApplication(application)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
|
||||
.addPackage(new com.facebook.react.shell.MainReactPackage())
|
||||
.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 ReactPackageAdapter() {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
return JitsiMeetView.createNativeModules(reactContext);
|
||||
}
|
||||
})
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a specific URL {@code String} in all existing
|
||||
* {@code JitsiMeetView}s.
|
||||
@@ -148,6 +102,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* implementation.
|
||||
*/
|
||||
public static boolean onBackPressed() {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager == null) {
|
||||
return false;
|
||||
} else {
|
||||
@@ -164,6 +121,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* @param activity {@code Activity} being destroyed.
|
||||
*/
|
||||
public static void onHostDestroy(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostDestroy(activity);
|
||||
}
|
||||
@@ -176,6 +136,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* @param activity {@code Activity} being paused.
|
||||
*/
|
||||
public static void onHostPause(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostPause(activity);
|
||||
}
|
||||
@@ -202,6 +165,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||
public static void onHostResume(
|
||||
Activity activity,
|
||||
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
|
||||
}
|
||||
@@ -230,6 +196,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onNewIntent(intent);
|
||||
}
|
||||
@@ -250,17 +219,37 @@ public class JitsiMeetView extends FrameLayout {
|
||||
*/
|
||||
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
|
||||
* natively.
|
||||
*/
|
||||
private Boolean pictureInPictureEnabled;
|
||||
|
||||
/**
|
||||
* React Native root view.
|
||||
*/
|
||||
private ReactRootView reactRootView;
|
||||
|
||||
/**
|
||||
* The URL of the current conference.
|
||||
*/
|
||||
// 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.
|
||||
*/
|
||||
@@ -271,15 +260,18 @@ public class JitsiMeetView extends FrameLayout {
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
if (reactInstanceManager == null) {
|
||||
initReactInstanceManager(((Activity) context).getApplication());
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,6 +302,19 @@ public class JitsiMeetView extends FrameLayout {
|
||||
return defaultURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link InviteController} which represents the entry point into
|
||||
* the invite feature of Jitsi Meet and is the Java counterpart of the
|
||||
* JavaScript {@code InviteButton}.
|
||||
*
|
||||
* @return the {@link InviteController} which represents the entry point
|
||||
* into the invite feature of Jitsi Meet and is the Java counterpart of the
|
||||
* JavaScript {@code InviteButton}
|
||||
*/
|
||||
public InviteController getInviteController() {
|
||||
return inviteController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link JitsiMeetViewListener} set on this {@code JitsiMeetView}.
|
||||
*
|
||||
@@ -320,6 +325,34 @@ public class JitsiMeetView extends FrameLayout {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
|
||||
* natively supported on Android API >= 26 (Oreo), so it should not be
|
||||
* enabled on older platform versions.
|
||||
*
|
||||
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean getPictureInPictureEnabled() {
|
||||
return
|
||||
PictureInPictureModule.isPictureInPictureSupported()
|
||||
&& (pictureInPictureEnabled == null
|
||||
|| pictureInPictureEnabled.booleanValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL of the current conference.
|
||||
*
|
||||
* 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}.
|
||||
*/
|
||||
String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the Welcome page is enabled. If {@code true}, the Welcome
|
||||
* page is rendered when this {@code JitsiMeetView} is not at a URL
|
||||
@@ -361,12 +394,32 @@ public class JitsiMeetView extends FrameLayout {
|
||||
if (defaultURL != null) {
|
||||
props.putString("defaultURL", defaultURL.toString());
|
||||
}
|
||||
|
||||
// externalAPIScope
|
||||
props.putString("externalAPIScope", externalAPIScope);
|
||||
|
||||
// inviteController
|
||||
InviteController inviteController = getInviteController();
|
||||
|
||||
if (inviteController != null) {
|
||||
props.putBoolean(
|
||||
"addPeopleEnabled",
|
||||
inviteController.isAddPeopleEnabled());
|
||||
props.putBoolean(
|
||||
"dialOutEnabled",
|
||||
inviteController.isDialOutEnabled());
|
||||
}
|
||||
|
||||
// pictureInPictureEnabled
|
||||
props.putBoolean(
|
||||
"pictureInPictureEnabled",
|
||||
getPictureInPictureEnabled());
|
||||
|
||||
// url
|
||||
if (urlObject != null) {
|
||||
props.putBundle("url", urlObject);
|
||||
}
|
||||
|
||||
// welcomePageEnabled
|
||||
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
||||
|
||||
@@ -384,7 +437,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||
if (reactRootView == null) {
|
||||
reactRootView = new ReactRootView(getContext());
|
||||
reactRootView.startReactApplication(
|
||||
reactInstanceManager, "App", props);
|
||||
ReactInstanceManagerHolder.getReactInstanceManager(),
|
||||
"App",
|
||||
props);
|
||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
||||
addView(reactRootView);
|
||||
} else {
|
||||
@@ -412,6 +467,69 @@ public class JitsiMeetView extends FrameLayout {
|
||||
loadURLObject(urlObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onUserLeaveHint} so we can do the required internal
|
||||
* processing.
|
||||
*
|
||||
* 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 onUserLeaveHint() {
|
||||
if (getPictureInPictureEnabled() && getURL() != null) {
|
||||
PictureInPictureModule pipModule
|
||||
= ReactInstanceManagerHolder.getNativeModule(
|
||||
PictureInPictureModule.class);
|
||||
|
||||
if (pipModule != null) {
|
||||
try {
|
||||
pipModule.enterPictureInPicture();
|
||||
} catch (RuntimeException re) {
|
||||
Log.e(TAG, "onUserLeaveHint: failed to enter PiP mode", re);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
TAG,
|
||||
"RNImmersiveModule#emitImmersiveStateChangeEvent() failed!",
|
||||
re);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default base {@code URL} used to join a conference when a
|
||||
* partial URL (e.g. a room name only) is specified to
|
||||
@@ -436,6 +554,31 @@ public class JitsiMeetView extends FrameLayout {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether Picture-in-Picture is enabled. Because Picture-in-Picture is
|
||||
* natively supported only since certain platform versions, specifying
|
||||
* {@code true} will have no effect on unsupported platform versions.
|
||||
*
|
||||
* @param pictureInPictureEnabled To enable Picture-in-Picture,
|
||||
* {@code true}; otherwise, {@code false}.
|
||||
*/
|
||||
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
|
||||
this.pictureInPictureEnabled = Boolean.valueOf(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the Welcome page is enabled. Must be called before
|
||||
* {@link #loadURL(URL)} for it to take effect.
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PictureInPictureParams;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.util.Rational;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
public class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
private final static String TAG = "PictureInPicture";
|
||||
|
||||
static boolean isPictureInPictureSupported() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
}
|
||||
|
||||
public PictureInPictureModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enters Picture-in-Picture (mode) for the current {@link Activity}.
|
||||
* Supported on Android API >= 26 (Oreo) only.
|
||||
*
|
||||
* @throws IllegalStateException if {@link #isPictureInPictureSupported()}
|
||||
* returns {@code false} or if {@link #getCurrentActivity()} returns
|
||||
* {@code null}.
|
||||
* @throws RuntimeException if
|
||||
* {@link Activity#enterPictureInPictureMode(PictureInPictureParams)} fails.
|
||||
* That method can also throw a {@link RuntimeException} in various cases,
|
||||
* including when the activity is not visible (paused or stopped), if the
|
||||
* screen is locked or if the user has an activity pinned.
|
||||
*/
|
||||
public void enterPictureInPicture() {
|
||||
if (!isPictureInPictureSupported()) {
|
||||
throw new IllegalStateException("Picture-in-Picture not supported");
|
||||
}
|
||||
|
||||
Activity currentActivity = getCurrentActivity();
|
||||
|
||||
if (currentActivity == null) {
|
||||
throw new IllegalStateException("No current Activity!");
|
||||
}
|
||||
|
||||
Log.d(TAG, "Entering Picture-in-Picture");
|
||||
|
||||
PictureInPictureParams.Builder builder
|
||||
= new PictureInPictureParams.Builder()
|
||||
.setAspectRatio(new Rational(1, 1));
|
||||
|
||||
// https://developer.android.com/reference/android/app/Activity.html#enterPictureInPictureMode(android.app.PictureInPictureParams)
|
||||
//
|
||||
// The system may disallow entering picture-in-picture in various cases,
|
||||
// including when the activity is not visible, if the screen is locked
|
||||
// or if the user has an activity pinned.
|
||||
if (!currentActivity.enterPictureInPictureMode(builder.build())) {
|
||||
throw new RuntimeException("Failed to enter Picture-in-Picture");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enters Picture-in-Picture (mode) for the current {@link Activity}.
|
||||
* Supported on Android API >= 26 (Oreo) only.
|
||||
*
|
||||
* @param promise a {@code Promise} which will resolve with a {@code null}
|
||||
* value upon success, and an {@link Exception} otherwise.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void enterPictureInPicture(Promise promise) {
|
||||
try {
|
||||
enterPictureInPicture();
|
||||
promise.resolve(null);
|
||||
} catch (RuntimeException re) {
|
||||
promise.reject(re);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
public class ReactContextUtils {
|
||||
public static boolean emitEvent(
|
||||
ReactContext reactContext,
|
||||
String eventName,
|
||||
@Nullable Object data) {
|
||||
if (reactContext == null) {
|
||||
// XXX If no ReactContext is specified, emit through the
|
||||
// ReactContext of ReactInstanceManager. ReactInstanceManager
|
||||
// cooperates with ReactContextUtils i.e. ReactInstanceManager will
|
||||
// not invoke ReactContextUtils without a ReactContext.
|
||||
return ReactInstanceManagerHolder.emitEvent(eventName, data);
|
||||
}
|
||||
|
||||
reactContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit(eventName, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.Application;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ReactInstanceManagerHolder {
|
||||
/**
|
||||
* React Native bridge. The instance manager allows embedding applications
|
||||
* to create multiple root views off the same JavaScript bundle.
|
||||
*/
|
||||
private static ReactInstanceManager reactInstanceManager;
|
||||
|
||||
private static List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
return Arrays.<NativeModule>asList(
|
||||
new AndroidSettingsModule(reactContext),
|
||||
new AppInfoModule(reactContext),
|
||||
new AudioModeModule(reactContext),
|
||||
new ExternalAPIModule(reactContext),
|
||||
new PictureInPictureModule(reactContext),
|
||||
new ProximityModule(reactContext),
|
||||
new WiFiStatsModule(reactContext),
|
||||
new org.jitsi.meet.sdk.invite.InviteModule(reactContext),
|
||||
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to send an event to JavaScript.
|
||||
*
|
||||
* @param eventName {@code String} containing the event name.
|
||||
* @param data {@code Object} optional ancillary data for the event.
|
||||
*/
|
||||
public static boolean emitEvent(
|
||||
String eventName,
|
||||
@Nullable Object data) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
ReactContext reactContext
|
||||
= reactInstanceManager.getCurrentReactContext();
|
||||
|
||||
return
|
||||
reactContext != null
|
||||
&& ReactContextUtils.emitEvent(
|
||||
reactContext,
|
||||
eventName,
|
||||
data);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a native React module for given class.
|
||||
*
|
||||
* @param nativeModuleClass the native module's class for which an instance
|
||||
* is to be retrieved from the {@link #reactInstanceManager}.
|
||||
* @param <T> the module's type.
|
||||
* @return {@link NativeModule} instance for given interface type or
|
||||
* {@code null} if no instance for this interface is available, or if
|
||||
* {@link #reactInstanceManager} has not been initialized yet.
|
||||
*/
|
||||
static <T extends NativeModule> T getNativeModule(
|
||||
Class<T> nativeModuleClass) {
|
||||
ReactContext reactContext
|
||||
= reactInstanceManager != null
|
||||
? reactInstanceManager.getCurrentReactContext() : null;
|
||||
|
||||
return reactContext != null
|
||||
? reactContext.getNativeModule(nativeModuleClass) : null;
|
||||
}
|
||||
|
||||
static ReactInstanceManager getReactInstanceManager() {
|
||||
return reactInstanceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to initialize the React Native instance manager. We
|
||||
* create a single instance in order to load the JavaScript bundle a single
|
||||
* time. All {@code ReactRootView} instances will be tied to the one and
|
||||
* only {@code ReactInstanceManager}.
|
||||
*
|
||||
* @param application {@code Application} instance which is running.
|
||||
*/
|
||||
static void initReactInstanceManager(Application application) {
|
||||
if (reactInstanceManager != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
.setApplication(application)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.addPackage(new com.calendarevents.CalendarEventsPackage())
|
||||
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
|
||||
.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() {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
return
|
||||
ReactInstanceManagerHolder.createNativeModules(
|
||||
reactContext);
|
||||
}
|
||||
})
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
* Module exposing WiFi statistics.
|
||||
*
|
||||
* Gathers rssi, signal in percentage, timestamp and the addresses
|
||||
* of the wifi device.
|
||||
*/
|
||||
class WiFiStatsModule extends ReactContextBaseJavaModule {
|
||||
/**
|
||||
* The name of {@code WiFiStatsModule} to be used in the React Native
|
||||
* bridge.
|
||||
*/
|
||||
private static final String MODULE_NAME = "WiFiStats";
|
||||
|
||||
/**
|
||||
* The {@code Log} tag {@code WiFiStatsModule} is to log messages with.
|
||||
*/
|
||||
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).
|
||||
*/
|
||||
public final static int SIGNAL_LEVEL_SCALE = 101;
|
||||
|
||||
/**
|
||||
* {@link Handler} for running all operations on the main thread.
|
||||
*/
|
||||
private final Handler mainThreadHandler
|
||||
= new Handler(Looper.getMainLooper());
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
* this module throughout the lifetime of the application.
|
||||
*
|
||||
* @param reactContext the {@link ReactApplicationContext} where this module
|
||||
* is created.
|
||||
*/
|
||||
public WiFiStatsModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name for this module to be used in the React Native bridge.
|
||||
*
|
||||
* @return a string with the module name.
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return MODULE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link InetAddress} represented by this int.
|
||||
*
|
||||
* @param value the int representation of the ip address.
|
||||
* @return the {@link InetAddress}.
|
||||
* @throws UnknownHostException - if IP address is of illegal length.
|
||||
*/
|
||||
public static InetAddress toInetAddress(int value)
|
||||
throws UnknownHostException {
|
||||
return InetAddress.getByAddress(
|
||||
new byte[] {
|
||||
(byte) value,
|
||||
(byte) (value >> 8),
|
||||
(byte) (value >> 16),
|
||||
(byte) (value >> 24)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method to retrieve WiFi stats.
|
||||
*
|
||||
* @param promise a {@link Promise} which will be resolved if WiFi stats are
|
||||
* retrieved successfully, and it will be rejected otherwise.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getWiFiStats(final Promise promise) {
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
||||
Context context
|
||||
= getReactApplicationContext().getApplicationContext();
|
||||
WifiManager wifiManager
|
||||
= (WifiManager) context
|
||||
.getSystemService(Context.WIFI_SERVICE);
|
||||
|
||||
if (!wifiManager.isWifiEnabled()) {
|
||||
promise.reject(new Exception("Wifi not enabled"));
|
||||
return;
|
||||
}
|
||||
|
||||
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
|
||||
|
||||
if (wifiInfo.getNetworkId() == -1) {
|
||||
promise.reject(new Exception("Wifi not connected"));
|
||||
return;
|
||||
}
|
||||
|
||||
int rssi = wifiInfo.getRssi();
|
||||
int signalLevel
|
||||
= WifiManager.calculateSignalLevel(
|
||||
rssi, SIGNAL_LEVEL_SCALE);
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("rssi", rssi)
|
||||
.put("signal", signalLevel)
|
||||
.put("timestamp",
|
||||
String.valueOf(System.currentTimeMillis()));
|
||||
|
||||
JSONArray addresses = new JSONArray();
|
||||
|
||||
InetAddress wifiAddress
|
||||
= toInetAddress(wifiInfo.getIpAddress());
|
||||
|
||||
try {
|
||||
Enumeration<NetworkInterface> e
|
||||
= NetworkInterface.getNetworkInterfaces();
|
||||
while (e.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = e.nextElement();
|
||||
boolean found = false;
|
||||
|
||||
// first check whether this is the desired interface
|
||||
Enumeration<InetAddress> as
|
||||
= networkInterface.getInetAddresses();
|
||||
while (as.hasMoreElements()) {
|
||||
InetAddress a = as.nextElement();
|
||||
if(a.equals(wifiAddress)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
// interface found let's put addresses
|
||||
// to the result object
|
||||
as = networkInterface.getInetAddresses();
|
||||
while (as.hasMoreElements()) {
|
||||
InetAddress a = as.nextElement();
|
||||
if (a.isLinkLocalAddress())
|
||||
continue;
|
||||
|
||||
addresses.put(a.getHostAddress());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
Log.wtf(TAG,
|
||||
"Unable to NetworkInterface.getNetworkInterfaces()"
|
||||
);
|
||||
}
|
||||
|
||||
result.put("addresses", addresses);
|
||||
promise.resolve(result.toString());
|
||||
|
||||
Log.d(TAG, "WiFi stats: " + result.toString());
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Failed to obtain wifi stats", e);
|
||||
promise.reject(
|
||||
new Exception("Failed to obtain wifi stats"));
|
||||
}
|
||||
}
|
||||
};
|
||||
mainThreadHandler.post(r);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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.invite;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
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.
|
||||
*/
|
||||
public class AddPeopleController {
|
||||
|
||||
/**
|
||||
* The AddPeopleControllerListener for this controller, used to pass query
|
||||
* results back to the native code that initiated the query.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
private final String uuid = UUID.randomUUID().toString();
|
||||
|
||||
public AddPeopleController(
|
||||
InviteController owner,
|
||||
ReactApplicationContext reactContext) {
|
||||
this.owner = new WeakReference<>(owner);
|
||||
this.reactContext = new WeakReference<>(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the invitation flow and free memory allocated to the
|
||||
* AddPeopleController. After calling this method, this object is invalid -
|
||||
* a new AddPeopleController will be passed to the caller through
|
||||
* beginAddPeople.
|
||||
*/
|
||||
public void endAddPeople() {
|
||||
InviteController owner = this.owner.get();
|
||||
|
||||
if (owner != null) {
|
||||
owner.endAddPeople(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the AddPeopleControllerListener for this controller, used to pass
|
||||
* query results back to the native code that initiated the query.
|
||||
*/
|
||||
public AddPeopleControllerListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
final ReactApplicationContext getReactApplicationContext() {
|
||||
return reactContext.get();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the unique identifier for this AddPeopleController
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send invites to selected users based on their item ids
|
||||
*
|
||||
* @param ids
|
||||
*/
|
||||
public void inviteById(List<String> ids) {
|
||||
InviteController owner = this.owner.get();
|
||||
|
||||
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(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);
|
||||
}
|
||||
}
|
||||
|
||||
void inviteSettled(ReadableArray failedInvitees) {
|
||||
AddPeopleControllerListener listener = getListener();
|
||||
|
||||
if (listener != null) {
|
||||
ArrayList<Map<String, Object>> jFailedInvitees = new ArrayList<>();
|
||||
|
||||
for (int i = 0, size = failedInvitees.size(); i < size; ++i) {
|
||||
jFailedInvitees.add(failedInvitees.getMap(i).toHashMap());
|
||||
}
|
||||
|
||||
listener.onInviteSettled(this, jFailedInvitees);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a search for entities to invite with the given query. Results will
|
||||
* be returned through the associated AddPeopleControllerListener's
|
||||
* onReceivedResults method.
|
||||
*
|
||||
* @param query
|
||||
*/
|
||||
public void performQuery(String query) {
|
||||
InviteController owner = this.owner.get();
|
||||
|
||||
if (owner != null) {
|
||||
owner.performQuery(this, query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
void receivedResultsForQuery(ReadableArray results, String query) {
|
||||
AddPeopleControllerListener listener = getListener();
|
||||
|
||||
if (listener != null) {
|
||||
List<Map<String, Object>> jvmResults = new ArrayList<>();
|
||||
|
||||
// cache results for use in submission later
|
||||
// convert to jvm array
|
||||
for(int i = 0; i < results.size(); i++) {
|
||||
ReadableMap map = results.getMap(i);
|
||||
|
||||
if(map.hasKey("id")) {
|
||||
items.put(map.getString("id"), map);
|
||||
} 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);
|
||||
}
|
||||
|
||||
jvmResults.add(map.toHashMap());
|
||||
}
|
||||
|
||||
listener.onReceivedResults(this, jvmResults, query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the AddPeopleControllerListener for this controller, used to pass
|
||||
* query results back to the native code that initiated the query.
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public void setListener(AddPeopleControllerListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.invite;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface AddPeopleControllerListener {
|
||||
/**
|
||||
* Called when the call to {@link AddPeopleController#inviteById(List)}
|
||||
* completes.
|
||||
*
|
||||
* @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 onInviteSettled(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> failedInvitees);
|
||||
|
||||
/**
|
||||
* Called when results are received for a query called through
|
||||
* AddPeopleController.query().
|
||||
*
|
||||
* @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 onReceivedResults(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> results,
|
||||
String query);
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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.invite;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import org.jitsi.meet.sdk.ReactContextUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
/**
|
||||
* Represents the entry point into the invite feature of Jitsi Meet and is the
|
||||
* Java counterpart of the JavaScript {@code InviteButton}.
|
||||
*/
|
||||
public class InviteController {
|
||||
private AddPeopleController addPeopleController;
|
||||
|
||||
/**
|
||||
* Whether adding/inviting people by name (as opposed to phone number) is
|
||||
* enabled.
|
||||
*/
|
||||
private Boolean addPeopleEnabled;
|
||||
|
||||
/**
|
||||
* Whether adding/inviting people by phone number (as opposed to name) is
|
||||
* enabled.
|
||||
*/
|
||||
private Boolean dialOutEnabled;
|
||||
|
||||
private final String externalAPIScope;
|
||||
|
||||
private InviteControllerListener listener;
|
||||
|
||||
public InviteController(String externalAPIScope) {
|
||||
this.externalAPIScope = externalAPIScope;
|
||||
}
|
||||
|
||||
void beginAddPeople(ReactApplicationContext reactContext) {
|
||||
InviteControllerListener listener = getListener();
|
||||
|
||||
if (listener != null) {
|
||||
// XXX For the sake of simplicity and in order to reduce the risk of
|
||||
// memory leaks, allow a single AddPeopleController at a time.
|
||||
AddPeopleController addPeopleController = this.addPeopleController;
|
||||
|
||||
if (addPeopleController != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize a new AddPeopleController to represent the click/tap
|
||||
// on the InviteButton and notify the InviteControllerListener
|
||||
// about the event.
|
||||
addPeopleController = new AddPeopleController(this, reactContext);
|
||||
|
||||
boolean success = false;
|
||||
|
||||
this.addPeopleController = addPeopleController;
|
||||
try {
|
||||
listener.beginAddPeople(addPeopleController);
|
||||
success = true;
|
||||
} finally {
|
||||
if (!success) {
|
||||
endAddPeople(addPeopleController);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void endAddPeople(AddPeopleController addPeopleController) {
|
||||
if (this.addPeopleController == addPeopleController) {
|
||||
this.addPeopleController = null;
|
||||
}
|
||||
}
|
||||
|
||||
public InviteControllerListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends JavaScript event to submit invitations to the given item ids
|
||||
*
|
||||
* @param invitees a WritableArray of WritableNativeMaps representing
|
||||
* selected items. Each map representing a selected item should match the
|
||||
* data passed back in the return from a query.
|
||||
*/
|
||||
boolean invite(
|
||||
AddPeopleController addPeopleController,
|
||||
WritableArray invitees) {
|
||||
return
|
||||
invite(
|
||||
addPeopleController.getUuid(),
|
||||
addPeopleController.getReactApplicationContext(),
|
||||
invitees);
|
||||
}
|
||||
|
||||
public Future<List<Map<String, Object>>> invite(
|
||||
final List<Map<String, Object>> invitees) {
|
||||
final boolean inviteBegan
|
||||
= invite(
|
||||
UUID.randomUUID().toString(),
|
||||
/* reactContext */ null,
|
||||
Arguments.makeNativeArray(invitees));
|
||||
FutureTask futureTask
|
||||
= new FutureTask(new Callable() {
|
||||
@Override
|
||||
public List<Map<String, Object>> call() {
|
||||
if (inviteBegan) {
|
||||
// TODO Complete the returned Future when the invite
|
||||
// settles.
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
// The invite failed to even begin so report that all
|
||||
// invitees failed.
|
||||
return invitees;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If the invite failed to even begin, complete the returned Future
|
||||
// already and the Future implementation will report that all invitees
|
||||
// failed.
|
||||
if (!inviteBegan) {
|
||||
futureTask.run();
|
||||
}
|
||||
|
||||
return futureTask;
|
||||
}
|
||||
|
||||
private boolean invite(
|
||||
String addPeopleControllerScope,
|
||||
ReactContext reactContext,
|
||||
WritableArray invitees) {
|
||||
WritableNativeMap data = new WritableNativeMap();
|
||||
|
||||
data.putString("addPeopleControllerScope", addPeopleControllerScope);
|
||||
data.putString("externalAPIScope", externalAPIScope);
|
||||
data.putArray("invitees", invitees);
|
||||
|
||||
return
|
||||
ReactContextUtils.emitEvent(
|
||||
reactContext,
|
||||
"org.jitsi.meet:features/invite#invite",
|
||||
data);
|
||||
}
|
||||
|
||||
void inviteSettled(
|
||||
String addPeopleControllerScope,
|
||||
ReadableArray failedInvitees) {
|
||||
AddPeopleController addPeopleController = this.addPeopleController;
|
||||
|
||||
if (addPeopleController != null
|
||||
&& addPeopleController.getUuid().equals(
|
||||
addPeopleControllerScope)) {
|
||||
try {
|
||||
addPeopleController.inviteSettled(failedInvitees);
|
||||
} finally {
|
||||
if (failedInvitees.size() == 0) {
|
||||
endAddPeople(addPeopleController);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAddPeopleEnabled() {
|
||||
Boolean b = this.addPeopleEnabled;
|
||||
|
||||
return
|
||||
(b == null || b.booleanValue()) ? (getListener() != null) : false;
|
||||
}
|
||||
|
||||
public boolean isDialOutEnabled() {
|
||||
Boolean b = this.dialOutEnabled;
|
||||
|
||||
return
|
||||
(b == null || b.booleanValue()) ? (getListener() != null) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a query for users to invite to the conference. Results will be
|
||||
* returned through the {@link AddPeopleControllerListener#onReceivedResults(AddPeopleController, List, String)}
|
||||
* method.
|
||||
*
|
||||
* @param query {@code String} to use for the query
|
||||
*/
|
||||
void performQuery(AddPeopleController addPeopleController, String query) {
|
||||
WritableNativeMap params = new WritableNativeMap();
|
||||
|
||||
params.putString("addPeopleControllerScope", addPeopleController.getUuid());
|
||||
params.putString("externalAPIScope", externalAPIScope);
|
||||
params.putString("query", query);
|
||||
ReactContextUtils.emitEvent(
|
||||
addPeopleController.getReactApplicationContext(),
|
||||
"org.jitsi.meet:features/invite#performQuery",
|
||||
params);
|
||||
}
|
||||
|
||||
void receivedResultsForQuery(
|
||||
String addPeopleControllerScope,
|
||||
String query,
|
||||
ReadableArray results) {
|
||||
AddPeopleController addPeopleController = this.addPeopleController;
|
||||
|
||||
if (addPeopleController != null
|
||||
&& addPeopleController.getUuid().equals(
|
||||
addPeopleControllerScope)) {
|
||||
addPeopleController.receivedResultsForQuery(results, query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the ability to add users to the call is enabled. If this is
|
||||
* enabled, an add user button will appear on the {@link JitsiMeetView}. If
|
||||
* enabled, and the user taps the add user button,
|
||||
* {@link InviteControllerListener#beginAddPeople(AddPeopleController)}
|
||||
* will be called.
|
||||
*
|
||||
* @param addPeopleEnabled {@code true} to enable the add people button;
|
||||
* otherwise, {@code false}
|
||||
*/
|
||||
public void setAddPeopleEnabled(boolean addPeopleEnabled) {
|
||||
this.addPeopleEnabled = Boolean.valueOf(addPeopleEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the ability to add phone numbers to the call is enabled.
|
||||
* Must be enabled along with {@link #setAddPeopleEnabled(boolean)} to be
|
||||
* effective.
|
||||
*
|
||||
* @param dialOutEnabled {@code true} to enable the ability to add phone
|
||||
* numbers to the call; otherwise, {@code false}
|
||||
*/
|
||||
public void setDialOutEnabled(boolean dialOutEnabled) {
|
||||
this.dialOutEnabled = Boolean.valueOf(dialOutEnabled);
|
||||
}
|
||||
|
||||
public void setListener(InviteControllerListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.invite;
|
||||
|
||||
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.
|
||||
*/
|
||||
void beginAddPeople(AddPeopleController addPeopleController);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.invite;
|
||||
|
||||
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.ReadableArray;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
|
||||
/**
|
||||
* Implements the react-native module of the feature invite.
|
||||
*/
|
||||
public class InviteModule extends ReactContextBaseJavaModule {
|
||||
public InviteModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that a click/tap has been performed on {@code InviteButton} and
|
||||
* that the execution flow for adding/inviting people to the current
|
||||
* conference/meeting is to begin
|
||||
*
|
||||
* @param externalAPIScope the unique identifier of the
|
||||
* {@code JitsiMeetView} whose {@code InviteButton} was clicked/tapped.
|
||||
*/
|
||||
@ReactMethod
|
||||
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);
|
||||
|
||||
if (inviteController != null) {
|
||||
inviteController.beginAddPeople(getReactApplicationContext());
|
||||
}
|
||||
}
|
||||
|
||||
private InviteController findInviteControllerByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
JitsiMeetView view
|
||||
= JitsiMeetView.findViewByExternalAPIScope(externalAPIScope);
|
||||
|
||||
return view == null ? null : view.getInviteController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Invite";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@ReactMethod
|
||||
public void inviteSettled(
|
||||
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);
|
||||
|
||||
if (inviteController == null) {
|
||||
Log.w(
|
||||
"InviteModule",
|
||||
"Invite settled, but failed to find active controller to notify");
|
||||
} else {
|
||||
inviteController.inviteSettled(
|
||||
addPeopleControllerScope,
|
||||
failedInvitees);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for results received from the JavaScript invite search call
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@ReactMethod
|
||||
public void receivedResults(
|
||||
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);
|
||||
|
||||
if (inviteController == null) {
|
||||
Log.w(
|
||||
"InviteModule",
|
||||
"Received results, but failed to find active controller to send results back");
|
||||
} else {
|
||||
inviteController.receivedResultsForQuery(
|
||||
addPeopleControllerScope,
|
||||
query,
|
||||
results);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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.net;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Constructs IPv6 addresses for IPv4 addresses in the NAT64 environment.
|
||||
*
|
||||
* NAT64 translates IPv4 to IPv6 addresses by adding "well known" prefix and
|
||||
* suffix configured by the administrator. Those are figured out by discovering
|
||||
* both IPv6 and IPv4 addresses of a host and then trying to find a place where
|
||||
* the IPv4 address fits into the format described here:
|
||||
* https://tools.ietf.org/html/rfc6052#section-2.2
|
||||
*/
|
||||
public class NAT64AddrInfo {
|
||||
/**
|
||||
* Coverts bytes array to upper case HEX string.
|
||||
*
|
||||
* @param bytes an array of bytes to be converted
|
||||
* @return ex. "010AFF" for an array of {1, 10, 255}.
|
||||
*/
|
||||
static String bytesToHexString(byte[] bytes) {
|
||||
StringBuilder hexStr = new StringBuilder();
|
||||
|
||||
for (byte b : bytes) {
|
||||
hexStr.append(String.format("%02X", b));
|
||||
}
|
||||
|
||||
return hexStr.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to discover the NAT64 prefix/suffix based on the IPv4 and IPv6
|
||||
* addresses resolved for given {@code host}.
|
||||
*
|
||||
* @param host the host for which the code will try to discover IPv4 and
|
||||
* IPv6 addresses which then will be used to figure out the NAT64 prefix.
|
||||
* @return {@link NAT64AddrInfo} instance if the NAT64 prefix/suffix was
|
||||
* successfully discovered or {@code null} if it failed for any reason.
|
||||
* @throws UnknownHostException thrown by {@link InetAddress#getAllByName}.
|
||||
*/
|
||||
public static NAT64AddrInfo discover(String host)
|
||||
throws UnknownHostException {
|
||||
InetAddress ipv4 = null;
|
||||
InetAddress ipv6 = null;
|
||||
|
||||
for(InetAddress addr : InetAddress.getAllByName(host)) {
|
||||
byte[] bytes = addr.getAddress();
|
||||
|
||||
if (bytes.length == 4) {
|
||||
ipv4 = addr;
|
||||
} else if (bytes.length == 16) {
|
||||
ipv6 = addr;
|
||||
}
|
||||
}
|
||||
|
||||
if (ipv4 != null && ipv6 != null) {
|
||||
return figureOutNAT64AddrInfo(ipv4.getAddress(), ipv6.getAddress());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on IPv4 and IPv6 addresses of the same host, the method will make
|
||||
* an attempt to figure out what are the NAT64 prefix and suffix.
|
||||
*
|
||||
* @param ipv4AddrBytes the IPv4 address of the same host in NAT64 network,
|
||||
* as returned by {@link InetAddress#getAddress()}.
|
||||
* @param ipv6AddrBytes the IPv6 address of the same host in NAT64 network,
|
||||
* as returned by {@link InetAddress#getAddress()}.
|
||||
* @return {@link NAT64AddrInfo} instance which contains the prefix/suffix
|
||||
* of the current NAT64 network or {@code null} if the prefix could not be
|
||||
* found.
|
||||
*/
|
||||
static NAT64AddrInfo figureOutNAT64AddrInfo(
|
||||
byte[] ipv4AddrBytes,
|
||||
byte[] ipv6AddrBytes) {
|
||||
String ipv6Str = bytesToHexString(ipv6AddrBytes);
|
||||
String ipv4Str = bytesToHexString(ipv4AddrBytes);
|
||||
|
||||
// NAT64 address format:
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |PL| 0-------------32--40--48--56--64--72--80--88--96--104---------|
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |32| prefix |v4(32) | u | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |40| prefix |v4(24) | u |(8)| suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |48| prefix |v4(16) | u | (16) | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |56| prefix |(8)| u | v4(24) | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |64| prefix | u | v4(32) | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |96| prefix | v4(32) |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
int prefixLength = 96;
|
||||
int suffixLength = 0;
|
||||
String prefix = null;
|
||||
String suffix = null;
|
||||
|
||||
if (ipv4Str.equalsIgnoreCase(ipv6Str.substring(prefixLength / 4))) {
|
||||
prefix = ipv6Str.substring(0, prefixLength / 4);
|
||||
} else {
|
||||
// Cut out the 'u' octet
|
||||
ipv6Str = ipv6Str.substring(0, 16) + ipv6Str.substring(18);
|
||||
|
||||
for (prefixLength = 64, suffixLength = 6; prefixLength >= 32; ) {
|
||||
if (ipv4Str.equalsIgnoreCase(
|
||||
ipv6Str.substring(
|
||||
prefixLength / 4, prefixLength / 4 + 8))) {
|
||||
prefix = ipv6Str.substring(0, prefixLength / 4);
|
||||
suffix = ipv6Str.substring(ipv6Str.length() - suffixLength);
|
||||
break;
|
||||
}
|
||||
|
||||
prefixLength -= 8;
|
||||
suffixLength += 2;
|
||||
}
|
||||
}
|
||||
|
||||
return prefix != null ? new NAT64AddrInfo(prefix, suffix) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An overload for {@link #hexStringToIPv6String(StringBuilder)}.
|
||||
*
|
||||
* @param hexStr a hex representation of IPv6 address bytes.
|
||||
* @return an IPv6 address string.
|
||||
*/
|
||||
static String hexStringToIPv6String(String hexStr) {
|
||||
return hexStringToIPv6String(new StringBuilder(hexStr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from HEX representation of IPv6 address bytes into IPv6 address
|
||||
* string which includes the ':' signs.
|
||||
*
|
||||
* @param str a hex representation of IPv6 address bytes.
|
||||
* @return eg. FE80:CD00:0000:0CDA:1357:0000:212F:749C
|
||||
*/
|
||||
static String hexStringToIPv6String(StringBuilder str) {
|
||||
for (int i = 32 - 4; i > 0; i -= 4) {
|
||||
str.insert(i, ":");
|
||||
}
|
||||
|
||||
return str.toString().toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an IPv4 address string and returns it's byte array representation.
|
||||
*
|
||||
* @param ipv4Address eg. '192.168.3.23'
|
||||
* @return byte representation of given IPv4 address string.
|
||||
* @throws IllegalArgumentException if the address is not in valid format.
|
||||
*/
|
||||
static byte[] ipv4AddressStringToBytes(String ipv4Address) {
|
||||
InetAddress address;
|
||||
|
||||
try {
|
||||
address = InetAddress.getByName(ipv4Address);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid IP address: " + ipv4Address, e);
|
||||
}
|
||||
|
||||
byte[] bytes = address.getAddress();
|
||||
|
||||
if (bytes.length != 4) {
|
||||
throw new IllegalArgumentException(
|
||||
"Not an IPv4 address: " + ipv4Address);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* The NAT64 prefix added to construct IPv6 from an IPv4 address.
|
||||
*/
|
||||
private final String prefix;
|
||||
|
||||
/**
|
||||
* The NAT64 suffix (if any) used to construct IPv6 from an IPv4 address.
|
||||
*/
|
||||
private final String suffix;
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link NAT64AddrInfo}.
|
||||
*
|
||||
* @param prefix the NAT64 prefix.
|
||||
* @param suffix the NAT64 suffix.
|
||||
*/
|
||||
private NAT64AddrInfo(String prefix, String suffix) {
|
||||
this.prefix = prefix;
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the NAT64 prefix and suffix will create an IPv6 representation
|
||||
* of the given IPv4 address.
|
||||
*
|
||||
* @param ipv4Address eg. '192.34.2.3'
|
||||
* @return IPv6 address string eg. FE80:CD00:0000:0CDA:1357:0000:212F:749C
|
||||
* @throws IllegalArgumentException if given string is not a valid IPv4
|
||||
* address.
|
||||
*/
|
||||
public String getIPv6Address(String ipv4Address) {
|
||||
byte[] ipv4AddressBytes = ipv4AddressStringToBytes(ipv4Address);
|
||||
StringBuilder newIPv6Str = new StringBuilder();
|
||||
|
||||
newIPv6Str.append(prefix);
|
||||
newIPv6Str.append(bytesToHexString(ipv4AddressBytes));
|
||||
|
||||
if (suffix != null) {
|
||||
// Insert the 'u' octet.
|
||||
newIPv6Str.insert(16, "00");
|
||||
newIPv6Str.append(suffix);
|
||||
}
|
||||
|
||||
return hexStringToIPv6String(newIPv6Str);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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.net;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* This module exposes the functionality of creating an IPv6 representation
|
||||
* of IPv4 addresses in NAT64 environment.
|
||||
*
|
||||
* See[1] and [2] for more info on what NAT64 is.
|
||||
* [1]: https://tools.ietf.org/html/rfc6146
|
||||
* [2]: https://tools.ietf.org/html/rfc6052
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private final static String HOST = "nat64.jitsi.net";
|
||||
|
||||
/**
|
||||
* How long is the {@link NAT64AddrInfo} instance valid.
|
||||
*/
|
||||
private final static long INFO_LIFETIME = 60 * 1000;
|
||||
|
||||
/**
|
||||
* The name of this module.
|
||||
*/
|
||||
private final static String MODULE_NAME = "NAT64AddrInfo";
|
||||
|
||||
/**
|
||||
* The {@code Log} tag {@code NAT64AddrInfoModule} is to log messages with.
|
||||
*/
|
||||
private final static String TAG = MODULE_NAME;
|
||||
|
||||
/**
|
||||
* The {@link NAT64AddrInfo} instance which holds NAT64 prefix/suffix.
|
||||
*/
|
||||
private NAT64AddrInfo info;
|
||||
|
||||
/**
|
||||
* When {@link #info} was created.
|
||||
*/
|
||||
private long infoTimestamp;
|
||||
|
||||
/**
|
||||
* Creates new {@link NAT64AddrInfoModule}.
|
||||
*
|
||||
* @param reactContext the react context to be used by the new module
|
||||
* instance.
|
||||
*/
|
||||
public NAT64AddrInfoModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to obtain IPv6 address for given IPv4 address in NAT64 environment.
|
||||
*
|
||||
* @param ipv4Address IPv4 address string.
|
||||
* @param promise a {@link Promise} which will be resolved either with IPv6
|
||||
* address for given IPv4 address or with {@code null} if no
|
||||
* {@link NAT64AddrInfo} was resolved for the current network. Will be
|
||||
* rejected if given {@code ipv4Address} is not a valid IPv4 address.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getIPv6Address(String ipv4Address, final Promise promise) {
|
||||
// Reset if cached for too long.
|
||||
if (System.currentTimeMillis() - infoTimestamp > INFO_LIFETIME) {
|
||||
info = null;
|
||||
}
|
||||
|
||||
if (info == null) {
|
||||
String host = HOST;
|
||||
|
||||
try {
|
||||
info = NAT64AddrInfo.discover(host);
|
||||
} catch (UnknownHostException e) {
|
||||
Log.e(TAG, "NAT64AddrInfo.discover: " + host, e);
|
||||
}
|
||||
infoTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
String result;
|
||||
|
||||
try {
|
||||
result = info == null ? null : info.getIPv6Address(ipv4Address);
|
||||
} catch (IllegalArgumentException exc) {
|
||||
Log.e(TAG, "Failed to get IPv6 address for: " + ipv4Address, exc);
|
||||
|
||||
// We don't want to reject. It's not a big deal if there's no IPv6
|
||||
// address resolved.
|
||||
result = null;
|
||||
}
|
||||
promise.resolve(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return MODULE_NAME;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.net;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link NAT64AddrInfo} class.
|
||||
*/
|
||||
public class NAT64AddrInfoTest {
|
||||
/**
|
||||
* Test case for the 96 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test96Prefix() {
|
||||
testPrefixSuffix(
|
||||
"260777000000000400000000", "", "203.0.113.1", "23.17.23.3");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the 64 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test64Prefix() {
|
||||
String prefix = "1FF2A227B3AAF3D2";
|
||||
String suffix = "BB87C8";
|
||||
|
||||
testPrefixSuffix(prefix, suffix, "48.46.87.34", "23.87.145.4");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the 56 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test56Prefix() {
|
||||
String prefix = "1FF2A227B3AAF3";
|
||||
String suffix = "A2BB87C8";
|
||||
|
||||
testPrefixSuffix(prefix, suffix, "34.72.234.255", "1.235.3.65");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the 48 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test48Prefix() {
|
||||
String prefix = "1FF2A227B3AA";
|
||||
String suffix = "72A2BB87C8";
|
||||
|
||||
testPrefixSuffix(prefix, suffix, "97.54.3.23", "77.49.0.33");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the 40 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test40Prefix() {
|
||||
String prefix = "1FF2A227B3";
|
||||
String suffix = "D972A2BB87C8";
|
||||
|
||||
testPrefixSuffix(prefix, suffix, "10.23.56.121", "97.65.32.21");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the 32 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test32Prefix()
|
||||
throws UnknownHostException {
|
||||
String prefix = "1FF2A227";
|
||||
String suffix = "20D972A2BB87C8";
|
||||
|
||||
testPrefixSuffix(prefix, suffix, "162.63.65.189", "135.222.84.206");
|
||||
}
|
||||
|
||||
private static String buildIPv6Addr(
|
||||
String prefix, String suffix, String ipv4Hex) {
|
||||
String ipv6Str = prefix + ipv4Hex + suffix;
|
||||
|
||||
if (suffix.length() > 0) {
|
||||
ipv6Str = new StringBuilder(ipv6Str).insert(16, "00").toString();
|
||||
}
|
||||
|
||||
return ipv6Str;
|
||||
}
|
||||
|
||||
private void testPrefixSuffix(
|
||||
String prefix, String suffix, String ipv4, String otherIPv4) {
|
||||
byte[] ipv4Bytes = NAT64AddrInfo.ipv4AddressStringToBytes(ipv4);
|
||||
String ipv4String = NAT64AddrInfo.bytesToHexString(ipv4Bytes);
|
||||
String ipv6Str = buildIPv6Addr(prefix, suffix, ipv4String);
|
||||
|
||||
BigInteger ipv6Address = new BigInteger(ipv6Str, 16);
|
||||
|
||||
NAT64AddrInfo nat64AddrInfo
|
||||
= NAT64AddrInfo.figureOutNAT64AddrInfo(
|
||||
ipv4Bytes, ipv6Address.toByteArray());
|
||||
|
||||
assertNotNull("Failed to figure out NAT64 info", nat64AddrInfo);
|
||||
|
||||
String newIPv6 = nat64AddrInfo.getIPv6Address(ipv4);
|
||||
|
||||
assertEquals(
|
||||
NAT64AddrInfo.hexStringToIPv6String(ipv6Address.toString(16)),
|
||||
newIPv6);
|
||||
|
||||
byte[] ipv4Addr2 = NAT64AddrInfo.ipv4AddressStringToBytes(otherIPv4);
|
||||
String ipv4Addr2Hex = NAT64AddrInfo.bytesToHexString(ipv4Addr2);
|
||||
|
||||
newIPv6 = nat64AddrInfo.getIPv6Address(otherIPv4);
|
||||
|
||||
assertEquals(
|
||||
NAT64AddrInfo.hexStringToIPv6String(
|
||||
buildIPv6Addr(prefix, suffix, ipv4Addr2Hex)),
|
||||
newIPv6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidIPv4Format() {
|
||||
testInvalidIPv4Format("256.1.2.3");
|
||||
testInvalidIPv4Format("FE80:CD00:0000:0CDA:1357:0000:212F:749C");
|
||||
}
|
||||
|
||||
private void testInvalidIPv4Format(String ipv4Str) {
|
||||
try {
|
||||
NAT64AddrInfo.ipv4AddressStringToBytes(ipv4Str);
|
||||
fail("Did not throw IllegalArgumentException");
|
||||
} catch (IllegalArgumentException exc) {
|
||||
/* OK */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,13 @@ 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-locale-detector'
|
||||
project(':react-native-locale-detector').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-locale-detector/android')
|
||||
include ':react-native-sound'
|
||||
project(':react-native-sound').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sound/android')
|
||||
include ':react-native-vector-icons'
|
||||
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
||||
include ':react-native-webrtc'
|
||||
project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android')
|
||||
include ':react-native-calendar-events'
|
||||
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
|
||||
|
||||
8
app.js
8
app.js
@@ -8,20 +8,13 @@ import './react/features/base/react/prop-types-polyfill.js';
|
||||
|
||||
import 'jquery';
|
||||
import 'jquery-contextmenu';
|
||||
import 'jquery-ui';
|
||||
import 'jQuery-Impromptu';
|
||||
import 'autosize';
|
||||
|
||||
import 'aui';
|
||||
import 'aui-experimental';
|
||||
import 'aui-css';
|
||||
import 'aui-experimental-css';
|
||||
|
||||
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';
|
||||
|
||||
@@ -47,7 +40,6 @@ window.APP = {
|
||||
|
||||
keyboardshortcut,
|
||||
remoteControl,
|
||||
settings,
|
||||
translation,
|
||||
UI
|
||||
};
|
||||
|
||||
726
conference.js
726
conference.js
File diff suppressed because it is too large
Load Diff
117
config.js
117
config.js
@@ -57,6 +57,9 @@ var config = {
|
||||
// P2P test mode disables automatic switching to P2P when there are 2
|
||||
// participants in the conference.
|
||||
p2pTestMode: false
|
||||
|
||||
// Enables the test specific features consumed by jitsi-meet-torture
|
||||
// testMode: false
|
||||
},
|
||||
|
||||
// Disables ICE/UDP by filtering out local and remote UDP candidates in
|
||||
@@ -94,7 +97,7 @@ var config = {
|
||||
|
||||
// w3c spec-compliant video constraints to use for video capture. Currently
|
||||
// used by browsers that return true from lib-jitsi-meet's
|
||||
// RTCBrowserType#usesNewGumFlow. The constraints are independency from
|
||||
// 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.
|
||||
// constraints: {
|
||||
@@ -150,22 +153,14 @@ var config = {
|
||||
// Required version of Chrome extension
|
||||
desktopSharingChromeMinExtVersion: '0.1',
|
||||
|
||||
// The ID of the jidesha extension for Firefox. If null, we assume that no
|
||||
// extension is required.
|
||||
desktopSharingFirefoxExtId: null,
|
||||
|
||||
// Whether desktop sharing should be disabled on Firefox.
|
||||
desktopSharingFirefoxDisabled: false,
|
||||
|
||||
// The maximum version of Firefox which requires a jidesha extension.
|
||||
// Example: if set to 41, we will require the extension for Firefox versions
|
||||
// up to and including 41. On Firefox 42 and higher, we will run without the
|
||||
// extension.
|
||||
// If set to -1, an extension will be required for all versions of Firefox.
|
||||
desktopSharingFirefoxMaxVersionExtRequired: 51,
|
||||
|
||||
// The URL to the Firefox extension for desktop sharing.
|
||||
desktopSharingFirefoxExtensionURL: null,
|
||||
// Optional desktop sharing frame rate options. Default value: min:5, max:5.
|
||||
// desktopSharingFrameRate: {
|
||||
// min: 5,
|
||||
// max: 5
|
||||
// },
|
||||
|
||||
// Try to start calls with screen-sharing instead of camera video.
|
||||
// startScreenSharing: false,
|
||||
@@ -186,6 +181,23 @@ var config = {
|
||||
// Disables or enables RTX (RFC 4588) (defaults to false).
|
||||
// disableRtx: false,
|
||||
|
||||
// Disables or enables TCC (the default is in Jicofo and set to true)
|
||||
// (draft-holmer-rmcat-transport-wide-cc-extensions-01). This setting
|
||||
// affects congestion control, it practically enables send-side bandwidth
|
||||
// estimations.
|
||||
// enableTcc: true,
|
||||
|
||||
// Disables or enables REMB (the default is in Jicofo and set to false)
|
||||
// (draft-alvestrand-rmcat-remb-03). This setting affects congestion
|
||||
// control, it practically enables recv-side bandwidth estimations. When
|
||||
// both TCC and REMB are enabled, TCC takes precedence. When both are
|
||||
// disabled, then bandwidth estimations are disabled.
|
||||
// enableRemb: false,
|
||||
|
||||
// Defines the minimum number of participants to start a call (the default
|
||||
// is set in Jicofo and set to 2).
|
||||
// minParticipants: 2,
|
||||
|
||||
// Use XEP-0215 to fetch STUN and TURN servers.
|
||||
// useStunTurn: true,
|
||||
|
||||
@@ -219,8 +231,6 @@ var config = {
|
||||
// Disable hiding of remote thumbnails when in a 1-on-1 conference call.
|
||||
// disable1On1Mode: false,
|
||||
|
||||
|
||||
// The minumum value a video's height (or width, whichever is smaller) needs
|
||||
// The minimum value a video's height (or width, whichever is smaller) needs
|
||||
// to be in order to be considered high-definition.
|
||||
minHDHeight: 540,
|
||||
@@ -241,8 +251,11 @@ var config = {
|
||||
// Stats
|
||||
//
|
||||
|
||||
// Whether to enable stats collection or not.
|
||||
// disableStats: false,
|
||||
// Whether to enable stats collection or not in the TraceablePeerConnection.
|
||||
// This can be useful for debugging purposes (post-processing/analysis of
|
||||
// the webrtc stats) as it is done in the jitsi-meet-torture bandwidth
|
||||
// estimation tests.
|
||||
// gatherStats: false,
|
||||
|
||||
// To enable sending statistics to callstats.io you must provide the
|
||||
// Application ID and Secret.
|
||||
@@ -288,6 +301,14 @@ var config = {
|
||||
{ urls: 'stun:stun2.l.google.com:19302' }
|
||||
],
|
||||
|
||||
// Sets the ICE transport policy for the p2p connection. At the time
|
||||
// of this writing the list of possible values are 'all' and 'relay',
|
||||
// but that is subject to change in the future. The enum is defined in
|
||||
// the WebRTC standard:
|
||||
// https://www.w3.org/TR/webrtc/#rtcicetransportpolicy-enum.
|
||||
// If not set, the effective value is 'all'.
|
||||
// iceTransportPolicy: 'all',
|
||||
|
||||
// If set to true, it will prefer to use H.264 for P2P calls (if H.264
|
||||
// is supported).
|
||||
preferH264: true
|
||||
@@ -301,16 +322,72 @@ var config = {
|
||||
// backToP2PDelay: 5
|
||||
},
|
||||
|
||||
// A list of scripts to load as lib-jitsi-meet "analytics handlers".
|
||||
// analyticsScriptUrls: [
|
||||
// "libs/analytics-ga.js", // google-analytics
|
||||
// "https://example.com/my-custom-analytics.js"
|
||||
// ],
|
||||
|
||||
// The Google Analytics Tracking ID
|
||||
// googleAnalyticsTrackingId = 'your-tracking-id-here-UA-123456-1',
|
||||
|
||||
// Information about the jitsi-meet instance we are connecting to, including
|
||||
// the user region as seen by the server.
|
||||
//
|
||||
|
||||
deploymentInfo: {
|
||||
// shard: "shard1",
|
||||
// region: "europe",
|
||||
// userRegion: "asia"
|
||||
}
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
alwaysVisibleToolbar
|
||||
autoRecord
|
||||
autoRecordToken
|
||||
debug
|
||||
debugAudioLevels
|
||||
deploymentInfo
|
||||
dialInConfCodeUrl
|
||||
dialInNumbersUrl
|
||||
dialOutAuthUrl
|
||||
dialOutCodesUrl
|
||||
disableRemoteControl
|
||||
displayJids
|
||||
enableLocalVideoFlip
|
||||
etherpad_base
|
||||
externalConnectUrl
|
||||
firefox_fake_device
|
||||
googleApiApplicationClientID
|
||||
iAmRecorder
|
||||
iAmSipGateway
|
||||
peopleSearchQueryTypes
|
||||
peopleSearchUrl
|
||||
requireDisplayName
|
||||
tokenAuthUrl
|
||||
*/
|
||||
|
||||
// List of undocumented settings used in lib-jitsi-meet
|
||||
/**
|
||||
_peerConnStatusOutOfLastNTimeout
|
||||
_peerConnStatusRtcMuteTimeout
|
||||
abTesting
|
||||
avgRtpStatsN
|
||||
callStatsConfIDNamespace
|
||||
callStatsCustomScriptUrl
|
||||
desktopSharingSources
|
||||
disableAEC
|
||||
disableAGC
|
||||
disableAP
|
||||
disableHPF
|
||||
disableNS
|
||||
enableLipSync
|
||||
enableTalkWhileMuted
|
||||
forceJVB121Ratio
|
||||
hiddenDomain
|
||||
ignoreStartMuted
|
||||
nick
|
||||
startBitrate
|
||||
*/
|
||||
};
|
||||
|
||||
/* eslint-enable no-unused-vars, no-var */
|
||||
|
||||
@@ -94,12 +94,20 @@ function connect(id, password, roomName) {
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailedHandler);
|
||||
|
||||
/* eslint-disable max-params */
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function connectionFailedHandler(error, message, credentials) {
|
||||
function connectionFailedHandler(error, message, credentials, details) {
|
||||
/* eslint-enable max-params */
|
||||
APP.store.dispatch(
|
||||
connectionFailed(connection, error, message, credentials));
|
||||
connectionFailed(
|
||||
connection, {
|
||||
credentials,
|
||||
details,
|
||||
message,
|
||||
name: error
|
||||
}));
|
||||
|
||||
if (isFatalJitsiConnectionError(error)) {
|
||||
connection.removeEventListener(
|
||||
|
||||
@@ -16,14 +16,17 @@ import parseURLParams from '../react/features/base/config/parseURLParams';
|
||||
*/
|
||||
|
||||
if (typeof createConnectionExternally === 'function') {
|
||||
// URL params have higher proirity than config params.
|
||||
// URL params have higher priority than config params.
|
||||
let url
|
||||
= parseURLParams(window.location, true, 'hash')[
|
||||
'config.externalConnectUrl']
|
||||
|| config.externalConnectUrl;
|
||||
const isRecorder
|
||||
= parseURLParams(window.location, true, 'hash')['config.iAmRecorder'];
|
||||
|
||||
let roomName;
|
||||
|
||||
if (url && (roomName = getRoomName())) {
|
||||
if (url && (roomName = getRoomName()) && !isRecorder) {
|
||||
url += `?room=${roomName}`;
|
||||
|
||||
const token = parseURLParams(window.location, true, 'search').jwt;
|
||||
|
||||
@@ -2,42 +2,6 @@
|
||||
* Project animations
|
||||
**/
|
||||
|
||||
/**
|
||||
* Slide in animation for extended toolbar.
|
||||
*/
|
||||
@include keyframes(slideInX) {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(0%); }
|
||||
}
|
||||
|
||||
@include keyframes(slideOutX) {
|
||||
0% { transform: translateX(0%); }
|
||||
100% { transform: translateX(-100%); }
|
||||
}
|
||||
|
||||
@include keyframes(slideInExtX) {
|
||||
0% { transform: translateX(-500%); }
|
||||
100% { transform: translateX(0%); }
|
||||
}
|
||||
|
||||
@include keyframes(slideOutExtX) {
|
||||
0% { transform: translateX(0%); }
|
||||
100% { transform: translateX(-500%); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide in / out animation for main toolbar.
|
||||
*/
|
||||
|
||||
@include keyframes(slideInY) {
|
||||
100% { transform: translateY(0%); }
|
||||
}
|
||||
|
||||
@include keyframes(slideOutY) {
|
||||
0% { transform: translateY(0%); }
|
||||
100% { transform: translateY(-100%); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide in animation for extended toolbar (inner) panel.
|
||||
*/
|
||||
@@ -53,17 +17,3 @@
|
||||
from { left: 0; }
|
||||
to { left: -$sidebarWidth; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide in animation for extended toolbar container
|
||||
**/
|
||||
|
||||
@include keyframes(slideOutExtContainer) {
|
||||
from { width: $sidebarWidth; }
|
||||
to { width: 0; }
|
||||
}
|
||||
|
||||
@include keyframes(slideInExtContainer) {
|
||||
from { width: 0; }
|
||||
to { width: $sidebarWidth; }
|
||||
}
|
||||
229
css/_aui_reset.scss
Normal file
229
css/_aui_reset.scss
Normal file
@@ -0,0 +1,229 @@
|
||||
/* Fonts and line heights */
|
||||
/**
|
||||
* RESET
|
||||
*/
|
||||
html,
|
||||
body,
|
||||
p,
|
||||
div,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
img,
|
||||
pre,
|
||||
form,
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul,
|
||||
ol,
|
||||
dl {
|
||||
margin: 0;
|
||||
}
|
||||
img,
|
||||
fieldset {
|
||||
border: 0;
|
||||
}
|
||||
@-moz-document url-prefix() {
|
||||
img {
|
||||
font-size: 0;
|
||||
}
|
||||
img:-moz-broken {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
/* https://github.com/necolas/normalize.css */
|
||||
/* Customised to remove styles for unsupported browsers */
|
||||
details,
|
||||
main,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
input[type="button"],
|
||||
input[type="submit"],
|
||||
input[type="reset"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
/**
|
||||
* TYPOGRAPHY - 14px base font size, agnostic font stack
|
||||
*/
|
||||
body {
|
||||
color: #333;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.42857142857143;
|
||||
}
|
||||
/* International Font Stacks*/
|
||||
[lang|=en] {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
[lang|=ja] {
|
||||
font-family: "Hiragino Kaku Gothic Pro", "ヒラギノ角ゴ Pro W3", "メイリオ", Meiryo, "MS Pゴシック", Verdana, Arial, sans-serif;
|
||||
}
|
||||
/* Default margins */
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
dl,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
blockquote,
|
||||
pre {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
/* No top margin to interfere with box padding */
|
||||
p:first-child,
|
||||
ul:first-child,
|
||||
ol:first-child,
|
||||
dl:first-child,
|
||||
h1:first-child,
|
||||
h2:first-child,
|
||||
h3:first-child,
|
||||
h4:first-child,
|
||||
h5:first-child,
|
||||
h6:first-child,
|
||||
blockquote:first-child,
|
||||
pre:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
/* Headings: desired line height in px / font size = unitless line height */
|
||||
h1 {
|
||||
color: #333;
|
||||
font-size: 32px;
|
||||
font-weight: normal;
|
||||
line-height: 1.25;
|
||||
text-transform: none;
|
||||
margin: 30px 0 0 0;
|
||||
}
|
||||
h2 {
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
line-height: 1.25;
|
||||
text-transform: none;
|
||||
margin: 30px 0 0 0;
|
||||
}
|
||||
h3 {
|
||||
color: #333;
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
text-transform: none;
|
||||
margin: 30px 0 0 0;
|
||||
}
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 1.25;
|
||||
text-transform: none;
|
||||
margin: 20px 0 0 0;
|
||||
}
|
||||
h5 {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 1.42857143;
|
||||
text-transform: none;
|
||||
margin: 20px 0 0 0;
|
||||
}
|
||||
h6 {
|
||||
color: #707070;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1.66666667;
|
||||
text-transform: uppercase;
|
||||
margin: 20px 0 0 0;
|
||||
}
|
||||
h1:first-child,
|
||||
h2:first-child,
|
||||
h3:first-child,
|
||||
h4:first-child,
|
||||
h5:first-child,
|
||||
h6:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
/* Nice styles for using subheadings */
|
||||
h1 + h2,
|
||||
h2 + h3,
|
||||
h3 + h4,
|
||||
h4 + h5,
|
||||
h5 + h6 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* Other typographical elements */
|
||||
small {
|
||||
color: #707070;
|
||||
font-size: 12px;
|
||||
line-height: 1.33333333333333;
|
||||
}
|
||||
code,
|
||||
kbd {
|
||||
font-family: monospace;
|
||||
}
|
||||
var,
|
||||
address,
|
||||
dfn,
|
||||
cite {
|
||||
font-style: italic;
|
||||
}
|
||||
cite:before {
|
||||
content: "\2014 \2009";
|
||||
}
|
||||
blockquote {
|
||||
border-left: 1px solid #ccc;
|
||||
color: #707070;
|
||||
margin-left: 19px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
blockquote > cite {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
q {
|
||||
color: #707070;
|
||||
}
|
||||
q:before {
|
||||
content: open-quote;
|
||||
}
|
||||
q:after {
|
||||
content: close-quote;
|
||||
}
|
||||
abbr {
|
||||
border-bottom: 1px #707070 dotted;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3572b0;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:focus,
|
||||
a:hover,
|
||||
a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -3,11 +3,17 @@
|
||||
* none is applied. Other browsers already support selecting within inputs while
|
||||
* user-select is none. As such, disallow user-select except on inputs.
|
||||
*/
|
||||
*:not(input) {
|
||||
* {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
@@ -22,6 +28,16 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AtlasKitThemeProvider sets a background color on an app-wrapping div, thereby
|
||||
* preventing transparency in filmstrip-only mode. The selector chosen to
|
||||
* override this behavior is specific to where the AtlasKitThemeProvider might
|
||||
* be placed within the app hierarchy.
|
||||
*/
|
||||
.filmstrip-only #react > .ckAJgx {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -50,7 +66,6 @@ input[type="reset"], input[type="submit"] {
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
resize: none;
|
||||
line-height: 1.5em;
|
||||
@@ -123,6 +138,9 @@ form {
|
||||
* Dialogs fade
|
||||
*/
|
||||
.aui-blanket {
|
||||
background: #000;
|
||||
transition: opacity 0.2s, visibility 0.2s;
|
||||
transition-delay: 0.1s;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,36 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
color: red;
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
#contacts_container {
|
||||
cursor: default;
|
||||
|
||||
#contacts {
|
||||
font-size: 12px;
|
||||
bottom: 0px;
|
||||
margin: 0;
|
||||
margin-top: 12px;
|
||||
padding: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-security,
|
||||
.icon-security-locked {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
#contacts {
|
||||
.contact-list-item {
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
color: $baseLight;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
height: 36px;
|
||||
list-style-type: none;
|
||||
padding: 0 10%;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background: $toolbarSelectBackground;
|
||||
}
|
||||
|
||||
.contact-list-item-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
margin: 0 8px 0 4px;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.avatar {
|
||||
padding: 0px;
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
font-size: 22pt;
|
||||
border-radius: 20px;
|
||||
max-height: 30px;
|
||||
max-width: 30px;
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* The dialog content element.
|
||||
*/
|
||||
.dial-out-content {
|
||||
margin-top: 5px;
|
||||
|
||||
/**
|
||||
* Wrap the contents in flex so items can be aligned on the same line.
|
||||
*/
|
||||
.form-control {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/**
|
||||
* The style of the flag icon.
|
||||
*/
|
||||
.dial-out-flag-icon {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
|
||||
/**
|
||||
* The style of the dial code element.
|
||||
*/
|
||||
.dial-out-code {
|
||||
margin-bottom: 0;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
/**
|
||||
* The dial-out dialog error element.
|
||||
*/
|
||||
.dial-out-error {
|
||||
color: $errorColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The style of the dial input element.
|
||||
*/
|
||||
.dial-out-input {
|
||||
display: inline-block;
|
||||
flex: 1;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styling the default dropdown inside the dial-out-content.
|
||||
*/
|
||||
.dropdown {
|
||||
position: relative;
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styling the default form-control inside the dial-out-content.
|
||||
*/
|
||||
.form-control {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
padding-left: 16px;
|
||||
|
||||
&:read-only {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-trigger-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,20 @@
|
||||
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;
|
||||
@@ -64,39 +78,8 @@
|
||||
* The local video identifier.
|
||||
*/
|
||||
&#filmstripLocalVideo {
|
||||
bottom: 32px;
|
||||
align-self: flex-end;
|
||||
display: block;
|
||||
|
||||
/**
|
||||
* The invite button style.
|
||||
*/
|
||||
.filmstrip__invite {
|
||||
padding-bottom: 5px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/**
|
||||
* The invite button group style.
|
||||
* TOFIX: use AtlasKit.ButtonGroup if it starts supporting different
|
||||
* flex grow options for the buttons.
|
||||
*/
|
||||
.invite-button-group {
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
& button {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
& > * {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
.flag-icon-background {
|
||||
background-size: contain;
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.flag-icon {
|
||||
background-size: contain;
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 1.33333333em;
|
||||
line-height: 1em;
|
||||
}
|
||||
.flag-icon:before {
|
||||
content: "\00a0";
|
||||
}
|
||||
.flag-icon-au {
|
||||
background-image: url(../images/countries/au.svg);
|
||||
}
|
||||
.flag-icon-ca {
|
||||
background-image: url(../images/countries/ca.svg);
|
||||
}
|
||||
.flag-icon-de {
|
||||
background-image: url(../images/countries/de.svg);
|
||||
}
|
||||
.flag-icon-gb {
|
||||
background-image: url(../images/countries/gb.svg);
|
||||
}
|
||||
.flag-icon-fr {
|
||||
background-image: url(../images/countries/fr.svg);
|
||||
}
|
||||
.flag-icon-us {
|
||||
background-image: url(../images/countries/us.svg);
|
||||
}
|
||||
@@ -24,7 +24,30 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-arrow_back:before {
|
||||
content: "\e5c4";
|
||||
}
|
||||
.icon-event_note:before {
|
||||
content: "\e616";
|
||||
}
|
||||
.icon-menu:before {
|
||||
content: "\e5d2";
|
||||
}
|
||||
.icon-navigate_before:before {
|
||||
content: "\e408";
|
||||
}
|
||||
.icon-navigate_next:before {
|
||||
content: "\e409";
|
||||
}
|
||||
.icon-public:before {
|
||||
content: "\e80b";
|
||||
}
|
||||
.icon-restore:before {
|
||||
content: "\e8b3";
|
||||
}
|
||||
.icon-timer:before {
|
||||
content: "\e425";
|
||||
}
|
||||
.icon-thumb-menu:before {
|
||||
content: "\e5d4";
|
||||
}
|
||||
@@ -157,3 +180,27 @@
|
||||
.icon-gsm-bars:before {
|
||||
content: "\e926";
|
||||
}
|
||||
.icon-open_in_new:before {
|
||||
content: "\e89e";
|
||||
}
|
||||
.icon-AUD:before {
|
||||
content: "\e900";
|
||||
}
|
||||
.icon-HD:before {
|
||||
content: "\e927";
|
||||
}
|
||||
.icon-LD:before {
|
||||
content: "\e928";
|
||||
}
|
||||
.icon-SD:before {
|
||||
content: "\e929";
|
||||
}
|
||||
.icon-camera-take-picture:before {
|
||||
content: "\e92a";
|
||||
}
|
||||
.icon-rec:before {
|
||||
content: "\e92b";
|
||||
}
|
||||
.icon-live:before {
|
||||
content: "\e92c";
|
||||
}
|
||||
|
||||
@@ -56,9 +56,6 @@
|
||||
margin-bottom: 0px;
|
||||
width: 100%;
|
||||
border-radius: 0px;
|
||||
> .aui-progress-indicator-value {
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__title {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
**/
|
||||
|
||||
.popupmenu {
|
||||
min-width: 75px;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -1,3 +1,81 @@
|
||||
.recordingSpinner {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.live-stream-dialog {
|
||||
/**
|
||||
* Set font-size to be consistent with Atlaskit FieldText.
|
||||
*/
|
||||
font-size: 14px;
|
||||
|
||||
.broadcast-dropdown,
|
||||
.broadcast-dropdown-trigger {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.form-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.live-stream-cta {
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.google-api {
|
||||
margin-top: 10px;
|
||||
min-height: 36px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Google sign in button must follow Google's design guidelines.
|
||||
* See: https://developers.google.com/identity/branding-guidelines
|
||||
*/
|
||||
.google-sign-in {
|
||||
background-color: #4285f4;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-family: Roboto, arial, sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 1px;
|
||||
|
||||
.google-cta {
|
||||
color: white;
|
||||
display: inline-block;
|
||||
/**
|
||||
* Hack the line height for vertical centering of text.
|
||||
*/
|
||||
line-height: 32px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.google-logo {
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.google-panel {
|
||||
align-items: center;
|
||||
border-bottom: 2px solid rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.stream-key-form {
|
||||
.helper-link {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,37 @@
|
||||
/**
|
||||
* Toolbar side panel main container element.
|
||||
*/
|
||||
.use-new-toolbox #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%;
|
||||
@@ -22,8 +53,10 @@
|
||||
/**
|
||||
* Form elements and blocks.
|
||||
*/
|
||||
input, select, a,
|
||||
.sideToolbarBlock, .form-control, .button-control {
|
||||
input,
|
||||
a,
|
||||
.sideToolbarBlock,
|
||||
.form-control {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
margin-left: 10%;
|
||||
@@ -34,23 +67,15 @@
|
||||
* Specify styling of elements inside a block.
|
||||
*/
|
||||
.sideToolbarBlock {
|
||||
input, button, a, select {
|
||||
input, a {
|
||||
margin-left: 0;
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
display: inline;
|
||||
width: auto !important;
|
||||
> label {
|
||||
margin-top: 5px;
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner container, for example contact list, settings or profile.
|
||||
* Inner container, for example settings or profile.
|
||||
*/
|
||||
.sideToolbarContainer__inner {
|
||||
display: none;
|
||||
@@ -80,42 +105,35 @@
|
||||
font-size: $toolbarTitleFontSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtitle specific properties.
|
||||
*/
|
||||
div.subTitle {
|
||||
color: $defaultSideBarFontColor !important;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
margin-left: 10%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/**
|
||||
* First element after a title.
|
||||
*/
|
||||
.first {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buttons in the side toolbar container.
|
||||
*/
|
||||
.button-control {
|
||||
margin: 9px 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#device_settings {
|
||||
width : auto !important;
|
||||
text-align: center;
|
||||
}
|
||||
.settings-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 10%;
|
||||
padding-right: 10%;
|
||||
|
||||
#deviceOptionsWrapper {
|
||||
button {
|
||||
float: none;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,350 +19,282 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.toolbar-container {
|
||||
display: block;
|
||||
left:0;
|
||||
min-height: 100px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right:0;
|
||||
text-align: center;
|
||||
top:0;
|
||||
z-index: $toolbarZ;
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common toolbar styles.
|
||||
* TODO: when the old filmstrip has been removed, remove the "new-" prefix.
|
||||
*/
|
||||
.toolbar {
|
||||
height: 100%;
|
||||
pointer-events: auto;
|
||||
position: relative;
|
||||
.new-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
bottom: calc((#{$newToolbarSize} * 2) * -1);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 8px;
|
||||
position: absolute;
|
||||
transition: bottom .3s ease-in;
|
||||
width: 100%;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
/**
|
||||
* Ensure nested elements that don't have a button class, maybe because they
|
||||
* are wrapped in a React Element, still display in a line.
|
||||
*/
|
||||
> div {
|
||||
display: inline-block;
|
||||
&.visible {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always on top overrides.
|
||||
*/
|
||||
&.always-on-top {
|
||||
/**
|
||||
* Toolbar button styles for always on top.
|
||||
*/
|
||||
> .button {
|
||||
font-size: $alwaysOnTopToolbarFontSize;
|
||||
height: $alwaysOnTopToolbarSize;
|
||||
line-height: $alwaysOnTopToolbarSize;
|
||||
width: $alwaysOnTopToolbarSize;
|
||||
}
|
||||
&.no-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar button styles.
|
||||
*/
|
||||
.button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
z-index: $zindex1;
|
||||
display: inline-block;
|
||||
font-size: $toolbarFontSize;
|
||||
height: $defaultToolbarSize;
|
||||
line-height: $defaultToolbarSize;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
top:0px;
|
||||
vertical-align: middle;
|
||||
width: $defaultToolbarSize;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover, &:active {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&_hangup, &_hangup:hover {
|
||||
color: $hangupColor;
|
||||
font-size: $hangupFontSize;
|
||||
}
|
||||
|
||||
&:not(.toggled) {
|
||||
&:hover, &:active {
|
||||
// sum opacity with background layer should give us 0.8
|
||||
background: $toolbarSelectBackground;
|
||||
}
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background: $toolbarToggleBackground;
|
||||
|
||||
&.icon-camera {
|
||||
@extend .icon-camera-disabled;
|
||||
}
|
||||
|
||||
&.icon-full-screen {
|
||||
@extend .icon-exit-full-screen;
|
||||
}
|
||||
|
||||
&.icon-microphone {
|
||||
@extend .icon-mic-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
|
||||
&:hover, &:active, &.selected {
|
||||
background: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary toolbar styles.
|
||||
*/
|
||||
&_primary {
|
||||
background-color: $toolbarBackground;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 30px;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: $defaultToolbarSize;
|
||||
border-radius: 3px;
|
||||
opacity: 0;
|
||||
|
||||
&.always-on-top {
|
||||
height: $alwaysOnTopToolbarSize;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
@include transform(translateX(-50%));
|
||||
|
||||
> div:first-child .button {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-top-left-radius: 3px;
|
||||
}
|
||||
> div:last-child .button {
|
||||
border-bottom-right-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&_primary a.button:last-child::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Secondary toolbar styles.
|
||||
*/
|
||||
&_secondary {
|
||||
background-color: $secondaryToolbarBg;
|
||||
position: absolute;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
.button-group-center,
|
||||
.button-group-left,
|
||||
.button-group-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.button-group-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-group-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
i {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: inherit;
|
||||
height: 100%;
|
||||
justify-content: flex-start;
|
||||
left: 0;
|
||||
padding-top: 24px;
|
||||
top: 0;
|
||||
transform: translateX(-100%);
|
||||
width: $defaultToolbarSize;
|
||||
-webkit-transform: translateX(-100%);
|
||||
line-height: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: $secToolbarFontSize;
|
||||
height: $secToolbarLineHeight;
|
||||
line-height: $secToolbarLineHeight;
|
||||
}
|
||||
i:hover {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
.button.toggled:not(.icon-raised-hand):not(.button-active) {
|
||||
background: $secondaryToolbarBg;
|
||||
i.toggled {
|
||||
background: $newToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
i.toggled:hover {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
i.disabled {
|
||||
cursor: initial
|
||||
}
|
||||
|
||||
i.disabled:hover {
|
||||
background-color: initial;
|
||||
}
|
||||
|
||||
.icon-hangup {
|
||||
color: $hangupColor;
|
||||
font-size: $newToolbarHangupFontSize;
|
||||
}
|
||||
|
||||
.overflow-menu {
|
||||
font-size: 1.2em;
|
||||
list-style-type: none;
|
||||
/**
|
||||
* Undo atlaskit padding by reducing margins.
|
||||
*/
|
||||
margin: -15px -24px;
|
||||
padding: 4px 0;
|
||||
|
||||
.overflow-menu-item {
|
||||
align-items: center;
|
||||
color: #B8C7E0;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
height: 22px;
|
||||
padding: 5px 12px;
|
||||
|
||||
&:hover {
|
||||
background: #313D52;
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
|
||||
&:hover, &:active, &.selected {
|
||||
background: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.beta-tag {
|
||||
background: #B8C7E0;
|
||||
border-radius: 2px;
|
||||
color: $newToolbarBackgroundColor;
|
||||
font-family: -apple-system, BlinkMacSystemFont, $baseFontFamily;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.overflow-menu-item-icon {
|
||||
margin-right: 10px;
|
||||
|
||||
i {
|
||||
display: inline;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background-color: initial;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-text {
|
||||
max-width: 150px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Styles the toolbar in filmstrip-only mode.
|
||||
*/
|
||||
&_filmstrip-only {
|
||||
background-color: $toolbarBackground;
|
||||
border-radius: 3px;
|
||||
.toolbox-button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: auto;
|
||||
width: $defaultFilmStripOnlyToolbarSize;
|
||||
line-height: $newToolbarSize;
|
||||
margin: 0 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
height: 37px;
|
||||
line-height: 37px !important;
|
||||
width: 37px;
|
||||
}
|
||||
.toolbar-button-with-badge {
|
||||
position: relative;
|
||||
|
||||
.button-popover-message {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.toolbar-button-wrapper:first-child .button {
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
.toolbar-button-wrapper:last-child .button {
|
||||
border-bottom-right-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
.badge-round {
|
||||
bottom: -5px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
min-width: 20px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar specific round badge.
|
||||
*/
|
||||
.badge-round {
|
||||
bottom: 9px;
|
||||
position: absolute;
|
||||
right: 9px;
|
||||
.toolbox-button-wth-dialog {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.toolbox-icon {
|
||||
height: $newToolbarSize;
|
||||
font-size: 24px;
|
||||
width: $newToolbarSize;
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip-only {
|
||||
.toolbox,
|
||||
.toolbox-toolbars {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
.always-on-top-toolbox,
|
||||
.filmstrip-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
i.toggled {
|
||||
background: $newToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
i.toggled:hover:not(.disabled) {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
.icon-hangup {
|
||||
color: $hangupColor;
|
||||
}
|
||||
|
||||
.toolbox-button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.always-on-top-toolbox {
|
||||
flex-direction: row;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
transform: translateX(-50%);
|
||||
z-index: $toolbarZ;
|
||||
|
||||
i {
|
||||
font-size: $newToolbarFontSize;
|
||||
height: $newToolbarSize;
|
||||
line-height: $newToolbarSize;
|
||||
width: $newToolbarSize;
|
||||
}
|
||||
|
||||
.icon-hangup {
|
||||
font-size: $newToolbarHangupFontSize;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
.toolbox-button:first-child i {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
|
||||
.toolbox-button:last-child i {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.subject {
|
||||
background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
box-shadow: 0 0 2px #000000, 0 0 10px #000000;
|
||||
margin-left: 40%;
|
||||
margin-right: 40%;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: auto;
|
||||
z-index: $zindex3;
|
||||
|
||||
&.subject_slide-in {
|
||||
top: 80px;
|
||||
@include transition(top .3s ease-in);
|
||||
.filmstrip-toolbox {
|
||||
i {
|
||||
font-size: 1.9em;
|
||||
height: 37px;
|
||||
line-height: 37px;
|
||||
width: 37px;
|
||||
}
|
||||
|
||||
&.subject_slide-out {
|
||||
top: 0;
|
||||
@include transition(top .3s ease-out);
|
||||
.toolbox-button:first-child i {
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
.toolbox-button:last-child i {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
#toolbar_button_profile {
|
||||
height: $toolbarAvatarSize + 2*$toolbarAvatarPadding;
|
||||
}
|
||||
|
||||
a.button>#avatar {
|
||||
border-radius: 50%;
|
||||
padding-bottom: $toolbarAvatarPadding;
|
||||
padding-top: $toolbarAvatarPadding;
|
||||
width: $toolbarAvatarSize;
|
||||
}
|
||||
|
||||
#feedbackButton {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* START of slide in animation for extended toolbar.
|
||||
*/
|
||||
@include keyframes(slideInX) {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(0%); }
|
||||
}
|
||||
|
||||
.slideInX {
|
||||
@include animation('slideInX .5s forwards');
|
||||
}
|
||||
|
||||
@include keyframes(slideOutX) {
|
||||
0% { transform: translateX(0%); }
|
||||
100% { transform: translateX(-100%); }
|
||||
}
|
||||
|
||||
.slideOutX {
|
||||
@include animation('slideOutX .5s forwards');
|
||||
}
|
||||
|
||||
@include keyframes(slideInExtX) {
|
||||
0% { transform: translateX(-500%); }
|
||||
100% { transform: translateX(0%); }
|
||||
}
|
||||
|
||||
.slideInExtX {
|
||||
@include animation('slideInExtX .5s forwards');
|
||||
}
|
||||
|
||||
@include keyframes(slideOutExtX) {
|
||||
0% { transform: translateX(0%); }
|
||||
100% { transform: translateX(-500%); }
|
||||
}
|
||||
|
||||
.slideOutExtX {
|
||||
@include animation('slideOutExtX .5s forwards');
|
||||
}
|
||||
|
||||
/**
|
||||
* END of slide out animation for extended toolbar.
|
||||
*/
|
||||
|
||||
/**
|
||||
* START of slide in / out animation for main toolbar.
|
||||
*/
|
||||
@include keyframes(slideInY) {
|
||||
100% { transform: translateY(0%); }
|
||||
}
|
||||
|
||||
.slideInY {
|
||||
@include animation('slideInY .5s forwards');
|
||||
}
|
||||
|
||||
@include keyframes(slideOutY) {
|
||||
0% { transform: translateY(0%); }
|
||||
100% { transform: translateY(-100%); }
|
||||
}
|
||||
|
||||
.slideOutY {
|
||||
@include animation('slideOutY .5s forwards');
|
||||
}
|
||||
/**
|
||||
* END of slide in / out animation for main toolbar.
|
||||
*/
|
||||
|
||||
/**
|
||||
* START of slide in animation for extended toolbar panel.
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
.flip-x {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides an element.
|
||||
*/
|
||||
@@ -5,6 +13,10 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an element.
|
||||
*/
|
||||
@@ -35,4 +47,4 @@
|
||||
display: -ms-flexbox !important;
|
||||
display: -webkit-flex !important;
|
||||
display: flex !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,24 +32,16 @@ $defaultDarkColor: #2b3d5c;
|
||||
/**
|
||||
* Toolbar
|
||||
*/
|
||||
$alwaysOnTopToolbarFontSize: 1em;
|
||||
$alwaysOnTopToolbarSize: 30px;
|
||||
$defaultToolbarSize: 50px;
|
||||
$defaultFilmStripOnlyToolbarSize: 37px;
|
||||
$secToolbarFontSize: 1.9em;
|
||||
$secToolbarLineHeight: 45px;
|
||||
$toolbarAvatarPadding: 10px;
|
||||
$toolbarAvatarSize: 40px;
|
||||
$toolbarFontSize: 1.9em;
|
||||
$newToolbarBackgroundColor: rgba(22, 38, 55, 0.8);
|
||||
$newToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);
|
||||
$newToolbarButtonToggleColor: rgba(14, 20, 35, 1);
|
||||
$newToolbarFontSize: 24px;
|
||||
$newToolbarHangupFontSize: 32px;
|
||||
$newToolbarSize: 40px;
|
||||
$newToolbarSizeWithPadding: calc(#{$newToolbarSize} + 24px);
|
||||
$toolbarTitleFontSize: 19px;
|
||||
|
||||
/**
|
||||
* Main controls
|
||||
* TODO: looks like we don't use it
|
||||
*/
|
||||
$inputSemiBackground: rgba(132, 132, 132, .8);
|
||||
$inputLightBackground: #EBEBEB;
|
||||
|
||||
/**
|
||||
* Video layout
|
||||
*/
|
||||
@@ -74,10 +66,6 @@ $feedbackInputBg: #fff;
|
||||
$feedbackTextColor: #000;
|
||||
$feedbackInputTextColor: #333;
|
||||
$feedbackInputPlaceholderColor: #777;
|
||||
$rateStarLabelColor: #333;
|
||||
$rateStarDefault: #ccc;
|
||||
$rateStarActivity: #165ecc;
|
||||
$rateStarSize: 34px;
|
||||
|
||||
/**
|
||||
* Modals
|
||||
@@ -138,8 +126,8 @@ $formPadding: 16px;
|
||||
/**
|
||||
* Unsupported browser
|
||||
*/
|
||||
$primaryUnsupportedBrowserButtonBgColor: #17a0db;
|
||||
$unsupportedBrowserButtonBgColor: #ff9a00;
|
||||
$primaryUnsupportedBrowserButtonBgColor: #0052CC;
|
||||
$unsupportedBrowserButtonBgColor: rgba(9, 30, 66, 0.04);
|
||||
$unsupportedBrowserTextColor: #4a4a4a;
|
||||
$unsupportedBrowserTextSmallFontSize: 17px;
|
||||
$unsupportedBrowserTitleColor: #fff;
|
||||
@@ -151,4 +139,12 @@ $unsupportedDesktopBrowserTextFontSize: 21px;
|
||||
* The size of the default watermark.
|
||||
*/
|
||||
$watermarkWidth: 186px;
|
||||
$watermarkHeight: 74px;
|
||||
$watermarkHeight: 74px;
|
||||
|
||||
/**
|
||||
* Welcome page variables.
|
||||
*/
|
||||
$welcomePageDescriptionColor: #fff;
|
||||
$welcomePageFontFamily: inherit;
|
||||
$welcomePageHeaderBackground: linear-gradient(#165ecc, #44A5FF);
|
||||
$welcomePageTitleColor: #fff;
|
||||
|
||||
@@ -2,6 +2,37 @@
|
||||
* 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;
|
||||
@@ -15,14 +46,7 @@
|
||||
* any parent is also fixed.
|
||||
*/
|
||||
position: fixed;
|
||||
|
||||
/**
|
||||
* z-index adjusting is needed because the video state indicator has to
|
||||
* display over the filmstrip when no videos are displayed but still be
|
||||
* clickable but its inline dialogs must display over the video state
|
||||
* indicator when videos are displayed.
|
||||
*/
|
||||
z-index: #{$tooltipsZ + 1};
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
/**
|
||||
* Hide videos by making them slight to the right.
|
||||
@@ -42,8 +66,9 @@
|
||||
&#remoteVideos {
|
||||
/**
|
||||
* Remove horizontal filmstrip padding used to prevent videos
|
||||
* from overlapping the left-side toolbar. An id selector is
|
||||
* used to match id specificity with filmstrip styles.
|
||||
* 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;
|
||||
@@ -51,21 +76,15 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styles the local Video and invite button to better fit the
|
||||
* vertical filmstrip layout.
|
||||
* 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;
|
||||
|
||||
.filmstrip__invite {
|
||||
padding-bottom: 0px;
|
||||
padding-top: 5px;
|
||||
z-index: $dropdownZ;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,6 +97,8 @@
|
||||
}
|
||||
|
||||
#filmstripRemoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
@@ -86,6 +107,11 @@
|
||||
|
||||
#filmstripRemoteVideosContainer {
|
||||
flex-direction: column-reverse;
|
||||
/**
|
||||
* Add padding as a hack for Firefox not to show scrollbars when
|
||||
* unnecessary.
|
||||
*/
|
||||
padding: 1px 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
@@ -115,6 +141,8 @@
|
||||
}
|
||||
|
||||
#remoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -141,20 +169,6 @@
|
||||
.filmstrip__videos-filmstripOnly {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* In filmstrip only mode, the toolbar is normally displayed in the
|
||||
* vertical center of the filmstrip strip. In vertical filmstrip mode,
|
||||
* that alignment makes the toolbar appear floating and detached from
|
||||
* the filmstrip. So, instead anchor the toolbar next to the local
|
||||
* video.
|
||||
*/
|
||||
.toolbar_filmstrip-only {
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,17 +182,9 @@
|
||||
* The class opening is for when the filmstrip is transitioning from hidden
|
||||
* to visible.
|
||||
*/
|
||||
.video-state-indicator.moveToCorner {
|
||||
transition: right 0.5s;
|
||||
|
||||
.large-video-labels {
|
||||
&.with-filmstrip {
|
||||
&#recordingLabel {
|
||||
right: 200px;
|
||||
}
|
||||
|
||||
&#videoResolutionLabel {
|
||||
right: 150px;
|
||||
}
|
||||
right: 150px;
|
||||
}
|
||||
|
||||
&.with-filmstrip.opening {
|
||||
@@ -224,3 +230,35 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@@ -12,14 +14,27 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.video_blurred_container {
|
||||
#largeVideoBackgroundContainer,
|
||||
.large-video-background {
|
||||
height: 100%;
|
||||
filter: blur(40px);
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
&.fit-full-height #largeVideoBackground {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.fit-full-width #largeVideoBackground {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
#largeVideoBackgroundContainer {
|
||||
filter: blur(40px);
|
||||
}
|
||||
|
||||
.videocontainer {
|
||||
@@ -211,6 +226,11 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#largeVideoContainer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#largeVideoWrapper {
|
||||
box-shadow: 0 0 20px -2px #444;
|
||||
}
|
||||
@@ -250,6 +270,7 @@
|
||||
/**
|
||||
* Positions video thumbnail display name and editor.
|
||||
*/
|
||||
#alwaysOnTop .displayname,
|
||||
.videocontainer .displayname,
|
||||
.videocontainer .editdisplayname {
|
||||
display: inline-block;
|
||||
@@ -269,6 +290,15 @@
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
#alwaysOnTop .displayname {
|
||||
font-size: 15px;
|
||||
position: inherit;
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions video thumbnail display name editor.
|
||||
*/
|
||||
@@ -507,6 +537,20 @@
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#videoNotAvailableScreen {
|
||||
text-align: center;
|
||||
#avatarContainer {
|
||||
height: 50vh;
|
||||
display:inline-block;
|
||||
margin-top: 25vh;
|
||||
|
||||
#avatar {
|
||||
border-radius: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sharedVideoAvatar {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@@ -605,3 +649,93 @@
|
||||
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 {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,206 +1,93 @@
|
||||
#welcome_page {
|
||||
height: 100%;
|
||||
body.welcome-page {
|
||||
background: inherit;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
font-family: $welcomePageFontFamily;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#disable_welcome {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.disable_welcome_position
|
||||
{
|
||||
margin: -139px auto 0px auto;
|
||||
padding-left: 39px;
|
||||
padding-top: 7px;
|
||||
width: 269px;
|
||||
height: 31px;
|
||||
display:block;
|
||||
}
|
||||
|
||||
#disable_welcome + label
|
||||
{
|
||||
background-image: url(../images/welcome_page/disable-welcome.png);
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
background-repeat: no-repeat;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #acacac;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
#disable_welcome:checked + label
|
||||
{
|
||||
background-image: url(../images/welcome_page/disable-welcome-selected.png);
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
background-repeat: no-repeat;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #acacac;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
#enter_room_form {
|
||||
border-radius: 1px;
|
||||
background-color: #FFFFFF;
|
||||
border: none;
|
||||
-moz-border-radius: 1px;
|
||||
-webkit-border-radius: 1px;
|
||||
-webkit-appearance: none;
|
||||
height: 55px;
|
||||
box-shadow: none;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.domain-name
|
||||
{
|
||||
float: left;
|
||||
height: 55px;
|
||||
line-height: 55px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
padding-left: 20px;
|
||||
color: $defaultDarkColor;
|
||||
}
|
||||
|
||||
.enter-room {
|
||||
&__field {
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
-webkit-appearance: none;
|
||||
width: 228px;
|
||||
height: 55px;
|
||||
line-height: 55px;
|
||||
font-weight: 500;
|
||||
box-shadow: none;
|
||||
float: left;
|
||||
background-color: #FFFFFF;
|
||||
.header {
|
||||
align-items: center;
|
||||
background: $welcomePageHeaderBackground;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
&__reload {
|
||||
display: block;
|
||||
width: 30px;
|
||||
color: #acacac;
|
||||
font-size: 1.9em;
|
||||
line-height: 55px;
|
||||
z-index: $zindex3;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
|
||||
.header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
margin-top: 120px;
|
||||
margin-bottom: 20px;
|
||||
max-width: calc(100% - 40px);
|
||||
min-height: 286px;
|
||||
width: 645px;
|
||||
}
|
||||
|
||||
.header-text-title {
|
||||
color: $welcomePageTitleColor;
|
||||
font-size: 48px;
|
||||
letter-spacing: -1px;
|
||||
line-height: 58px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-text-description {
|
||||
color: $welcomePageDescriptionColor;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.header-image {
|
||||
background-image: url(../images/welcome_page/curves.png);
|
||||
background-size: contain;
|
||||
height: 209px;
|
||||
position: absolute;
|
||||
width: 1070px;
|
||||
}
|
||||
|
||||
#new_enter_room {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
max-width: calc(100% - 40px);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
.enter-room-input {
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
width: 73px;
|
||||
height: 45px;
|
||||
background-color: #21B9FC;
|
||||
moz-border-radius: 1px;
|
||||
-webkit-border-radius: 1px;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
margin-top: 5px;
|
||||
font-size: 19px;
|
||||
padding-top: 6px;
|
||||
outline: none;
|
||||
float:left;
|
||||
position: relative;
|
||||
z-index: $zindex2;
|
||||
.welcome-page-button {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
#enter_room_container {
|
||||
margin: 70px auto 0px auto;
|
||||
display: table;
|
||||
.welcome.with-content {
|
||||
.header {
|
||||
min-height: 552px;
|
||||
}
|
||||
.header-image {
|
||||
left: -61px;
|
||||
top: 401px;
|
||||
}
|
||||
}
|
||||
|
||||
#enter_room{
|
||||
float:left;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#welcome_page_header
|
||||
{
|
||||
background-image: url(../images/welcome_page/pattern-header.png);
|
||||
height: 290px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#welcome_page_main
|
||||
{
|
||||
background-image:url(../images/welcome_page/pattern-body.png);
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
margin-top: 290px;
|
||||
}
|
||||
|
||||
#brand_header
|
||||
{
|
||||
background-image:url(../images/welcome_page/header-big.png);
|
||||
width: 583px;
|
||||
height: 274px;
|
||||
margin: -110px auto 0px auto;
|
||||
}
|
||||
|
||||
#header_text
|
||||
{
|
||||
width: 885px;
|
||||
height: 100px;
|
||||
color: #ffffff;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
margin: 0px auto 0px auto;
|
||||
}
|
||||
|
||||
#features
|
||||
{
|
||||
margin-top: 30px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.feature_row
|
||||
{
|
||||
position: relative;
|
||||
width: 976px;
|
||||
margin: 0px auto 30px auto;
|
||||
padding-right: 75px;
|
||||
|
||||
}
|
||||
|
||||
.feature_holder
|
||||
{
|
||||
float:left;
|
||||
width: 169px;
|
||||
padding-left: 75px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.feature_icon
|
||||
{
|
||||
background-image:url(../images/welcome_page/bubble.png);
|
||||
background-repeat: no-repeat;
|
||||
width: 169px;
|
||||
height: 169px;
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
/*font-weight: bold;*/
|
||||
text-align: center;
|
||||
display: table-cell;
|
||||
padding: 50px 26px 0px 20px;
|
||||
}
|
||||
|
||||
.feature_description
|
||||
{
|
||||
width: 190px;
|
||||
color: #ffffff;
|
||||
font-size: 16px;
|
||||
padding-top: 30px;
|
||||
line-height: 22px;
|
||||
font-weight: 200;
|
||||
.welcome.without-content {
|
||||
.header {
|
||||
height: 100%;
|
||||
}
|
||||
.header-image {
|
||||
bottom: -20px;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
1
css/_welcome_page_content.scss
Normal file
1
css/_welcome_page_content.scss
Normal file
@@ -0,0 +1 @@
|
||||
/** Insert custom CSS for any additional content in the welcome page **/
|
||||
75
css/deep-linking/_desktop.scss
Normal file
75
css/deep-linking/_desktop.scss
Normal file
@@ -0,0 +1,75 @@
|
||||
.deep-linking-desktop {
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 55px;
|
||||
background-color: #f1f2f5;
|
||||
padding-top: 15px;
|
||||
padding-left: 50px;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
flex: 0 0 55px;
|
||||
.logo {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-flow: row;
|
||||
.leftColumn {
|
||||
left: 0px;
|
||||
width: 50%;
|
||||
min-height: 156px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
.leftColumnContent{
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: 100%;
|
||||
.image {
|
||||
background-image: url('../images/deep-linking-image.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.rightColumn {
|
||||
top: 0px;
|
||||
width: 50%;
|
||||
min-height: 156px;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
.rightColumnContent {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
padding: 20px 20px 20px 60px;
|
||||
.title {
|
||||
color: #1c2946;
|
||||
}
|
||||
.description {
|
||||
color: #606a80;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.buttons {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
css/deep-linking/_main.scss
Normal file
3
css/deep-linking/_main.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
@import 'desktop';
|
||||
@import 'mobile';
|
||||
@import 'no-mobile-app';
|
||||
117
css/deep-linking/_mobile.scss
Normal file
117
css/deep-linking/_mobile.scss
Normal file
@@ -0,0 +1,117 @@
|
||||
.deep-linking-mobile {
|
||||
background-color: #fff;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
background-color: #f1f2f5;
|
||||
text-align: center;
|
||||
.logo {
|
||||
margin-top: 15px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
&__body {
|
||||
color: $unsupportedBrowserTextColor;
|
||||
margin: auto;
|
||||
max-width: 40em;
|
||||
padding: 35px 0 40px 0;
|
||||
text-align: center;
|
||||
width: 75%;
|
||||
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-weight: bolder;
|
||||
padding: 10px 10px 0px 10px;
|
||||
}
|
||||
|
||||
&__text,
|
||||
.deep-linking-dial-in {
|
||||
font-size: 1.2em;
|
||||
line-height: em(29px, 21px);
|
||||
margin-bottom: 0.65em;
|
||||
|
||||
&_small {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 1em;
|
||||
margin-top: em(21, 18);
|
||||
|
||||
strong {
|
||||
font-size: em(21, 18);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__href {
|
||||
height: 2.2857142857142856em;
|
||||
line-height: 2.2857142857142856em;
|
||||
margin: 18px auto 20px;
|
||||
max-width: 300px;
|
||||
width: auto;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
&__button {
|
||||
border: 0;
|
||||
height: 2.2857142857142856em;
|
||||
line-height: 2.2857142857142856em;
|
||||
margin: 18px auto 10px;
|
||||
padding: 0px 10px 0px 10px;
|
||||
max-width: 300px;
|
||||
width: auto;
|
||||
@include border-radius(3px);
|
||||
background-color: $unsupportedBrowserButtonBgColor;
|
||||
color: #505F79;
|
||||
font-weight: bold;
|
||||
|
||||
&:active {
|
||||
background-color: $unsupportedBrowserButtonBgColor;
|
||||
}
|
||||
|
||||
&_primary {
|
||||
background-color: $primaryUnsupportedBrowserButtonBgColor;
|
||||
color: #FFFFFF;
|
||||
|
||||
&:active {
|
||||
background-color: $primaryUnsupportedBrowserButtonBgColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.deep-linking-dial-in {
|
||||
display: none;
|
||||
|
||||
&.has-numbers {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dial-in-numbers-list {
|
||||
color: $unsupportedBrowserTextColor;
|
||||
}
|
||||
|
||||
.dial-in-numbers-body {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
width: auto;
|
||||
|
||||
&__title {
|
||||
border-bottom: 1px solid $auiBorderColor;
|
||||
border-bottom: 1px solid $inlayBorderColor;
|
||||
color: $unsupportedBrowserTitleColor;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.5px;
|
||||
@@ -28,11 +28,9 @@
|
||||
@import 'font-awesome';
|
||||
/* Fonts END */
|
||||
|
||||
@import 'flag-icon';
|
||||
|
||||
/* Modules BEGIN */
|
||||
|
||||
@import 'dial-out';
|
||||
@import 'aui_reset';
|
||||
@import 'base';
|
||||
@import 'utils';
|
||||
@import 'overlay/overlay';
|
||||
@@ -50,10 +48,10 @@
|
||||
@import 'popup_menu';
|
||||
@import 'recording';
|
||||
@import 'login_menu';
|
||||
@import 'contact_list';
|
||||
@import 'chat';
|
||||
@import 'ringing/ringing';
|
||||
@import 'welcome_page';
|
||||
@import 'welcome_page_content';
|
||||
@import 'toolbars';
|
||||
@import 'side_toolbar_container';
|
||||
@import 'jquery.contextMenu';
|
||||
@@ -64,7 +62,6 @@
|
||||
@import 'components/button-control';
|
||||
@import 'components/input-control';
|
||||
@import 'components/input-slider';
|
||||
@import "modals/invite/invite";
|
||||
@import "connection-info";
|
||||
@import 'aui-components/dropdown';
|
||||
@import '404';
|
||||
@@ -74,5 +71,6 @@
|
||||
@import 'unsupported-browser/main';
|
||||
@import 'modals/invite/add-people';
|
||||
@import 'vertical_filmstrip_overrides';
|
||||
@import 'deep-linking/main';
|
||||
|
||||
/* Modules END */
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
.dialog {
|
||||
visibility: visible;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
min-height: 131px;
|
||||
overflow: visible;
|
||||
visibility: visible;
|
||||
width: 400px;
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: $auiDialogColor;
|
||||
@@ -10,10 +14,27 @@
|
||||
|
||||
&-icon {
|
||||
color: $auiDialogColor;
|
||||
text-indent: -999em;
|
||||
|
||||
&-small {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
||||
&:before {
|
||||
color: inherit;
|
||||
font-family: "FontAwesome";
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
left: 0;
|
||||
line-height: 1;
|
||||
margin-top: -8px;
|
||||
position: absolute;
|
||||
text-indent: 0;
|
||||
speak: none;
|
||||
top: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +43,10 @@
|
||||
right: 20px;
|
||||
position: absolute;
|
||||
top: -49px;
|
||||
|
||||
&:before {
|
||||
content: "\f00d";
|
||||
}
|
||||
}
|
||||
|
||||
&-dialog2 {
|
||||
@@ -31,8 +56,16 @@
|
||||
}
|
||||
|
||||
&-header {
|
||||
height: em(58, 12);
|
||||
border-bottom: 1px solid $auiBorderColor;
|
||||
border-radius: 5px 5px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
display: table;
|
||||
font-weight: normal;
|
||||
height: em(58, 12);
|
||||
margin-top: -69px;
|
||||
padding: 0 20px;
|
||||
width: 100%;
|
||||
|
||||
h2 {
|
||||
font-size: em(20, 12);
|
||||
@@ -41,19 +74,40 @@
|
||||
}
|
||||
|
||||
&-main {
|
||||
display: table-cell;
|
||||
padding-right: 0;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&-footer {
|
||||
border-top: 1px solid $auiBorderColor;
|
||||
border-radius: 0 0 5px 5px;
|
||||
box-sizing: border-box;
|
||||
height: 51px;
|
||||
overflow: hidden;
|
||||
padding: 10px 20px;
|
||||
width: 100%;
|
||||
|
||||
&:empty {
|
||||
height: 5px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
font-size: em(14, 12);
|
||||
min-height: 0;
|
||||
background-color: $auiDialogContentBg;
|
||||
background-color: $auiDialogBg;
|
||||
box-sizing: border-box;
|
||||
color: $auiDialogColor;
|
||||
font-size: em(14, 12);
|
||||
overflow: auto;
|
||||
max-height: 100%;
|
||||
padding: 20px;
|
||||
|
||||
p,span, h3 {
|
||||
font-weight: $labelFontWeight;
|
||||
@@ -72,13 +126,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
.input-control {
|
||||
background-color: $auiDialogContentBg;
|
||||
color: $auiDialogColor;
|
||||
}
|
||||
|
||||
.form-control:not(:last-child) {
|
||||
border-bottom: 1px solid $auiBorderColor;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 420px) {
|
||||
.aui-dialog2-small .aui-dialog2-content {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dialog-form {
|
||||
color: $modalTextColor;
|
||||
margin-top: 5px !important;
|
||||
|
||||
.input-control {
|
||||
@@ -90,6 +154,14 @@
|
||||
&-error {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override Atlaskit dropdown styling when in a modal because the dropdown
|
||||
* backgrounds clash with the modal backgrounds.
|
||||
*/
|
||||
.htclLc[data-role=droplistContent] {
|
||||
border: 1px solid #455166;
|
||||
}
|
||||
}
|
||||
.modal-dialog-footer {
|
||||
font-size: $modalButtonFontSize;
|
||||
|
||||
@@ -36,13 +36,12 @@
|
||||
}
|
||||
|
||||
.desktop-picker-source {
|
||||
color: $defaultDarkFontColor;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
|
||||
&.is-selected {
|
||||
.desktop-source-preview-image-container {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
background: rgba(255,255,255,0.3);
|
||||
border-radius: $borderRadius;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
.device-selection {
|
||||
color: $feedbackInputTextColor;
|
||||
|
||||
.device-selectors {
|
||||
font-size: 14px;
|
||||
|
||||
> div {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
> div:last-child {
|
||||
margin-bottom: 5px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.device-selector-icon {
|
||||
@@ -20,21 +14,21 @@
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.device-selector-label {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
/* device-selector-trigger stylings attempt to mimic AtlasKit button */
|
||||
.device-selector-trigger {
|
||||
background-color: rgba(9, 30, 66, 0.04);
|
||||
border-radius: 3px;
|
||||
color: #505f79;
|
||||
background-color: #0E1624;
|
||||
border: 1px solid #455166;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
height: 2.3em;
|
||||
justify-content: space-between;
|
||||
line-height: 2.3em;
|
||||
overflow: hidden;
|
||||
padding: 0 8px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(9,30,66,.08);
|
||||
}
|
||||
}
|
||||
.device-selector-trigger-disabled {
|
||||
.device-selector-trigger {
|
||||
@@ -107,20 +101,21 @@
|
||||
|
||||
.audio-output-preview {
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
|
||||
a {
|
||||
color: 4C9AFF;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-input-preview {
|
||||
background: #f4f5f7;
|
||||
background: #1B2638;
|
||||
border-radius: 5px;
|
||||
height: 6px;
|
||||
height: 8px;
|
||||
|
||||
.audio-input-preview-level {
|
||||
background: #0052cc;
|
||||
background: #4C9AFF;
|
||||
border-radius: 5px;
|
||||
height: 100%;
|
||||
-webkit-transition: width .1s ease-in-out;
|
||||
|
||||
@@ -47,10 +47,6 @@
|
||||
|
||||
.feedback-dialog {
|
||||
.details {
|
||||
margin-top: 20px;
|
||||
padding-left: 60px;
|
||||
padding-right: 60px;
|
||||
|
||||
textarea {
|
||||
min-height: 100px;
|
||||
}
|
||||
@@ -77,16 +73,15 @@
|
||||
text-align: center;
|
||||
|
||||
.star-label {
|
||||
color: $rateStarLabelColor;
|
||||
font-size: 14px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.star-btn {
|
||||
color: $rateStarDefault;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: $rateStarSize;
|
||||
font-size: 34px;
|
||||
outline: none;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
@@ -95,7 +90,7 @@
|
||||
&.active,
|
||||
&:hover,
|
||||
&.starHover {
|
||||
color: $rateStarActivity;
|
||||
color: #36B37E;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -11,17 +11,19 @@
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Styles the loading element in the MultiSelectAutocomplete.
|
||||
*/
|
||||
.autocomplete-loading {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
min-width: 260px;
|
||||
padding: 20px;
|
||||
.add-telephone-icon {
|
||||
display: flex;
|
||||
height: 28px;
|
||||
transform: scaleX(-1);
|
||||
width: 28px;
|
||||
|
||||
i {
|
||||
line-height: 28px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
.use-new-toolbox {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-dialog {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
|
||||
.info-dialog-action-link {
|
||||
display: inline-block;
|
||||
line-height: 1.5em;
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.info-dialog-action-link:before {
|
||||
color: $linkFontColor;
|
||||
content: '\2022';
|
||||
font-size: 1.5em;
|
||||
padding: 0 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.info-dialog-action-link:first-child:before {
|
||||
@@ -22,6 +30,8 @@
|
||||
}
|
||||
|
||||
.info-dialog-action-links {
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -32,21 +42,39 @@
|
||||
.info-dialog-copy-element {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.info-dialog-column {
|
||||
margin-right: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
a,
|
||||
a:active,
|
||||
a:focus,
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.info-dialog-conference-url {
|
||||
margin: 10px 0;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.info-dialog-conference-url,
|
||||
.info-dialog-live-stream-url {
|
||||
width: max-content;
|
||||
width: -moz-max-content;
|
||||
width: -webkit-max-content;
|
||||
word-break: break-all;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.info-dialog-dial-in {
|
||||
white-space: nowrap;
|
||||
|
||||
.conference-id,
|
||||
.phone-number {
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
.info-dialog-icon {
|
||||
@@ -54,7 +82,76 @@
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.info-dialog-url-text,
|
||||
.info-dialog-url-text:hover {
|
||||
color: inherit;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.info-dialog-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.info-dialog-password,
|
||||
.info-password,
|
||||
.info-password-form {
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.info-password-field {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.info-password-none,
|
||||
.info-password-remote {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.info-password-input {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.info-password-local {
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
.dial-in-page {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 24px;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
.dial-in-numbers-list {
|
||||
font-size: 24px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.dial-in-conference-id {
|
||||
text-align: center;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
.info-dialog,
|
||||
.dial-in-page {
|
||||
* {
|
||||
user-select: text;
|
||||
-moz-user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Sets the default cursor the remove password link. The link doesn't use
|
||||
* the href attribute, so we need to set the cursor manually.
|
||||
*/
|
||||
#inviteDialogRemovePassword {
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
.invite-dialog {
|
||||
.dial-in-numbers {
|
||||
.dial-in-numbers-conference-id {
|
||||
color: orange;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
/*
|
||||
* dial-in-numbers-copy styling is needed for the feature of copying
|
||||
* text to the clipboard. The styling keeps the element invisible
|
||||
* to the user but still programmatically selectable for copying.
|
||||
*/
|
||||
.dial-in-numbers-copy {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.is-disabled,
|
||||
.is-loading {
|
||||
.dial-in-numbers-trigger-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-control {
|
||||
padding: 0;
|
||||
|
||||
&__container {
|
||||
/**
|
||||
* Ensure contents display in a line and vertically centered.
|
||||
*/
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
font-size: $modalButtonFontSize;
|
||||
}
|
||||
}
|
||||
|
||||
&__input-container {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
|
||||
.dropdown-button-trigger {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inviteLink {
|
||||
color: $readOnlyInputColor;
|
||||
}
|
||||
|
||||
.lock-state {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.password-overview {
|
||||
margin-top: 10px;
|
||||
|
||||
.form-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.password-overview-status,
|
||||
.remove-password {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.password-overview-toggle-edit,
|
||||
.remove-password-link {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.remove-password {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.remove-password-current {
|
||||
color: $inputControlEmColor;
|
||||
}
|
||||
}
|
||||
@@ -42,10 +42,6 @@
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
.speaker-stats-item:nth-child(even) {
|
||||
background: whitesmoke;
|
||||
}
|
||||
|
||||
.speaker-stats-item__name,
|
||||
.speaker-stats-item__time {
|
||||
overflow: hidden;
|
||||
|
||||
@@ -135,62 +135,64 @@
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
i {
|
||||
line-height: $videoStateIndicatorSize;
|
||||
.modal-dialog-form {
|
||||
.video-quality-dialog-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
.centeredVideoLabel.moveToCorner {
|
||||
z-index: $tooltipsZ;
|
||||
}
|
||||
|
||||
#videoResolutionLabel {
|
||||
z-index: #{$tooltipsZ + 1};
|
||||
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;
|
||||
-webkit-transition: all 2s 2s linear;
|
||||
transition: all 2s 2s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.moveToCorner {
|
||||
.large-video-labels {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
right: 30px;
|
||||
transition: right 0.5s;
|
||||
z-index: $zindex3;
|
||||
|
||||
.circular-label {
|
||||
color: white;
|
||||
font-family: -apple-system, BlinkMacSystemFont, $baseFontFamily;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -11,9 +11,16 @@
|
||||
}
|
||||
|
||||
#reloadProgressBar {
|
||||
width: 180px;
|
||||
background: #e9e9e9;
|
||||
border-radius: 3px;
|
||||
height: 5px;
|
||||
margin: 5px auto;
|
||||
> .aui-progress-indicator-value {
|
||||
overflow: hidden;
|
||||
width: 180px;
|
||||
|
||||
.progress-indicator-fill {
|
||||
background: $reloadProgressBarBg;
|
||||
height: 100%;
|
||||
transition: width .5s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ $sliderThumbBackground: #3572b0;
|
||||
/**
|
||||
* Buttons
|
||||
*/
|
||||
$buttonBackground: #f5f5f5;
|
||||
$buttonHoverBackground: #e9e9e9;
|
||||
$buttonBorder: #ccc;
|
||||
$buttonHoverBorder: #999;
|
||||
$buttonColor: #333;
|
||||
$buttonBackground: #44A5FF;
|
||||
$buttonHoverBackground: #2c4062;
|
||||
$buttonBorder: transparent;
|
||||
$buttonHoverBorder: transparent;
|
||||
$buttonColor: #eceef1;
|
||||
|
||||
$buttonLightBackground: #f5f5f5;
|
||||
$buttonLightHoverBackground: #e9e9e9;
|
||||
@@ -47,10 +47,10 @@ $reloadProgressBarBg: #0074E0;
|
||||
/**
|
||||
* Dialog colors
|
||||
**/
|
||||
$auiDialogColor: #333;
|
||||
$auiDialogBg: #f5f5f5;
|
||||
$auiDialogContentBg: $baseLight;
|
||||
$auiBorderColor: #ccc;
|
||||
$auiDialogColor: #eceef1;
|
||||
$auiDialogBg: #253858;
|
||||
$auiDialogContentBg: #344563;
|
||||
$auiBorderColor: #253858;
|
||||
$dialogTitleFontWeight: 400;
|
||||
$dialogErrorText: #344563;
|
||||
|
||||
@@ -58,7 +58,7 @@ $dialogErrorText: #344563;
|
||||
* Inlay colors
|
||||
**/
|
||||
$inlayColorBg: lighten($defaultBackground, 20%);
|
||||
$inlayBorderColor: lighten($auiDialogContentBg, 10%);
|
||||
$inlayBorderColor: lighten($baseLight, 10%);
|
||||
$inlayIconBg: #000;
|
||||
$inlayIconColor: #fff;
|
||||
$inlayFilmstripOnlyColor: #474747;
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
@import 'no-mobile-app';
|
||||
@import 'unsupported-desktop-browser';
|
||||
@import 'unsupported-mobile-browser';
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
.unsupported-mobile-browser {
|
||||
background-color: #fff;
|
||||
height: 100vh;
|
||||
padding: 35px 0;
|
||||
width: 100vw;
|
||||
|
||||
&__body {
|
||||
color: $unsupportedBrowserTextColor;
|
||||
margin: auto;
|
||||
max-width: 40em;
|
||||
text-align: center;
|
||||
width: 75%;
|
||||
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 1.8em;
|
||||
line-height: em(29px, 21px);
|
||||
margin-bottom: 0.65em;
|
||||
|
||||
&_small {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 1em;
|
||||
margin-top: em(21, 18);
|
||||
|
||||
strong {
|
||||
font-size: em(21, 18);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__logo {
|
||||
height: 108px;
|
||||
width: 77px;
|
||||
}
|
||||
|
||||
&__button {
|
||||
border: 0;
|
||||
height: 42px;
|
||||
margin: 0 auto;
|
||||
max-width: 300px;
|
||||
width: 98%;
|
||||
@include border-radius(8px);
|
||||
background-color: $unsupportedBrowserButtonBgColor;
|
||||
font-size: 1.5em;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.5px;
|
||||
text-shadow: 0px 1px 2px $unsupportedBrowserTextColor;
|
||||
|
||||
// Disable standard button effects.
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
|
||||
&:active {
|
||||
background-color: $unsupportedBrowserButtonBgColor;
|
||||
}
|
||||
|
||||
&_primary {
|
||||
background-color: $primaryUnsupportedBrowserButtonBgColor;
|
||||
|
||||
&:active {
|
||||
background-color: $primaryUnsupportedBrowserButtonBgColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
debian/jitsi-meet-prosody.postinst
vendored
40
debian/jitsi-meet-prosody.postinst
vendored
@@ -50,17 +50,21 @@ case "$1" in
|
||||
|
||||
db_get jicofo/jicofo-authpassword
|
||||
if [ -z "$RET" ] ; then
|
||||
db_input critical jicofo/jicofo-authpassword || true
|
||||
db_go
|
||||
# if password is missing generate it, and store it
|
||||
JICOFO_AUTH_PASSWORD=`head -c 8 /dev/urandom | tr '\0-\377' 'a-zA-Z0-9a-zA-Z0-9a-zA-Z0-9a-zA-Z0-9@@@@####'`
|
||||
db_set jicofo/jicofo-authpassword "$JICOFO_AUTH_PASSWORD"
|
||||
else
|
||||
JICOFO_AUTH_PASSWORD="$RET"
|
||||
fi
|
||||
JICOFO_AUTH_PASSWORD="$RET"
|
||||
|
||||
db_get jicofo/jicofosecret
|
||||
if [ -z "$RET" ] ; then
|
||||
db_input critical jicofo/jicofosecret || true
|
||||
db_go
|
||||
# if secret is missing generate it, and store it
|
||||
JICOFO_SECRET=`head -c 8 /dev/urandom | tr '\0-\377' 'a-zA-Z0-9a-zA-Z0-9a-zA-Z0-9a-zA-Z0-9@@@@####'`
|
||||
db_set jicofo/jicofosecret "$JICOFO_SECRET"
|
||||
else
|
||||
JICOFO_SECRET="$RET"
|
||||
fi
|
||||
JICOFO_SECRET="$RET"
|
||||
|
||||
JICOFO_AUTH_DOMAIN="auth.$JVB_HOSTNAME"
|
||||
|
||||
@@ -88,6 +92,7 @@ case "$1" in
|
||||
if [ ! -f $PROSODY_HOST_CONFIG ] && ! grep -q "VirtualHost \"$JVB_HOSTNAME\"" $PROSODY_CONFIG_OLD; then
|
||||
PROSODY_CONFIG_PRESENT="false"
|
||||
mkdir -p /etc/prosody/conf.avail/
|
||||
mkdir -p /etc/prosody/conf.d/
|
||||
cp /usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example $PROSODY_HOST_CONFIG
|
||||
sed -i "s/jitmeet.example.com/$JVB_HOSTNAME/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/jitmeetSecret/$JVB_SECRET/g" $PROSODY_HOST_CONFIG
|
||||
@@ -112,23 +117,18 @@ case "$1" in
|
||||
fi
|
||||
|
||||
if [ ! -f /var/lib/prosody/$JVB_HOSTNAME.crt ]; then
|
||||
HOST="$( (hostname -s; echo localhost) | head -n 1)"
|
||||
DOMAIN="$( (hostname -d; echo localdomain) | head -n 1)"
|
||||
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj \
|
||||
"/O=$DOMAIN/OU=$HOST/CN=$JVB_HOSTNAME/emailAddress=webmaster@$HOST.$DOMAIN" \
|
||||
-keyout /var/lib/prosody/$JVB_HOSTNAME.key \
|
||||
-out /var/lib/prosody/$JVB_HOSTNAME.crt
|
||||
# prosodyctl takes care for the permissions
|
||||
# echo for using all default values
|
||||
echo | prosodyctl cert generate $JVB_HOSTNAME
|
||||
|
||||
ln -sf /var/lib/prosody/$JVB_HOSTNAME.key /etc/prosody/certs/$JVB_HOSTNAME.key
|
||||
ln -sf /var/lib/prosody/$JVB_HOSTNAME.crt /etc/prosody/certs/$JVB_HOSTNAME.crt
|
||||
fi
|
||||
|
||||
if [ ! -f /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt ]; then
|
||||
HOST="$( (hostname -s; echo localhost) | head -n 1)"
|
||||
DOMAIN="$( (hostname -d; echo localdomain) | head -n 1)"
|
||||
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj \
|
||||
"/O=$DOMAIN/OU=$HOST/CN=$JICOFO_AUTH_DOMAIN/emailAddress=webmaster@$HOST.$DOMAIN" \
|
||||
-keyout /var/lib/prosody/$JICOFO_AUTH_DOMAIN.key \
|
||||
-out /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt
|
||||
# prosodyctl takes care for the permissions
|
||||
# echo for using all default values
|
||||
echo | prosodyctl cert generate $JICOFO_AUTH_DOMAIN
|
||||
|
||||
AUTH_KEY_FILE="/etc/prosody/certs/$JICOFO_AUTH_DOMAIN.key"
|
||||
AUTH_CRT_FILE="/etc/prosody/certs/$JICOFO_AUTH_DOMAIN.crt"
|
||||
@@ -137,7 +137,9 @@ case "$1" in
|
||||
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt $AUTH_CRT_FILE
|
||||
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt /usr/local/share/ca-certificates/$JICOFO_AUTH_DOMAIN.crt
|
||||
|
||||
update-ca-certificates
|
||||
# we need to force updating certificates, in some cases java trust
|
||||
# store not get re-generated with latest changes
|
||||
update-ca-certificates -f
|
||||
|
||||
# don't fail on systems with custom config ($PROSODY_HOST_CONFIG is missing)
|
||||
if [ -f $PROSODY_HOST_CONFIG ]; then
|
||||
|
||||
8
debian/jitsi-meet-prosody.postrm
vendored
8
debian/jitsi-meet-prosody.postrm
vendored
@@ -35,6 +35,14 @@ case "$1" in
|
||||
if [ -n "$RET" ]; then
|
||||
rm -f /etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua
|
||||
rm -f /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
|
||||
|
||||
# clean up generated certificates
|
||||
rm -f /etc/prosody/certs/$JVB_HOSTNAME.crt
|
||||
rm -f /etc/prosody/certs/$JVB_HOSTNAME.key
|
||||
rm -f /etc/prosody/certs/auth.$JVB_HOSTNAME.crt
|
||||
rm -f /etc/prosody/certs/auth.$JVB_HOSTNAME.key
|
||||
rm -rf /var/lib/prosody/auth.$JVB_HOSTNAME.*
|
||||
rm -rf /var/lib/prosody/$JVB_HOSTNAME.*
|
||||
fi
|
||||
|
||||
# Clear the debconf variable
|
||||
|
||||
24
debian/jitsi-meet.README.source
vendored
24
debian/jitsi-meet.README.source
vendored
@@ -1,24 +0,0 @@
|
||||
jitsi-meet for Debian
|
||||
---------------------
|
||||
|
||||
The jitsi-meet package is built from the sources of Jitsi Meet.
|
||||
|
||||
Jitsi Meet is downloaded from https://github.com/jitsi/jitsi-meet and the git files are removed. you can recreate the source with 'git clone https://github.com/jitsi/jitsi-meet.git'.
|
||||
Use something like the script below to update from Git
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
VERSION=1.0.1
|
||||
|
||||
echo "*.min.js export-ignore" > .gitattributes
|
||||
echo "jquery-2.1.1.* export-ignore" >> .gitattributes
|
||||
echo "jquery-ui.js export-ignore" >> .gitattributes
|
||||
echo ".gitignore export-ignore" >> .gitattributes
|
||||
|
||||
sed -i "s/1.0.1/$VERSION/g" debian/changelog
|
||||
|
||||
git archive --worktree-attributes --format tar --prefix jitsi-meet-$VERSION/ -o ../jitsi-meet_${VERSION}.orig.tar master
|
||||
tar --transform "s,^,jitsi-meet-$VERSION/," -rf ../jitsi-meet_${VERSION}.orig.tar
|
||||
cd ..
|
||||
|
||||
bzip2 jitsi-meet_${VERSION}.orig.tar
|
||||
22
debian/patches/jquery-package
vendored
22
debian/patches/jquery-package
vendored
@@ -1,22 +0,0 @@
|
||||
Description: Update the used js files for jquery to generic ones, to be able to use local system installed version (through symlinks).
|
||||
Index: jitsi-meet/index.html
|
||||
===================================================================
|
||||
--- jitsi-meet.orig/index.html
|
||||
+++ jitsi-meet/index.html
|
||||
@@ -10,13 +10,13 @@
|
||||
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
|
||||
<meta itemprop="image" content="/images/jitsilogo.png"/>
|
||||
<script src="https://api.callstats.io/static/callstats.min.js"></script>
|
||||
- <script src="libs/jquery-2.1.1.min.js"></script>
|
||||
+ <script src="libs/jquery.min.js"></script>
|
||||
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsSHA/1.5.0/sha.js"></script>
|
||||
<script src="config.js?v=11"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="libs/strophe/strophe.min.js?v=2"></script>
|
||||
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
|
||||
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
|
||||
- <script src="libs/jquery-ui.js"></script>
|
||||
+ <script src="libs/jquery-ui.min.js"></script>
|
||||
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||
- <script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
|
||||
1
debian/patches/series
vendored
1
debian/patches/series
vendored
@@ -1 +0,0 @@
|
||||
jquery-package
|
||||
@@ -2,11 +2,12 @@
|
||||
1. Go to https://icomoon.io/app/
|
||||
2. Go to "Manage Projects" from the menu on the top left.
|
||||
3. Use "Import project" and select <code>fonts/selection.json</code> from Jitsi Meet.
|
||||
4. Import icons (e.g. svg files) using the "import items" button.
|
||||
5. Go to "generate font" and make sure the identifiers for the new icons are correct.
|
||||
6. Download the result in a zip file using the "download" button.
|
||||
7. Copy <code>selection.json</code> and <code>fonts/jitsi.*</code> from the zip file to <code>fonts/</code> in Jitsi Meet
|
||||
8. Copy the class for the new icon from <code>style.css</code> in the zip file to <code>css/font.css</code> in Jitsi Meet (do *not* copy the whole file)
|
||||
4. Click "load".
|
||||
5. Add the new icons using the "Add icons from library" button...
|
||||
6. Go to "generate font" and make sure the identifiers for the new icons are correct.
|
||||
7. Download the result in a zip file using the "download" button.
|
||||
8. Copy <code>selection.json</code> and <code>fonts/jitsi.*</code> from the zip file to <code>fonts/</code> in Jitsi Meet
|
||||
9. Copy the class for the new icon from <code>style.css</code> in the zip file to <code>css/_font.scss</code> in Jitsi Meet (do *not* copy the whole file)
|
||||
10. Copy the <code>selection.json</code> file to <code>react/features/base/font-icons</code> overwriting <code>jitsi.json</code>
|
||||
|
||||
Sample commit: https://github.com/jitsi/jitsi-meet/commit/68bc819b89aec12364fcf07b81efa83a1900eed6
|
||||
|
||||
|
||||
84
doc/api.md
84
doc/api.md
@@ -27,6 +27,8 @@ Its constructor gets a number of options:
|
||||
* **interfaceConfigOverwrite**: (optional) JS object with overrides for options defined in [interface_config.js].
|
||||
* **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:
|
||||
|
||||
@@ -96,11 +98,6 @@ api.executeCommand('toggleFilmStrip')
|
||||
api.executeCommand('toggleChat')
|
||||
```
|
||||
|
||||
* **toggleContactList** - Hides / shows the contact list. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleContactList')
|
||||
```
|
||||
|
||||
* **toggleShareScreen** - Starts / stops screen sharing. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleShareScreen')
|
||||
@@ -140,18 +137,33 @@ The `event` parameter is a String object with the name of the event.
|
||||
The `listener` parameter is a Function object with one argument that will be notified when the event occurs with data related to the event.
|
||||
|
||||
The following events are currently supported:
|
||||
* **avatarChanged** - event notifications about avatar
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id, // the id of the participant that changed his avatar.
|
||||
"avatarURL": avatarURL // the new avatar URL.
|
||||
}
|
||||
```
|
||||
|
||||
* **audioAvailabilityChanged** - event notifications about audio availability status changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"available": available // new available status - boolean
|
||||
"available": available // new available status - boolean
|
||||
}
|
||||
```
|
||||
|
||||
* **audioMuteStatusChanged** - event notifications about audio mute status changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"muted": muted // new muted status - boolean
|
||||
"muted": muted // new muted status - boolean
|
||||
}
|
||||
```
|
||||
|
||||
* **screenSharingStatusChanged** - receives event notifications about turning on/off the local user screen sharing. The listener will receive object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"on": on //whether screen sharing is on
|
||||
}
|
||||
```
|
||||
|
||||
@@ -159,9 +171,9 @@ The following events are currently supported:
|
||||
messages. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"from": from, // JID of the user that sent the message
|
||||
"nick": nick, // the nickname of the user that sent the message
|
||||
"message": txt // the text of the message
|
||||
"from": from, // The id of the user that sent the message
|
||||
"nick": nick, // the nickname of the user that sent the message
|
||||
"message": txt // the text of the message
|
||||
}
|
||||
```
|
||||
|
||||
@@ -169,58 +181,62 @@ messages. The listener will receive an object with the following structure:
|
||||
messages. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"message": txt // the text of the message
|
||||
"message": txt // the text of the message
|
||||
}
|
||||
```
|
||||
|
||||
* **displayNameChanged** - event notifications about display name
|
||||
* **displayNameChange** - event notifications about display name
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"jid": jid, // the JID of the participant that changed his display name
|
||||
"displayname": displayName // the new display name
|
||||
"id": id, // the id of the participant that changed his display name
|
||||
"displayname": displayName // the new display name
|
||||
}
|
||||
```
|
||||
|
||||
* **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"jid": jid // the JID of the participant
|
||||
"id": id, // the id of the participant
|
||||
"displayName": displayName // the display name of the participant
|
||||
}
|
||||
```
|
||||
|
||||
* **participantLeft** - event notifications about participants that leave the room. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"jid": jid // the JID of the participant
|
||||
"id": id // the id of the participant
|
||||
}
|
||||
```
|
||||
|
||||
* **videoConferenceJoined** - event notifications fired when the local user has joined the video conference. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"roomName": room // the room name of the conference
|
||||
"roomName": room, // the room name of the conference
|
||||
"id": id, // the id of the local participant
|
||||
"displayName": displayName, // the display name of the local participant
|
||||
"avatarURL": avatarURL // the avatar URL of the local participant
|
||||
}
|
||||
```
|
||||
|
||||
* **videoConferenceLeft** - event notifications fired when the local user has left the video conference. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"roomName": room // the room name of the conference
|
||||
"roomName": room // the room name of the conference
|
||||
}
|
||||
```
|
||||
|
||||
* **videoAvailabilityChanged** - event notifications about video availability status changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"available": available // new available status - boolean
|
||||
"available": available // new available status - boolean
|
||||
}
|
||||
```
|
||||
|
||||
* **videoMuteStatusChanged** - event notifications about video mute status changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"muted": muted // new muted status - boolean
|
||||
"muted": muted // new muted status - boolean
|
||||
}
|
||||
```
|
||||
|
||||
@@ -264,6 +280,16 @@ You can get the number of participants in the conference with the following API
|
||||
var numberOfParticipants = api.getNumberOfParticipants();
|
||||
```
|
||||
|
||||
You can get the avatar URL of a participant in the conference with the following API function:
|
||||
```javascript
|
||||
var avatarURL = api.getAvatarURL(participantId);
|
||||
```
|
||||
|
||||
You can get the display name of a participant in the conference with the following API function:
|
||||
```javascript
|
||||
var displayName = api.getDisplayName(participantId);
|
||||
```
|
||||
|
||||
You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
|
||||
```javascript
|
||||
var iframe = api.getIFrame();
|
||||
@@ -271,32 +297,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()
|
||||
|
||||
@@ -45,7 +45,7 @@ modules_enabled = {
|
||||
-- Not essential, but recommended
|
||||
"private"; -- Private XML storage (for room bookmarks, etc.)
|
||||
"vcard"; -- Allow users to set vCards
|
||||
|
||||
|
||||
-- These are commented by default as they have a performance impact
|
||||
--"privacy"; -- Support privacy lists
|
||||
"compression"; -- Stream compression (requires the lua-zlib package installed)
|
||||
@@ -181,6 +181,13 @@ VirtualHost "jitsi.example.com"
|
||||
|
||||
c2s_require_encryption = false
|
||||
|
||||
VirtualHost "auth.jitsi.example.com"
|
||||
ssl = {
|
||||
key = "/var/lib/prosody/auth.jitsi.example.com.key";
|
||||
certificate = "/var/lib/prosody/auth.jitsi.example.com.crt";
|
||||
}
|
||||
authentication = "internal_plain"
|
||||
|
||||
------ Components ------
|
||||
-- You can specify components to add hosts that provide special services,
|
||||
-- like multi-user conferences, and transports.
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Server Installation for Jitsi Meet
|
||||
|
||||
|
||||
:warning: **WARNING:** Manual installation is not recommended. We recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document. The current document describes the steps that are needed to install a working deployment, but steps are easy to mess up, and the debian packages are more up-to-date, where this document sometimes is not updated to latest changes.
|
||||
|
||||
|
||||
|
||||
This describes configuring a server `jitsi.example.com` running Debian or a Debian Derivative. You will need to
|
||||
change references to that to match your host, and generate some passwords for
|
||||
`YOURSECRET1`, `YOURSECRET2` and `YOURSECRET3`.
|
||||
@@ -60,6 +65,10 @@ VirtualHost "jitsi.example.com"
|
||||
- add domain with authentication for conference focus user:
|
||||
```
|
||||
VirtualHost "auth.jitsi.example.com"
|
||||
ssl = {
|
||||
key = "/var/lib/prosody/auth.jitsi.example.com.key";
|
||||
certificate = "/var/lib/prosody/auth.jitsi.example.com.crt";
|
||||
}
|
||||
authentication = "internal_plain"
|
||||
```
|
||||
- add focus user to server admins:
|
||||
@@ -83,8 +92,16 @@ ln -s /etc/prosody/conf.avail/jitsi.example.com.cfg.lua /etc/prosody/conf.d/jits
|
||||
Generate certs for the domain:
|
||||
```sh
|
||||
prosodyctl cert generate jitsi.example.com
|
||||
prosodyctl cert generate auth.jitsi.example.com
|
||||
```
|
||||
|
||||
Add auth.jitsi.example.com to the trusted certificates on the local machine:
|
||||
```sh
|
||||
ln -sf /var/lib/prosody/auth.jitsi.example.com.crt /usr/local/share/ca-certificates/auth.jitsi.example.com.crt
|
||||
update-ca-certificates -f
|
||||
```
|
||||
Note that the `-f` flag is necessary if there are symlinks left from a previous installation.
|
||||
|
||||
Create conference focus user:
|
||||
```sh
|
||||
prosodyctl register focus auth.jitsi.example.com YOURSECRET3
|
||||
|
||||
@@ -20,6 +20,14 @@ wget -qO - https://download.jitsi.org/jitsi-key.gpg.key | apt-key add -
|
||||
apt-get update
|
||||
```
|
||||
|
||||
(If you get an error:
|
||||
E: The method driver /usr/lib/apt/methods/https could not be found.
|
||||
run:
|
||||
```sh
|
||||
apt-get install apt-transport-https
|
||||
```
|
||||
)
|
||||
|
||||
### Install Jitsi Meet
|
||||
|
||||
Note : Something to consider before installation is how you're planning to serve Jitsi Meet. The installer will check if Nginx or Apache is present (with this order) and configure a virtualhost within the web server it finds to serve Jitsi Meet. If none of the above is found it then configures itself to be served via jetty. So if for example you are planning on deploying Jitsi Meet with a web server, you have to make sure to install the server **before** installing jitsi-meet.
|
||||
@@ -84,7 +92,7 @@ Enjoy!
|
||||
## Uninstall
|
||||
|
||||
```sh
|
||||
apt-get purge jigasi jitsi-meet jitsi-meet-web-config jitsi-meet-web jicofo jitsi-videobridge
|
||||
apt-get purge jigasi jitsi-meet jitsi-meet-web-config jitsi-meet-prosody jitsi-meet-web jicofo jitsi-videobridge
|
||||
```
|
||||
|
||||
Sometimes the following packages will fail to uninstall properly:
|
||||
|
||||
57
doc/sipgw-config.md
Normal file
57
doc/sipgw-config.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Configuring sipgw jibri with jitsi-meet
|
||||
|
||||
This document describes how you can configure jitsi-meet to use sipgw jibri and enable rooms in 'Add people dialog'
|
||||
You will need a working deployment of jibri configured to use a regular sip video device, for more info check out the [jibri documentation](https://github.com/jitsi/jibri/blob/master/README.md).
|
||||
|
||||
This feature is available for non-guests of the system, so this relies on setting in config.js ``enableUserRolesBasedOnToken: true`` and providing a jwt token when accessing the conference.
|
||||
|
||||
* Jicofo configuration:
|
||||
edit /etc/jitsi/jicofo/sip-communicator.properties (or similar), set the appropriate MUC to look for the Jibri Controllers. This should be the same MUC as is referenced in jibri's config.json file. Restart Jicofo after setting this property.
|
||||
|
||||
```
|
||||
org.jitsi.jicofo.jibri.SIP_BREWERY=TheSipBrewery@conference.yourdomain.com
|
||||
```
|
||||
|
||||
* Jitsi Meet configuration:
|
||||
- config.js: add
|
||||
```
|
||||
enableUserRolesBasedOnToken: true,
|
||||
peopleSearchQueryTypes: ['conferenceRooms'],
|
||||
peopleSearchUrl: 'https://api.yourdomain.com/testpath/searchpeople',
|
||||
```
|
||||
- interface_config.js:
|
||||
```
|
||||
ADD_PEOPLE_APP_NAME: 'Jitsi'
|
||||
```
|
||||
|
||||
The combination of the above settings and providing a jwt token will enable a button under invite option which will show the dialog 'Add people'.
|
||||
|
||||
## People search service
|
||||
|
||||
When searching in the dialog, a request for results is made to the `peopleSearchUrl` service.
|
||||
|
||||
The request is in the following format:
|
||||
```
|
||||
https://api.yourdomain.com/testpath/searchpeople?query=testroomname&queryTypes=[%22conferenceRooms%22]&jwt=somejwt
|
||||
```
|
||||
The parameters are:
|
||||
- query - The text entered by the user.
|
||||
- queryTypes - What type of results we want people, rooms, conferenceRooms. This is the value from config.js `peopleSearchQueryTypes`
|
||||
- jwt - The token used by the user to access the conference.
|
||||
|
||||
The response of the service is a json in the following format:
|
||||
```
|
||||
[
|
||||
{
|
||||
"id": "address@sip.domain.com",
|
||||
"name": "Some room name",
|
||||
"type": "videosipgw"
|
||||
},
|
||||
{
|
||||
"id": "address2@sip.domain.com",
|
||||
"name": "Some room name2",
|
||||
"type": "videosipgw"
|
||||
}
|
||||
]
|
||||
```
|
||||
Type should be `videosipgw`, `name` is the name shown to the user and `id` is the sip address to be called by the sipgw jibri.
|
||||
4678
flow-typed/npm/lodash_v4.x.x.js
vendored
4678
flow-typed/npm/lodash_v4.x.x.js
vendored
File diff suppressed because it is too large
Load Diff
48
flow-typed/npm/react-redux_v5.x.x.js
vendored
48
flow-typed/npm/react-redux_v5.x.x.js
vendored
@@ -1,8 +1,5 @@
|
||||
// flow-typed signature: 4e93c65cedbfbf7f1ab3fe4e800943d3
|
||||
// flow-typed version: 9092387fd2/react-redux_v5.x.x/flow_>=v0.53.x <=v0.53.x
|
||||
|
||||
// flow-typed signature: 8db7b853f57c51094bf0ab8b2650fd9c
|
||||
// flow-typed version: ab8db5f14d/react-redux_v5.x.x/flow_>=v0.30.x
|
||||
// flow-typed signature: 59b0c4be0e1408f21e2446be96c79804
|
||||
// flow-typed version: 9092387fd2/react-redux_v5.x.x/flow_>=v0.54.x
|
||||
|
||||
import type { Dispatch, Store } from "redux";
|
||||
|
||||
@@ -34,6 +31,22 @@ declare module "react-redux" {
|
||||
|
||||
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>,
|
||||
@@ -41,13 +54,18 @@ declare module "react-redux" {
|
||||
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> = (
|
||||
component: React$ComponentType<P>
|
||||
) => ConnectedComponentClass<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<{
|
||||
store: Store<S, A>,
|
||||
@@ -77,12 +95,12 @@ declare module "react-redux" {
|
||||
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<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,
|
||||
@@ -93,7 +111,7 @@ declare module "react-redux" {
|
||||
|
||||
declare function connect<S, A, OP, SP, DP>(
|
||||
mapStateToProps: MapStateToProps<S, OP, SP>,
|
||||
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
|
||||
mapDispatchToProps: MapDispatchToProps<A, OP, DP> | Null,
|
||||
mergeProps: Null,
|
||||
options?: ConnectOptions
|
||||
): Connector<OP, $Supertype<SP & DP & OP>>;
|
||||
|
||||
58
flow-typed/npm/redux_v3.x.x.js
vendored
58
flow-typed/npm/redux_v3.x.x.js
vendored
@@ -1,5 +1,5 @@
|
||||
// flow-typed signature: 33b83b6284653250e74578cf4dbe6124
|
||||
// flow-typed version: e282e4128f/redux_v3.x.x/flow_>=v0.33.x
|
||||
// flow-typed signature: ec7daead5cb4fec5ab25fedbedef29e8
|
||||
// flow-typed version: 2c04631d20/redux_v3.x.x/flow_>=v0.55.x
|
||||
|
||||
declare module 'redux' {
|
||||
|
||||
@@ -55,55 +55,5 @@ declare module 'redux' {
|
||||
|
||||
declare export function combineReducers<O: Object, A>(reducers: O): CombinedReducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
|
||||
|
||||
declare export function compose<A, B>(ab: (a: A) => B): (a: A) => B
|
||||
declare export function compose<A, B, C>(
|
||||
bc: (b: B) => C,
|
||||
ab: (a: A) => B
|
||||
): (a: A) => C
|
||||
declare export function compose<A, B, C, D>(
|
||||
cd: (c: C) => D,
|
||||
bc: (b: B) => C,
|
||||
ab: (a: A) => B
|
||||
): (a: A) => D
|
||||
declare export function compose<A, B, C, D, E>(
|
||||
de: (d: D) => E,
|
||||
cd: (c: C) => D,
|
||||
bc: (b: B) => C,
|
||||
ab: (a: A) => B
|
||||
): (a: A) => E
|
||||
declare export function compose<A, B, C, D, E, F>(
|
||||
ef: (e: E) => F,
|
||||
de: (d: D) => E,
|
||||
cd: (c: C) => D,
|
||||
bc: (b: B) => C,
|
||||
ab: (a: A) => B
|
||||
): (a: A) => F
|
||||
declare export function compose<A, B, C, D, E, F, G>(
|
||||
fg: (f: F) => G,
|
||||
ef: (e: E) => F,
|
||||
de: (d: D) => E,
|
||||
cd: (c: C) => D,
|
||||
bc: (b: B) => C,
|
||||
ab: (a: A) => B
|
||||
): (a: A) => G
|
||||
declare export function compose<A, B, C, D, E, F, G, H>(
|
||||
gh: (g: G) => H,
|
||||
fg: (f: F) => G,
|
||||
ef: (e: E) => F,
|
||||
de: (d: D) => E,
|
||||
cd: (c: C) => D,
|
||||
bc: (b: B) => C,
|
||||
ab: (a: A) => B
|
||||
): (a: A) => H
|
||||
declare export function compose<A, B, C, D, E, F, G, H, I>(
|
||||
hi: (h: H) => I,
|
||||
gh: (g: G) => H,
|
||||
fg: (f: F) => G,
|
||||
ef: (e: E) => F,
|
||||
de: (d: D) => E,
|
||||
cd: (c: C) => D,
|
||||
bc: (b: B) => C,
|
||||
ab: (a: A) => B
|
||||
): (a: A) => I
|
||||
|
||||
}
|
||||
declare export var compose: $Compose;
|
||||
}
|
||||
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
@@ -11,11 +11,21 @@
|
||||
<glyph unicode="" glyph-name="add" d="M810 470h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" />
|
||||
<glyph unicode="" glyph-name="bluetooth" d="M550 328l-80 82v-162zM470 776v-162l80 82zM670 696l-184-184 184-184-244-242h-42v324l-196-196-60 60 238 238-238 238 60 60 196-196v324h42zM834 738c40-64 62-142 62-222 0-84-24-160-66-226l-50 50c26 52 42 110 42 172s-16 120-42 172zM608 512l98 98c12-30 20-64 20-98s-8-70-20-100z" />
|
||||
<glyph unicode="" glyph-name="headset" d="M512 982c212 0 384-172 384-384v-300c0-70-58-128-128-128h-128v342h170v86c0 166-132 298-298 298s-298-132-298-298v-86h170v-342h-128c-70 0-128 58-128 128v300c0 212 172 384 384 384z" />
|
||||
<glyph unicode="" glyph-name="navigate_before" d="M658 708l-196-196 196-196-60-60-256 256 256 256z" />
|
||||
<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="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" />
|
||||
<glyph unicode="" glyph-name="recDisable" horiz-adv-x="1140" d="M1123.444 1003.015c-23.593 26.481-64.131 28.989-90.74 5.395l-1008.269-893.436c-26.609-23.468-28.991-64.131-5.46-90.676 12.674-14.306 30.308-21.649 48.126-21.649 15.123 0 30.372 5.401 42.544 16.195l130.045 115.22c90.743-81.844 210.569-132.165 342.473-132.101 282.816 0.061 510.913 227.969 511.287 510.972 0.126 109.934-34.682 211.367-93.499 294.72l118.088 104.625c26.483 23.526 28.997 64.129 5.404 90.735zM944.422 513.818c0.128-200.922-161.896-363.201-362.509-362.952-87.56 0.123-167.573 31.151-230.061 82.569l331.277 293.509v-73.176c1.071-60.993 32.696-92.18 94.944-93.692 61.997 1.512 93.686 32.763 95.131 93.756v41.096h-72.227v-47.499c0.251-4.642-0.564-10.607-2.511-17.949-1.25-3.261-3.448-6.020-6.525-8.093-3.197-2.572-7.845-3.828-13.868-3.828-10.543 0.31-17.132 4.268-19.827 11.921-1.068 3.512-1.947 6.905-2.508 10.163-0.254 2.887-0.377 5.532-0.377 7.786v143.511l42.477 37.634c0.215-0.432 0.452-0.851 0.63-1.303 1.947-6.467 2.762-12.799 2.511-19.076v-36.772h72.227v30.121c-0.246 31.245-9.086 54.699-26.363 70.447l40.711 36.069c35.787-56.055 56.803-122.585 56.867-194.244zM239.795 395.47c-12.613 37.023-19.827 76.557-19.827 117.913-0.19 200.236 161.584 362.009 361.945 362.135 56.853 0 110.313-13.302 158.133-36.398l117.846 104.421c-79.444 50.952-173.758 80.817-275.292 80.948-283.377 0.181-511.354-227.729-511.789-511.675-0.126-79.567 18.636-154.679 51.137-221.882l117.848 104.538zM388.576 690.020h-97.514v-249.057l72.23 64.070v0.689h0.815l117.72 104.418c0 0.564 0.123 0.94 0.123 1.509 0.753 53.898-30.369 80.069-93.374 78.37zM405.959 625.517c1.942-2.767 3.074-6.469 3.323-11.112 0.312-4.452 0.438-9.6 0.438-15.246 0.251-10.916-0.689-19.83-2.949-26.985-2.952-7.594-10.983-11.357-24.159-11.357h-19.325v74.043h15.31c7.842 0 13.865-0.683 18.072-2.19 4.397-1.573 7.468-3.953 9.29-7.153z" />
|
||||
<glyph unicode="" glyph-name="recEnable" horiz-adv-x="1142" d="M581.278 1025.708c284.857-0.19 514.807-230.517 514.427-514.997-0.378-285.047-230.073-514.553-514.869-514.615-284.541-0.062-515.311 230.517-514.933 514.422 0.439 285.936 230.009 515.439 515.375 515.19zM580.579 875.756c-201.764-0.123-364.666-163.032-364.478-364.663 0-202.018 162.524-364.735 364.478-364.984 202.018-0.316 365.174 163.030 365.048 365.423-0.252 201.767-163.156 364.35-365.048 364.224zM287.698 688.907h98.196c63.442 1.767 94.785-24.518 94.027-78.863 0.254-19.081-2.211-34.882-7.456-47.521-6.005-12.508-18.706-21.988-38.167-28.181v-0.819c28.373-6.259 43.031-23.573 43.981-51.946v-57.689c0-11.247 0.254-22.813 0.758-34.756 0.819-12.005 3.033-20.979 6.696-27.043h-71.846c-3.727 6.064-6.128 15.038-7.14 27.043-1.012 11.943-1.454 23.509-1.138 34.756v52.321c0 9.603-2.214 16.553-6.573 20.979-4.675 4.107-12.701 6.19-24.012 6.19h-14.599v-141.291h-72.73v326.82zM360.428 558.861h19.463c13.271 0 21.359 3.794 24.331 11.375 2.276 7.204 3.221 16.304 2.969 27.171 0 5.815-0.126 10.867-0.442 15.418-0.252 4.675-1.392 8.404-3.352 11.247-1.831 3.157-4.926 5.561-9.352 7.14-4.233 1.454-10.299 2.211-18.2 2.211h-15.418v-74.564zM498.372 688.907h162.082v-62.687h-89.35v-65.587h78.103v-62.685h-78.103v-73.11h92.822v-62.749h-165.557v326.818zM682.507 599.999c0.316 31.782 9.416 55.542 27.425 71.407 17.44 15.29 40.185 22.936 68.181 22.936 28.247 0 51.119-7.646 68.623-23 17.82-15.798 26.92-39.623 27.171-71.407v-30.333h-72.73v37.031c0.254 6.192-0.57 12.639-2.527 19.209-1.264 3.157-3.475 5.938-6.573 8.214-3.221 1.515-7.898 2.404-13.964 2.404-10.615-0.316-17.249-3.855-19.967-10.618-2.211-6.573-3.223-13.017-2.907-19.209v-161.956c0-2.273 0.126-4.865 0.38-7.772 0.568-3.411 1.454-6.824 2.527-10.233 2.717-7.775 9.352-11.756 19.967-12.007 6.067 0 10.744 1.261 13.964 3.791 3.098 2.15 5.309 4.867 6.573 8.216 1.96 7.33 2.782 13.33 2.527 18.007v47.837h72.73v-41.328c-1.451-61.547-33.364-93.015-95.794-94.469-62.685 1.454-94.53 32.922-95.607 94.343v148.937z" />
|
||||
<glyph unicode="" glyph-name="event_note" d="M598 426v-84h-300v84h300zM810 214v468h-596v-468h596zM810 896c46 0 86-40 86-86v-596c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h42v86h86v-86h340v86h86v-86h42zM726 598v-86h-428v86h428z" />
|
||||
<glyph unicode="" glyph-name="phone-talk" d="M640 512c0 70-58 128-128 128v86c118 0 214-96 214-214h-86zM810 512c0 166-132 298-298 298v86c212 0 384-172 384-384h-86zM854 362c24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44l-94-94c62-122 162-220 282-282l94 94c12 12 30 14 44 10 48-16 98-24 152-24z" />
|
||||
<glyph unicode="" glyph-name="public" d="M764 282c56 60 90 142 90 230 0 142-88 266-214 316v-18c0-46-40-84-86-84h-84v-86c0-24-20-42-44-42h-84v-86h256c24 0 42-18 42-42v-128h42c38 0 70-26 82-60zM470 174v82c-46 0-86 40-86 86v42l-204 204c-6-24-10-50-10-76 0-174 132-318 300-338zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
||||
<glyph unicode="" glyph-name="open_in_new" d="M598 896h298v-298h-86v152l-418-418-60 60 418 418h-152v86zM810 214v298h86v-298c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h298v-86h-298v-596h596z" />
|
||||
<glyph unicode="" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" />
|
||||
<glyph unicode="" glyph-name="AUD" 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-512zM308.25 387.3h57.225l-87.675 252.525h-62.125l-87.675-252.525h53.025l19.425 60.2h88.725l19.075-60.2zM461.9 639.825h-52.85v-165.375c0-56 41.125-93.625 105.7-93.625 64.75 0 105.875 37.625 105.875 93.625v165.375h-52.85v-159.95c0-31.85-19.075-52.15-53.025-52.15-33.775 0-52.85 20.3-52.85 52.15v159.95zM682.225 640v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM735.075 594.85v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15zM243.5 587.325l-31.675-99.050h66.15l-31.325 99.050h-3.15z" />
|
||||
<glyph unicode="" glyph-name="avatar" d="M512 204c106 0 200 56 256 138-2 84-172 132-256 132-86 0-254-48-256-132 56-82 150-138 256-138zM512 810c-70 0-128-58-128-128s58-128 128-128 128 58 128 128-58 128-128 128zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
||||
<glyph unicode="" glyph-name="download" d="M726 470h-128v170h-172v-170h-128l214-214zM826 596c110-8 198-100 198-212 0-118-96-214-214-214h-554c-142 0-256 114-256 256 0 132 100 240 228 254 54 102 160 174 284 174 156 0 284-110 314-258z" />
|
||||
<glyph unicode="" glyph-name="mic-camera-combined" d="M756.704 628.138l267.296 202.213v-635.075l-267.296 202.213v-191.923c0-12.085-11.296-21.863-25.216-21.863h-706.272c-13.92 0-25.216 9.777-25.216 21.863v612.25c0 12.085 11.296 21.863 25.216 21.863h706.272c13.92 0 25.216-9.777 25.216-21.863v-189.679zM371.338 376.228c47.817 0 86.529 40.232 86.529 89.811v184.835c0 49.651-38.713 89.883-86.529 89.883-47.788 0-86.515-40.232-86.515-89.883v-184.835c0-49.579 38.756-89.811 86.515-89.811v0zM356.754 314.070v-32.78h33.718v33.412c73.858 9.606 131.235 73.73 131.235 151.351v88.232h-30.636v-88.232c0-67.57-53.696-122.534-119.734-122.534-66.024 0-119.691 54.964-119.691 122.534v88.232h-30.636v-88.232c0-79.215 59.674-144.502 135.744-151.969v-0.014z" />
|
||||
@@ -54,4 +64,10 @@
|
||||
<glyph unicode="" glyph-name="visibility-off" d="M506 640h6c70 0 128-58 128-128v-8zM322 606c-14-28-24-60-24-94 0-118 96-214 214-214 34 0 66 10 94 24l-66 66c-8-2-18-4-28-4-70 0-128 58-128 128 0 10 2 20 4 28zM86 842l54 54 756-756-54-54c-47.968 47.365-96.266 94.401-144 142-58-24-120-36-186-36-214 0-396 132-470 320 34 84 90 156 160 212-39.017 38.983-77.307 78.693-116 118zM512 726c-28 0-54-6-78-16l-92 92c52 20 110 30 170 30 214 0 394-132 468-320-32-80-82-148-146-202l-124 124c10 24 16 50 16 78 0 118-96 214-214 214z" />
|
||||
<glyph unicode="" glyph-name="dialpad" d="M512 982c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 726c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM768 726c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM768 470c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 470c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM768 810c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86zM256 470c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM256 726c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM256 982c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 214c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86z" />
|
||||
<glyph unicode="" glyph-name="gsm-bars-black" d="M896 1024c70.692 0 128-57.308 128-128v-768c0-70.692-57.308-128-128-128s-128 57.308-128 128v768c0 70.692 57.308 128 128 128zM512 768c70.692 0 128-57.308 128-128v-512c0-70.692-57.308-128-128-128s-128 57.308-128 128v512c0 70.692 57.308 128 128 128zM128 384v0c70.692 0 128-57.308 128-128v-128c0-70.692-57.308-128-128-128s-128 57.308-128 128v128c0 70.692 57.308 128 128 128v0z" />
|
||||
<glyph unicode="" glyph-name="HD" 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-512zM481.359 384v255.823h-54.273v-103.18h-116.813v103.18h-54.273v-255.823h54.273v106.903h116.813v-106.903h54.273zM544.258 640v-256h102.077c77.636 0 121.665 46.626 121.665 129.773 0 80.133-44.569 126.227-121.665 126.227h-102.077zM598.531 594.26v-164.521h39.177c47.983 0 74.94 29.075 74.94 83.147 0 51.767-27.855 81.374-74.94 81.374h-39.177z" />
|
||||
<glyph unicode="" glyph-name="LD" 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-512zM472.4 433.325h-112.35v206.5h-52.85v-252.525h165.2v46.025zM520.35 640v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM573.2 594.85v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15z" />
|
||||
<glyph unicode="" glyph-name="SD" 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-512zM281.6 451.175c1.925-47.075 40.95-76.65 101.15-76.65 63.35 0 102.375 31.15 102.375 82.075 0 39.2-21.875 61.075-72.625 71.75l-30.45 6.475c-29.575 6.3-41.65 15.225-41.65 30.8 0 19.25 17.5 31.5 43.925 31.5 25.55 0 44.1-13.3 46.55-33.25h49.7c-1.575 44.975-40.95 76.125-96.6 76.125-58.275 0-96.6-31.325-96.6-78.925 0-38.5 22.575-62.475 68.6-72.1l32.9-7c30.975-6.65 43.575-15.925 43.575-32.025 0-19.075-19.425-32.375-46.9-32.375-29.75 0-50.4 13.125-52.85 33.6h-51.1zM535 633.7v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM587.85 588.55v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15z" />
|
||||
<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" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 24 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.
1067
fonts/selection.json
1067
fonts/selection.json
File diff suppressed because it is too large
Load Diff
BIN
images/calendar@2x.png
Normal file
BIN
images/calendar@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
images/calendar@3x.png
Normal file
BIN
images/calendar@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
@@ -1,9 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
|
||||
<g stroke-width="1pt">
|
||||
<path fill="#006" d="M0 0h640v480H0z"/>
|
||||
<path d="M0 0v27.95L307.037 250h38.647v-27.95L38.647 0H0zm345.684 0v27.95L38.647 250H0v-27.95L307.037 0h38.647z" fill="#fff"/>
|
||||
<path d="M144.035 0v250h57.614V0h-57.615zM0 83.333v83.333h345.684V83.333H0z" fill="#fff"/>
|
||||
<path d="M0 100v50h345.684v-50H0zM155.558 0v250h34.568V0h-34.568zM0 250l115.228-83.334h25.765L25.765 250H0zM0 0l115.228 83.333H89.463L0 18.633V0zm204.69 83.333L319.92 0h25.764L230.456 83.333H204.69zM345.685 250l-115.228-83.334h25.765l89.464 64.7V250z" fill="#c00"/>
|
||||
<path d="M299.762 392.523l-43.653 3.795 6.013 43.406-30.187-31.764-30.186 31.764 6.014-43.406-43.653-3.795 37.68-22.364-24.244-36.495 40.97 15.514 13.42-41.713 13.42 41.712 40.97-15.515-24.242 36.494m224.444 62.372l-10.537-15.854 17.81 6.742 5.824-18.125 5.825 18.126 17.807-6.742-10.537 15.854 16.37 9.718-18.965 1.65 2.616 18.85-13.116-13.793-13.117 13.794 2.616-18.85-18.964-1.65m16.368-291.815l-10.537-15.856 17.81 6.742 5.824-18.122 5.825 18.12 17.807-6.74-10.537 15.855 16.37 9.717-18.965 1.65 2.616 18.85-13.116-13.793-13.117 13.794 2.616-18.85-18.964-1.65m-89.418 104.883l-10.537-15.853 17.808 6.742 5.825-18.125 5.825 18.125 17.808-6.742-10.536 15.853 16.37 9.72-18.965 1.65 2.615 18.85-13.117-13.795-13.117 13.795 2.617-18.85-18.964-1.65m216.212-37.929l-10.558-15.854 17.822 6.742 5.782-18.125 5.854 18.125 17.772-6.742-10.508 15.854 16.362 9.718-18.97 1.65 2.608 18.85-13.118-13.793-13.117 13.793 2.61-18.85-18.936-1.65m-22.251 73.394l-10.367 6.425 2.914-11.84-9.316-7.863 12.165-.896 4.605-11.29 4.606 11.29 12.165.897-9.317 7.863 2.912 11.84" fill-rule="evenodd" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user