mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-31 03:42:29 +00:00
Compare commits
617 Commits
node_10_bu
...
3397
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a14fead0f3 | ||
|
|
466e1e3eb8 | ||
|
|
8a90f0dab1 | ||
|
|
d7e0aa3f61 | ||
|
|
37b343a797 | ||
|
|
149485905c | ||
|
|
7f1df5629e | ||
|
|
f42d0411b1 | ||
|
|
8d1d573266 | ||
|
|
d86b60ea72 | ||
|
|
dfe5fbb702 | ||
|
|
09f881c0f5 | ||
|
|
f1546008f9 | ||
|
|
d8df7fde84 | ||
|
|
1c809eb428 | ||
|
|
e94edcd4ae | ||
|
|
b48651396f | ||
|
|
e2044074ad | ||
|
|
f060ac9db1 | ||
|
|
5a53d7f32a | ||
|
|
4eec13da1c | ||
|
|
cb8282dfe5 | ||
|
|
5cd0b1a9be | ||
|
|
504fadaf71 | ||
|
|
7187e540a8 | ||
|
|
34dffbfc5e | ||
|
|
a9637f93c3 | ||
|
|
0e8b0a9c5c | ||
|
|
e66b596a0d | ||
|
|
6f320f463d | ||
|
|
02955ab57c | ||
|
|
a9d76a2577 | ||
|
|
dcf31baf3a | ||
|
|
1e346f10ab | ||
|
|
a114d55fac | ||
|
|
afde717ca4 | ||
|
|
fb5a45f714 | ||
|
|
f5ac18da18 | ||
|
|
103ae363f6 | ||
|
|
9bde673397 | ||
|
|
ff6b27eafa | ||
|
|
8cb19ccbf6 | ||
|
|
198eba3682 | ||
|
|
a49f62238b | ||
|
|
a8233bdb84 | ||
|
|
ec2826e0fc | ||
|
|
3d9606f6da | ||
|
|
01458eeff9 | ||
|
|
0318568a30 | ||
|
|
4d04141f24 | ||
|
|
afbc622fb9 | ||
|
|
fbc7f865ec | ||
|
|
2a4bac7a27 | ||
|
|
b45a5da6e2 | ||
|
|
2fad9f9ba8 | ||
|
|
8b0e5b9d15 | ||
|
|
0889ffdf27 | ||
|
|
86d0d4fc22 | ||
|
|
7e9df74e60 | ||
|
|
3eca67e1ad | ||
|
|
c040b3a7dd | ||
|
|
3f4a71c26d | ||
|
|
e7db8d6812 | ||
|
|
7d2ac0244d | ||
|
|
c0efea5168 | ||
|
|
5ed53dcef5 | ||
|
|
746159a1ac | ||
|
|
43a8fd2a53 | ||
|
|
a26bb2c1a6 | ||
|
|
e4af5ddbe9 | ||
|
|
768cff48a4 | ||
|
|
384f0d4317 | ||
|
|
0ec4e6a805 | ||
|
|
5cc01b074e | ||
|
|
deaf5ba612 | ||
|
|
740c1eb84f | ||
|
|
2d45709a6a | ||
|
|
6bbc2927ab | ||
|
|
08891b17b6 | ||
|
|
aab3428347 | ||
|
|
bf7b1c5cfc | ||
|
|
7b347baab6 | ||
|
|
f9b3d470e9 | ||
|
|
7b78fa45f4 | ||
|
|
34dcbd991e | ||
|
|
70dc22c107 | ||
|
|
89719520e2 | ||
|
|
ce7bdb35ac | ||
|
|
bf8c716477 | ||
|
|
93e8d755d3 | ||
|
|
c09eee0985 | ||
|
|
4f6a0d7d3a | ||
|
|
dd5233d31b | ||
|
|
31638133b7 | ||
|
|
b886f8d72d | ||
|
|
a6555c5d24 | ||
|
|
98c8fb09c4 | ||
|
|
b76b261cab | ||
|
|
0b6c51f666 | ||
|
|
1cb9bbc7a4 | ||
|
|
fee9bdb98c | ||
|
|
fb82cf4517 | ||
|
|
85388b8d23 | ||
|
|
33f133ac25 | ||
|
|
774c5ecd18 | ||
|
|
ccc5e19e3c | ||
|
|
32a81b0be5 | ||
|
|
59db39d4d9 | ||
|
|
2219298501 | ||
|
|
f92d530b0a | ||
|
|
e98c169c2f | ||
|
|
6bf962817b | ||
|
|
4c286b8580 | ||
|
|
37639a5614 | ||
|
|
b7198ba4b3 | ||
|
|
2180d33e3d | ||
|
|
43a0ae578e | ||
|
|
154200460d | ||
|
|
9a92dc578c | ||
|
|
eb38300c0d | ||
|
|
37b1ccbe61 | ||
|
|
725f9b1961 | ||
|
|
b172639237 | ||
|
|
c7013f5c4b | ||
|
|
aefa836406 | ||
|
|
f6c410610a | ||
|
|
603d161788 | ||
|
|
c1f8a35156 | ||
|
|
866dc4dbc6 | ||
|
|
69a12395d2 | ||
|
|
17627291e8 | ||
|
|
30669c7699 | ||
|
|
4abc2db24a | ||
|
|
2b4ace75ae | ||
|
|
3217ef2bb4 | ||
|
|
e5caca9cfd | ||
|
|
42c85c22a9 | ||
|
|
fd0eef4c84 | ||
|
|
ab022c62f5 | ||
|
|
f9c0c3e2f6 | ||
|
|
70ec7c5b3d | ||
|
|
ca38bd53fe | ||
|
|
b3cae9a962 | ||
|
|
b768b88491 | ||
|
|
9f339c452b | ||
|
|
c34f9cf233 | ||
|
|
76642b7c4b | ||
|
|
b78989f5f2 | ||
|
|
088b5d95c2 | ||
|
|
c6e5adbe0e | ||
|
|
8bb56be317 | ||
|
|
84b917d708 | ||
|
|
18d908ce84 | ||
|
|
3987655f2a | ||
|
|
ee3b8af4cf | ||
|
|
9625be1db3 | ||
|
|
21c0745504 | ||
|
|
26c9ee5f9c | ||
|
|
9b7af64e11 | ||
|
|
f26c5570df | ||
|
|
d37a0eee3a | ||
|
|
024fc73e63 | ||
|
|
bdaabf6d3d | ||
|
|
7a677ead93 | ||
|
|
e7812c7d84 | ||
|
|
ea54713f9a | ||
|
|
5a99697ae2 | ||
|
|
ec09085a50 | ||
|
|
b731459ea4 | ||
|
|
f73d3a4063 | ||
|
|
4c3cf8c14a | ||
|
|
1f371ab055 | ||
|
|
dfeb26597b | ||
|
|
384f7d7890 | ||
|
|
4d9dcf5d43 | ||
|
|
e217e10af5 | ||
|
|
7feec7c11d | ||
|
|
36eb27e233 | ||
|
|
b791fc32fd | ||
|
|
b1a70240fc | ||
|
|
50d7c1521f | ||
|
|
5d9762b429 | ||
|
|
4e78502c9e | ||
|
|
6ff733dae0 | ||
|
|
2dc59b9ea0 | ||
|
|
e65918564b | ||
|
|
ce9744b9c3 | ||
|
|
b413457a4f | ||
|
|
75daedf9ab | ||
|
|
45eeea447a | ||
|
|
16fcc55ad1 | ||
|
|
a70009e486 | ||
|
|
06502e5aac | ||
|
|
6316447d4b | ||
|
|
df5fa71b92 | ||
|
|
10e951c17c | ||
|
|
f12317dc59 | ||
|
|
829e5597d5 | ||
|
|
f2e0704b93 | ||
|
|
a7aaf31c79 | ||
|
|
4967488e56 | ||
|
|
ed1d3d3df5 | ||
|
|
427f49367b | ||
|
|
659e420005 | ||
|
|
9a46606f0d | ||
|
|
3b911b0362 | ||
|
|
7ae8ae5791 | ||
|
|
a386740103 | ||
|
|
451949f49d | ||
|
|
61ed459971 | ||
|
|
c30a4a0aa6 | ||
|
|
e839684ae9 | ||
|
|
15c5a2339b | ||
|
|
d7e112aaf0 | ||
|
|
24339b2461 | ||
|
|
5b02c575f7 | ||
|
|
1f8904a95b | ||
|
|
50268a08a0 | ||
|
|
985385f364 | ||
|
|
f662edd135 | ||
|
|
7ebcf69937 | ||
|
|
40364ae269 | ||
|
|
030af37668 | ||
|
|
e29bc4bbb6 | ||
|
|
13212a5980 | ||
|
|
2a5adfc601 | ||
|
|
19e8e8710a | ||
|
|
3b24124d57 | ||
|
|
6894de00cc | ||
|
|
6d0b6bee85 | ||
|
|
ac02a17943 | ||
|
|
043d4db314 | ||
|
|
6a919916d3 | ||
|
|
1de4897a6b | ||
|
|
0175690a2b | ||
|
|
c7979a3944 | ||
|
|
15fd27543a | ||
|
|
64f8a8d700 | ||
|
|
95f684da2f | ||
|
|
f3f8dc2072 | ||
|
|
08efc46f21 | ||
|
|
e32336b96f | ||
|
|
c91880859b | ||
|
|
cff78d7a83 | ||
|
|
49a0c03ff0 | ||
|
|
2c592f61c3 | ||
|
|
c025c7e132 | ||
|
|
278d3a163b | ||
|
|
0e92e73789 | ||
|
|
75527e01dd | ||
|
|
b53a034aaf | ||
|
|
4d1c0cf219 | ||
|
|
a667c9bff2 | ||
|
|
e652117571 | ||
|
|
a6719896a2 | ||
|
|
f5a7e0bccb | ||
|
|
f94db0da2c | ||
|
|
460593a93e | ||
|
|
19a27e75bd | ||
|
|
cb8e9eed5e | ||
|
|
2715e81f1d | ||
|
|
f941f15def | ||
|
|
7712c6913c | ||
|
|
45b6a8b5d5 | ||
|
|
26ca0e6630 | ||
|
|
7978f9f5f4 | ||
|
|
d39290f9fa | ||
|
|
f696a6dbe2 | ||
|
|
bf3bcd65d6 | ||
|
|
a7018970ca | ||
|
|
20edb7c279 | ||
|
|
59b00d022b | ||
|
|
53722fd2e6 | ||
|
|
54bab793e5 | ||
|
|
975ff9c83d | ||
|
|
5b3e8a9b5e | ||
|
|
aedcfba263 | ||
|
|
b97cb3509a | ||
|
|
7d8ea85ea0 | ||
|
|
e4c3e15791 | ||
|
|
56135bd085 | ||
|
|
549b495d16 | ||
|
|
f3abca6462 | ||
|
|
405905be82 | ||
|
|
468b02b812 | ||
|
|
3fa5aed950 | ||
|
|
90803c8ff6 | ||
|
|
dbc88b972e | ||
|
|
1c47720a08 | ||
|
|
59fc3642a6 | ||
|
|
19b4b92150 | ||
|
|
8400d01d75 | ||
|
|
d04068344a | ||
|
|
55a971c0fd | ||
|
|
20c1b1cfae | ||
|
|
ecb44b6ab4 | ||
|
|
039805eba3 | ||
|
|
22277ad799 | ||
|
|
2af1e8da95 | ||
|
|
12d0aef686 | ||
|
|
f439ad2999 | ||
|
|
81d4f694b7 | ||
|
|
c737d46d90 | ||
|
|
3f2a559d64 | ||
|
|
bdb3099073 | ||
|
|
b3a05db286 | ||
|
|
08f2edf350 | ||
|
|
98c7430b6f | ||
|
|
ebdcbe122a | ||
|
|
31c1034be7 | ||
|
|
a9d82a79ea | ||
|
|
27e1f5a1bc | ||
|
|
dbedee5e22 | ||
|
|
636c63397b | ||
|
|
67e7994e36 | ||
|
|
40f03fedc2 | ||
|
|
55149670da | ||
|
|
5739e1deaa | ||
|
|
b6e2701991 | ||
|
|
38b1be1291 | ||
|
|
555f8b3a99 | ||
|
|
ea4d49f2a0 | ||
|
|
d7eea8abbc | ||
|
|
1b8ef9a05a | ||
|
|
ac7311cb52 | ||
|
|
f498c8d402 | ||
|
|
8c8b09878c | ||
|
|
03f8d8b51a | ||
|
|
32083fc44d | ||
|
|
c4361ed7da | ||
|
|
a95d38a0f4 | ||
|
|
981600a999 | ||
|
|
0d674001d2 | ||
|
|
f7b930409b | ||
|
|
4312512d2f | ||
|
|
b7b2745dae | ||
|
|
fc129d9849 | ||
|
|
54c36198d0 | ||
|
|
16b440bbd0 | ||
|
|
d56d01cebb | ||
|
|
d872728966 | ||
|
|
877cea59e7 | ||
|
|
a4b3f8ade6 | ||
|
|
2ac5d136dc | ||
|
|
7e320a5d38 | ||
|
|
7289e59ca9 | ||
|
|
e1b989e99b | ||
|
|
070a34e30d | ||
|
|
e144c2fb3e | ||
|
|
1a5e2763c1 | ||
|
|
a4121fef36 | ||
|
|
76638f524d | ||
|
|
8ea693616d | ||
|
|
2442ef80b0 | ||
|
|
87f171caa4 | ||
|
|
e094b6516a | ||
|
|
2941f5dde4 | ||
|
|
eec7a1b628 | ||
|
|
5f7a515610 | ||
|
|
b7133f5717 | ||
|
|
f77e1dc591 | ||
|
|
4d817fc6c2 | ||
|
|
b8a7037959 | ||
|
|
6f95c50d6e | ||
|
|
9f3ef43daa | ||
|
|
46713cab3b | ||
|
|
8065cc0348 | ||
|
|
045a2d6aca | ||
|
|
d7d9bc4eeb | ||
|
|
33db155eb9 | ||
|
|
1cfd6164f5 | ||
|
|
d9cf33b4c4 | ||
|
|
2b56822a41 | ||
|
|
c34fee4305 | ||
|
|
ddc8a670f9 | ||
|
|
7c911eca96 | ||
|
|
f3c83f6e6d | ||
|
|
b9a14acd3c | ||
|
|
c998dbb47e | ||
|
|
573cc64fcd | ||
|
|
53c232fd76 | ||
|
|
3b6e34e96b | ||
|
|
8fe5814831 | ||
|
|
2305effa5c | ||
|
|
e729f0948c | ||
|
|
6f57d58dd9 | ||
|
|
d0858b95b8 | ||
|
|
5afb057387 | ||
|
|
5d86e3b674 | ||
|
|
f8294fb312 | ||
|
|
3ad99e24cf | ||
|
|
14990a427a | ||
|
|
a1383bf730 | ||
|
|
9bfe54475b | ||
|
|
d5a43426ed | ||
|
|
612028ce3c | ||
|
|
3cec4989fd | ||
|
|
77f220753f | ||
|
|
13165990fc | ||
|
|
63ff0c27a9 | ||
|
|
f2b2cfda44 | ||
|
|
1b59b21fa8 | ||
|
|
6241172af8 | ||
|
|
8e58ce7500 | ||
|
|
37d3625210 | ||
|
|
111397d944 | ||
|
|
211b3b55b1 | ||
|
|
8c0317cac0 | ||
|
|
15c8f2b125 | ||
|
|
349b1ff70e | ||
|
|
244f206a87 | ||
|
|
82963b0aaf | ||
|
|
92a412f814 | ||
|
|
4b99caa1a9 | ||
|
|
6190173ea7 | ||
|
|
54df0f5d0f | ||
|
|
4d440f5f64 | ||
|
|
10624e87f5 | ||
|
|
7f29b47f3a | ||
|
|
7c69308270 | ||
|
|
50b4212463 | ||
|
|
bb8fc8770a | ||
|
|
79209535ea | ||
|
|
4bddae0bdb | ||
|
|
c203a452f7 | ||
|
|
515d2f11ce | ||
|
|
f7134722d0 | ||
|
|
d8fa52fcaf | ||
|
|
f7162c1500 | ||
|
|
998db80db1 | ||
|
|
4575e7e119 | ||
|
|
230b2b02fa | ||
|
|
8a241ba2b7 | ||
|
|
82f714b608 | ||
|
|
8c9ba325ca | ||
|
|
693d4357a0 | ||
|
|
eef31d05cf | ||
|
|
d7475a44e4 | ||
|
|
d09bfa7c0a | ||
|
|
69dfa30142 | ||
|
|
c13424f7c0 | ||
|
|
e7d0bf7b66 | ||
|
|
653471e1c0 | ||
|
|
e960002c45 | ||
|
|
c503187dc1 | ||
|
|
71563fec5b | ||
|
|
5bf692a386 | ||
|
|
f25556e63a | ||
|
|
a5696bb3e4 | ||
|
|
29809ab024 | ||
|
|
5c0ae10ccb | ||
|
|
82f6931ee8 | ||
|
|
2ea5f3c1aa | ||
|
|
889644f7bd | ||
|
|
4898f81596 | ||
|
|
20d597e402 | ||
|
|
fcd12a9cf6 | ||
|
|
03094030a5 | ||
|
|
16f47e5e0a | ||
|
|
634f304815 | ||
|
|
148d4ebb90 | ||
|
|
a1ebba0ef7 | ||
|
|
7e231c2826 | ||
|
|
5cbddb4874 | ||
|
|
5a5dd6f5c5 | ||
|
|
c2b2b4eba4 | ||
|
|
155c7ba633 | ||
|
|
5ad98dd058 | ||
|
|
e5a8d95f1f | ||
|
|
2d57d22a3f | ||
|
|
132dd98ecf | ||
|
|
9b47dd1403 | ||
|
|
5b45542009 | ||
|
|
0b6496bf4d | ||
|
|
8ac701ab74 | ||
|
|
80bfeb6613 | ||
|
|
22a1917107 | ||
|
|
ce01b31514 | ||
|
|
274b7148b0 | ||
|
|
937c74f49e | ||
|
|
be2bd9e2e6 | ||
|
|
50d3f46934 | ||
|
|
14cc4ea54a | ||
|
|
9215b1e8b2 | ||
|
|
d996d51653 | ||
|
|
ebcde745ef | ||
|
|
fc75adc6ff | ||
|
|
3c4907ee0a | ||
|
|
914a64df7a | ||
|
|
eb34b0b11d | ||
|
|
f6d3ca23a5 | ||
|
|
fe33ad5026 | ||
|
|
380d9c75d1 | ||
|
|
7a09befd87 | ||
|
|
3b4037553a | ||
|
|
f1ca2cac96 | ||
|
|
8d1fd9841e | ||
|
|
8b399e8caf | ||
|
|
6b68fba220 | ||
|
|
d4c0840659 | ||
|
|
46a9891763 | ||
|
|
24bd62c22a | ||
|
|
fef47684d9 | ||
|
|
58887577b4 | ||
|
|
c3a91c3194 | ||
|
|
0469e5af5e | ||
|
|
eeb0697e52 | ||
|
|
e3415df6a3 | ||
|
|
b36fd96b07 | ||
|
|
07bcb38dd6 | ||
|
|
699b13066e | ||
|
|
1e83891a70 | ||
|
|
001e8fe0a7 | ||
|
|
f97869ffde | ||
|
|
0fc69416d4 | ||
|
|
f8f544c615 | ||
|
|
ac624b104f | ||
|
|
d18f582922 | ||
|
|
85b141db89 | ||
|
|
e4b1e40cc6 | ||
|
|
bc06d969b5 | ||
|
|
1ff8d52b6b | ||
|
|
5598b8443a | ||
|
|
920c179f56 | ||
|
|
b57eaed940 | ||
|
|
4da8c626f7 | ||
|
|
65519ec926 | ||
|
|
342718f673 | ||
|
|
f35653b8fa | ||
|
|
80e8afa9c1 | ||
|
|
3212bde6e6 | ||
|
|
506b15e3b5 | ||
|
|
62e7fd7e8e | ||
|
|
4bc09dd8b9 | ||
|
|
f6e6b09e78 | ||
|
|
9645de33bc | ||
|
|
22a602768c | ||
|
|
3ebad112a2 | ||
|
|
545ad0e1a6 | ||
|
|
053437c86e | ||
|
|
0a9333af02 | ||
|
|
0031fd2678 | ||
|
|
32798b1a80 | ||
|
|
2ea856acee | ||
|
|
b8b2fb2d56 | ||
|
|
f89f3f144f | ||
|
|
22199cb57a | ||
|
|
e5c9c69ec9 | ||
|
|
d48bef6c11 | ||
|
|
0c3d037cb5 | ||
|
|
47830dfc3d | ||
|
|
45291e1054 | ||
|
|
66832ada68 | ||
|
|
f11b6cbb1e | ||
|
|
34f2ff9b85 | ||
|
|
21b0ed691b | ||
|
|
8b359f48db | ||
|
|
b66e2e4104 | ||
|
|
ee8d2df355 | ||
|
|
bc77a62626 | ||
|
|
b15533d75f | ||
|
|
cf9a65f475 | ||
|
|
d7ba4a8a2a | ||
|
|
26ba974757 | ||
|
|
7614ceda68 | ||
|
|
10163274d3 | ||
|
|
2b91745af1 | ||
|
|
c9b910b1c1 | ||
|
|
e452867e12 | ||
|
|
f83d609f1a | ||
|
|
822bc31d69 | ||
|
|
cea12c9a8b | ||
|
|
05b7e6facc | ||
|
|
34e6ea2f26 | ||
|
|
9c4ca38222 | ||
|
|
5292d14412 | ||
|
|
e207ad609a | ||
|
|
35da17f5a6 | ||
|
|
fb6949f7ba | ||
|
|
9013c0db39 | ||
|
|
99542e29e6 | ||
|
|
a1ef845663 | ||
|
|
1396d59ce2 | ||
|
|
29bc18df01 | ||
|
|
1ba66e4b65 | ||
|
|
80afe30e9e | ||
|
|
957606b3f8 | ||
|
|
769a2c7c94 | ||
|
|
f349357d3c | ||
|
|
9c2f816c29 | ||
|
|
b844a9f06b | ||
|
|
d4e18e78fa | ||
|
|
f13cfe70f3 | ||
|
|
5cb4bec633 | ||
|
|
4409bbabb7 | ||
|
|
d6216f21d5 | ||
|
|
3a32f7f3f0 | ||
|
|
d5fb2c2717 | ||
|
|
c4f1588bb0 | ||
|
|
609f3887f2 | ||
|
|
77f8f85b96 | ||
|
|
14adc0b887 | ||
|
|
c288d0e18c | ||
|
|
eaafc21133 | ||
|
|
72c1fa38be | ||
|
|
45068f68db | ||
|
|
e0cbb838be | ||
|
|
c28c70fb2f | ||
|
|
280178f5d1 | ||
|
|
e9b2518f8a | ||
|
|
1e3e71c2ff | ||
|
|
007d60eb6c | ||
|
|
85f487cca5 | ||
|
|
b24e7ec5f0 | ||
|
|
a045353e6e | ||
|
|
420c466f80 | ||
|
|
7a2c465c4a |
@@ -33,7 +33,6 @@
|
||||
[libs]
|
||||
node_modules/react-native/Libraries/react-native/react-native-interface.js
|
||||
node_modules/react-native/flow/
|
||||
node_modules/react-native/flow-github/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
@@ -83,4 +82,4 @@ module.file_ext=.jsx
|
||||
module.file_ext=.json
|
||||
|
||||
[version]
|
||||
^0.78.0
|
||||
^0.92.0
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Before posting, please make sure you check https://community.jitsi.org
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
*This Issue tracker is only for reporting bugs and tracking code related issues.*
|
||||
|
||||
Before posting, please make sure you check community.jitsi.org to see if the same or similar bugs have already been discussed. General questions, installation help, and feature requests can also be posted to community.jitsi.org.
|
||||
Before posting, please make sure you check community.jitsi.org to see if the same or similar bugs have already been discussed.
|
||||
General questions, installation help, and feature requests can also be posted to community.jitsi.org.
|
||||
|
||||
## Description
|
||||
---
|
||||
10
.github/ISSUE_TEMPLATE/2-help.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/2-help.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Need help with Jitsi Meet?
|
||||
about: Please ask it in our community at https://community.jitsi.org
|
||||
|
||||
---
|
||||
|
||||
If you have a question about Jitsi Meet that is not a bug report or feature
|
||||
request, please post it in https://community.jitsi.org
|
||||
|
||||
Questions posted to this repository will be closed.
|
||||
23
.github/ISSUE_TEMPLATE/3-feature-request.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/3-feature-request.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: "Feature request"
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for suggesting an idea to make Jitsi Meet better.
|
||||
|
||||
Please fill in as much of the template below as you're able.
|
||||
|
||||
Note that the ultimate decission for implementing features lies on the Jitsi team, not all feature requests shall be accepted.
|
||||
-->
|
||||
|
||||
**Is your feature request related to a problem you are facing?**
|
||||
Please describe the problem you are trying to solve.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
Please describe the desired behavior.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
Please describe alternative solutions or features you have considered.
|
||||
|
||||
16
.github/stale.yml
vendored
Normal file
16
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 90
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- confirmed
|
||||
staleLabel: wontfix
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -35,6 +35,7 @@ xcuserdata
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
|
||||
@@ -69,10 +70,22 @@ buck-out/
|
||||
*/fastlane/Preview.html
|
||||
*/fastlane/screenshots
|
||||
|
||||
# Bundle artifact
|
||||
# Build artifacts
|
||||
*.jsbundle
|
||||
*.framework
|
||||
android/app/release
|
||||
|
||||
# precommit-hook
|
||||
.jshintignore
|
||||
.jshintrc
|
||||
|
||||
# VSCode files
|
||||
android/.project
|
||||
android/.settings/org.eclipse.buildship.core.prefs
|
||||
|
||||
# Secrets
|
||||
android/app/dropbox.key
|
||||
android/app/google-services.json
|
||||
ios/app/dropbox.key
|
||||
ios/app/GoogleService-Info.plist
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
osx_image: xcode10
|
||||
osx_image: xcode10.2
|
||||
language: objective-c
|
||||
script:
|
||||
- "./ios/travis-ci/build-ipa.sh"
|
||||
|
||||
@@ -13,7 +13,7 @@ Found a bug and know how to fix it? Great! Please read on.
|
||||
## Contributor License Agreement
|
||||
While the Jitsi projects are released under the
|
||||
[Apache License 2.0](https://github.com/jitsi/jitsi-meet/blob/master/LICENSE), the copyright
|
||||
holder and principal creator is [Atlassian](https://www.atlassian.com/). To
|
||||
holder and principal creator is [8x8](https://www.8x8.com/). To
|
||||
ensure that we can continue making these projects available under an Open Source license,
|
||||
we need you to sign our Apache-based contributor
|
||||
license agreement as either a [corporation](https://jitsi.org/ccla) or an
|
||||
|
||||
2
Makefile
2
Makefile
@@ -43,6 +43,8 @@ deploy-appbundle:
|
||||
$(BUILD_DIR)/alwaysontop.min.js \
|
||||
$(BUILD_DIR)/alwaysontop.min.map \
|
||||
$(OUTPUT_DIR)/analytics-ga.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.map \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-lib-jitsi-meet:
|
||||
|
||||
36
README.md
36
README.md
@@ -1,16 +1,16 @@
|
||||
# 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, [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).
|
||||
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. Jitsi Meet in action can be seen at [here at the session #482 of the VoIP Users Conference](http://youtu.be/7vFUVClsNh0).
|
||||
|
||||
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 .
|
||||
The Jitsi Meet client runs in your browser, without installing anything on your computer. You can try it out 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.
|
||||
Jitsi Meet allows very efficient collaboration. Users can 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.
|
||||
On the client side, no installation is necessary. You just point your browser to the URL of your deployment. This section is about installing a 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. You can also see a demonstration of the process in [this tutorial video](https://jitsi.org/tutorial).
|
||||
Installing Jitsi Meet is a simple experience. For Debian-based system, following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system. You can also see a demonstration of the process in [this tutorial video](https://jitsi.org/tutorial).
|
||||
|
||||
For other systems, or if you wish to install all components manually, see the [detailed manual installation instructions](https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md).
|
||||
|
||||
@@ -33,7 +33,7 @@ You can get our mobile versions from here:
|
||||
|
||||
## Building the sources
|
||||
|
||||
Node.js >= 8 and npm >= 6 are required.
|
||||
Node.js >= 10 and npm >= 6 are required.
|
||||
|
||||
On Debian/Ubuntu systems, the required packages can be installed with:
|
||||
```
|
||||
@@ -59,7 +59,7 @@ To work with local copy you must change the path to:
|
||||
"lib-jitsi-meet": "file:///Users/name/local-lib-jitsi-meet-copy",
|
||||
```
|
||||
|
||||
To make the project you must force it to take the sources as 'npm update' will not do it.
|
||||
To make the project you must force it to take the sources as 'npm update':
|
||||
```
|
||||
npm install lib-jitsi-meet --force && make
|
||||
```
|
||||
@@ -84,8 +84,8 @@ cd ../jitsi-meet
|
||||
npm link lib-jitsi-meet
|
||||
```
|
||||
|
||||
So now after changes in local `lib-jitsi-meet` repository you can rebuild it with `npm run install` and your `jitsi-meet` repository will use that modified library.
|
||||
Note: when using node version 4.x, the make file of jitsi-meet do npm update which will delete the link, no longer the case with version 6.x.
|
||||
After changes in local `lib-jitsi-meet` repository, you can rebuild it with `npm run install` and your `jitsi-meet` repository will use that modified library.
|
||||
Note: when using node version 4.x, the make file of jitsi-meet do npm update which will delete the link. It is no longer the case with version 6.x.
|
||||
|
||||
If you do not want to use local repository anymore you should run
|
||||
```bash
|
||||
@@ -100,7 +100,7 @@ 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:
|
||||
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
|
||||
@@ -115,18 +115,16 @@ see our [guidelines for contributing](CONTRIBUTING.md).
|
||||
|
||||
## Embedding in external applications
|
||||
|
||||
Jitsi Meet provides a very flexible way of embedding it in external applications by using the [Jitsi Meet API](doc/api.md).
|
||||
Jitsi Meet provides a very flexible way of embedding 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.
|
||||
WebRTC does not provide a way of conducting multi-party conversations with end-to-end encryption.
|
||||
Unless you consistently compare DTLS fingerprints with your peers vocally, the same goes for one-to-one calls.
|
||||
As a result, your stream is encrypted on the network but decrypted on the machine that hosts the bridge when using Jitsi Meet.
|
||||
|
||||
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
|
||||
all server components. In that case, your security guarantees will be roughly
|
||||
equivalent to a direct one-to-one WebRTC call. This is the uniqueness of
|
||||
Jitsi Meet in terms of security.
|
||||
|
||||
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
|
||||
@@ -138,4 +136,4 @@ Instructions on how to build it can be found [here](doc/mobile.md).
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by then ESTOS' developer Philipp Hancke who then contributed it to the community where development continues with joint forces!
|
||||
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by ESTOS' developer Philipp Hancke who then contributed it to the community where development continues with joint forces!
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
# Jitsi Meet SDK for Android
|
||||
|
||||
## Sample applications using the SDK
|
||||
|
||||
If you want to see how easy integrating the Jitsi Meet SDK into a native application is, take a look at the
|
||||
[sample applications repository](https://github.com/jitsi/jitsi-meet-sdk-samples).
|
||||
|
||||
## Build your own, or use a pre-build SDK artifacts/binaries
|
||||
Jitsi conveniently provides a pre-build SDK artifacts/binaries in its Maven repository. When you do not require any modification to the SDK itself, it's suggested to use the pre-build SDK. This avoids the complexity of building and installing your own SDK artifacts/binaries.
|
||||
|
||||
Jitsi conveniently provides a pre-build SDK artifacts/binaries in its Maven repository. When you do not require any
|
||||
modification to the SDK itself or any of its dependencies, it's suggested to use the pre-build SDK. This avoids the
|
||||
complexity of building and installing your own SDK artifacts/binaries.
|
||||
|
||||
### Use pre-build SDK artifacts/binaries
|
||||
|
||||
In your project, add the Maven repository
|
||||
`https://github.com/jitsi/jitsi-maven-repository/raw/master/releases` and the
|
||||
dependency `org.jitsi.react:jitsi-meet-sdk` into your `build.gradle` files.
|
||||
@@ -27,86 +36,58 @@ Dependency definitions belong in the individual module `build.gradle` files:
|
||||
```gradle
|
||||
dependencies {
|
||||
// (other dependencies)
|
||||
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
|
||||
implementation ('org.jitsi.react:jitsi-meet-sdk:2.+') { transitive = true }
|
||||
}
|
||||
```
|
||||
|
||||
### Build and use your own SDK artifacts/binaries
|
||||
|
||||
1. Install all required [dependencies](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md).
|
||||
<details>
|
||||
<summary>Show building instructions</summary>
|
||||
|
||||
2. Create the SDK-release assembly, by invoking the following in the jitsi-meet
|
||||
project source:
|
||||
Start by making sure that your development environment [is set up correctly](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md).
|
||||
|
||||
```bash
|
||||
cd android/
|
||||
./gradlew :sdk:assembleRelease
|
||||
```
|
||||
When this successfully executes, artifacts/binaries are ready to be published
|
||||
into a Maven repository of your choice.
|
||||
A note on dependencies: Apart from the SDK, Jitsi also publishes a binary Maven artifact for some of the SDK dependencies (that are not otherwise publicly available) to the Jitsi Maven repository. When you're planning to use a SDK that is built from source, you'll likely use a version of the source code that is newer (or at least _different_) than the version of the source that was used to create the binary SDK artifact. As a consequence, the dependencies that your project will need, might also be different from those that are published in the Jitsi Maven repository. This might lead to build problems, caused by dependencies that are unavailable.
|
||||
|
||||
3. Configure the Maven repositories in which you are going to publish the
|
||||
artifacts/binaries during step 4.
|
||||
If you want to use a SDK that is built from source, you will likely benefit from composing a local Maven repository that contains these dependencies. The text below describes how you create a repository that includes both the SDK as well as these dependencies. For illustration purposes, we'll define the location of this local Maven repository as `/tmp/repo`
|
||||
|
||||
In the file `android/sdk/build.gradle` modify the line that contains
|
||||
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
|
||||
In source code form, the Android SDK dependencies are locked/pinned by package.json and package-lock.json of the Jitsi Meet project. To obtain the data, execute NPM in the jitsi-meet project directory:
|
||||
|
||||
Change this value (which represents the Maven repository location used internally
|
||||
by the Jitsi Developers) to the location of the repository that you'd like to use.
|
||||
npm install
|
||||
|
||||
4. Publish the Maven artifact/binary of Jitsi Meet SDK for Android in the Maven
|
||||
repository configured in step 3:
|
||||
This will pull in the dependencies in either binary format, or in source code format, somewhere under /node_modules/
|
||||
|
||||
```bash
|
||||
./gradlew :sdk:publish
|
||||
cd ../
|
||||
```
|
||||
5. In _your_ project, add the Maven repository that you configured in step 3, as well
|
||||
as the dependency `org.jitsi.react:jitsi-meet-sdk` into your `build.gradle`
|
||||
file. Note that it's needed to pull in the transitive dependencies:
|
||||
Third-party React Native _modules_, which Jitsi Meet SDK for Android depends on, are download by NPM in source code
|
||||
or binary form. These need to be assembled into Maven artifacts, and then published to your local Maven repository.
|
||||
A script is provided to facilitate this. From the root of the jitsi-meet project repository, run:
|
||||
|
||||
```gradle
|
||||
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
|
||||
```
|
||||
./android/scripts/release-sdk.sh /tmp/repo
|
||||
|
||||
Generally, if you are modifying the JavaScript code of Jitsi Meet SDK for Android only,
|
||||
the above will suffice. If you would like to publish a third-party react-native module
|
||||
which Jitsi Meet SDK for Android depends on (and is not publicly available in Maven
|
||||
repositories) continue below.
|
||||
This will build and publish the SDK, and all of its dependencies to the specified Maven repository (`/tmp/repo`) in
|
||||
this example.
|
||||
|
||||
6. Create the release assembly for _each_ third-party react-native module that you
|
||||
need, replacing it's name in the example below.
|
||||
You're now ready to use the artifacts. In _your_ project, add the Maven repository that you used above (`/tmp/repo`) into your top-level `build.gradle` file:
|
||||
|
||||
```bash
|
||||
./gradlew :react-native-webrtc:assembleRelease
|
||||
```
|
||||
allprojects {
|
||||
repositories {
|
||||
maven { url "file:/tmp/repo" }
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
7. Configure the Maven repositories in which you are going to publish the
|
||||
artifacts/binaries during step 8.
|
||||
You can use your local repository to replace the Jitsi repository (`maven { url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases" }`) when you published _all_ subprojects. If you didn't do that, you'll have to add both repositories. Make sure your local repository is listed first!
|
||||
|
||||
In the file `android/build.gradle` (note that this is a different file than the file
|
||||
that was modified in step 3) modify the line that contains
|
||||
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
|
||||
Then, define the dependency `org.jitsi.react:jitsi-meet-sdk` into the `build.gradle` file of your module:
|
||||
|
||||
Change this value (which represents the Maven repository location used internally
|
||||
by the Jitsi Developers) to the location of the repository that you'd like to use.
|
||||
You can use the same repository as the one you configured in step 3 if you want.
|
||||
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
|
||||
|
||||
8. Publish the Maven artifact/binary of _each_ third-party react-native module that
|
||||
you need, replacing it's name in the example below. For example, to publish
|
||||
react-native-webrtc:
|
||||
|
||||
```bash
|
||||
./gradlew :react-native-webrtc:publish
|
||||
```
|
||||
|
||||
Note that there should not be a need to explicitly add these dependencies in
|
||||
_your_ project, as they will be pulled in as transitive dependencies of
|
||||
`jitsi-meet-sdk`.
|
||||
Note that there should not be a need to explicitly add the other dependencies, as they will be pulled in as transitive
|
||||
dependencies of `jitsi-meet-sdk`.
|
||||
|
||||
</details>
|
||||
|
||||
## Using the API
|
||||
=======
|
||||
|
||||
Jitsi Meet SDK is an Android library which embodies the whole Jitsi Meet
|
||||
experience and makes it reusable by third-party apps.
|
||||
@@ -134,25 +115,42 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
```
|
||||
|
||||
Alternatively, you can use the `org.jitsi.meet.sdk.JitsiMeetView` class which
|
||||
extends `android.view.View`:
|
||||
extends `android.view.View`.
|
||||
|
||||
Note that this should only be needed when `JitsiMeetActivity` cannot be used for
|
||||
some reason. Extending `JitsiMeetView` requires manual wiring of the view to
|
||||
the activity, using a lot of boilerplate code. Using the Activity instead of the
|
||||
View is strongly recommended.
|
||||
|
||||
<details>
|
||||
<summary>Show example</summary>
|
||||
|
||||
```java
|
||||
package org.jitsi.example;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
import org.jitsi.meet.sdk.ReactActivityLifecycleCallbacks;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
// Example
|
||||
//
|
||||
public class MainActivity extends FragmentActivity implements JitsiMeetActivityInterface {
|
||||
private JitsiMeetView view;
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(
|
||||
int requestCode,
|
||||
int resultCode,
|
||||
Intent data) {
|
||||
JitsiMeetActivityDelegate.onActivityResult(
|
||||
this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!JitsiMeetView.onBackPressed()) {
|
||||
// Invoke the default handler if it wasn't handled by React.
|
||||
super.onBackPressed();
|
||||
}
|
||||
JitsiMeetActivityDelegate.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -160,7 +158,10 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
view = new JitsiMeetView(this);
|
||||
view.loadURL(null);
|
||||
JitsiMeetConferenceOptions options = new JitsiMeetConferenceOptions.Builder()
|
||||
.setRoom("https://meet.jit.si/test123")
|
||||
.build();
|
||||
view.join(options);
|
||||
|
||||
setContentView(view);
|
||||
}
|
||||
@@ -172,155 +173,127 @@ public class MainActivity extends AppCompatActivity {
|
||||
view.dispose();
|
||||
view = null;
|
||||
|
||||
JitsiMeetView.onHostDestroy(this);
|
||||
JitsiMeetActivityDelegate.onHostDestroy(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
JitsiMeetView.onNewIntent(intent);
|
||||
JitsiMeetActivityDelegate.onNewIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
final int requestCode,
|
||||
final String[] permissions,
|
||||
final int[] grantResults) {
|
||||
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
JitsiMeetView.onHostResume(this);
|
||||
JitsiMeetActivityDelegate.onHostResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
JitsiMeetView.onHostPause(this);
|
||||
JitsiMeetActivityDelegate.onHostPause(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Starting with SDK version 1.22, a Glide module must be provided by the host app.
|
||||
This makes it possible to use the Glide image processing library from both the
|
||||
SDK and the host app itself.
|
||||
|
||||
You can use the code in `JitsiGlideModule.java` and adjust the package name.
|
||||
When building, add the following code in your `app/build.gradle` file, adjusting
|
||||
the Glide version to match the one in https://github.com/jitsi/jitsi-meet/blob/master/android/build.gradle
|
||||
|
||||
```
|
||||
// Glide
|
||||
implementation("com.github.bumptech.glide:glide:${glideVersion}") {
|
||||
exclude group: "com.android.support", module: "glide"
|
||||
}
|
||||
implementation("com.github.bumptech.glide:annotations:${glideVersion}") {
|
||||
exclude group: "com.android.support", module: "annotations"
|
||||
}
|
||||
```
|
||||
|
||||
### JitsiMeetActivity
|
||||
|
||||
This class encapsulates a high level API in the form of an Android `Activity`
|
||||
which displays a single `JitsiMeetView`.
|
||||
|
||||
#### getDefaultURL()
|
||||
|
||||
See JitsiMeetView.getDefaultURL.
|
||||
|
||||
#### isPictureInPictureEnabled()
|
||||
|
||||
See JitsiMeetView.isPictureInPictureEnabled.
|
||||
|
||||
#### isWelcomePageEnabled()
|
||||
|
||||
See JitsiMeetView.isWelcomePageEnabled.
|
||||
|
||||
#### loadURL(URL)
|
||||
|
||||
See JitsiMeetView.loadURL.
|
||||
|
||||
#### setDefaultURL(URL)
|
||||
|
||||
See JitsiMeetView.setDefaultURL.
|
||||
|
||||
#### setPictureInPictureEnabled(boolean)
|
||||
|
||||
See JitsiMeetView.setPictureInPictureEnabled.
|
||||
|
||||
#### setWelcomePageEnabled(boolean)
|
||||
|
||||
See JitsiMeetView.setWelcomePageEnabled.
|
||||
This class encapsulates a high level API in the form of an Android `FragmentActivity`
|
||||
which displays a single `JitsiMeetView`. You can pass a URL as a `ACTION_VIEW`
|
||||
on the Intent when starting it and it will join the conference, and will be
|
||||
automatically terminated (finish() will be called on the activity) when the
|
||||
conference ends or fails.
|
||||
|
||||
### JitsiMeetView
|
||||
|
||||
The `JitsiMeetView` class is the core of Jitsi Meet SDK. It's designed to
|
||||
display a Jitsi Meet conference (or a welcome page).
|
||||
|
||||
#### join(options)
|
||||
|
||||
Joins the conference specified by the given `JitsiMeetConferenceOptions`.
|
||||
|
||||
#### leave()
|
||||
|
||||
Leaves the currently active conference. If the welcome page is enabled it will
|
||||
go back to it, otherwise a black window will be shown.
|
||||
|
||||
#### dispose()
|
||||
|
||||
Releases all resources associated with this view. This method MUST be called
|
||||
when the Activity holding this view is going to be destroyed, usually in the
|
||||
`onDestroy()` method.
|
||||
|
||||
#### getDefaultURL()
|
||||
|
||||
Returns the default base URL used to join a conference when a partial URL (e.g.
|
||||
a room name only) is specified to `loadURLString`/`loadURLObject`. If not set or
|
||||
if set to `null`, the default built in JavaScript is used: https://meet.jit.si.
|
||||
|
||||
#### getListener()
|
||||
|
||||
Returns the `JitsiMeetViewListener` instance attached to the view.
|
||||
|
||||
#### isPictureInPictureEnabled()
|
||||
|
||||
Returns `true` if Picture-in-Picture is enabled; `false`, otherwise. If not
|
||||
explicitly set (by a preceding `setPictureInPictureEnabled` call), defaults to
|
||||
`true` if the platform supports Picture-in-Picture natively; `false`, otherwise.
|
||||
|
||||
#### isWelcomePageEnabled()
|
||||
|
||||
Returns true if the Welcome page is enabled; otherwise, false. If false, a black
|
||||
empty view will be rendered when not in a conference. Defaults to false.
|
||||
|
||||
#### loadURL(URL)
|
||||
|
||||
Loads a specific URL which may identify a conference to join. If the specified
|
||||
URL is null and the Welcome page is enabled, the Welcome page is displayed
|
||||
instead.
|
||||
|
||||
#### loadURLString(String)
|
||||
|
||||
Loads a specific URL which may identify a conference to join. If the specified
|
||||
URL is null and the Welcome page is enabled, the Welcome page is displayed
|
||||
instead.
|
||||
|
||||
#### loadURLObject(Bundle)
|
||||
|
||||
Loads a specific URL which may identify a conference to join. The URL is
|
||||
specified in the form of a Bundle of properties which (1) internally are
|
||||
sufficient to construct a URL (string) while (2) abstracting the specifics of
|
||||
constructing the URL away from API clients/consumers. If the specified URL is
|
||||
null and the Welcome page is enabled, the Welcome page is displayed instead.
|
||||
|
||||
Example:
|
||||
|
||||
```java
|
||||
Bundle config = new Bundle();
|
||||
config.putBoolean("startWithAudioMuted", true);
|
||||
config.putBoolean("startWithVideoMuted", false);
|
||||
Bundle urlObject = new Bundle();
|
||||
urlObject.putBundle("config", config);
|
||||
urlObject.putString("url", "https://meet.jit.si/Test123");
|
||||
view.loadURLObject(urlObject);
|
||||
```
|
||||
|
||||
#### setDefaultURL(URL)
|
||||
|
||||
Sets the default URL. See `getDefaultURL` for more information.
|
||||
|
||||
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)
|
||||
### JitsiMeetConferenceOptions
|
||||
|
||||
Sets whether Picture-in-Picture is enabled. If not set, Jitsi Meet SDK
|
||||
automatically enables/disables Picture-in-Picture based on native platform
|
||||
support.
|
||||
This object encapsulates all the options that can be tweaked when joining
|
||||
a conference.
|
||||
|
||||
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
|
||||
effect.
|
||||
Example:
|
||||
|
||||
#### setWelcomePageEnabled(boolean)
|
||||
```java
|
||||
JitsiMeetConferenceOptions options = new JitsiMeetConferenceOptions.Builder()
|
||||
.setServerURL(new URL("https://meet.jit.si"))
|
||||
.setRoom("test123")
|
||||
.setAudioMuted(false)
|
||||
.setVideoMuted(false)
|
||||
.setAudioOnly(false)
|
||||
.setWelcomePageEnabled(false)
|
||||
.build();
|
||||
```
|
||||
|
||||
Sets whether the Welcome page is enabled. See `isWelcomePageEnabled` for more
|
||||
information.
|
||||
See the `JitsiMeetConferenceOptions` implementation for all available options.
|
||||
|
||||
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
|
||||
effect.
|
||||
### JitsiMeetActivityDelegate
|
||||
|
||||
This class handles the interaction between `JitsiMeetView` and its enclosing
|
||||
`Activity`. Generally this shouldn't be consumed by users, because they'd be
|
||||
using `JitsiMeetActivity` instead, which is already completely integrated.
|
||||
|
||||
All its methods are static.
|
||||
|
||||
#### onActivityResult(...)
|
||||
|
||||
Helper method to handle results of auxiliary activities launched by the SDK.
|
||||
Should be called from the activity method of the same name.
|
||||
|
||||
#### onBackPressed()
|
||||
|
||||
@@ -329,34 +302,29 @@ If this function returns `true`, it means the action was handled and thus no
|
||||
extra processing is required; otherwise the app should call the parent's
|
||||
`onBackPressed` method.
|
||||
|
||||
This is a static method.
|
||||
|
||||
#### onHostDestroy(activity)
|
||||
#### onHostDestroy(...)
|
||||
|
||||
Helper method which should be called from the activity's `onDestroy` method.
|
||||
|
||||
This is a static method.
|
||||
|
||||
#### onHostPause(activity)
|
||||
|
||||
Helper method which should be called from the activity's `onPause` method.
|
||||
|
||||
This is a static method.
|
||||
|
||||
#### onHostResume(activity)
|
||||
#### onHostResume(...)
|
||||
|
||||
Helper method which should be called from the activity's `onResume` or `onStop`
|
||||
method.
|
||||
|
||||
This is a static method.
|
||||
#### onHostStop(...)
|
||||
|
||||
#### onNewIntent(intent)
|
||||
Helper method which should be called from the activity's `onSstop` method.
|
||||
|
||||
#### onNewIntent(...)
|
||||
|
||||
Helper method for integrating the *deep linking* functionality. If your app's
|
||||
activity is launched in "singleTask" mode this method should be called from the
|
||||
activity's `onNewIntent` method.
|
||||
|
||||
This is a static method.
|
||||
#### onRequestPermissionsResult(...)
|
||||
|
||||
Helper method to handle permission requests inside the SDK. It should be called
|
||||
from the activity method of the same name.
|
||||
|
||||
#### onUserLeaveHint()
|
||||
|
||||
@@ -370,31 +338,20 @@ This is a static method.
|
||||
`JitsiMeetViewListener` provides an interface apps can implement to listen to
|
||||
the state of the Jitsi Meet conference displayed in a `JitsiMeetView`.
|
||||
|
||||
### JitsiMeetViewAdapter
|
||||
|
||||
A default implementation of the `JitsiMeetViewListener` interface. Apps may
|
||||
extend the class instead of implementing the interface in order to minimize
|
||||
boilerplate.
|
||||
|
||||
##### onConferenceFailed
|
||||
|
||||
Called when a joining a conference was unsuccessful or when there was an error
|
||||
while in a conference.
|
||||
|
||||
The `data` `Map` contains an "error" key describing the error and a "url" key
|
||||
with the conference URL.
|
||||
|
||||
#### onConferenceJoined
|
||||
|
||||
Called when a conference was joined.
|
||||
|
||||
The `data` `Map` contains a "url" key with the conference URL.
|
||||
|
||||
#### onConferenceLeft
|
||||
#### onConferenceTerminated
|
||||
|
||||
Called when a conference was left.
|
||||
Called when a conference was terminated either by user choice or due to a
|
||||
failure.
|
||||
|
||||
The `data` `Map` contains a "url" key with the conference URL.
|
||||
The `data` `Map` contains an "error" key with the error and a "url" key
|
||||
with the conference URL. If the conference finished gracefully no `error`
|
||||
key will be present.
|
||||
|
||||
#### onConferenceWillJoin
|
||||
|
||||
@@ -402,85 +359,11 @@ Called before a conference is joined.
|
||||
|
||||
The `data` `Map` contains a "url" key with the conference URL.
|
||||
|
||||
#### onConferenceWillLeave
|
||||
|
||||
Called before a conference is left.
|
||||
|
||||
The `data` `Map` contains a "url" key with the conference URL.
|
||||
|
||||
#### onLoadConfigError
|
||||
|
||||
Called when loading the main configuration file from the Jitsi Meet deployment
|
||||
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.** { *; }
|
||||
```
|
||||
rules file: https://github.com/jitsi/jitsi-meet/blob/master/android/app/proguard-rules.pro
|
||||
|
||||
## Picture-in-Picture
|
||||
|
||||
@@ -488,10 +371,6 @@ rules file:
|
||||
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.
|
||||
|
||||
## Dropbox integration
|
||||
|
||||
To setup the Dropbox integration, follow these steps:
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
boolean googleServicesEnabled \
|
||||
= project.file('google-services.json').exists() && !rootProject.ext.libreBuild
|
||||
|
||||
// Crashlytics integration is done as part of Firebase now, so it gets
|
||||
// automagically activated with google-services.json
|
||||
if (googleServicesEnabled) {
|
||||
apply plugin: 'io.fabric'
|
||||
}
|
||||
|
||||
// Use the number of seconds/10 since Jan 1 2019 as the versionCode.
|
||||
// This lets us upload a new build at most every 10 seconds for the
|
||||
// next ~680 years.
|
||||
// https://stackoverflow.com/a/38643838
|
||||
def vcode = (int)(((new Date().getTime()/1000) - 1546297200) / 10)
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'org.jitsi.meet'
|
||||
versionCode Integer.parseInt("${version}")
|
||||
versionName "1.9.${version}"
|
||||
versionCode vcode
|
||||
versionName project.appVersion
|
||||
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'x86'
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
// The project react-native does not provide 64-bit binaries at the
|
||||
// time of this writing. Unfortunately, packaging any 64-bit
|
||||
// binaries into the .apk will crash the app at runtime on 64-bit
|
||||
// platforms.
|
||||
exclude '/lib/mips64/**'
|
||||
exclude '/lib/arm64-v8a/**'
|
||||
exclude '/lib/x86_64/**'
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +36,25 @@ android {
|
||||
debug {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-debug.pro'
|
||||
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
|
||||
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.pro'
|
||||
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
|
||||
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
if (rootProject.ext.libreBuild) {
|
||||
srcDir "src"
|
||||
exclude "**/GoogleServicesHelper.java"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,34 +64,49 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url 'https://maven.fabric.io/public' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
implementation 'com.google.android.gms:play-services-auth:15.0.0'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||
|
||||
// Firebase
|
||||
// - Crashlytics
|
||||
// - Dynamic Links
|
||||
implementation 'com.google.firebase:firebase-core:16.0.6'
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
|
||||
implementation 'com.google.firebase:firebase-dynamic-links:16.1.5'
|
||||
}
|
||||
|
||||
implementation project(':sdk')
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
|
||||
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
|
||||
|
||||
// Glide
|
||||
implementation("com.github.bumptech.glide:glide:${rootProject.ext.glideVersion}") {
|
||||
exclude group: "com.android.support", module: "glide"
|
||||
}
|
||||
implementation("com.github.bumptech.glide:annotations:${rootProject.ext.glideVersion}") {
|
||||
exclude group: "com.android.support", module: "annotations"
|
||||
}
|
||||
annotationProcessor "com.github.bumptech.glide:compiler:${rootProject.ext.glideVersion}"
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
// Dropbox integration
|
||||
//
|
||||
|
||||
def plistParser = new XmlSlurper(
|
||||
/* validating */ false,
|
||||
/* namespaceAware */ false,
|
||||
/* allowDocTypeDeclaration */ true)
|
||||
plistParser.setFeature(
|
||||
'http://apache.org/xml/features/nonvalidating/load-external-dtd',
|
||||
false)
|
||||
def plist = plistParser.parse('../ios/app/src/Info.plist')
|
||||
def dropboxScheme = plist.dict.array.dict.array.string.find { string ->
|
||||
string.text().startsWith('db-')
|
||||
def dropboxAppKey
|
||||
if (project.file('dropbox.key').exists()) {
|
||||
dropboxAppKey = project.file('dropbox.key').text.trim() - 'db-'
|
||||
}
|
||||
def dropboxAppKey = dropboxScheme?.text() - 'db-'
|
||||
|
||||
if (dropboxAppKey) {
|
||||
android.defaultConfig.resValue('string', 'dropbox_app_key', "${dropboxAppKey}")
|
||||
@@ -87,27 +122,59 @@ gradle.projectsEvaluated {
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="db-${dropboxAppKey}" />
|
||||
</intent-filter>
|
||||
</activity>""";
|
||||
</activity>"""
|
||||
|
||||
android.applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
output.processManifest.doLast {
|
||||
def f = new File(manifestOutputDirectory, 'AndroidManifest.xml')
|
||||
if (!f.isFile()) {
|
||||
f = new File(new File(manifestOutputDirectory, output.dirName), 'AndroidManifest.xml')
|
||||
}
|
||||
if (f.exists()) {
|
||||
def charset = 'UTF-8'
|
||||
def s = f.getText(charset)
|
||||
s = s.replace('</application>', "${dropboxActivity}</application>")
|
||||
f.write(s, charset)
|
||||
}
|
||||
output.getProcessManifestProvider().get().doLast {
|
||||
def outputDir = manifestOutputDirectory.get().asFile
|
||||
def manifestPath = new File(outputDir, 'AndroidManifest.xml')
|
||||
def charset = 'UTF-8'
|
||||
def text
|
||||
text = manifestPath.getText(charset)
|
||||
text = text.replace('</application>', "${dropboxActivity}</application>")
|
||||
manifestPath.write(text, charset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run React packager
|
||||
android.applicationVariants.all { variant ->
|
||||
def targetName = variant.name.capitalize()
|
||||
|
||||
def currentRunPackagerTask = tasks.create(
|
||||
name: "run${targetName}ReactPackager",
|
||||
type: Exec) {
|
||||
group = "react"
|
||||
description = "Run the React packager."
|
||||
|
||||
doFirst {
|
||||
println "Starting the React packager..."
|
||||
|
||||
def androidRoot = file("${projectDir}/../")
|
||||
|
||||
// Set up the call to the script
|
||||
workingDir androidRoot
|
||||
|
||||
// Run the packager
|
||||
commandLine("scripts/run-packager.sh")
|
||||
}
|
||||
|
||||
// Set up dev mode
|
||||
def devEnabled = !targetName.toLowerCase().contains("release")
|
||||
|
||||
// Only enable for dev builds
|
||||
enabled devEnabled
|
||||
}
|
||||
|
||||
def packageTask = variant.packageApplicationProvider.get()
|
||||
|
||||
packageTask.dependsOn(currentRunPackagerTask)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (project.file('google-services.json').exists()) {
|
||||
if (googleServicesEnabled) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
|
||||
25
android/app/proguard-rules.pro
vendored
25
android/app/proguard-rules.pro
vendored
@@ -45,10 +45,7 @@
|
||||
-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
|
||||
-keep,includedescriptorclasses class com.facebook.react.bridge.** { *; }
|
||||
|
||||
# okhttp
|
||||
|
||||
@@ -65,9 +62,26 @@
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-dontwarn okio.**
|
||||
|
||||
# FastImage
|
||||
# FastImage + Glide
|
||||
|
||||
-keep public class com.dylanvann.fastimage.* {*;}
|
||||
-keep public class com.dylanvann.fastimage.** {*;}
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
||||
# WebRTC
|
||||
|
||||
-keep class org.webrtc.** { *; }
|
||||
-dontwarn org.chromium.build.BuildHooksAndroid
|
||||
|
||||
# Jisti Meet SDK
|
||||
|
||||
-keep class org.jitsi.meet.** { *; }
|
||||
-keep class org.jitsi.meet.sdk.** { *; }
|
||||
|
||||
# We added the following when we switched minifyEnabled on. Probably because we
|
||||
# ran the app and hit problems...
|
||||
@@ -79,7 +93,6 @@
|
||||
-keep class com.facebook.react.bridge.ReadableType { *; }
|
||||
-keep class com.facebook.react.bridge.queue.NativeRunnable { *; }
|
||||
-keep class com.facebook.react.devsupport.** { *; }
|
||||
-keep class org.webrtc.** { *; }
|
||||
|
||||
-dontwarn com.facebook.react.devsupport.**
|
||||
-dontwarn com.google.appengine.**
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:name=".MainApplication"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
@@ -15,6 +16,7 @@
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -23,11 +25,8 @@
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:host="beta.hipchat.me" android:scheme="https" />
|
||||
<data android:host="alpha.jitsi.net" android:scheme="https" />
|
||||
<data android:host="beta.meet.jit.si" android:scheme="https" />
|
||||
<data android:host="chaos.hipchat.me" android:scheme="https" />
|
||||
<data android:host="enso.me" android:scheme="https" />
|
||||
<data android:host="hipchat.me" android:scheme="https" />
|
||||
<data android:host="meet.jit.si" android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.jitsi.meet;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
|
||||
import io.fabric.sdk.android.Fabric;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
|
||||
/**
|
||||
* Helper class to initialize Google related services and functionality.
|
||||
* This functionality is compiled conditionally and called via reflection, that's why it was
|
||||
* extracted here.
|
||||
*
|
||||
* "Libre builds" (builds with the LIBRE_BUILD flag set) will not include this file.
|
||||
*/
|
||||
final class GoogleServicesHelper {
|
||||
public static void initialize(JitsiMeetActivity activity) {
|
||||
if (BuildConfig.GOOGLE_SERVICES_ENABLED) {
|
||||
Log.d(activity.getClass().getSimpleName(), "Initializing Google Services");
|
||||
|
||||
Fabric.with(activity, new Crashlytics());
|
||||
|
||||
FirebaseDynamicLinks.getInstance().getDynamicLink(activity.getIntent())
|
||||
.addOnSuccessListener(activity, pendingDynamicLinkData -> {
|
||||
Uri dynamicLink = null;
|
||||
|
||||
if (pendingDynamicLinkData != null) {
|
||||
dynamicLink = pendingDynamicLinkData.getLink();
|
||||
}
|
||||
|
||||
if (dynamicLink != null) {
|
||||
activity.join(dynamicLink.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.jitsi.meet;
|
||||
|
||||
import com.bumptech.glide.annotation.GlideModule;
|
||||
import com.bumptech.glide.module.AppGlideModule;
|
||||
|
||||
/**
|
||||
* An AppGlideModule needs to be present for image loading events to work in
|
||||
* react-native-fast-image. However, if this is defined by the SDK it will cause trouble with
|
||||
* apps which are using Glide themselves.
|
||||
*
|
||||
* In order to avoid the problem, define a Jitsi Glide module here, so applications already using
|
||||
* it are not in trouble.
|
||||
*/
|
||||
@GlideModule
|
||||
public final class JitsiGlideModule extends AppGlideModule {
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 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.
|
||||
@@ -16,215 +17,134 @@
|
||||
|
||||
package org.jitsi.meet;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeet;
|
||||
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 org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
|
||||
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The one and only {@link Activity} that the Jitsi Meet app needs. The
|
||||
* The one and only Activity that the Jitsi Meet app needs. The
|
||||
* {@code Activity} is launched in {@code singleTask} mode, so it will be
|
||||
* created upon application initialization and there will be a single instance
|
||||
* of it. Further attempts at launching the application once it was already
|
||||
* launched will result in {@link Activity#onNewIntent(Intent)} being called.
|
||||
*
|
||||
* This {@code Activity} extends {@link JitsiMeetActivity} to keep the React
|
||||
* Native CLI working, since the latter always tries to launch an
|
||||
* {@code Activity} named {@code MainActivity} when doing
|
||||
* {@code react-native run-android}.
|
||||
* launched will result in {@link MainActivity#onNewIntent(Intent)} being called.
|
||||
*/
|
||||
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.
|
||||
* 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.
|
||||
*/
|
||||
private static final String ADD_PEOPLE_CONTROLLER_QUERY = null;
|
||||
private static final int OVERLAY_PERMISSION_REQUEST_CODE
|
||||
= (int) (Math.random() * Short.MAX_VALUE);
|
||||
|
||||
// JitsiMeetActivity overrides
|
||||
//
|
||||
|
||||
@Override
|
||||
protected JitsiMeetView initializeView() {
|
||||
JitsiMeetView view = super.initializeView();
|
||||
protected boolean extraInitialize() {
|
||||
Log.d(this.getClass().getSimpleName(), "LIBRE_BUILD="+BuildConfig.LIBRE_BUILD);
|
||||
|
||||
// XXX In order to increase (1) awareness of API breakages and (2) API
|
||||
// coverage, utilize JitsiMeetViewListener in the Debug configuration of
|
||||
// the app.
|
||||
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(
|
||||
"ReactNative",
|
||||
JitsiMeetViewListener.class.getSimpleName() + " "
|
||||
+ name + " "
|
||||
+ data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceFailed(Map<String, Object> data) {
|
||||
on("CONFERENCE_FAILED", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceJoined(Map<String, Object> data) {
|
||||
on("CONFERENCE_JOINED", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceLeft(Map<String, Object> data) {
|
||||
on("CONFERENCE_LEFT", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceWillJoin(Map<String, Object> data) {
|
||||
on("CONFERENCE_WILL_JOIN", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceWillLeave(Map<String, Object> data) {
|
||||
on("CONFERENCE_WILL_LEAVE", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadConfigError(Map<String, Object> data) {
|
||||
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());
|
||||
// Setup Crashlytics and Firebase Dynamic Links
|
||||
// Here we are using reflection since it may have been disabled at compile time.
|
||||
try {
|
||||
Class<?> cls = Class.forName("org.jitsi.meet.GoogleServicesHelper");
|
||||
Method m = cls.getMethod("initialize", JitsiMeetActivity.class);
|
||||
m.invoke(null, this);
|
||||
} catch (Exception e) {
|
||||
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
||||
}
|
||||
|
||||
return view;
|
||||
// In Debug builds React needs permission to write over other apps in
|
||||
// order to display the warning and error overlays.
|
||||
if (BuildConfig.DEBUG) {
|
||||
if (canRequestOverlayPermission() && !Settings.canDrawOverlays(this)) {
|
||||
Intent intent
|
||||
= new Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:" + getPackageName()));
|
||||
|
||||
startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onAddPeopleControllerInviteSettled(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> failedInvitees) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
@Override
|
||||
protected void initialize() {
|
||||
// Set default options
|
||||
JitsiMeetConferenceOptions defaultOptions
|
||||
= new JitsiMeetConferenceOptions.Builder()
|
||||
.setWelcomePageEnabled(true)
|
||||
.setServerURL(buildURL("https://meet.jit.si"))
|
||||
.build();
|
||||
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
|
||||
|
||||
// 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();
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
private void onAddPeopleControllerReceivedResults(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> results,
|
||||
String query) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
@Override
|
||||
public void onConferenceTerminated(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference terminated: " + data);
|
||||
}
|
||||
|
||||
int size = results.size();
|
||||
// Activity lifecycle method overrides
|
||||
//
|
||||
|
||||
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());
|
||||
}
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
|
||||
&& canRequestOverlayPermission()) {
|
||||
if (Settings.canDrawOverlays(this)) {
|
||||
initialize();
|
||||
return;
|
||||
}
|
||||
|
||||
addPeopleController.inviteById(ids);
|
||||
|
||||
return;
|
||||
throw new RuntimeException("Overlay permission is required when running in Debug mode.");
|
||||
}
|
||||
|
||||
// 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();
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
|
||||
// want to enable some options.
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
JitsiMeet.showDevOptions();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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);
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
private void onInviteControllerBeginAddPeople(
|
||||
InviteController inviteController,
|
||||
AddPeopleController addPeopleController) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
// Helper methods
|
||||
//
|
||||
|
||||
// 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();
|
||||
private @Nullable URL buildURL(String urlStr) {
|
||||
try {
|
||||
return new URL(urlStr);
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canRequestOverlayPermission() {
|
||||
return
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:navigationBarColor">#1081B2</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
6
android/app/src/main/res/xml/network_security_config.xml
Normal file
6
android/app/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="false">localhost</domain>
|
||||
<domain includeSubdomains="false">10.0.2.2</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
@@ -1,3 +1,5 @@
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
// Top-level build file where you can add configuration options common to all
|
||||
// sub-projects/modules.
|
||||
|
||||
@@ -5,10 +7,14 @@ buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
repositories {
|
||||
maven { url 'https://maven.fabric.io/public' }
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
classpath 'com.google.gms:google-services:3.2.1'
|
||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
classpath 'io.fabric.tools:gradle:1.27.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files.
|
||||
@@ -17,10 +23,8 @@ buildscript {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven { url "https://maven.google.com" }
|
||||
google()
|
||||
jcenter()
|
||||
maven { url "$rootDir/../node_modules/jsc-android/dist" }
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from
|
||||
// npm.
|
||||
maven { url "$rootDir/../node_modules/react-native/android" }
|
||||
@@ -34,15 +38,9 @@ allprojects {
|
||||
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
|
||||
def version = new JsonSlurper().parseText(file.text).version
|
||||
details.useVersion version
|
||||
}
|
||||
if (details.requested.group == 'org.webkit'
|
||||
&& details.requested.name == 'android-jsc') {
|
||||
def file = new File("$rootDir/../node_modules/jsc-android/package.json")
|
||||
def version = new groovy.json.JsonSlurper().parseText(file.text).version
|
||||
details.useVersion "r${version.tokenize('.')[0]}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,27 +55,33 @@ allprojects {
|
||||
publishing {
|
||||
publications {}
|
||||
repositories {
|
||||
maven { url "file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases" }
|
||||
maven {
|
||||
url rootProject.ext.mavenRepo
|
||||
if (!rootProject.ext.mavenRepo.startsWith("file")) {
|
||||
credentials {
|
||||
username rootProject.ext.mavenUser
|
||||
password rootProject.ext.mavenPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use the number of seconds/10 since Jan 1 2019 as the version qualifier number.
|
||||
// This will last for the next ~680 years.
|
||||
// https://stackoverflow.com/a/38643838
|
||||
def versionQualifierNumber = (int)(((new Date().getTime()/1000) - 1546297200) / 10)
|
||||
|
||||
afterEvaluate { project ->
|
||||
if (project.name.startsWith('react-native-')) {
|
||||
def npmManifest = project.file('../package.json')
|
||||
def json = new groovy.json.JsonSlurper().parseText(npmManifest.text)
|
||||
def json = new JsonSlurper().parseText(npmManifest.text)
|
||||
|
||||
// React Native modules have an npm peer dependency on react-native,
|
||||
// they do not have an npm dependency on it. Further below though we
|
||||
// choose a react-native version (range) when we represent them as
|
||||
// Maven artifacts. Effectively, we are forking the projects by not
|
||||
// complying with the full range of their npm peer dependency and,
|
||||
// consequently, we should qualify their version.
|
||||
def versionQualifier = '-jitsi-1'
|
||||
if ('react-native-webrtc'.equals(project.name))
|
||||
versionQualifier = '-jitsi-1'
|
||||
// Release every dependency the SDK has with a -jitsi-XXX qualified version. This allows
|
||||
// us to pin the dependencies and make sure they are always updated, no matter what.
|
||||
|
||||
project.version = "${json.version}${versionQualifier}"
|
||||
project.version = "${json.version}-jitsi-${versionQualifierNumber}"
|
||||
|
||||
project.android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
@@ -90,15 +94,6 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
task androidJavadocs(type: Javadoc) {
|
||||
source = android.sourceSets.main.java.source
|
||||
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
|
||||
failOnError false
|
||||
}
|
||||
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
|
||||
classifier = 'javadoc'
|
||||
from androidJavadocs.destinationDir
|
||||
}
|
||||
task androidSourcesJar(type: Jar) {
|
||||
classifier = 'sources'
|
||||
from android.sourceSets.main.java.source
|
||||
@@ -114,7 +109,6 @@ allprojects {
|
||||
extension "aar"
|
||||
}
|
||||
artifact(androidSourcesJar)
|
||||
artifact(androidJavadocsJar)
|
||||
pom.withXml {
|
||||
def pomXml = asNode()
|
||||
pomXml.appendNode('name', project.name)
|
||||
@@ -135,7 +129,7 @@ allprojects {
|
||||
// (third-party) React Native modules we utilize can
|
||||
// depend not on a specific react-native release but
|
||||
// a wider range.
|
||||
if (artifactId.equals('react-native')) {
|
||||
if (artifactId == 'react-native') {
|
||||
def versionNumber = VersionNumber.parse(version)
|
||||
version = "${versionNumber.major}.${versionNumber.minor}"
|
||||
}
|
||||
@@ -153,17 +147,73 @@ allprojects {
|
||||
}
|
||||
|
||||
ext {
|
||||
buildToolsVersion = "27.0.3"
|
||||
compileSdkVersion = 27
|
||||
buildToolsVersion = "28.0.3"
|
||||
compileSdkVersion = 28
|
||||
minSdkVersion = 21
|
||||
targetSdkVersion = 26
|
||||
supportLibVersion = "27.1.1"
|
||||
targetSdkVersion = 28
|
||||
supportLibVersion = "28.0.0"
|
||||
|
||||
// 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
|
||||
// third-party Maven repositories so we have to deploy to a Maven repository
|
||||
// of ours.
|
||||
moduleGroupId = 'com.facebook.react'
|
||||
|
||||
// Maven repo where artifacts will be published
|
||||
mavenRepo = System.env.MVN_REPO ?: ""
|
||||
mavenUser = System.env.MVN_USER ?: ""
|
||||
mavenPassword = System.env.MVN_PASSWORD ?: ""
|
||||
|
||||
// Glide
|
||||
excludeAppGlideModule = true
|
||||
glideVersion = "4.7.1" // keep in sync with react-native-fast-image
|
||||
|
||||
// Libre build
|
||||
libreBuild = (System.env.LIBRE_BUILD ?: "true").toBoolean()
|
||||
}
|
||||
|
||||
// If Android SDK is not installed, accept its license so that it
|
||||
// is automatically downloaded.
|
||||
afterEvaluate { project ->
|
||||
// Either the environment variable ANDROID_HOME or the property sdk.dir in
|
||||
// local.properties identifies where Android SDK is installed.
|
||||
def androidHome = System.env.ANDROID_HOME
|
||||
if (!androidHome) {
|
||||
// ANDROID_HOME is not set. Is sdk.dir set?
|
||||
def file = file("${project.rootDir}/local.properties")
|
||||
def props = new Properties()
|
||||
if (file.canRead()) {
|
||||
file.withInputStream {
|
||||
props.load(it)
|
||||
androidHome = props.'sdk.dir'
|
||||
}
|
||||
}
|
||||
if (!androidHome && (!file.exists() || file.canWrite())) {
|
||||
// Neither ANDROID_HOME nor sdk.dir is set. Set sdk.dir (because
|
||||
// environment variables cannot be set).
|
||||
props.'sdk.dir' = "${project.buildDir}/android-sdk".toString()
|
||||
file.withOutputStream {
|
||||
props.store(it, null)
|
||||
androidHome = props.'sdk.dir'
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the license is not accepted, accept it so that automatic downloading
|
||||
// kicks in.
|
||||
// The license hash can be taken from the accepted licenses, by doing this
|
||||
// on your local machine the file is
|
||||
// ${androidHome}/licenses/android-sdk-license
|
||||
if (androidHome) {
|
||||
def dir = file("${androidHome}/licenses")
|
||||
dir.mkdirs()
|
||||
def file = file("${dir.path}/android-sdk-license")
|
||||
if (!file.exists()) {
|
||||
file.withWriter {
|
||||
def hash = 'd56f5187479451eabf01fb78af6dfcb131a6481e'
|
||||
it.write(hash, 0, hash.length())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force the version of the Android build tools we have chosen on all
|
||||
@@ -180,8 +230,3 @@ subprojects { subproject ->
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '4.4'
|
||||
distributionUrl = distributionUrl.replace("bin", "all")
|
||||
}
|
||||
|
||||
2
android/fastlane/Appfile
Normal file
2
android/fastlane/Appfile
Normal file
@@ -0,0 +1,2 @@
|
||||
json_key_file("")
|
||||
package_name("org.jitsi.meet")
|
||||
34
android/fastlane/Fastfile
Normal file
34
android/fastlane/Fastfile
Normal file
@@ -0,0 +1,34 @@
|
||||
ENV["FASTLANE_SKIP_UPDATE_CHECK"] = "1"
|
||||
opt_out_usage
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Deploy a new version to Goolge Play (Closed Beta)"
|
||||
lane :deploy do
|
||||
# Cleanup
|
||||
gradle(task: "clean")
|
||||
|
||||
# Build and sign the app
|
||||
gradle(
|
||||
task: "assemble",
|
||||
build_type: "Release",
|
||||
print_command: false,
|
||||
properties: {
|
||||
"android.injected.signing.store.file" => ENV["JITSI_KEYSTORE"],
|
||||
"android.injected.signing.store.password" => ENV["JITSI_KEYSTORE_PASSWORD"],
|
||||
"android.injected.signing.key.alias" => ENV["JITSI_KEY_ALIAS"],
|
||||
"android.injected.signing.key.password" => ENV["JITSI_KEY_PASSWORD"],
|
||||
}
|
||||
)
|
||||
|
||||
# Upload built artifact to the Closed Beta track
|
||||
upload_to_play_store(
|
||||
track: "Closed Beta",
|
||||
json_key: ENV["JITSI_JSON_KEY_FILE"],
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_images: true,
|
||||
skip_upload_screenshots: true
|
||||
)
|
||||
end
|
||||
end
|
||||
29
android/fastlane/README.md
Normal file
29
android/fastlane/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
fastlane documentation
|
||||
================
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
Install _fastlane_ using
|
||||
```
|
||||
[sudo] gem install fastlane -NV
|
||||
```
|
||||
or alternatively using `brew cask install fastlane`
|
||||
|
||||
# Available Actions
|
||||
## Android
|
||||
### android deploy
|
||||
```
|
||||
fastlane android deploy
|
||||
```
|
||||
Deploy a new version to Goolge Play (Closed Beta)
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
|
||||
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
|
||||
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
@@ -17,4 +17,5 @@
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
version=1
|
||||
appVersion=19.2.0
|
||||
sdkVersion=2.1.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#Fri Sep 08 10:42:14 CEST 2017
|
||||
#Fri Mar 08 13:36:51 CET 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
|
||||
81
android/scripts/release-sdk.sh
Executable file
81
android/scripts/release-sdk.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -u
|
||||
|
||||
|
||||
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
|
||||
DEFAULT_MVN_REPO="${THIS_DIR}/../../../jitsi-maven-repository/releases"
|
||||
THE_MVN_REPO=${MVN_REPO:-${1:-$DEFAULT_MVN_REPO}}
|
||||
MVN_HTTP=0
|
||||
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
RN_VERSION=$(jq -r '.dependencies."react-native"' ${THIS_DIR}/../../package.json)
|
||||
|
||||
if [[ $THE_MVN_REPO == http* ]]; then
|
||||
MVN_HTTP=1
|
||||
else
|
||||
MVN_REPO_PATH=$(realpath $THE_MVN_REPO)
|
||||
THE_MVN_REPO="file:${MVN_REPO_PATH}"
|
||||
fi
|
||||
|
||||
export MVN_REPO=$THE_MVN_REPO
|
||||
|
||||
echo "Releasing Jitsi Meet SDK ${SDK_VERSION}"
|
||||
echo "Using ${MVN_REPO} as the Maven repo"
|
||||
|
||||
if [[ $MVN_HTTP == 1 ]]; then
|
||||
# Push React Native
|
||||
echo "Pushing React Native ${RN_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/react-native/android/com/facebook/react/react-native/${RN_VERSION}
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-DrepositoryId=${MVN_REPO_ID} \
|
||||
-Dfile=react-native-${RN_VERSION}.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=react-native-${RN_VERSION}.pom || true
|
||||
popd
|
||||
else
|
||||
# Check if an SDK with that same version has already been released
|
||||
if [[ -d ${MVN_REPO}/org/jitsi/react/jitsi-meet-sdk/${SDK_VERSION} ]]; then
|
||||
echo "There is already a release with that version in the Maven repo!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# First push React Native, if necessary
|
||||
if [[ ! -d ${MVN_REPO}/com/facebook/react/react-native/${RN_VERSION} ]]; then
|
||||
echo "Pushing React Native ${RN_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/react-native/android/com/facebook/react/react-native/${RN_VERSION}
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-Dfile=react-native-${RN_VERSION}.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=react-native-${RN_VERSION}.pom
|
||||
popd
|
||||
fi
|
||||
fi
|
||||
|
||||
# Now build and publish the Jitsi Meet SDK and its dependencies
|
||||
echo "Building and publishing the Jitsi Meet SDK"
|
||||
pushd ${THIS_DIR}/../
|
||||
./gradlew clean assembleRelease publish
|
||||
popd
|
||||
|
||||
if [[ $MVN_HTTP == 0 ]]; then
|
||||
# The artifacts are now on the Maven repo, commit them
|
||||
pushd ${MVN_REPO_PATH}
|
||||
if [[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == "true" ]]; then
|
||||
git add -A .
|
||||
git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}"
|
||||
fi
|
||||
popd
|
||||
|
||||
# Tag the release
|
||||
git tag android-sdk-${SDK_VERSION}
|
||||
fi
|
||||
|
||||
# Done!
|
||||
echo "Finished! Don't forget to push the tag and the Maven repo artifacts."
|
||||
24
android/scripts/run-packager.sh
Executable file
24
android/scripts/run-packager.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is executed bt Gradle to start the React packager for Debug
|
||||
# targets.
|
||||
|
||||
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
|
||||
|
||||
export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}"
|
||||
echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${THIS_DIR}/../../node_modules/react-native/scripts/.packager.env"
|
||||
|
||||
if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then
|
||||
if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then
|
||||
echo "Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly"
|
||||
exit 2
|
||||
fi
|
||||
else
|
||||
CMD="${THIS_DIR}/../../node_modules/react-native/scripts/launchPackager.command"
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
open -g "${CMD}" || echo "Can't start packager automatically"
|
||||
else
|
||||
xdg-open "${CMD}" || echo "Can't start packager automatically"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -10,10 +10,25 @@ android {
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {}
|
||||
debug {
|
||||
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
if (rootProject.ext.libreBuild) {
|
||||
srcDir "src"
|
||||
exclude "**/AmplitudeModule.java"
|
||||
}
|
||||
exclude "test/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,25 +39,31 @@ dependencies {
|
||||
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
|
||||
api 'com.facebook.react:react-native:+'
|
||||
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.amplitude:android-sdk:2.14.1'
|
||||
implementation(project(":react-native-google-signin")) {
|
||||
exclude group: 'com.google.android.gms'
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
}
|
||||
|
||||
implementation project(':react-native-background-timer')
|
||||
implementation project(':react-native-calendar-events')
|
||||
implementation project(':react-native-community-async-storage')
|
||||
implementation(project(':react-native-fast-image')) {
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
implementation(project(":react-native-google-signin")) {
|
||||
exclude group: 'com.google.android.gms'
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
implementation project(':react-native-immersive')
|
||||
implementation project(':react-native-keep-awake')
|
||||
implementation project(':react-native-linear-gradient')
|
||||
implementation project(':react-native-locale-detector')
|
||||
implementation project(':react-native-sound')
|
||||
implementation project(':react-native-vector-icons')
|
||||
implementation project(':react-native-webrtc')
|
||||
implementation project(':react-native-webview')
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
@@ -107,13 +128,17 @@ android.libraryVariants.all { def variant ->
|
||||
|
||||
currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
|
||||
currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
|
||||
|
||||
variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
|
||||
variant.mergeResources.dependsOn(currentBundleTask)
|
||||
|
||||
def assetsDir = variant.mergeAssets.outputDir
|
||||
def mergeAssetsTask = variant.mergeAssetsProvider.get()
|
||||
def mergeResourcesTask = variant.mergeResourcesProvider.get()
|
||||
|
||||
mergeAssetsTask.dependsOn(currentBundleTask)
|
||||
mergeResourcesTask.dependsOn(currentBundleTask)
|
||||
|
||||
mergeAssetsTask.doLast {
|
||||
def assetsDir = mergeAssetsTask.outputDir
|
||||
|
||||
variant.mergeAssets.doLast {
|
||||
// Bundle fonts
|
||||
//
|
||||
copy {
|
||||
@@ -124,6 +149,7 @@ android.libraryVariants.all { def variant ->
|
||||
// Bundle sounds
|
||||
//
|
||||
copy {
|
||||
from("${projectDir}/../../sounds/incomingMessage.wav")
|
||||
from("${projectDir}/../../sounds/joined.wav")
|
||||
from("${projectDir}/../../sounds/left.wav")
|
||||
from("${projectDir}/../../sounds/outgoingRinging.wav")
|
||||
@@ -138,19 +164,19 @@ android.libraryVariants.all { def variant ->
|
||||
//
|
||||
if (currentBundleTask.enabled) {
|
||||
copy {
|
||||
from(jsBundleDir)
|
||||
from(jsBundleFile)
|
||||
into(assetsDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variant.mergeResources.doLast {
|
||||
mergeResourcesTask.doLast {
|
||||
// Copy React resources
|
||||
//
|
||||
if (currentBundleTask.enabled) {
|
||||
copy {
|
||||
from(resourcesDir)
|
||||
into(variant.mergeResources.outputDir)
|
||||
into(mergeResourcesTask.outputDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +188,7 @@ publishing {
|
||||
aarArchive(MavenPublication) {
|
||||
groupId 'org.jitsi.react'
|
||||
artifactId 'jitsi-meet-sdk'
|
||||
version '1.9.0'
|
||||
version System.env.OVERRIDE_SDK_VERSION ?: project.sdkVersion
|
||||
|
||||
artifact("${project.buildDir}/outputs/aar/${project.name}-release.aar") {
|
||||
extension "aar"
|
||||
@@ -181,8 +207,7 @@ publishing {
|
||||
def groupId = it.moduleGroup
|
||||
def artifactId = it.moduleName
|
||||
|
||||
if (artifactId.startsWith('react-native-')
|
||||
&& groupId.equals('jitsi-meet')) {
|
||||
if (artifactId.startsWith('react-native-') && groupId.equals('jitsi-meet')) {
|
||||
groupId = rootProject.ext.moduleGroupId
|
||||
}
|
||||
|
||||
@@ -196,6 +221,14 @@ publishing {
|
||||
|
||||
}
|
||||
repositories {
|
||||
maven { url "file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases" }
|
||||
maven {
|
||||
url rootProject.ext.mavenRepo
|
||||
if (!rootProject.ext.mavenRepo.startsWith("file")) {
|
||||
credentials {
|
||||
username rootProject.ext.mavenUser
|
||||
password rootProject.ext.mavenPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
android/sdk/proguard-rules.pro
vendored
25
android/sdk/proguard-rules.pro
vendored
@@ -1,25 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/scorretge/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,30 +1,49 @@
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.jitsi.meet.sdk">
|
||||
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<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" />
|
||||
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<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
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true">
|
||||
<activity
|
||||
android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
</application>
|
||||
</manifest>
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true">
|
||||
<activity
|
||||
android:name=".JitsiMeetActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:launchMode="singleTask"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
|
||||
<service
|
||||
android:name=".ConnectionService"
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.telecom.ConnectionService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import com.amplitude.api.Amplitude;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Implements the react-native module for the Amplitude integration.
|
||||
*/
|
||||
@ReactModule(name = AmplitudeModule.NAME)
|
||||
class AmplitudeModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "Amplitude";
|
||||
|
||||
public AmplitudeModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Amplitude SDK.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance. Should
|
||||
* be used only for multi-project logging.
|
||||
* @param apiKey The API_KEY of the Amplitude project.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void init(String instanceName, String apiKey) {
|
||||
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user properties for an Amplitude instance.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance.
|
||||
* @param userProps JSON string with user properties to be set.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setUserProperties(String instanceName, ReadableMap userProps) {
|
||||
if (userProps != null) {
|
||||
Amplitude.getInstance(instanceName).setUserProperties(
|
||||
new JSONObject(userProps.toHashMap()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an analytics event.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance.
|
||||
* @param eventType The event type.
|
||||
* @param eventPropsString JSON string with the event properties.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void logEvent(String instanceName, String eventType, String eventPropsString) {
|
||||
try {
|
||||
JSONObject eventProps = new JSONObject(eventPropsString);
|
||||
Amplitude.getInstance(instanceName).logEvent(eventType, eventProps);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
@@ -15,17 +15,21 @@ import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
@ReactModule(name = AndroidSettingsModule.NAME)
|
||||
class AndroidSettingsModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "AndroidSettings";
|
||||
|
||||
public AndroidSettingsModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "AndroidSettings";
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
||||
@@ -23,13 +23,17 @@ import android.content.pm.PackageManager;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ReactModule(name = AppInfoModule.NAME)
|
||||
class AppInfoModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "AppInfo";
|
||||
|
||||
public AppInfoModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
@@ -60,6 +64,9 @@ class AppInfoModule
|
||||
|
||||
Map<String, Object> constants = new HashMap<>();
|
||||
|
||||
constants.put(
|
||||
"buildNumber",
|
||||
packageInfo == null ? "" : String.valueOf(packageInfo.versionCode));
|
||||
constants.put(
|
||||
"name",
|
||||
applicationInfo == null
|
||||
@@ -68,12 +75,13 @@ class AppInfoModule
|
||||
constants.put(
|
||||
"version",
|
||||
packageInfo == null ? "" : packageInfo.versionName);
|
||||
constants.put("LIBRE_BUILD", BuildConfig.LIBRE_BUILD);
|
||||
|
||||
return constants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "AppInfo";
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.content.pm.PackageManager;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
@@ -34,6 +35,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -57,10 +59,12 @@ import java.util.concurrent.Executors;
|
||||
* Before a call has started and after it has ended the
|
||||
* {@code AudioModeModule.DEFAULT} mode should be used.
|
||||
*/
|
||||
class AudioModeModule
|
||||
extends ReactContextBaseJavaModule
|
||||
@ReactModule(name = AudioModeModule.NAME)
|
||||
class AudioModeModule extends ReactContextBaseJavaModule
|
||||
implements AudioManager.OnAudioFocusChangeListener {
|
||||
|
||||
public static final String NAME = "AudioMode";
|
||||
|
||||
/**
|
||||
* Constants representing the audio mode.
|
||||
* - DEFAULT: Used before and after every call. It represents the default
|
||||
@@ -89,16 +93,74 @@ class AudioModeModule
|
||||
*/
|
||||
private static final int TYPE_USB_HEADSET = 22;
|
||||
|
||||
/**
|
||||
* The name of {@code AudioModeModule} to be used in the React Native
|
||||
* bridge.
|
||||
*/
|
||||
private static final String MODULE_NAME = "AudioMode";
|
||||
|
||||
/**
|
||||
* The {@code Log} tag {@code AudioModeModule} is to log messages with.
|
||||
*/
|
||||
static final String TAG = MODULE_NAME;
|
||||
static final String TAG = NAME;
|
||||
|
||||
/**
|
||||
* Converts any of the "DEVICE_" constants into the corresponding
|
||||
* {@link android.telecom.CallAudioState} "ROUTE_" number.
|
||||
*
|
||||
* @param audioDevice one of the "DEVICE_" constants.
|
||||
* @return a route number {@link android.telecom.CallAudioState#ROUTE_EARPIECE} if
|
||||
* no match is found.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private static int audioDeviceToRouteInt(String audioDevice) {
|
||||
if (audioDevice == null) {
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
}
|
||||
switch (audioDevice) {
|
||||
case DEVICE_BLUETOOTH:
|
||||
return android.telecom.CallAudioState.ROUTE_BLUETOOTH;
|
||||
case DEVICE_EARPIECE:
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
case DEVICE_HEADPHONES:
|
||||
return android.telecom.CallAudioState.ROUTE_WIRED_HEADSET;
|
||||
case DEVICE_SPEAKER:
|
||||
return android.telecom.CallAudioState.ROUTE_SPEAKER;
|
||||
default:
|
||||
Log.e(TAG, "Unsupported device name: " + audioDevice);
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates given route mask into the "DEVICE_" list.
|
||||
*
|
||||
* @param supportedRouteMask an integer coming from
|
||||
* {@link android.telecom.CallAudioState#getSupportedRouteMask()}.
|
||||
* @return a list of device names.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private static Set<String> routesToDeviceNames(int supportedRouteMask) {
|
||||
Set<String> devices = new HashSet<>();
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_EARPIECE)
|
||||
== android.telecom.CallAudioState.ROUTE_EARPIECE) {
|
||||
devices.add(DEVICE_EARPIECE);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_BLUETOOTH)
|
||||
== android.telecom.CallAudioState.ROUTE_BLUETOOTH) {
|
||||
devices.add(DEVICE_BLUETOOTH);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_SPEAKER)
|
||||
== android.telecom.CallAudioState.ROUTE_SPEAKER) {
|
||||
devices.add(DEVICE_SPEAKER);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_WIRED_HEADSET)
|
||||
== android.telecom.CallAudioState.ROUTE_WIRED_HEADSET) {
|
||||
devices.add(DEVICE_HEADPHONES);
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the ConnectionService is used for selecting audio devices.
|
||||
*/
|
||||
static boolean useConnectionService() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicator that we have lost audio focus.
|
||||
@@ -204,6 +266,15 @@ class AudioModeModule
|
||||
*/
|
||||
private String selectedDevice;
|
||||
|
||||
/**
|
||||
* Used on API >= 26 to store the most recently reported audio devices.
|
||||
* Makes it easier to compare for a change, because the devices are stored
|
||||
* as a mask in the {@link android.telecom.CallAudioState}. The mask is populated into
|
||||
* the {@link #availableDevices} on each update.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private int supportedRouteMask;
|
||||
|
||||
/**
|
||||
* User selected device. When null the default is used depending on the
|
||||
* mode.
|
||||
@@ -224,21 +295,25 @@ class AudioModeModule
|
||||
= (AudioManager)
|
||||
reactContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
// Setup runtime device change detection.
|
||||
setupAudioRouteChangeDetection();
|
||||
// Starting Oreo the ConnectionImpl from ConnectionService is used to
|
||||
// detect the available devices.
|
||||
if (!useConnectionService()) {
|
||||
// Setup runtime device change detection.
|
||||
setupAudioRouteChangeDetection();
|
||||
|
||||
// Do an initial detection on Android >= M.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
runInAudioThread(onAudioDeviceChangeRunner);
|
||||
} else {
|
||||
// On Android < M, detect if we have an earpiece.
|
||||
PackageManager pm = reactContext.getPackageManager();
|
||||
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
|
||||
availableDevices.add(DEVICE_EARPIECE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// Do an initial detection on Android >= M.
|
||||
runInAudioThread(onAudioDeviceChangeRunner);
|
||||
} else {
|
||||
// On Android < M, detect if we have an earpiece.
|
||||
PackageManager pm = reactContext.getPackageManager();
|
||||
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
|
||||
availableDevices.add(DEVICE_EARPIECE);
|
||||
}
|
||||
|
||||
// Always assume there is a speaker.
|
||||
availableDevices.add(DEVICE_SPEAKER);
|
||||
}
|
||||
|
||||
// Always assume there is a speaker.
|
||||
availableDevices.add(DEVICE_SPEAKER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +371,7 @@ class AudioModeModule
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return MODULE_NAME;
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,6 +429,44 @@ class AudioModeModule
|
||||
});
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
void onCallAudioStateChange(Object callAudioState_) {
|
||||
final android.telecom.CallAudioState callAudioState
|
||||
= (android.telecom.CallAudioState)callAudioState_;
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int newSupportedRoutes = callAudioState.getSupportedRouteMask();
|
||||
boolean audioDevicesChanged
|
||||
= supportedRouteMask != newSupportedRoutes;
|
||||
if (audioDevicesChanged) {
|
||||
supportedRouteMask = newSupportedRoutes;
|
||||
availableDevices = routesToDeviceNames(supportedRouteMask);
|
||||
Log.d(TAG,
|
||||
"Available audio devices: "
|
||||
+ availableDevices.toString());
|
||||
}
|
||||
|
||||
boolean audioRouteChanged
|
||||
= audioDeviceToRouteInt(selectedDevice)
|
||||
!= callAudioState.getRoute();
|
||||
|
||||
if (audioRouteChanged || audioDevicesChanged) {
|
||||
// Reset user selection
|
||||
userSelectedDevice = null;
|
||||
|
||||
// If the OS changes the Audio Route or Devices we could have lost
|
||||
// the selected audio device
|
||||
selectedDevice = null;
|
||||
|
||||
if (mode != -1) {
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
|
||||
* when the audio focus of the system is updated.
|
||||
@@ -417,6 +530,31 @@ class AudioModeModule
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The API >= 26 way of adjusting the audio route.
|
||||
*
|
||||
* @param audioDevice one of the "DEVICE_" names to set as the audio route.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private void setAudioRoute(String audioDevice) {
|
||||
int newAudioRoute = audioDeviceToRouteInt(audioDevice);
|
||||
|
||||
RNConnectionService.setAudioRoute(newAudioRoute);
|
||||
}
|
||||
|
||||
/**
|
||||
* The API < 26 way of adjusting the audio route.
|
||||
*
|
||||
* @param audioDevice one of the "DEVICE_" names to set as the audio route.
|
||||
*/
|
||||
private void setAudioRoutePreO(String audioDevice) {
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
|
||||
|
||||
// Turn speaker on / off
|
||||
audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to set the output route to a Bluetooth device.
|
||||
*
|
||||
@@ -475,7 +613,7 @@ class AudioModeModule
|
||||
|
||||
/**
|
||||
* Setup the audio route change detection mechanism. We use the
|
||||
* {@link android.media.AudioDeviceCallback} API on Android >= 23 only.
|
||||
* {@link android.media.AudioDeviceCallback} on 23 >= Android API < 26.
|
||||
*/
|
||||
private void setupAudioRouteChangeDetection() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
@@ -486,7 +624,7 @@ class AudioModeModule
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio route change detection mechanism for Android API >= 23.
|
||||
* Audio route change detection mechanism for 23 >= Android API < 26.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private void setupAudioRouteChangeDetectionM() {
|
||||
@@ -542,27 +680,31 @@ class AudioModeModule
|
||||
Log.d(TAG, "Update audio route for mode: " + mode);
|
||||
|
||||
if (mode == DEFAULT) {
|
||||
audioFocusLost = false;
|
||||
audioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
audioManager.abandonAudioFocus(this);
|
||||
audioManager.setSpeakerphoneOn(false);
|
||||
setBluetoothAudioRoute(false);
|
||||
if (!useConnectionService()) {
|
||||
audioFocusLost = false;
|
||||
audioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
audioManager.abandonAudioFocus(this);
|
||||
audioManager.setSpeakerphoneOn(false);
|
||||
setBluetoothAudioRoute(false);
|
||||
}
|
||||
selectedDevice = null;
|
||||
userSelectedDevice = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
audioManager.setMicrophoneMute(false);
|
||||
if (!useConnectionService()) {
|
||||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
audioManager.setMicrophoneMute(false);
|
||||
|
||||
if (audioManager.requestAudioFocus(
|
||||
if (audioManager.requestAudioFocus(
|
||||
this,
|
||||
AudioManager.STREAM_VOICE_CALL,
|
||||
AudioManager.AUDIOFOCUS_GAIN)
|
||||
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
Log.d(TAG, "Audio focus request failed");
|
||||
return false;
|
||||
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
Log.d(TAG, "Audio focus request failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
|
||||
@@ -596,11 +738,11 @@ class AudioModeModule
|
||||
selectedDevice = audioDevice;
|
||||
Log.d(TAG, "Selected audio device: " + audioDevice);
|
||||
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
|
||||
|
||||
// Turn speaker on / off
|
||||
audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
|
||||
if (useConnectionService()) {
|
||||
setAudioRoute(audioDevice);
|
||||
} else {
|
||||
setAudioRoutePreO(audioDevice);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2018 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.
|
||||
@@ -21,7 +22,6 @@ import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
@@ -170,7 +170,7 @@ public abstract class BaseReactView<ListenerT>
|
||||
* @param data - The details of the event associated with/specific to the
|
||||
* specified {@code name}.
|
||||
*/
|
||||
public abstract void onExternalAPIEvent(String name, ReadableMap data);
|
||||
protected abstract void onExternalAPIEvent(String name, ReadableMap data);
|
||||
|
||||
protected void onExternalAPIEvent(
|
||||
Map<String, Method> listenerMethods,
|
||||
@@ -194,29 +194,10 @@ public abstract class BaseReactView<ListenerT>
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
|
||||
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
|
||||
|
||||
// FIXME The singleton pattern employed by RNImmersiveModule is not
|
||||
// advisable because a react-native mobule is consumable only after its
|
||||
// BaseJavaModule#initialize() has completed and here we have no
|
||||
// knowledge of whether the precondition is really met.
|
||||
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
|
||||
|
||||
if (hasFocus && immersive != null) {
|
||||
try {
|
||||
immersive.emitImmersiveStateChangeEvent();
|
||||
} catch (RuntimeException re) {
|
||||
// FIXME I don't know how to check myself whether
|
||||
// BaseJavaModule#initialize() has been invoked and thus
|
||||
// RNImmersiveModule is consumable. A safe workaround is to
|
||||
// swallow the failure because the whole full-screen/immersive
|
||||
// functionality is brittle anyway, akin to the icing on the
|
||||
// cake, and has been working without onWindowFocusChanged for a
|
||||
// very long time.
|
||||
Log.e(
|
||||
"RNImmersiveModule",
|
||||
"emitImmersiveStateChangeEvent() failed!",
|
||||
re);
|
||||
}
|
||||
immersive.emitImmersiveStateChangeEvent();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,451 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.telecom.CallAudioState;
|
||||
import android.telecom.Connection;
|
||||
import android.telecom.ConnectionRequest;
|
||||
import android.telecom.DisconnectCause;
|
||||
import android.telecom.PhoneAccount;
|
||||
import android.telecom.PhoneAccountHandle;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.telecom.VideoProfile;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Jitsi Meet implementation of {@link ConnectionService}. At the time of this
|
||||
* writing it implements only the outgoing call scenario.
|
||||
*
|
||||
* NOTE the class needs to be public, but is not part of the SDK API and should
|
||||
* never be used directly.
|
||||
*
|
||||
* @author Pawel Domas
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class ConnectionService extends android.telecom.ConnectionService {
|
||||
|
||||
/**
|
||||
* Tag used for logging.
|
||||
*/
|
||||
static final String TAG = "JitsiConnectionService";
|
||||
|
||||
/**
|
||||
* The extra added to the {@link ConnectionImpl} and
|
||||
* {@link ConnectionRequest} which stores the {@link PhoneAccountHandle}
|
||||
* created for the call.
|
||||
*/
|
||||
static final String EXTRA_PHONE_ACCOUNT_HANDLE
|
||||
= "org.jitsi.meet.sdk.connection_service.PHONE_ACCOUNT_HANDLE";
|
||||
|
||||
/**
|
||||
* Connections mapped by call UUID.
|
||||
*/
|
||||
static private final Map<String, ConnectionImpl> connections
|
||||
= new HashMap<>();
|
||||
|
||||
/**
|
||||
* The start call Promises mapped by call UUID.
|
||||
*/
|
||||
static private final HashMap<String, Promise> startCallPromises
|
||||
= new HashMap<>();
|
||||
|
||||
/**
|
||||
* Adds {@link ConnectionImpl} to the list.
|
||||
*
|
||||
* @param connection - {@link ConnectionImpl}
|
||||
*/
|
||||
static void addConnection(ConnectionImpl connection) {
|
||||
connections.put(connection.getCallUUID(), connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link ConnectionImpl} instances held in this list.
|
||||
*
|
||||
* @return a list of {@link ConnectionImpl}.
|
||||
*/
|
||||
static List<ConnectionImpl> getConnections() {
|
||||
return new ArrayList<>(connections.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if running a Samsung device.
|
||||
*/
|
||||
static boolean isSamsungDevice() {
|
||||
return android.os.Build.MANUFACTURER.toLowerCase().contains("samsung");
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a start call promise.
|
||||
*
|
||||
* @param uuid - the call UUID to which the start call promise belongs to.
|
||||
* @param promise - the Promise instance to be stored for later use.
|
||||
*/
|
||||
static void registerStartCallPromise(String uuid, Promise promise) {
|
||||
startCallPromises.put(uuid, promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes {@link ConnectionImpl} from the list.
|
||||
*
|
||||
* @param connection - {@link ConnectionImpl}
|
||||
*/
|
||||
static void removeConnection(ConnectionImpl connection) {
|
||||
connections.remove(connection.getCallUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to adjusts the connection's state to
|
||||
* {@link android.telecom.Connection#STATE_ACTIVE}.
|
||||
*
|
||||
* @param callUUID the call UUID which identifies the connection.
|
||||
*/
|
||||
static void setConnectionActive(String callUUID) {
|
||||
ConnectionImpl connection = connections.get(callUUID);
|
||||
|
||||
if (connection != null) {
|
||||
connection.setActive();
|
||||
} else {
|
||||
Log.e(TAG, String.format(
|
||||
"setConnectionActive - no connection for UUID: %s",
|
||||
callUUID));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to adjusts the connection's state to
|
||||
* {@link android.telecom.Connection#STATE_DISCONNECTED}.
|
||||
*
|
||||
* @param callUUID the call UUID which identifies the connection.
|
||||
* @param cause disconnection reason.
|
||||
*/
|
||||
static void setConnectionDisconnected(String callUUID, DisconnectCause cause) {
|
||||
ConnectionImpl connection = connections.get(callUUID);
|
||||
|
||||
if (connection != null) {
|
||||
if (isSamsungDevice()) {
|
||||
// Required to release the audio focus correctly.
|
||||
connection.setOnHold();
|
||||
// Prevents from including in the native phone calls history
|
||||
connection.setConnectionProperties(
|
||||
Connection.PROPERTY_SELF_MANAGED
|
||||
| Connection.PROPERTY_IS_EXTERNAL_CALL);
|
||||
}
|
||||
// Note that the connection is not removed from the list here, but
|
||||
// in ConnectionImpl's state changed callback. It's a safer
|
||||
// approach, because in case the app would crash on the JavaScript
|
||||
// side the calls would be cleaned up by the system they would still
|
||||
// be removed from the ConnectionList.
|
||||
connection.setDisconnected(cause);
|
||||
connection.destroy();
|
||||
} else {
|
||||
Log.e(TAG, "endCall no connection for UUID: " + callUUID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a start call promise. Must be called after the Promise is
|
||||
* rejected or resolved.
|
||||
*
|
||||
* @param uuid the call UUID which identifies the call to which the promise
|
||||
* belongs to.
|
||||
* @return the unregistered Promise instance or <tt>null</tt> if there
|
||||
* wasn't any for the given call UUID.
|
||||
*/
|
||||
static Promise unregisterStartCallPromise(String uuid) {
|
||||
return startCallPromises.remove(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to adjusts the call's state.
|
||||
*
|
||||
* @param callUUID the call UUID which identifies the connection.
|
||||
* @param callState a map which carries the properties to be modified. See
|
||||
* "KEY_*" constants in {@link ConnectionImpl} for the list of keys.
|
||||
*/
|
||||
static void updateCall(String callUUID, ReadableMap callState) {
|
||||
ConnectionImpl connection = connections.get(callUUID);
|
||||
|
||||
if (connection != null) {
|
||||
if (callState.hasKey(ConnectionImpl.KEY_HAS_VIDEO)) {
|
||||
boolean hasVideo
|
||||
= callState.getBoolean(ConnectionImpl.KEY_HAS_VIDEO);
|
||||
|
||||
Log.d(TAG, String.format(
|
||||
"updateCall: %s hasVideo: %s", callUUID, hasVideo));
|
||||
connection.setVideoState(
|
||||
hasVideo
|
||||
? VideoProfile.STATE_BIDIRECTIONAL
|
||||
: VideoProfile.STATE_AUDIO_ONLY);
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "updateCall no connection for UUID: " + callUUID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection onCreateOutgoingConnection(
|
||||
PhoneAccountHandle accountHandle, ConnectionRequest request) {
|
||||
ConnectionImpl connection = new ConnectionImpl();
|
||||
|
||||
connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
|
||||
connection.setAddress(
|
||||
request.getAddress(),
|
||||
TelecomManager.PRESENTATION_UNKNOWN);
|
||||
connection.setExtras(request.getExtras());
|
||||
|
||||
connection.setAudioModeIsVoip(true);
|
||||
|
||||
// NOTE there's a time gap between the placeCall and this callback when
|
||||
// things could get out of sync, but they are put back in sync once
|
||||
// the startCall Promise is resolved below. That's because on
|
||||
// the JavaScript side there's a logic to sync up in .then() callback.
|
||||
connection.setVideoState(request.getVideoState());
|
||||
|
||||
Bundle moreExtras = new Bundle();
|
||||
|
||||
moreExtras.putParcelable(
|
||||
EXTRA_PHONE_ACCOUNT_HANDLE,
|
||||
Objects.requireNonNull(request.getAccountHandle(), "accountHandle"));
|
||||
connection.putExtras(moreExtras);
|
||||
|
||||
addConnection(connection);
|
||||
|
||||
Promise startCallPromise
|
||||
= unregisterStartCallPromise(connection.getCallUUID());
|
||||
|
||||
if (startCallPromise != null) {
|
||||
Log.d(TAG,
|
||||
"onCreateOutgoingConnection " + connection.getCallUUID());
|
||||
startCallPromise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, String.format(
|
||||
"onCreateOutgoingConnection: no start call Promise for %s",
|
||||
connection.getCallUUID()));
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection onCreateIncomingConnection(
|
||||
PhoneAccountHandle accountHandle, ConnectionRequest request) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateIncomingConnectionFailed(
|
||||
PhoneAccountHandle accountHandle, ConnectionRequest request) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOutgoingConnectionFailed(
|
||||
PhoneAccountHandle accountHandle, ConnectionRequest request) {
|
||||
PhoneAccountHandle theAccountHandle = request.getAccountHandle();
|
||||
String callUUID = theAccountHandle.getId();
|
||||
|
||||
Log.e(TAG, "onCreateOutgoingConnectionFailed " + callUUID);
|
||||
|
||||
if (callUUID != null) {
|
||||
Promise startCallPromise = unregisterStartCallPromise(callUUID);
|
||||
|
||||
if (startCallPromise != null) {
|
||||
startCallPromise.reject(
|
||||
"CREATE_OUTGOING_CALL_FAILED",
|
||||
"The request has been denied by the system");
|
||||
} else {
|
||||
Log.e(TAG, String.format(
|
||||
"startCallFailed - no start call Promise for UUID: %s",
|
||||
callUUID));
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "onCreateOutgoingConnectionFailed - no call UUID");
|
||||
}
|
||||
|
||||
unregisterPhoneAccount(theAccountHandle);
|
||||
}
|
||||
|
||||
private void unregisterPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
|
||||
TelecomManager telecom = getSystemService(TelecomManager.class);
|
||||
if (telecom != null) {
|
||||
if (phoneAccountHandle != null) {
|
||||
telecom.unregisterPhoneAccount(phoneAccountHandle);
|
||||
} else {
|
||||
Log.e(TAG, "unregisterPhoneAccount - account handle is null");
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "unregisterPhoneAccount - telecom is null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers new {@link PhoneAccountHandle}.
|
||||
*
|
||||
* @param context the current Android context.
|
||||
* @param address the phone account's address. At the time of this writing
|
||||
* it's the call handle passed from the Java Script side.
|
||||
* @param callUUID the call's UUID for which the account is to be created.
|
||||
* It will be used as the account's id.
|
||||
* @return {@link PhoneAccountHandle} described by the given arguments.
|
||||
*/
|
||||
static PhoneAccountHandle registerPhoneAccount(
|
||||
Context context, Uri address, String callUUID) {
|
||||
PhoneAccountHandle phoneAccountHandle
|
||||
= new PhoneAccountHandle(
|
||||
new ComponentName(context, ConnectionService.class),
|
||||
callUUID);
|
||||
|
||||
PhoneAccount.Builder builder
|
||||
= PhoneAccount.builder(phoneAccountHandle, address.toString())
|
||||
.setAddress(address)
|
||||
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
|
||||
PhoneAccount.CAPABILITY_VIDEO_CALLING |
|
||||
PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
|
||||
.addSupportedUriScheme(PhoneAccount.SCHEME_SIP);
|
||||
|
||||
PhoneAccount account = builder.build();
|
||||
|
||||
TelecomManager telecomManager
|
||||
= context.getSystemService(TelecomManager.class);
|
||||
telecomManager.registerPhoneAccount(account);
|
||||
|
||||
return phoneAccountHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection implementation for Jitsi Meet's {@link ConnectionService}.
|
||||
*
|
||||
* @author Pawel Domas
|
||||
*/
|
||||
class ConnectionImpl extends Connection {
|
||||
|
||||
/**
|
||||
* The constant which defines the key for the "has video" property.
|
||||
* The key is used in the map which carries the call's state passed as
|
||||
* the argument of the {@link RNConnectionService#updateCall} method.
|
||||
*/
|
||||
static final String KEY_HAS_VIDEO = "hasVideo";
|
||||
|
||||
/**
|
||||
* Called when system wants to disconnect the call.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onDisconnect() {
|
||||
Log.d(TAG, "onDisconnect " + getCallUUID());
|
||||
WritableNativeMap data = new WritableNativeMap();
|
||||
data.putString("callUUID", getCallUUID());
|
||||
ReactInstanceManagerHolder.emitEvent(
|
||||
"org.jitsi.meet:features/connection_service#disconnect",
|
||||
data);
|
||||
// The JavaScript side will not go back to the native with
|
||||
// 'endCall', so the Connection must be removed immediately.
|
||||
setConnectionDisconnected(
|
||||
getCallUUID(),
|
||||
new DisconnectCause(DisconnectCause.LOCAL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when system wants to abort the call.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onAbort() {
|
||||
Log.d(TAG, "onAbort " + getCallUUID());
|
||||
WritableNativeMap data = new WritableNativeMap();
|
||||
data.putString("callUUID", getCallUUID());
|
||||
ReactInstanceManagerHolder.emitEvent(
|
||||
"org.jitsi.meet:features/connection_service#abort",
|
||||
data);
|
||||
// The JavaScript side will not go back to the native with
|
||||
// 'endCall', so the Connection must be removed immediately.
|
||||
setConnectionDisconnected(
|
||||
getCallUUID(),
|
||||
new DisconnectCause(DisconnectCause.CANCELED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHold() {
|
||||
// What ?! Android will still call this method even if we do not add
|
||||
// the HOLD capability, so do the same thing as on abort.
|
||||
// TODO implement HOLD
|
||||
Log.d(TAG, String.format(
|
||||
"onHold %s - HOLD is not supported, aborting the call...",
|
||||
getCallUUID()));
|
||||
this.onAbort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there's change to the call audio state. Either by
|
||||
* the system after the connection is initialized or in response to
|
||||
* {@link #setAudioRoute(int)}.
|
||||
*
|
||||
* @param state the new {@link CallAudioState}
|
||||
*/
|
||||
@Override
|
||||
public void onCallAudioStateChanged(CallAudioState state) {
|
||||
Log.d(TAG, "onCallAudioStateChanged: " + state);
|
||||
AudioModeModule audioModeModule
|
||||
= ReactInstanceManagerHolder
|
||||
.getNativeModule(AudioModeModule.class);
|
||||
if (audioModeModule != null) {
|
||||
audioModeModule.onCallAudioStateChange(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the account when the call is disconnected.
|
||||
*
|
||||
* @param state - the new connection's state.
|
||||
*/
|
||||
@Override
|
||||
public void onStateChanged(int state) {
|
||||
Log.d(TAG,
|
||||
String.format("onStateChanged: %s %s",
|
||||
Connection.stateToString(state),
|
||||
getCallUUID()));
|
||||
|
||||
if (state == STATE_DISCONNECTED) {
|
||||
removeConnection(this);
|
||||
unregisterPhoneAccount(getPhoneAccountHandle());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the UUID of the call associated with this connection.
|
||||
*
|
||||
* @return call UUID
|
||||
*/
|
||||
String getCallUUID() {
|
||||
return getPhoneAccountHandle().getId();
|
||||
}
|
||||
|
||||
private PhoneAccountHandle getPhoneAccountHandle() {
|
||||
return getExtras().getParcelable(
|
||||
ConnectionService.EXTRA_PHONE_ACCOUNT_HANDLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"ConnectionImpl[address=%s, uuid=%s]@%d",
|
||||
getAddress(), getCallUUID(), hashCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 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.
|
||||
@@ -21,15 +22,14 @@ import android.app.Activity;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
|
||||
/**
|
||||
* Defines the default behavior of {@code JitsiMeetActivity} and
|
||||
* Defines the default behavior of {@code JitsiMeetFragment} and
|
||||
* {@code JitsiMeetView} upon invoking the back button if no
|
||||
* {@code JitsiMeetView} handles the invocation. For example, a
|
||||
* {@code JitsiMeetView} may (1) handle the invocation of the back button
|
||||
* during a conference by leaving the conference and (2) not handle the
|
||||
* invocation when not in a conference.
|
||||
*/
|
||||
public class DefaultHardwareBackBtnHandlerImpl
|
||||
implements DefaultHardwareBackBtnHandler {
|
||||
class DefaultHardwareBackBtnHandlerImpl implements DefaultHardwareBackBtnHandler {
|
||||
|
||||
/**
|
||||
* The {@code Activity} to which the default handling of the back button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.jitsi.meet.sdk.dropbox;
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
@@ -20,6 +20,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.dropbox.core.android.Auth;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -27,9 +28,13 @@ import java.util.Map;
|
||||
/**
|
||||
* Implements the react-native module for the dropbox integration.
|
||||
*/
|
||||
public class Dropbox
|
||||
@ReactModule(name = DropboxModule.NAME)
|
||||
class DropboxModule
|
||||
extends ReactContextBaseJavaModule
|
||||
implements LifecycleEventListener {
|
||||
|
||||
public static final String NAME = "Dropbox";
|
||||
|
||||
private String appKey;
|
||||
|
||||
private String clientId;
|
||||
@@ -38,12 +43,14 @@ public class Dropbox
|
||||
|
||||
private Promise promise;
|
||||
|
||||
public Dropbox(ReactApplicationContext reactContext) {
|
||||
public DropboxModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
String pkg = reactContext.getApplicationContext().getPackageName();
|
||||
int resId = reactContext.getResources()
|
||||
.getIdentifier("dropbox_app_key", "string", pkg);
|
||||
appKey
|
||||
= reactContext.getString(
|
||||
org.jitsi.meet.sdk.R.string.dropbox_app_key);
|
||||
= reactContext.getString(resId);
|
||||
isEnabled = !TextUtils.isEmpty(appKey);
|
||||
|
||||
clientId = generateClientId();
|
||||
@@ -129,7 +136,7 @@ public class Dropbox
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Dropbox";
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,14 +22,18 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
/**
|
||||
* Module implementing an API for sending events from JavaScript to native code.
|
||||
*/
|
||||
@ReactModule(name = ExternalAPIModule.NAME)
|
||||
class ExternalAPIModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String TAG = ExternalAPIModule.class.getSimpleName();
|
||||
public static final String NAME = "ExternalAPI";
|
||||
|
||||
private static final String TAG = NAME;
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
@@ -49,7 +53,7 @@ class ExternalAPIModule
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ExternalAPI";
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
63
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeet.java
Normal file
63
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeet.java
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 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.os.Bundle;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
|
||||
public class JitsiMeet {
|
||||
/**
|
||||
* Default {@link JitsiMeetConferenceOptions} which will be used for all conferences. When
|
||||
* joining a conference these options will be merged with the ones passed to
|
||||
* {@link JitsiMeetView} join().
|
||||
*/
|
||||
private static JitsiMeetConferenceOptions defaultConferenceOptions;
|
||||
|
||||
public static JitsiMeetConferenceOptions getDefaultConferenceOptions() {
|
||||
return defaultConferenceOptions;
|
||||
}
|
||||
|
||||
public static void setDefaultConferenceOptions(JitsiMeetConferenceOptions options) {
|
||||
defaultConferenceOptions = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the default conference options as a {@link Bundle}.
|
||||
*
|
||||
* @return a {@link Bundle} with the default conference options.
|
||||
*/
|
||||
static Bundle getDefaultProps() {
|
||||
if (defaultConferenceOptions != null) {
|
||||
return defaultConferenceOptions.asProps();
|
||||
}
|
||||
|
||||
return new Bundle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in development mode. It displays the React Native development menu.
|
||||
*/
|
||||
public static void showDevOptions() {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.showDevOptionsDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,330 +16,184 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.KeyEvent;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Base Activity for applications integrating Jitsi Meet at a higher level. It
|
||||
* contains all the required wiring between the {@code JitsiMeetView} and
|
||||
* the Activity lifecycle methods already implemented.
|
||||
*
|
||||
* 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 JitsiMeetView} static methods.
|
||||
* A base activity for SDK users to embed. It uses {@link JitsiMeetFragment} to do the heavy
|
||||
* lifting and wires the remaining Activity lifecycle methods so it works out of the box.
|
||||
*/
|
||||
public class JitsiMeetActivity
|
||||
extends AppCompatActivity implements JitsiMeetActivityInterface {
|
||||
public class JitsiMeetActivity extends FragmentActivity
|
||||
implements JitsiMeetActivityInterface, JitsiMeetViewListener {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private static final int OVERLAY_PERMISSION_REQUEST_CODE
|
||||
= (int) (Math.random() * Short.MAX_VALUE);
|
||||
protected static final String TAG = JitsiMeetActivity.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* The default behavior of this {@code JitsiMeetActivity} upon invoking the
|
||||
* back button if {@link #view} does not handle the invocation.
|
||||
*/
|
||||
private DefaultHardwareBackBtnHandler defaultBackButtonImpl;
|
||||
public static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
|
||||
public static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions";
|
||||
|
||||
/**
|
||||
* The default base {@code URL} used to join a conference when a partial URL
|
||||
* (e.g. a room name only) is specified. The value is used only while
|
||||
* {@link #view} equals {@code null}.
|
||||
*/
|
||||
private URL defaultURL;
|
||||
// Helpers for starting the activity
|
||||
//
|
||||
|
||||
/**
|
||||
* Instance of the {@link JitsiMeetView} which this activity will display.
|
||||
*/
|
||||
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}.
|
||||
*/
|
||||
private boolean welcomePageEnabled;
|
||||
|
||||
private boolean canRequestOverlayPermission() {
|
||||
return
|
||||
BuildConfig.DEBUG
|
||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& getApplicationInfo().targetSdkVersion
|
||||
>= Build.VERSION_CODES.M;
|
||||
public static void launch(Context context, JitsiMeetConferenceOptions options) {
|
||||
Intent intent = new Intent(context, JitsiMeetActivity.class);
|
||||
intent.setAction(ACTION_JITSI_MEET_CONFERENCE);
|
||||
intent.putExtra(JITSI_MEET_CONFERENCE_OPTIONS, options);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#getDefaultURL()
|
||||
*/
|
||||
public URL getDefaultURL() {
|
||||
return view == null ? defaultURL : view.getDefaultURL();
|
||||
public static void launch(Context context, String url) {
|
||||
JitsiMeetConferenceOptions options
|
||||
= new JitsiMeetConferenceOptions.Builder().setRoom(url).build();
|
||||
launch(context, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the {@link #view} of this {@code JitsiMeetActivity} with a
|
||||
* new {@link JitsiMeetView} instance.
|
||||
*/
|
||||
private void initializeContentView() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@link JitsiMeetView} instance.
|
||||
*
|
||||
* @return a new {@code JitsiMeetView} instance.
|
||||
*/
|
||||
protected JitsiMeetView initializeView() {
|
||||
JitsiMeetView view = new JitsiMeetView(this);
|
||||
|
||||
// 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);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#isPictureInPictureEnabled()
|
||||
*/
|
||||
public boolean isPictureInPictureEnabled() {
|
||||
return
|
||||
view == null
|
||||
? pictureInPictureEnabled
|
||||
: view.isPictureInPictureEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#isWelcomePageEnabled()
|
||||
*/
|
||||
public boolean isWelcomePageEnabled() {
|
||||
return view == null ? welcomePageEnabled : view.isWelcomePageEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given URL and displays the conference. If the specified URL is
|
||||
* null, the welcome page is displayed instead.
|
||||
*
|
||||
* @param url The conference URL.
|
||||
*/
|
||||
public void loadURL(@Nullable URL url) {
|
||||
view.loadURL(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(
|
||||
int requestCode,
|
||||
int resultCode,
|
||||
Intent data) {
|
||||
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
|
||||
&& canRequestOverlayPermission()) {
|
||||
if (Settings.canDrawOverlays(this)) {
|
||||
initializeContentView();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ReactActivityLifecycleCallbacks.onActivityResult(
|
||||
this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!ReactActivityLifecycleCallbacks.onBackPressed()) {
|
||||
// JitsiMeetView didn't handle the invocation of the back button.
|
||||
// Generally, an Activity extender would very likely want to invoke
|
||||
// Activity#onBackPressed(). For the sake of consistency with
|
||||
// JitsiMeetView and within the Jitsi Meet SDK for Android though,
|
||||
// JitsiMeetActivity does what JitsiMeetView would've done if it
|
||||
// were able to handle the invocation.
|
||||
if (defaultBackButtonImpl == null) {
|
||||
super.onBackPressed();
|
||||
} else {
|
||||
defaultBackButtonImpl.invokeDefaultOnBackPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Overrides
|
||||
//
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// In Debug builds React needs permission to write over other apps in
|
||||
// order to display the warning and error overlays.
|
||||
if (canRequestOverlayPermission() && !Settings.canDrawOverlays(this)) {
|
||||
Intent intent
|
||||
= new Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:" + getPackageName()));
|
||||
setContentView(R.layout.activity_jitsi_meet);
|
||||
|
||||
startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE);
|
||||
return;
|
||||
if (!extraInitialize()) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
initializeContentView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
public void finish() {
|
||||
leave();
|
||||
|
||||
if (view != null) {
|
||||
view.dispose();
|
||||
view = null;
|
||||
}
|
||||
|
||||
ReactActivityLifecycleCallbacks.onHostDestroy(this);
|
||||
super.finish();
|
||||
}
|
||||
|
||||
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
ReactInstanceManager reactInstanceManager;
|
||||
// Helper methods
|
||||
//
|
||||
|
||||
if (!super.onKeyUp(keyCode, event)
|
||||
&& BuildConfig.DEBUG
|
||||
&& (reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager())
|
||||
!= null
|
||||
&& keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
reactInstanceManager.showDevOptionsDialog();
|
||||
return true;
|
||||
protected JitsiMeetView getJitsiView() {
|
||||
JitsiMeetFragment fragment
|
||||
= (JitsiMeetFragment) getSupportFragmentManager().findFragmentById(R.id.jitsiFragment);
|
||||
return fragment.getJitsiView();
|
||||
}
|
||||
|
||||
public void join(@Nullable String url) {
|
||||
JitsiMeetConferenceOptions options
|
||||
= new JitsiMeetConferenceOptions.Builder()
|
||||
.setRoom(url)
|
||||
.build();
|
||||
join(options);
|
||||
}
|
||||
|
||||
public void join(JitsiMeetConferenceOptions options) {
|
||||
getJitsiView().join(options);
|
||||
}
|
||||
|
||||
public void leave() {
|
||||
getJitsiView().leave();
|
||||
}
|
||||
|
||||
private @Nullable JitsiMeetConferenceOptions getConferenceOptions(Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(action)) {
|
||||
Uri uri = intent.getData();
|
||||
if (uri != null) {
|
||||
return new JitsiMeetConferenceOptions.Builder().setRoom(uri.toString()).build();
|
||||
}
|
||||
} else if (ACTION_JITSI_MEET_CONFERENCE.equals(action)) {
|
||||
return intent.getParcelableExtra(JITSI_MEET_CONFERENCE_OPTIONS);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function called during activity initialization. If {@code true} is returned, the
|
||||
* initialization is delayed and the {@link JitsiMeetActivity#initialize()} method is not
|
||||
* called. In this case, it's up to the subclass to call the initialize method when ready.
|
||||
*
|
||||
* This is mainly required so we do some extra initialization in the Jitsi Meet app.
|
||||
*
|
||||
* @return {@code true} if the initialization will be delayed, {@code false} otherwise.
|
||||
*/
|
||||
protected boolean extraInitialize() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void initialize() {
|
||||
// Listen for conference events.
|
||||
getJitsiView().setListener(this);
|
||||
|
||||
// Join the room specified by the URL the app was launched with.
|
||||
// Joining without the room option displays the welcome page.
|
||||
join(getConferenceOptions(getIntent()));
|
||||
}
|
||||
|
||||
// Activity lifecycle methods
|
||||
//
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
JitsiMeetActivityDelegate.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
// XXX At least twice we received bug reports about malfunctioning
|
||||
// loadURL in the Jitsi Meet SDK while the Jitsi Meet app seemed to
|
||||
// functioning as expected in our testing. But that was to be expected
|
||||
// because the app does not exercise loadURL. In order to increase the
|
||||
// test coverage of loadURL, channel deep linking through loadURL.
|
||||
Uri uri;
|
||||
JitsiMeetConferenceOptions options;
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())
|
||||
&& (uri = intent.getData()) != null
|
||||
&& JitsiMeetView.loadURLStringInViews(uri.toString())) {
|
||||
if ((options = getConferenceOptions(intent)) != null) {
|
||||
join(options);
|
||||
return;
|
||||
}
|
||||
|
||||
ReactActivityLifecycleCallbacks.onNewIntent(intent);
|
||||
}
|
||||
|
||||
// https://developer.android.com/reference/android/support/v4/app/ActivityCompat.OnRequestPermissionsResultCallback
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
final int requestCode,
|
||||
final String[] permissions,
|
||||
final int[] grantResults) {
|
||||
ReactActivityLifecycleCallbacks.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
defaultBackButtonImpl = new DefaultHardwareBackBtnHandlerImpl(this);
|
||||
ReactActivityLifecycleCallbacks.onHostResume(this, defaultBackButtonImpl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
ReactActivityLifecycleCallbacks.onHostPause(this);
|
||||
defaultBackButtonImpl = null;
|
||||
JitsiMeetActivityDelegate.onNewIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUserLeaveHint() {
|
||||
if (view != null) {
|
||||
view.enterPictureInPicture();
|
||||
}
|
||||
getJitsiView().enterPictureInPicture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the {@code PermissionAwareActivity} interface.
|
||||
*/
|
||||
// JitsiMeetActivityInterface
|
||||
//
|
||||
|
||||
@Override
|
||||
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
|
||||
ReactActivityLifecycleCallbacks.requestPermissions(this, permissions, requestCode, listener);
|
||||
JitsiMeetActivityDelegate.requestPermissions(this, permissions, requestCode, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setDefaultURL(URL)
|
||||
*/
|
||||
public void setDefaultURL(URL defaultURL) {
|
||||
if (view == null) {
|
||||
this.defaultURL = defaultURL;
|
||||
} else {
|
||||
view.setDefaultURL(defaultURL);
|
||||
}
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setPictureInPictureEnabled(boolean)
|
||||
*/
|
||||
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
|
||||
if (view == null) {
|
||||
this.pictureInPictureEnabled
|
||||
= Boolean.valueOf(pictureInPictureEnabled);
|
||||
} else {
|
||||
view.setPictureInPictureEnabled(pictureInPictureEnabled);
|
||||
}
|
||||
// JitsiMeetViewListener
|
||||
//
|
||||
|
||||
@Override
|
||||
public void onConferenceJoined(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference joined: " + data);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setWelcomePageEnabled(boolean)
|
||||
*/
|
||||
public void setWelcomePageEnabled(boolean welcomePageEnabled) {
|
||||
if (view == null) {
|
||||
this.welcomePageEnabled = welcomePageEnabled;
|
||||
} else {
|
||||
view.setWelcomePageEnabled(welcomePageEnabled);
|
||||
}
|
||||
@Override
|
||||
public void onConferenceTerminated(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference terminated: " + data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceWillJoin(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference will join: " + data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
* Copyright @ 2018 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.
|
||||
@@ -24,7 +25,6 @@ import android.os.Build;
|
||||
import com.calendarevents.CalendarEventsPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
/**
|
||||
@@ -32,7 +32,22 @@ import com.facebook.react.modules.core.PermissionListener;
|
||||
* {@link Activity} lifecycle methods in order for the React side to be aware of
|
||||
* it.
|
||||
*/
|
||||
public class ReactActivityLifecycleCallbacks {
|
||||
public class JitsiMeetActivityDelegate {
|
||||
/**
|
||||
* Needed for making sure this class working with the "PermissionsAndroid"
|
||||
* React Native module.
|
||||
*/
|
||||
private static PermissionListener permissionListener;
|
||||
private static Callback permissionsCallback;
|
||||
|
||||
/**
|
||||
* Tells whether or not the permissions request is currently in progress.
|
||||
*
|
||||
* @return {@code true} if the permssions are being requested or {@code false} otherwise.
|
||||
*/
|
||||
static boolean arePermissionsBeingRequested() {
|
||||
return permissionListener != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
@@ -57,13 +72,6 @@ public class ReactActivityLifecycleCallbacks {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed for making sure this class working with the "PermissionsAndroid"
|
||||
* React Native module.
|
||||
*/
|
||||
private static PermissionListener permissionListener;
|
||||
private static Callback permissionsCallback;
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@link Activity#onBackPressed} so we can do the required internal
|
||||
@@ -73,15 +81,12 @@ public class ReactActivityLifecycleCallbacks {
|
||||
* otherwise. If {@code false}, the application should call the
|
||||
* {@code super}'s implementation.
|
||||
*/
|
||||
public static boolean onBackPressed() {
|
||||
public static void onBackPressed() {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager == null) {
|
||||
return false;
|
||||
} else {
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onBackPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,25 +128,11 @@ public class ReactActivityLifecycleCallbacks {
|
||||
* @param activity {@code Activity} being resumed.
|
||||
*/
|
||||
public static void onHostResume(Activity activity) {
|
||||
onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onResume} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being resumed.
|
||||
* @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} to
|
||||
* handle invoking the back button if no {@link BaseReactView} handles it.
|
||||
*/
|
||||
public static void onHostResume(
|
||||
Activity activity,
|
||||
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
|
||||
reactInstanceManager.onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
|
||||
}
|
||||
|
||||
if (permissionsCallback != null) {
|
||||
@@ -6,7 +6,7 @@ import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||
|
||||
/**
|
||||
* This interface serves as the umbrella interface that applications not using
|
||||
* {@code JitsiMeetActivity} must implement in order to ensure full
|
||||
* {@code JitsiMeetFragment} must implement in order to ensure full
|
||||
* functionality.
|
||||
*/
|
||||
public interface JitsiMeetActivityInterface
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
|
||||
/**
|
||||
* This class represents the options when joining a Jitsi Meet conference. The user can create an
|
||||
* instance by using {@link JitsiMeetConferenceOptions.Builder} and setting the desired options
|
||||
* there.
|
||||
*
|
||||
* The resulting {@link JitsiMeetConferenceOptions} object is immutable and represents how the
|
||||
* conference will be joined.
|
||||
*/
|
||||
public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
/**
|
||||
* Server where the conference should take place.
|
||||
*/
|
||||
private URL serverURL;
|
||||
/**
|
||||
* Room name.
|
||||
*/
|
||||
private String room;
|
||||
/**
|
||||
* JWT token used for authentication.
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* Color scheme override, see: https://github.com/jitsi/jitsi-meet/blob/dbedee5e22e5dcf9c92db96ef5bb3c9982fc526d/react/features/base/color-scheme/defaultScheme.js
|
||||
*/
|
||||
private Bundle colorScheme;
|
||||
|
||||
/**
|
||||
* Set to {@code true} to join the conference with audio / video muted or to start in audio
|
||||
* only mode respectively.
|
||||
*/
|
||||
private Boolean audioMuted;
|
||||
private Boolean audioOnly;
|
||||
private Boolean videoMuted;
|
||||
|
||||
/**
|
||||
* Set to {@code true} to enable the welcome page. Typically SDK users won't need this enabled
|
||||
* since the host application decides which meeting to join.
|
||||
*/
|
||||
private Boolean welcomePageEnabled;
|
||||
|
||||
/**
|
||||
* Class used to build the immutable {@link JitsiMeetConferenceOptions} object.
|
||||
*/
|
||||
public static class Builder {
|
||||
private URL serverURL;
|
||||
private String room;
|
||||
private String token;
|
||||
|
||||
private Bundle colorScheme;
|
||||
|
||||
private Boolean audioMuted;
|
||||
private Boolean audioOnly;
|
||||
private Boolean videoMuted;
|
||||
|
||||
private Boolean welcomePageEnabled;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
/**\
|
||||
* Sets the server URL.
|
||||
* @param url - {@link URL} of the server where the conference should take place.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setServerURL(URL url) {
|
||||
this.serverURL = url;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the room where the conference will take place.
|
||||
* @param room - Name of the room.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setRoom(String room) {
|
||||
this.room = room;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the JWT token to be used for authentication when joining a conference.
|
||||
* @param token - The JWT token to be used for authentication.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setToken(String token) {
|
||||
this.token = token;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color scheme override so the app is themed. See:
|
||||
* https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/color-scheme/defaultScheme.js
|
||||
* for the structure.
|
||||
* @param colorScheme - A color scheme to be applied to the app.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setColorScheme(Bundle colorScheme) {
|
||||
this.colorScheme = colorScheme;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the conference will be joined with the microphone muted.
|
||||
* @param muted - Muted indication.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setAudioMuted(boolean muted) {
|
||||
this.audioMuted = muted;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the conference will be joined in audio-only mode. In this mode no video is
|
||||
* sent or received.
|
||||
* @param audioOnly - Audio-mode indicator.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setAudioOnly(boolean audioOnly) {
|
||||
this.audioOnly = audioOnly;
|
||||
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Indicates the conference will be joined with the camera muted.
|
||||
* @param videoMuted - Muted indication.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setVideoMuted(boolean videoMuted) {
|
||||
this.videoMuted = videoMuted;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the welcome page enabled / disabled. The welcome page lists recent meetings and
|
||||
* calendar appointments and it's meant to be used by standalone applications. Defaults to
|
||||
* false.
|
||||
* @param enabled - Whether the welcome page should be enabled or not.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setWelcomePageEnabled(boolean enabled) {
|
||||
this.welcomePageEnabled = enabled;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the immutable {@link JitsiMeetConferenceOptions} object with the configuration
|
||||
* that this {@link Builder} instance specified.
|
||||
* @return - The built {@link JitsiMeetConferenceOptions} object.
|
||||
*/
|
||||
public JitsiMeetConferenceOptions build() {
|
||||
JitsiMeetConferenceOptions options = new JitsiMeetConferenceOptions();
|
||||
|
||||
options.serverURL = this.serverURL;
|
||||
options.room = this.room;
|
||||
options.token = this.token;
|
||||
options.colorScheme = this.colorScheme;
|
||||
options.audioMuted = this.audioMuted;
|
||||
options.audioOnly = this.audioOnly;
|
||||
options.videoMuted = this.videoMuted;
|
||||
options.welcomePageEnabled = this.welcomePageEnabled;
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
private JitsiMeetConferenceOptions() {
|
||||
}
|
||||
|
||||
private JitsiMeetConferenceOptions(Parcel in) {
|
||||
room = in.readString();
|
||||
token = in.readString();
|
||||
colorScheme = in.readBundle();
|
||||
byte tmpAudioMuted = in.readByte();
|
||||
audioMuted = tmpAudioMuted == 0 ? null : tmpAudioMuted == 1;
|
||||
byte tmpAudioOnly = in.readByte();
|
||||
audioOnly = tmpAudioOnly == 0 ? null : tmpAudioOnly == 1;
|
||||
byte tmpVideoMuted = in.readByte();
|
||||
videoMuted = tmpVideoMuted == 0 ? null : tmpVideoMuted == 1;
|
||||
byte tmpWelcomePageEnabled = in.readByte();
|
||||
welcomePageEnabled = tmpWelcomePageEnabled == 0 ? null : tmpWelcomePageEnabled == 1;
|
||||
}
|
||||
|
||||
Bundle asProps() {
|
||||
Bundle props = new Bundle();
|
||||
|
||||
if (colorScheme != null) {
|
||||
props.putBundle("colorScheme", colorScheme);
|
||||
}
|
||||
|
||||
if (welcomePageEnabled != null) {
|
||||
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
||||
}
|
||||
|
||||
// TODO: get rid of this.
|
||||
props.putBoolean("pictureInPictureEnabled", true);
|
||||
|
||||
Bundle config = new Bundle();
|
||||
|
||||
if (audioMuted != null) {
|
||||
config.putBoolean("startWithAudioMuted", audioMuted);
|
||||
}
|
||||
if (audioOnly != null) {
|
||||
config.putBoolean("startAudioOnly", audioOnly);
|
||||
}
|
||||
if (videoMuted != null) {
|
||||
config.putBoolean("startWithVideoMuted", videoMuted);
|
||||
}
|
||||
|
||||
Bundle urlProps = new Bundle();
|
||||
|
||||
// The room is fully qualified
|
||||
if (room != null && room.contains("://")) {
|
||||
urlProps.putString("url", room);
|
||||
} else {
|
||||
if (serverURL != null) {
|
||||
urlProps.putString("serverURL", serverURL.toString());
|
||||
}
|
||||
if (room != null) {
|
||||
urlProps.putString("room", room);
|
||||
}
|
||||
}
|
||||
|
||||
if (token != null) {
|
||||
urlProps.putString("jwt", token);
|
||||
}
|
||||
|
||||
urlProps.putBundle("config", config);
|
||||
props.putBundle("url", urlProps);
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
// Parcelable interface
|
||||
//
|
||||
|
||||
public static final Creator<JitsiMeetConferenceOptions> CREATOR = new Creator<JitsiMeetConferenceOptions>() {
|
||||
@Override
|
||||
public JitsiMeetConferenceOptions createFromParcel(Parcel in) {
|
||||
return new JitsiMeetConferenceOptions(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JitsiMeetConferenceOptions[] newArray(int size) {
|
||||
return new JitsiMeetConferenceOptions[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(room);
|
||||
dest.writeString(token);
|
||||
dest.writeBundle(colorScheme);
|
||||
dest.writeByte((byte) (audioMuted == null ? 0 : audioMuted ? 1 : 2));
|
||||
dest.writeByte((byte) (audioOnly == null ? 0 : audioOnly ? 1 : 2));
|
||||
dest.writeByte((byte) (videoMuted == null ? 0 : videoMuted ? 1 : 2));
|
||||
dest.writeByte((byte) (welcomePageEnabled == null ? 0 : welcomePageEnabled ? 1 : 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 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.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Base {@link Fragment} for applications integrating Jitsi Meet at a higher level. It
|
||||
* contains all the required wiring between the {@code JitsiMeetView} and
|
||||
* the Fragment lifecycle methods already implemented.
|
||||
*
|
||||
* In this fragment 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 lifecycle methods associated with this Fragment are
|
||||
* hooked to the React Native subsystem via proxy calls through the
|
||||
* {@code JitsiMeetActivityDelegate} static methods.
|
||||
*/
|
||||
public class JitsiMeetFragment extends Fragment {
|
||||
|
||||
/**
|
||||
* Instance of the {@link JitsiMeetView} which this activity will display.
|
||||
*/
|
||||
private JitsiMeetView view;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return this.view = new JitsiMeetView(getActivity());
|
||||
}
|
||||
|
||||
public JitsiMeetView getJitsiView() {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
JitsiMeetActivityDelegate.onActivityResult(
|
||||
getActivity(), requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
if (view != null) {
|
||||
view.dispose();
|
||||
view = null;
|
||||
}
|
||||
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
JitsiMeetActivityDelegate.onHostDestroy(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
JitsiMeetActivityDelegate.onHostResume(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
JitsiMeetActivityDelegate.onHostPause(getActivity());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 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.
|
||||
@@ -24,14 +25,11 @@ import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import org.jitsi.meet.sdk.invite.InviteController;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
public class JitsiMeetView
|
||||
extends BaseReactView<JitsiMeetViewListener> {
|
||||
|
||||
public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener> {
|
||||
|
||||
/**
|
||||
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
|
||||
@@ -46,51 +44,6 @@ public class JitsiMeetView
|
||||
*/
|
||||
private static final String TAG = JitsiMeetView.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Loads a specific URL {@code String} in all existing
|
||||
* {@code JitsiMeetView}s.
|
||||
*
|
||||
* @param urlString he URL {@code String} to load in all existing
|
||||
* {@code JitsiMeetView}s.
|
||||
* @return If the specified {@code urlString} was submitted for loading in
|
||||
* at least one {@code JitsiMeetView}, then {@code true}; otherwise,
|
||||
* {@code false}.
|
||||
*/
|
||||
public static boolean loadURLStringInViews(String urlString) {
|
||||
boolean loaded = false;
|
||||
|
||||
synchronized (views) {
|
||||
for (BaseReactView view : views) {
|
||||
if (view instanceof JitsiMeetView) {
|
||||
((JitsiMeetView)view).loadURLString(urlString);
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default base {@code URL} used to join a conference when a partial URL
|
||||
* (e.g. a room name only) is specified to {@link #loadURLString(String)} or
|
||||
* {@link #loadURLObject(Bundle)}.
|
||||
*/
|
||||
private URL defaultURL;
|
||||
|
||||
/**
|
||||
* The entry point into the invite feature of Jitsi Meet. The Java
|
||||
* counterpart of the JavaScript {@code InviteButton}.
|
||||
*/
|
||||
private final InviteController inviteController;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The URL of the current conference.
|
||||
*/
|
||||
@@ -99,16 +52,60 @@ public class JitsiMeetView
|
||||
private volatile String url;
|
||||
|
||||
/**
|
||||
* Whether the Welcome page is enabled.
|
||||
* Helper method to recursively merge 2 {@link Bundle} objects representing React Native props.
|
||||
*
|
||||
* @param a - The first {@link Bundle}.
|
||||
* @param b - The second {@link Bundle}.
|
||||
* @return The merged {@link Bundle} object.
|
||||
*/
|
||||
private boolean welcomePageEnabled;
|
||||
private static Bundle mergeProps(@Nullable Bundle a, @Nullable Bundle b) {
|
||||
Bundle result = new Bundle();
|
||||
|
||||
if (a == null) {
|
||||
if (b != null) {
|
||||
result.putAll(b);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (b == null) {
|
||||
result.putAll(a);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Start by putting all of a in the result.
|
||||
result.putAll(a);
|
||||
|
||||
// Iterate over each key in b and override if appropriate.
|
||||
for (String key : b.keySet()) {
|
||||
Object bValue = b.get(key);
|
||||
Object aValue = a.get(key);
|
||||
String valueType = bValue.getClass().getSimpleName();
|
||||
|
||||
if (valueType.contentEquals("Boolean")) {
|
||||
result.putBoolean(key, (Boolean)bValue);
|
||||
} else if (valueType.contentEquals("String")) {
|
||||
result.putString(key, (String)bValue);
|
||||
} else if (valueType.contentEquals("Bundle")) {
|
||||
result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue));
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported type: " + valueType);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public JitsiMeetView(@NonNull Context context) {
|
||||
super(context);
|
||||
|
||||
// The entry point into the invite feature of Jitsi Meet. The Java
|
||||
// counterpart of the JavaScript InviteButton.
|
||||
inviteController = new InviteController(externalAPIScope);
|
||||
// Check if the parent Activity implements JitsiMeetActivityInterface,
|
||||
// otherwise things may go wrong.
|
||||
if (!(context instanceof JitsiMeetActivityInterface)) {
|
||||
throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,176 +118,60 @@ public class JitsiMeetView
|
||||
* page.
|
||||
*/
|
||||
public void enterPictureInPicture() {
|
||||
if (isPictureInPictureEnabled() && getURL() != null) {
|
||||
PictureInPictureModule pipModule
|
||||
= ReactInstanceManagerHolder.getNativeModule(
|
||||
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);
|
||||
}
|
||||
if (pipModule != null
|
||||
&& PictureInPictureModule.isPictureInPictureSupported()
|
||||
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()
|
||||
&& this.url != null) {
|
||||
try {
|
||||
pipModule.enterPictureInPicture();
|
||||
} catch (RuntimeException re) {
|
||||
Log.e(TAG, "failed to enter PiP mode", re);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default base {@code URL} used to join a conference when a
|
||||
* partial URL (e.g. a room name only) is specified to
|
||||
* {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. If not
|
||||
* set or if set to {@code null}, the default built in JavaScript is used:
|
||||
* https://meet.jit.si
|
||||
*
|
||||
* @return The default base {@code URL} or {@code null}.
|
||||
* Joins the conference specified by the given {@link JitsiMeetConferenceOptions}. If there is
|
||||
* already an active conference, it will be left and the new one will be joined.
|
||||
* @param options - Description of what conference must be joined and what options will be used
|
||||
* when doing so.
|
||||
*/
|
||||
public URL getDefaultURL() {
|
||||
return defaultURL;
|
||||
public void join(@Nullable JitsiMeetConferenceOptions options) {
|
||||
setProps(options != null ? options.asProps() : new Bundle());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* Leaves the currently active conference.
|
||||
*/
|
||||
public InviteController getInviteController() {
|
||||
return inviteController;
|
||||
public void leave() {
|
||||
setProps(new Bundle());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* Helper method to set the React Native props.
|
||||
* @param newProps - New props to be set on the React Native view.
|
||||
*/
|
||||
String getURL() {
|
||||
return url;
|
||||
}
|
||||
private void setProps(@NonNull Bundle newProps) {
|
||||
// Merge the default options with the newly provided ones.
|
||||
Bundle props = mergeProps(JitsiMeet.getDefaultProps(), newProps);
|
||||
|
||||
/**
|
||||
* Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
|
||||
* natively supported on Android API >= 26 (Oreo), so it should not be
|
||||
* enabled on older platform versions.
|
||||
*
|
||||
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean isPictureInPictureEnabled() {
|
||||
return
|
||||
PictureInPictureModule.isPictureInPictureSupported()
|
||||
&& (pictureInPictureEnabled == null
|
||||
|| pictureInPictureEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the Welcome page is enabled. If {@code true}, the Welcome
|
||||
* page is rendered when this {@code JitsiMeetView} is not at a URL
|
||||
* identifying a Jitsi Meet conference/room.
|
||||
*
|
||||
* @return {@code true} if the Welcome page is enabled; otherwise,
|
||||
* {@code false}.
|
||||
*/
|
||||
public boolean isWelcomePageEnabled() {
|
||||
return welcomePageEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a specific {@link URL} which may identify a conference to join. If
|
||||
* the specified {@code URL} is {@code null} and the Welcome page is
|
||||
* enabled, the Welcome page is displayed instead.
|
||||
*
|
||||
* @param url The {@code URL} to load which may identify a conference to
|
||||
* join.
|
||||
*/
|
||||
public void loadURL(@Nullable URL url) {
|
||||
loadURLString(url == null ? null : url.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a specific URL which may identify a conference to join. The URL is
|
||||
* specified in the form of a {@link Bundle} of properties which (1)
|
||||
* internally are sufficient to construct a URL {@code String} while (2)
|
||||
* abstracting the specifics of constructing the URL away from API
|
||||
* clients/consumers. If the specified URL is {@code null} and the Welcome
|
||||
* page is enabled, the Welcome page is displayed instead.
|
||||
*
|
||||
* @param urlObject The URL to load which may identify a conference to join.
|
||||
*/
|
||||
public void loadURLObject(@Nullable Bundle urlObject) {
|
||||
Bundle props = new Bundle();
|
||||
|
||||
// defaultURL
|
||||
if (defaultURL != null) {
|
||||
props.putString("defaultURL", defaultURL.toString());
|
||||
}
|
||||
|
||||
// inviteController
|
||||
InviteController inviteController = getInviteController();
|
||||
|
||||
if (inviteController != null) {
|
||||
props.putBoolean(
|
||||
"addPeopleEnabled",
|
||||
inviteController.isAddPeopleEnabled());
|
||||
props.putBoolean(
|
||||
"dialOutEnabled",
|
||||
inviteController.isDialOutEnabled());
|
||||
}
|
||||
|
||||
// pictureInPictureEnabled
|
||||
props.putBoolean(
|
||||
"pictureInPictureEnabled",
|
||||
isPictureInPictureEnabled());
|
||||
|
||||
// url
|
||||
if (urlObject != null) {
|
||||
props.putBundle("url", urlObject);
|
||||
}
|
||||
|
||||
// welcomePageEnabled
|
||||
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
||||
|
||||
// XXX The method loadURLObject: is supposed to be imperative i.e.
|
||||
// XXX The setProps() method is supposed to be imperative i.e.
|
||||
// a second invocation with one and the same URL is expected to join
|
||||
// the respective conference again if the first invocation was followed
|
||||
// by leaving the conference. However, React and, respectively,
|
||||
// appProperties/initialProperties are declarative expressions i.e. one
|
||||
// and the same URL will not trigger componentWillReceiveProps in the
|
||||
// and the same URL will not trigger an automatic re-render in the
|
||||
// JavaScript source code. The workaround implemented bellow introduces
|
||||
// imperativeness in React Component props by defining a unique value
|
||||
// per loadURLObject: invocation.
|
||||
// "imperativeness" in React Component props by defining a unique value
|
||||
// per setProps() invocation.
|
||||
props.putLong("timestamp", System.currentTimeMillis());
|
||||
|
||||
createReactRootView("App", props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a specific URL {@link String} which may identify a conference to
|
||||
* join. If the specified URL {@code String} is {@code null} and the Welcome
|
||||
* page is enabled, the Welcome page is displayed instead.
|
||||
*
|
||||
* @param urlString The URL {@code String} to load which may identify a
|
||||
* conference to join.
|
||||
*/
|
||||
public void loadURLString(@Nullable String urlString) {
|
||||
Bundle urlObject;
|
||||
|
||||
if (urlString == null) {
|
||||
urlObject = null;
|
||||
} else {
|
||||
urlObject = new Bundle();
|
||||
urlObject.putString("url", urlString);
|
||||
}
|
||||
loadURLObject(urlObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* The internal processing for the URL of the current conference set on the
|
||||
* associated {@link JitsiMeetView}.
|
||||
@@ -300,18 +181,16 @@ public class JitsiMeetView
|
||||
* by/associated with the specified {@code eventName}.
|
||||
*/
|
||||
private void maybeSetViewURL(String eventName, ReadableMap eventData) {
|
||||
String url = eventData.getString("url");
|
||||
|
||||
switch(eventName) {
|
||||
case "CONFERENCE_WILL_JOIN":
|
||||
setURL(eventData.getString("url"));
|
||||
this.url = url;
|
||||
break;
|
||||
|
||||
case "CONFERENCE_FAILED":
|
||||
case "CONFERENCE_WILL_LEAVE":
|
||||
case "LOAD_CONFIG_ERROR":
|
||||
String url = eventData.getString("url");
|
||||
|
||||
if (url != null && url.equals(getURL())) {
|
||||
setURL(null);
|
||||
case "CONFERENCE_TERMINATED":
|
||||
if (url != null && url.equals(this.url)) {
|
||||
this.url = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -325,7 +204,7 @@ public class JitsiMeetView
|
||||
* by/associated with the specified {@code name}.
|
||||
*/
|
||||
@Override
|
||||
public void onExternalAPIEvent(String name, ReadableMap data) {
|
||||
protected void onExternalAPIEvent(String name, ReadableMap data) {
|
||||
// XXX The JitsiMeetView property URL was introduced in order to address
|
||||
// an exception in the Picture-in-Picture functionality which arose
|
||||
// because of delays related to bridging between JavaScript and Java. To
|
||||
@@ -335,53 +214,4 @@ public class JitsiMeetView
|
||||
|
||||
onExternalAPIEvent(LISTENER_METHODS, name, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default base {@code URL} used to join a conference when a
|
||||
* partial URL (e.g. a room name only) is specified to
|
||||
* {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. Must be
|
||||
* called before {@link #loadURL(URL)} for it to take effect.
|
||||
*
|
||||
* @param defaultURL The {@code URL} to be set as the default base URL.
|
||||
* @see #getDefaultURL()
|
||||
*/
|
||||
public void setDefaultURL(URL defaultURL) {
|
||||
this.defaultURL = defaultURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = 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.
|
||||
*
|
||||
* @param welcomePageEnabled {@code true} to enable the Welcome page;
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
public void setWelcomePageEnabled(boolean welcomePageEnabled) {
|
||||
this.welcomePageEnabled = welcomePageEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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 java.util.Map;
|
||||
|
||||
/**
|
||||
* Implements {@link JitsiMeetViewListener} so apps don't have to add stubs for
|
||||
* all methods in the interface if they are only interested in some.
|
||||
*/
|
||||
public abstract class JitsiMeetViewAdapter
|
||||
implements JitsiMeetViewListener {
|
||||
|
||||
@Override
|
||||
public void onConferenceFailed(Map<String, Object> data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceJoined(Map<String, Object> data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceLeft(Map<String, Object> data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceWillJoin(Map<String, Object> data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceWillLeave(Map<String, Object> data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadConfigError(Map<String, Object> data) {
|
||||
}
|
||||
}
|
||||
@@ -22,15 +22,6 @@ import java.util.Map;
|
||||
* Interface for listening to events coming from Jitsi Meet.
|
||||
*/
|
||||
public interface JitsiMeetViewListener {
|
||||
/**
|
||||
* Called when joining a conference fails or an ongoing conference is
|
||||
* interrupted due to a failure.
|
||||
*
|
||||
* @param data Map with an "error" key describing the problem, and a "url"
|
||||
* key with the conference URL.
|
||||
*/
|
||||
void onConferenceFailed(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* Called when a conference was joined.
|
||||
*
|
||||
@@ -39,11 +30,16 @@ public interface JitsiMeetViewListener {
|
||||
void onConferenceJoined(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* Called when the conference was left, typically after hanging up.
|
||||
* Called when the active conference ends, be it because of user choice or
|
||||
* because of a failure.
|
||||
*
|
||||
* @param data Map with a "url" key with the conference URL.
|
||||
* @param data Map with an "error" key with the error and a "url" key with
|
||||
* the conference URL. If the conference finished gracefully no `error`
|
||||
* key will be present. The possible values for "error" are described here:
|
||||
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConnectionErrors.js
|
||||
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConferenceErrors.js
|
||||
*/
|
||||
void onConferenceLeft(Map<String, Object> data);
|
||||
void onConferenceTerminated(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* Called before the conference is joined.
|
||||
@@ -51,21 +47,4 @@ public interface JitsiMeetViewListener {
|
||||
* @param data Map with a "url" key with the conference URL.
|
||||
*/
|
||||
void onConferenceWillJoin(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* Called before the conference is left.
|
||||
*
|
||||
* @param data Map with a "url" key with the conference URL.
|
||||
*/
|
||||
void onConferenceWillLeave(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* Called when loading the main configuration file from the Jitsi Meet
|
||||
* deployment fails.
|
||||
*
|
||||
* @param data Map with an "error" key with the error and a "url" key with
|
||||
* the conference URL which necessitated the loading of the configuration
|
||||
* file.
|
||||
*/
|
||||
void onLoadConfigError(Map<String, Object> data);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Based on https://github.com/DylanVann/react-native-locale-detector
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Module which provides information about the system locale.
|
||||
*/
|
||||
class LocaleDetector extends ReactContextBaseJavaModule {
|
||||
|
||||
public LocaleDetector(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@code Map} of constants this module exports to JS. Supports JSON
|
||||
* types.
|
||||
*
|
||||
* @return a {@link Map} of constants this module exports to JS
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
Context context = getReactApplicationContext();
|
||||
HashMap<String,Object> constants = new HashMap<>();
|
||||
constants.put("locale", context.getResources().getConfiguration().locale.toString());
|
||||
return constants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "LocaleDetector";
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.PictureInPictureParams;
|
||||
import android.os.Build;
|
||||
@@ -26,11 +27,15 @@ import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
@ReactModule(name = PictureInPictureModule.NAME)
|
||||
class PictureInPictureModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
private final static String TAG = "PictureInPicture";
|
||||
public static final String NAME = "PictureInPicture";
|
||||
|
||||
private static final String TAG = NAME;
|
||||
|
||||
static boolean isPictureInPictureSupported() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
@@ -53,6 +58,7 @@ class PictureInPictureModule
|
||||
* including when the activity is not visible (paused or stopped), if the
|
||||
* screen is locked or if the user has an activity pinned.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public void enterPictureInPicture() {
|
||||
if (!isPictureInPictureSupported()) {
|
||||
throw new IllegalStateException("Picture-in-Picture not supported");
|
||||
@@ -99,6 +105,6 @@ class PictureInPictureModule
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
/**
|
||||
* Module implementing a simple API to enable a proximity sensor-controlled
|
||||
@@ -31,14 +32,11 @@ import com.facebook.react.bridge.UiThreadUtil;
|
||||
* object it will dim the screen and disable touch controls. The functionality
|
||||
* is used with the conference audio-only mode.
|
||||
*/
|
||||
@ReactModule(name = ProximityModule.NAME)
|
||||
class ProximityModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* The name of {@code ProximityModule} to be used in the React Native
|
||||
* bridge.
|
||||
*/
|
||||
private static final String MODULE_NAME = "Proximity";
|
||||
public static final String NAME = "Proximity";
|
||||
|
||||
/**
|
||||
* This type of wake lock (the one activated by the proximity sensor) has
|
||||
@@ -74,7 +72,7 @@ class ProximityModule
|
||||
wakeLock
|
||||
= powerManager.newWakeLock(
|
||||
PROXIMITY_SCREEN_OFF_WAKE_LOCK,
|
||||
MODULE_NAME);
|
||||
"jitsi:"+NAME);
|
||||
} catch (Throwable ignored) {
|
||||
wakeLock = null;
|
||||
}
|
||||
@@ -89,7 +87,7 @@ class ProximityModule
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return MODULE_NAME;
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.telecom.DisconnectCause;
|
||||
import android.telecom.PhoneAccount;
|
||||
import android.telecom.PhoneAccountHandle;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.telecom.VideoProfile;
|
||||
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 com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
/**
|
||||
* The react-native side of Jitsi Meet's {@link ConnectionService}. Exposes
|
||||
* the Java Script API.
|
||||
*
|
||||
* @author Pawel Domas
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@ReactModule(name = RNConnectionService.NAME)
|
||||
class RNConnectionService
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "ConnectionService";
|
||||
|
||||
private static final String TAG = ConnectionService.TAG;
|
||||
|
||||
/**
|
||||
* Sets the audio route on all existing {@link android.telecom.Connection}s
|
||||
*
|
||||
* @param audioRoute the new audio route to be set. See
|
||||
* {@link android.telecom.CallAudioState} constants prefixed with "ROUTE_".
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
static void setAudioRoute(int audioRoute) {
|
||||
for (ConnectionService.ConnectionImpl c
|
||||
: ConnectionService.getConnections()) {
|
||||
c.setAudioRoute(audioRoute);
|
||||
}
|
||||
}
|
||||
|
||||
RNConnectionService(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new outgoing call.
|
||||
*
|
||||
* @param callUUID - unique call identifier assigned by Jitsi Meet to
|
||||
* a conference call.
|
||||
* @param handle - a call handle which by default is Jitsi Meet room's URL.
|
||||
* @param hasVideo - whether or not user starts with the video turned on.
|
||||
* @param promise - the Promise instance passed by the React-native bridge,
|
||||
* so that this method returns a Promise on the JS side.
|
||||
*
|
||||
* NOTE regarding the "missingPermission" suppress - SecurityException will
|
||||
* be handled as part of the Exception try catch block and the Promise will
|
||||
* be rejected.
|
||||
*/
|
||||
@SuppressLint("MissingPermission")
|
||||
@ReactMethod
|
||||
public void startCall(
|
||||
String callUUID,
|
||||
String handle,
|
||||
boolean hasVideo,
|
||||
Promise promise) {
|
||||
Log.d(TAG,
|
||||
String.format("startCall UUID=%s, h=%s, v=%s",
|
||||
callUUID,
|
||||
handle,
|
||||
hasVideo));
|
||||
|
||||
ReactApplicationContext ctx = getReactApplicationContext();
|
||||
|
||||
Uri address = Uri.fromParts(PhoneAccount.SCHEME_SIP, handle, null);
|
||||
PhoneAccountHandle accountHandle
|
||||
= ConnectionService.registerPhoneAccount(
|
||||
getReactApplicationContext(), address, callUUID);
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putParcelable(
|
||||
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
|
||||
accountHandle);
|
||||
extras.putInt(
|
||||
TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
|
||||
hasVideo
|
||||
? VideoProfile.STATE_BIDIRECTIONAL
|
||||
: VideoProfile.STATE_AUDIO_ONLY);
|
||||
|
||||
ConnectionService.registerStartCallPromise(callUUID, promise);
|
||||
|
||||
try {
|
||||
TelecomManager tm
|
||||
= (TelecomManager) ctx.getSystemService(
|
||||
Context.TELECOM_SERVICE);
|
||||
|
||||
tm.placeCall(address, extras);
|
||||
} catch (Exception e) {
|
||||
ConnectionService.unregisterStartCallPromise(callUUID);
|
||||
promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the JS side of things to mark the call as failed.
|
||||
*
|
||||
* @param callUUID - the call's UUID.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void reportCallFailed(String callUUID) {
|
||||
Log.d(TAG, "reportCallFailed " + callUUID);
|
||||
ConnectionService.setConnectionDisconnected(
|
||||
callUUID,
|
||||
new DisconnectCause(DisconnectCause.ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the JS side of things to mark the call as disconnected.
|
||||
*
|
||||
* @param callUUID - the call's UUID.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void endCall(String callUUID) {
|
||||
Log.d(TAG, "endCall " + callUUID);
|
||||
ConnectionService.setConnectionDisconnected(
|
||||
callUUID,
|
||||
new DisconnectCause(DisconnectCause.LOCAL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the JS side of things to mark the call as active.
|
||||
*
|
||||
* @param callUUID - the call's UUID.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void reportConnectedOutgoingCall(String callUUID) {
|
||||
Log.d(TAG, "reportConnectedOutgoingCall " + callUUID);
|
||||
ConnectionService.setConnectionActive(callUUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the JS side to update the call's state.
|
||||
*
|
||||
* @param callUUID - the call's UUID.
|
||||
* @param callState - the map which carries infor about the current call's
|
||||
* state. See static fields in {@link ConnectionService.ConnectionImpl}
|
||||
* prefixed with "KEY_" for the values supported by the Android
|
||||
* implementation.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void updateCall(String callUUID, ReadableMap callState) {
|
||||
ConnectionService.updateCall(callUUID, callState);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 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.
|
||||
@@ -20,16 +21,26 @@ import android.app.Application;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactPackage;
|
||||
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 com.facebook.react.devsupport.DevInternalSettings;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
class ReactInstanceManagerHolder {
|
||||
/**
|
||||
* FIXME (from linter): Do not place Android context classes in static
|
||||
* fields (static reference to ReactInstanceManager which has field
|
||||
* mApplicationContext pointing to Context); this is a memory leak (and
|
||||
* also breaks Instant Run).
|
||||
*
|
||||
* React Native bridge. The instance manager allows embedding applications
|
||||
* to create multiple root views off the same JavaScript bundle.
|
||||
*/
|
||||
@@ -37,18 +48,32 @@ class ReactInstanceManagerHolder {
|
||||
|
||||
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.dropbox.Dropbox(reactContext),
|
||||
new org.jitsi.meet.sdk.invite.InviteModule(reactContext),
|
||||
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
|
||||
);
|
||||
List<NativeModule> nativeModules
|
||||
= new ArrayList<>(Arrays.<NativeModule>asList(
|
||||
new AndroidSettingsModule(reactContext),
|
||||
new AppInfoModule(reactContext),
|
||||
new AudioModeModule(reactContext),
|
||||
new DropboxModule(reactContext),
|
||||
new ExternalAPIModule(reactContext),
|
||||
new LocaleDetector(reactContext),
|
||||
new PictureInPictureModule(reactContext),
|
||||
new ProximityModule(reactContext),
|
||||
new WiFiStatsModule(reactContext),
|
||||
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)));
|
||||
|
||||
if (AudioModeModule.useConnectionService()) {
|
||||
nativeModules.add(new RNConnectionService(reactContext));
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> amplitudeModuleClass = Class.forName("AmplitudeModule");
|
||||
Constructor constructor = amplitudeModuleClass.getConstructor(ReactApplicationContext.class);
|
||||
nativeModules.add((NativeModule)constructor.newInstance(reactContext));
|
||||
} catch (Exception e) {
|
||||
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
||||
}
|
||||
|
||||
return nativeModules;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +82,7 @@ class ReactInstanceManagerHolder {
|
||||
* @param eventName {@code String} containing the event name.
|
||||
* @param data {@code Object} optional ancillary data for the event.
|
||||
*/
|
||||
public static boolean emitEvent(
|
||||
static void emitEvent(
|
||||
String eventName,
|
||||
@Nullable Object data) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
@@ -67,15 +92,12 @@ class ReactInstanceManagerHolder {
|
||||
ReactContext reactContext
|
||||
= reactInstanceManager.getCurrentReactContext();
|
||||
|
||||
return
|
||||
reactContext != null
|
||||
&& ReactContextUtils.emitEvent(
|
||||
reactContext,
|
||||
eventName,
|
||||
data);
|
||||
if (reactContext != null) {
|
||||
reactContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit(eventName, data);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,34 +137,50 @@ class ReactInstanceManagerHolder {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ReactPackage> packages
|
||||
= new ArrayList<>(Arrays.asList(
|
||||
new com.BV.LinearGradient.LinearGradientPackage(),
|
||||
new com.calendarevents.CalendarEventsPackage(),
|
||||
new com.corbt.keepawake.KCKeepAwakePackage(),
|
||||
new com.dylanvann.fastimage.FastImageViewPackage(),
|
||||
new com.facebook.react.shell.MainReactPackage(),
|
||||
new com.oblador.vectoricons.VectorIconsPackage(),
|
||||
new com.ocetnik.timer.BackgroundTimerPackage(),
|
||||
new com.oney.WebRTCModule.WebRTCModulePackage(),
|
||||
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
|
||||
new com.reactnativecommunity.webview.RNCWebViewPackage(),
|
||||
new com.rnimmersive.RNImmersivePackage(),
|
||||
new com.zmxv.RNSound.RNSoundPackage(),
|
||||
new ReactPackageAdapter() {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
return ReactInstanceManagerHolder.createNativeModules(reactContext);
|
||||
}
|
||||
}));
|
||||
|
||||
try {
|
||||
Class<?> googlePackageClass = Class.forName("co.apptailor.googlesignin.RNGoogleSigninPackage");
|
||||
Constructor constructor = googlePackageClass.getConstructor();
|
||||
packages.add((ReactPackage)constructor.newInstance());
|
||||
} catch (Exception e) {
|
||||
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
||||
}
|
||||
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
.setApplication(application)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.addPackage(new co.apptailor.googlesignin.RNGoogleSigninPackage())
|
||||
.addPackage(new com.BV.LinearGradient.LinearGradientPackage())
|
||||
.addPackage(new com.calendarevents.CalendarEventsPackage())
|
||||
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
|
||||
.addPackage(new com.dylanvann.fastimage.FastImageViewPackage())
|
||||
.addPackage(new com.facebook.react.shell.MainReactPackage())
|
||||
.addPackage(new com.i18n.reactnativei18n.ReactNativeI18n())
|
||||
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
|
||||
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
|
||||
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
|
||||
.addPackage(new com.rnimmersive.RNImmersivePackage())
|
||||
.addPackage(new com.zmxv.RNSound.RNSoundPackage())
|
||||
.addPackage(new ReactPackageAdapter() {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
return
|
||||
ReactInstanceManagerHolder.createNativeModules(
|
||||
reactContext);
|
||||
}
|
||||
})
|
||||
.addPackages(packages)
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||
.build();
|
||||
|
||||
// Disable delta updates on Android, they have caused trouble.
|
||||
DevInternalSettings devSettings
|
||||
= (DevInternalSettings)reactInstanceManager.getDevSupportManager().getDevSettings();
|
||||
if (devSettings != null) {
|
||||
devSettings.setBundleDeltasEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
@@ -43,19 +44,16 @@ import java.util.concurrent.Executors;
|
||||
* Gathers rssi, signal in percentage, timestamp and the addresses of the wifi
|
||||
* device.
|
||||
*/
|
||||
@ReactModule(name = WiFiStatsModule.NAME)
|
||||
class WiFiStatsModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* The name of {@code WiFiStatsModule} to be used in the React Native
|
||||
* bridge.
|
||||
*/
|
||||
private static final String MODULE_NAME = "WiFiStats";
|
||||
public static final String NAME = "WiFiStats";
|
||||
|
||||
/**
|
||||
* The {@code Log} tag {@code WiFiStatsModule} is to log messages with.
|
||||
*/
|
||||
static final String TAG = MODULE_NAME;
|
||||
static final String TAG = NAME;
|
||||
|
||||
/**
|
||||
* The scale used for the signal value. A level of the signal, given in the
|
||||
@@ -87,7 +85,7 @@ class WiFiStatsModule
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return MODULE_NAME;
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,7 +50,7 @@ public class IncomingCallView
|
||||
* by/associated with the specified {@code name}.
|
||||
*/
|
||||
@Override
|
||||
public void onExternalAPIEvent(String name, ReadableMap data) {
|
||||
protected void onExternalAPIEvent(String name, ReadableMap data) {
|
||||
onExternalAPIEvent(LISTENER_METHODS, name, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
* {@link AddPeopleControllerListener#onReceivedResults(AddPeopleController, List, String)}.
|
||||
*
|
||||
* @param query {@code String} to use for the query
|
||||
*/
|
||||
void performQuery(AddPeopleController addPeopleController, String query) {
|
||||
WritableNativeMap params = new WritableNativeMap();
|
||||
|
||||
params.putString("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;
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
* 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.BaseReactView;
|
||||
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)
|
||||
BaseReactView.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
@@ -32,9 +33,12 @@ import java.net.UnknownHostException;
|
||||
* [1]: https://tools.ietf.org/html/rfc6146
|
||||
* [2]: https://tools.ietf.org/html/rfc6052
|
||||
*/
|
||||
@ReactModule(name = NAT64AddrInfoModule.NAME)
|
||||
public class NAT64AddrInfoModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public final static String NAME = "NAT64AddrInfo";
|
||||
|
||||
/**
|
||||
* The host for which the module wil try to resolve both IPv4 and IPv6
|
||||
* addresses in order to figure out the NAT64 prefix.
|
||||
@@ -46,15 +50,10 @@ public class NAT64AddrInfoModule
|
||||
*/
|
||||
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;
|
||||
private final static String TAG = NAME;
|
||||
|
||||
/**
|
||||
* The {@link NAT64AddrInfo} instance which holds NAT64 prefix/suffix.
|
||||
@@ -119,6 +118,6 @@ public class NAT64AddrInfoModule
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return MODULE_NAME;
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
|
||||
12
android/sdk/src/main/res/layout/activity_jitsi_meet.xml
Normal file
12
android/sdk/src/main/res/layout/activity_jitsi_meet.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".JitsiMeetActivity">
|
||||
<fragment
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:name="org.jitsi.meet.sdk.JitsiMeetFragment"
|
||||
android:id="@+id/jitsiFragment"/>
|
||||
</FrameLayout>
|
||||
@@ -3,6 +3,10 @@ rootProject.name = 'jitsi-meet'
|
||||
include ':app', ':sdk'
|
||||
include ':react-native-background-timer'
|
||||
project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android')
|
||||
include ':react-native-calendar-events'
|
||||
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
|
||||
include ':react-native-community-async-storage'
|
||||
project(':react-native-community-async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/async-storage/android')
|
||||
include ':react-native-fast-image'
|
||||
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android')
|
||||
include ':react-native-google-signin'
|
||||
@@ -13,13 +17,11 @@ include ':react-native-keep-awake'
|
||||
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
|
||||
include ':react-native-linear-gradient'
|
||||
project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
|
||||
include ':react-native-locale-detector'
|
||||
project(':react-native-locale-detector').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-locale-detector/android')
|
||||
include ':react-native-sound'
|
||||
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')
|
||||
include ':react-native-webview'
|
||||
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
|
||||
|
||||
7
app.js
7
app.js
@@ -1,15 +1,8 @@
|
||||
/* application specific logic */
|
||||
|
||||
// FIXME: remove once atlaskit work with React 16
|
||||
// It seems that @atlaskit/icon is importing PropTypes from React, but it
|
||||
// happens through some glyph coffee script template. It could be that more
|
||||
// things are broken there (not only the icon).
|
||||
import './react/features/base/react/prop-types-polyfill.js';
|
||||
|
||||
import 'jquery';
|
||||
import 'jquery-contextmenu';
|
||||
import 'jQuery-Impromptu';
|
||||
import 'autosize';
|
||||
|
||||
import conference from './conference';
|
||||
import API from './modules/API';
|
||||
|
||||
411
conference.js
411
conference.js
@@ -38,6 +38,7 @@ import {
|
||||
conferenceFailed,
|
||||
conferenceJoined,
|
||||
conferenceLeft,
|
||||
conferenceSubjectChanged,
|
||||
conferenceWillJoin,
|
||||
conferenceWillLeave,
|
||||
dataChannelOpened,
|
||||
@@ -48,6 +49,7 @@ import {
|
||||
setDesktopSharingEnabled
|
||||
} from './react/features/base/conference';
|
||||
import {
|
||||
checkAndNotifyForNewDevice,
|
||||
getAvailableDevices,
|
||||
setAudioOutputDeviceId,
|
||||
updateDeviceList
|
||||
@@ -76,10 +78,10 @@ import {
|
||||
dominantSpeakerChanged,
|
||||
getAvatarURLByParticipantId,
|
||||
getLocalParticipant,
|
||||
getNormalizedDisplayName,
|
||||
getParticipantById,
|
||||
localParticipantConnectionStatusChanged,
|
||||
localParticipantRoleChanged,
|
||||
MAX_DISPLAY_NAME_LENGTH,
|
||||
participantConnectionStatusChanged,
|
||||
participantPresenceChanged,
|
||||
participantRoleChanged,
|
||||
@@ -88,6 +90,7 @@ import {
|
||||
import { updateSettings } from './react/features/base/settings';
|
||||
import {
|
||||
createLocalTracksF,
|
||||
destroyLocalTracks,
|
||||
isLocalTrackMuted,
|
||||
replaceLocalTrack,
|
||||
trackAdded,
|
||||
@@ -346,7 +349,10 @@ class ConferenceConnector {
|
||||
// not enough rights to create conference
|
||||
case JitsiConferenceErrors.AUTHENTICATION_REQUIRED: {
|
||||
// Schedule reconnect to check if someone else created the room.
|
||||
this.reconnectTimeout = setTimeout(() => room.join(), 5000);
|
||||
this.reconnectTimeout = setTimeout(() => {
|
||||
APP.store.dispatch(conferenceWillJoin(room));
|
||||
room.join();
|
||||
}, 5000);
|
||||
|
||||
const { password }
|
||||
= APP.store.getState()['features/base/conference'];
|
||||
@@ -432,8 +438,8 @@ class ConferenceConnector {
|
||||
hasRead: true,
|
||||
error: code,
|
||||
message: msg,
|
||||
timestamp: Date.now(),
|
||||
type: 'error'
|
||||
messageType: 'error',
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
}
|
||||
break;
|
||||
@@ -712,13 +718,21 @@ export default {
|
||||
this.roomName = options.roomName;
|
||||
|
||||
return (
|
||||
this.createInitialLocalTracksAndConnect(
|
||||
|
||||
// Initialize the device list first. This way, when creating tracks
|
||||
// based on preferred devices, loose label matching can be done in
|
||||
// cases where the exact ID match is no longer available, such as
|
||||
// when the camera device has switched USB ports.
|
||||
this._initDeviceList()
|
||||
.catch(error => logger.warn(
|
||||
'initial device list initialization failed', error))
|
||||
.then(() => this.createInitialLocalTracksAndConnect(
|
||||
options.roomName, {
|
||||
startAudioOnly: config.startAudioOnly,
|
||||
startScreenSharing: config.startScreenSharing,
|
||||
startWithAudioMuted: config.startWithAudioMuted,
|
||||
startWithVideoMuted: config.startWithVideoMuted
|
||||
})
|
||||
}))
|
||||
.then(([ tracks, con ]) => {
|
||||
tracks.forEach(track => {
|
||||
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|
||||
@@ -763,7 +777,10 @@ export default {
|
||||
this.setVideoMuteStatus(true);
|
||||
}
|
||||
|
||||
this._initDeviceList();
|
||||
// Initialize device list a second time to ensure device labels
|
||||
// get populated in case of an initial gUM acceptance; otherwise
|
||||
// they may remain as empty strings.
|
||||
this._initDeviceList(true);
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
this.recorder = new Recorder();
|
||||
@@ -1233,6 +1250,7 @@ export default {
|
||||
= connection.initJitsiConference(
|
||||
APP.conference.roomName,
|
||||
this._getConferenceOptions());
|
||||
|
||||
APP.store.dispatch(conferenceWillJoin(room));
|
||||
this._setLocalAudioVideoStreams(localTracks);
|
||||
this._room = room; // FIXME do not use this
|
||||
@@ -1320,7 +1338,14 @@ export default {
|
||||
this.isSharingScreen = newStream && newStream.videoType === 'desktop';
|
||||
|
||||
if (wasSharingScreen !== this.isSharingScreen) {
|
||||
APP.API.notifyScreenSharingStatusChanged(this.isSharingScreen);
|
||||
const details = {};
|
||||
|
||||
if (this.isSharingScreen) {
|
||||
details.sourceType = newStream.sourceType;
|
||||
}
|
||||
|
||||
APP.API.notifyScreenSharingStatusChanged(
|
||||
this.isSharingScreen, details);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1396,6 +1421,8 @@ export default {
|
||||
receiver.stop();
|
||||
}
|
||||
|
||||
this._stopProxyConnection();
|
||||
|
||||
let promise = null;
|
||||
|
||||
if (didHaveVideo) {
|
||||
@@ -1471,9 +1498,12 @@ export default {
|
||||
|
||||
/**
|
||||
* Creates desktop (screensharing) {@link JitsiLocalTrack}
|
||||
*
|
||||
* @param {Object} [options] - Screen sharing options that will be passed to
|
||||
* createLocalTracks.
|
||||
*
|
||||
* @param {Object} [options.desktopSharing]
|
||||
* @param {Object} [options.desktopStream] - An existing desktop stream to
|
||||
* use instead of creating a new desktop stream.
|
||||
* @return {Promise.<JitsiLocalTrack>} - A Promise resolved with
|
||||
* {@link JitsiLocalTrack} for the screensharing or rejected with
|
||||
* {@link JitsiTrackError}.
|
||||
@@ -1486,47 +1516,52 @@ export default {
|
||||
const didHaveVideo = Boolean(this.localVideo);
|
||||
const wasVideoMuted = this.isLocalVideoMuted();
|
||||
|
||||
return createLocalTracksF({
|
||||
desktopSharingSources: options.desktopSharingSources,
|
||||
devices: [ 'desktop' ],
|
||||
desktopSharingExtensionExternalInstallation: {
|
||||
interval: 500,
|
||||
checkAgain: () => DSExternalInstallationInProgress,
|
||||
listener: (status, url) => {
|
||||
switch (status) {
|
||||
case 'waitingForExtension': {
|
||||
DSExternalInstallationInProgress = true;
|
||||
externalInstallation = true;
|
||||
const listener = () => {
|
||||
// Wait a little bit more just to be sure that we
|
||||
// won't miss the extension installation
|
||||
setTimeout(
|
||||
() => {
|
||||
const getDesktopStreamPromise = options.desktopStream
|
||||
? Promise.resolve([ options.desktopStream ])
|
||||
: createLocalTracksF({
|
||||
desktopSharingSourceDevice: options.desktopSharingSources
|
||||
? null : config._desktopSharingSourceDevice,
|
||||
desktopSharingSources: options.desktopSharingSources,
|
||||
devices: [ 'desktop' ],
|
||||
desktopSharingExtensionExternalInstallation: {
|
||||
interval: 500,
|
||||
checkAgain: () => DSExternalInstallationInProgress,
|
||||
listener: (status, url) => {
|
||||
switch (status) {
|
||||
case 'waitingForExtension': {
|
||||
DSExternalInstallationInProgress = true;
|
||||
externalInstallation = true;
|
||||
const listener = () => {
|
||||
// Wait a little bit more just to be sure that
|
||||
// we won't miss the extension installation
|
||||
setTimeout(() => {
|
||||
DSExternalInstallationInProgress = false;
|
||||
},
|
||||
500);
|
||||
APP.UI.removeListener(
|
||||
APP.UI.removeListener(
|
||||
UIEvents.EXTERNAL_INSTALLATION_CANCELED,
|
||||
listener);
|
||||
};
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.EXTERNAL_INSTALLATION_CANCELED,
|
||||
listener);
|
||||
};
|
||||
APP.UI.showExtensionExternalInstallationDialog(url);
|
||||
break;
|
||||
}
|
||||
case 'extensionFound':
|
||||
// Close the dialog.
|
||||
externalInstallation && $.prompt.close();
|
||||
break;
|
||||
default:
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.EXTERNAL_INSTALLATION_CANCELED,
|
||||
listener);
|
||||
APP.UI.showExtensionExternalInstallationDialog(url);
|
||||
break;
|
||||
}
|
||||
case 'extensionFound':
|
||||
// Close the dialog.
|
||||
externalInstallation && $.prompt.close();
|
||||
break;
|
||||
default:
|
||||
|
||||
// Unknown status
|
||||
// Unknown status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).then(([ desktopStream ]) => {
|
||||
});
|
||||
|
||||
return getDesktopStreamPromise.then(([ desktopStream ]) => {
|
||||
// Stores the "untoggle" handler which remembers whether was
|
||||
// there any video before and whether was it muted.
|
||||
this._untoggleScreenSharing
|
||||
@@ -1644,6 +1679,7 @@ export default {
|
||||
// Handling:
|
||||
// JitsiTrackErrors.PERMISSION_DENIED
|
||||
// JitsiTrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
|
||||
// JitsiTrackErrors.CONSTRAINT_FAILED
|
||||
// JitsiTrackErrors.GENERAL
|
||||
// and any other
|
||||
let descriptionKey;
|
||||
@@ -1667,6 +1703,9 @@ export default {
|
||||
descriptionKey = 'dialog.screenSharingPermissionDeniedError';
|
||||
titleKey = 'dialog.screenSharingFailedToInstallTitle';
|
||||
}
|
||||
} else if (error.name === JitsiTrackErrors.CONSTRAINT_FAILED) {
|
||||
descriptionKey = 'dialog.cameraConstraintFailedError';
|
||||
titleKey = 'deviceError.cameraError';
|
||||
} else {
|
||||
descriptionKey = 'dialog.screenSharingFailedToInstall';
|
||||
titleKey = 'dialog.screenSharingFailedToInstallTitle';
|
||||
@@ -1806,6 +1845,8 @@ export default {
|
||||
room.on(JitsiConferenceEvents.TALK_WHILE_MUTED, () => {
|
||||
APP.UI.showToolbar(6000);
|
||||
});
|
||||
room.on(JitsiConferenceEvents.SUBJECT_CHANGED,
|
||||
subject => APP.store.dispatch(conferenceSubjectChanged(subject)));
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
|
||||
@@ -1840,7 +1881,7 @@ export default {
|
||||
JitsiConferenceEvents.DISPLAY_NAME_CHANGED,
|
||||
(id, displayName) => {
|
||||
const formattedDisplayName
|
||||
= displayName.substr(0, MAX_DISPLAY_NAME_LENGTH);
|
||||
= getNormalizedDisplayName(displayName);
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
@@ -1890,21 +1931,6 @@ export default {
|
||||
JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
|
||||
(participant, name, oldValue, newValue) => {
|
||||
switch (name) {
|
||||
case 'features_screen-sharing': {
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: participant.getId(),
|
||||
features: { 'screen-sharing': true }
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case 'raisedHand':
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: participant.getId(),
|
||||
raisedHand: newValue === 'true'
|
||||
}));
|
||||
break;
|
||||
case 'remoteControlSessionStatus':
|
||||
APP.UI.setRemoteControlActiveStatus(
|
||||
participant.getId(),
|
||||
@@ -2080,9 +2106,7 @@ export default {
|
||||
})
|
||||
.then(() => {
|
||||
logger.log('switched local video device');
|
||||
APP.store.dispatch(updateSettings({
|
||||
cameraDeviceId
|
||||
}));
|
||||
this._updateVideoDeviceId();
|
||||
})
|
||||
.catch(err => {
|
||||
APP.UI.showCameraErrorNotification(err);
|
||||
@@ -2111,12 +2135,11 @@ export default {
|
||||
|
||||
return stream;
|
||||
})
|
||||
.then(stream => {
|
||||
this.useAudioStream(stream);
|
||||
.then(stream => this.useAudioStream(stream))
|
||||
.then(() => {
|
||||
logger.log('switched local audio device');
|
||||
APP.store.dispatch(updateSettings({
|
||||
micDeviceId
|
||||
}));
|
||||
|
||||
this._updateAudioDeviceId();
|
||||
})
|
||||
.catch(err => {
|
||||
APP.UI.showMicErrorNotification(err);
|
||||
@@ -2216,34 +2239,6 @@ export default {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onConferenceJoined() {
|
||||
if (APP.logCollector) {
|
||||
// Start the LogCollector's periodic "store logs" task
|
||||
APP.logCollector.start();
|
||||
APP.logCollectorStarted = true;
|
||||
|
||||
// Make an attempt to flush in case a lot of logs have been
|
||||
// cached, before the collector was started.
|
||||
APP.logCollector.flush();
|
||||
|
||||
// This event listener will flush the logs, before
|
||||
// the statistics module (CallStats) is stopped.
|
||||
//
|
||||
// NOTE The LogCollector is not stopped, because this event can
|
||||
// be triggered multiple times during single conference
|
||||
// (whenever statistics module is stopped). That includes
|
||||
// the case when Jicofo terminates the single person left in the
|
||||
// room. It will then restart the media session when someone
|
||||
// eventually join the room which will start the stats again.
|
||||
APP.conference.addConferenceListener(
|
||||
JitsiConferenceEvents.BEFORE_STATISTICS_DISPOSED,
|
||||
() => {
|
||||
if (APP.logCollector) {
|
||||
APP.logCollector.flush();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
APP.UI.initConference();
|
||||
|
||||
APP.keyboardshortcut.init();
|
||||
@@ -2293,20 +2288,23 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* Inits list of current devices and event listener for device change.
|
||||
* Updates the list of current devices.
|
||||
* @param {boolean} setDeviceListChangeHandler - Whether to add the deviceList change handlers.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_initDeviceList() {
|
||||
_initDeviceList(setDeviceListChangeHandler = false) {
|
||||
const { mediaDevices } = JitsiMeetJS;
|
||||
|
||||
if (mediaDevices.isDeviceListAvailable()
|
||||
&& mediaDevices.isDeviceChangeAvailable()) {
|
||||
this.deviceChangeListener = devices =>
|
||||
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
|
||||
mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
if (setDeviceListChangeHandler) {
|
||||
this.deviceChangeListener = devices =>
|
||||
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
|
||||
mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
}
|
||||
|
||||
const { dispatch } = APP.store;
|
||||
|
||||
@@ -2315,17 +2313,9 @@ export default {
|
||||
// Ugly way to synchronize real device IDs with local
|
||||
// storage and settings menu. This is a workaround until
|
||||
// getConstraints() method will be implemented in browsers.
|
||||
if (this.localAudio) {
|
||||
dispatch(updateSettings({
|
||||
micDeviceId: this.localAudio.getDeviceId()
|
||||
}));
|
||||
}
|
||||
this._updateAudioDeviceId();
|
||||
|
||||
if (this.localVideo) {
|
||||
dispatch(updateSettings({
|
||||
cameraDeviceId: this.localVideo.getDeviceId()
|
||||
}));
|
||||
}
|
||||
this._updateVideoDeviceId();
|
||||
|
||||
APP.UI.onAvailableDevicesChanged(devices);
|
||||
});
|
||||
@@ -2334,6 +2324,33 @@ export default {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the settings for the currently used video device, extracting
|
||||
* the device id from the used track.
|
||||
* @private
|
||||
*/
|
||||
_updateVideoDeviceId() {
|
||||
if (this.localVideo
|
||||
&& this.localVideo.videoType === 'camera') {
|
||||
APP.store.dispatch(updateSettings({
|
||||
cameraDeviceId: this.localVideo.getDeviceId()
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the settings for the currently used audio device, extracting
|
||||
* the device id from the used track.
|
||||
* @private
|
||||
*/
|
||||
_updateAudioDeviceId() {
|
||||
if (this.localAudio) {
|
||||
APP.store.dispatch(updateSettings({
|
||||
micDeviceId: this.localAudio.getDeviceId()
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Event listener for JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED to
|
||||
* handle change of available media devices.
|
||||
@@ -2342,6 +2359,8 @@ export default {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_onDeviceListChanged(devices) {
|
||||
const oldDevices = APP.store.getState()['features/base/devices'].availableDevices;
|
||||
|
||||
APP.store.dispatch(updateDeviceList(devices));
|
||||
|
||||
const newDevices
|
||||
@@ -2353,6 +2372,10 @@ export default {
|
||||
const promises = [];
|
||||
const audioWasMuted = this.isLocalAudioMuted();
|
||||
const videoWasMuted = this.isLocalVideoMuted();
|
||||
const requestedInput = {
|
||||
audio: Boolean(newDevices.audioinput),
|
||||
video: Boolean(newDevices.videoinput)
|
||||
};
|
||||
|
||||
if (typeof newDevices.audiooutput !== 'undefined') {
|
||||
const { dispatch } = APP.store;
|
||||
@@ -2364,6 +2387,46 @@ export default {
|
||||
promises.push(setAudioOutputPromise);
|
||||
}
|
||||
|
||||
// Handles the use case when the default device is changed (we are always stopping the streams because it's
|
||||
// simpler):
|
||||
// If the default device is changed we need to first stop the local streams and then call GUM. Otherwise GUM
|
||||
// will return a stream using the old default device.
|
||||
if (requestedInput.audio && this.localAudio) {
|
||||
this.localAudio.stopStream();
|
||||
}
|
||||
|
||||
if (requestedInput.video && this.localVideo) {
|
||||
this.localVideo.stopStream();
|
||||
}
|
||||
|
||||
// Let's handle unknown/non-preferred devices
|
||||
const newAvailDevices
|
||||
= APP.store.getState()['features/base/devices'].availableDevices;
|
||||
let newAudioDevices = [];
|
||||
let oldAudioDevices = [];
|
||||
|
||||
if (typeof newDevices.audiooutput === 'undefined') {
|
||||
newAudioDevices = newAvailDevices.audioOutput;
|
||||
oldAudioDevices = oldDevices.audioOutput;
|
||||
}
|
||||
|
||||
if (!requestedInput.audio) {
|
||||
newAudioDevices = newAudioDevices.concat(newAvailDevices.audioInput);
|
||||
oldAudioDevices = oldAudioDevices.concat(oldDevices.audioInput);
|
||||
}
|
||||
|
||||
// check for audio
|
||||
if (newAudioDevices.length > 0) {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices));
|
||||
}
|
||||
|
||||
// check for video
|
||||
if (!requestedInput.video) {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
|
||||
}
|
||||
|
||||
promises.push(
|
||||
mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
|
||||
createLocalTracksF,
|
||||
@@ -2382,8 +2445,25 @@ export default {
|
||||
});
|
||||
|
||||
return Promise.all(muteSyncPromises)
|
||||
.then(() => Promise.all(
|
||||
this._setLocalAudioVideoStreams(tracks)));
|
||||
.then(() =>
|
||||
Promise.all(Object.keys(requestedInput).map(mediaType => {
|
||||
if (requestedInput[mediaType]) {
|
||||
const useStream
|
||||
= mediaType === 'audio'
|
||||
? this.useAudioStream.bind(this)
|
||||
: this.useVideoStream.bind(this);
|
||||
|
||||
// Use the new stream or null if we failed to obtain it.
|
||||
return useStream(tracks.find(track => track.getType() === mediaType) || null)
|
||||
.then(() => {
|
||||
mediaType === 'audio'
|
||||
? this._updateAudioDeviceId()
|
||||
: this._updateVideoDeviceId();
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
})));
|
||||
})
|
||||
.then(() => {
|
||||
// Log and sync known mute state.
|
||||
@@ -2415,7 +2495,7 @@ export default {
|
||||
*/
|
||||
updateAudioIconEnabled() {
|
||||
const audioMediaDevices
|
||||
= APP.store.getState()['features/base/devices'].audioInput;
|
||||
= APP.store.getState()['features/base/devices'].availableDevices.audioInput;
|
||||
const audioDeviceCount
|
||||
= audioMediaDevices ? audioMediaDevices.length : 0;
|
||||
|
||||
@@ -2438,7 +2518,7 @@ export default {
|
||||
*/
|
||||
updateVideoIconEnabled() {
|
||||
const videoMediaDevices
|
||||
= APP.store.getState()['features/base/devices'].videoInput;
|
||||
= APP.store.getState()['features/base/devices'].availableDevices.videoInput;
|
||||
const videoDeviceCount
|
||||
= videoMediaDevices ? videoMediaDevices.length : 0;
|
||||
|
||||
@@ -2466,7 +2546,13 @@ export default {
|
||||
*/
|
||||
hangup(requestFeedback = false) {
|
||||
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
|
||||
APP.UI.removeLocalMedia();
|
||||
|
||||
this._stopProxyConnection();
|
||||
|
||||
APP.store.dispatch(destroyLocalTracks());
|
||||
this._localTracksInitialized = false;
|
||||
this.localVideo = null;
|
||||
this.localAudio = null;
|
||||
|
||||
// Remove unnecessary event listeners from firing callbacks.
|
||||
if (this.deviceChangeListener) {
|
||||
@@ -2475,6 +2561,9 @@ export default {
|
||||
this.deviceChangeListener);
|
||||
}
|
||||
|
||||
APP.UI.removeAllListeners();
|
||||
APP.remoteControl.removeAllListeners();
|
||||
|
||||
let requestFeedbackPromise;
|
||||
|
||||
if (requestFeedback) {
|
||||
@@ -2495,6 +2584,9 @@ export default {
|
||||
requestFeedbackPromise,
|
||||
this.leaveRoomAndDisconnect()
|
||||
]).then(values => {
|
||||
this._room = undefined;
|
||||
room = undefined;
|
||||
|
||||
APP.API.notifyReadyToClose();
|
||||
maybeRedirectToWelcomePage(values[0]);
|
||||
});
|
||||
@@ -2508,7 +2600,8 @@ export default {
|
||||
leaveRoomAndDisconnect() {
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
|
||||
return room.leave().then(disconnect, disconnect);
|
||||
return room.leave()
|
||||
.then(disconnect, disconnect);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2614,8 +2707,7 @@ export default {
|
||||
* @param nickname {string} the new display name
|
||||
*/
|
||||
changeLocalDisplayName(nickname = '') {
|
||||
const formattedNickname
|
||||
= nickname.trim().substr(0, MAX_DISPLAY_NAME_LENGTH);
|
||||
const formattedNickname = getNormalizedDisplayName(nickname);
|
||||
const { id, name } = getLocalParticipant(APP.store.getState());
|
||||
|
||||
if (formattedNickname === name) {
|
||||
@@ -2647,7 +2739,6 @@ export default {
|
||||
});
|
||||
|
||||
if (room) {
|
||||
room.setDisplayName(formattedNickname);
|
||||
APP.UI.changeDisplayName(id, formattedNickname);
|
||||
}
|
||||
},
|
||||
@@ -2675,6 +2766,72 @@ export default {
|
||||
return this.localVideo.sourceType;
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback invoked by the external api create or update a direct connection
|
||||
* from the local client to an external client.
|
||||
*
|
||||
* @param {Object} event - The object containing information that should be
|
||||
* passed to the {@code ProxyConnectionService}.
|
||||
* @returns {void}
|
||||
*/
|
||||
onProxyConnectionEvent(event) {
|
||||
if (!this._proxyConnection) {
|
||||
this._proxyConnection = new JitsiMeetJS.ProxyConnectionService({
|
||||
|
||||
/**
|
||||
* Pass the {@code JitsiConnection} instance which will be used
|
||||
* to fetch TURN credentials.
|
||||
*/
|
||||
jitsiConnection: APP.connection,
|
||||
|
||||
/**
|
||||
* The proxy connection feature is currently tailored towards
|
||||
* taking a proxied video stream and showing it as a local
|
||||
* desktop screen.
|
||||
*/
|
||||
convertVideoToDesktop: true,
|
||||
|
||||
/**
|
||||
* Callback invoked to pass messages from the local client back
|
||||
* out to the external client.
|
||||
*
|
||||
* @param {string} peerJid - The jid of the intended recipient
|
||||
* of the message.
|
||||
* @param {Object} data - The message that should be sent. For
|
||||
* screensharing this is an iq.
|
||||
* @returns {void}
|
||||
*/
|
||||
onSendMessage: (peerJid, data) =>
|
||||
APP.API.sendProxyConnectionEvent({
|
||||
data,
|
||||
to: peerJid
|
||||
}),
|
||||
|
||||
/**
|
||||
* Callback invoked when the remote peer of the proxy connection
|
||||
* has provided a video stream, intended to be used as a local
|
||||
* desktop stream.
|
||||
*
|
||||
* @param {JitsiLocalTrack} remoteProxyStream - The media
|
||||
* stream to use as a local desktop stream.
|
||||
* @returns {void}
|
||||
*/
|
||||
onRemoteStream: desktopStream => {
|
||||
if (desktopStream.videoType !== 'desktop') {
|
||||
logger.warn('Received a non-desktop stream to proxy.');
|
||||
desktopStream.dispose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleScreenSharing(undefined, { desktopStream });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._proxyConnection.processMessage(event);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the video muted status.
|
||||
*
|
||||
@@ -2709,5 +2866,19 @@ export default {
|
||||
if (score === -1 || (score >= 1 && score <= 5)) {
|
||||
APP.store.dispatch(submitFeedback(score, message, room));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Terminates any proxy screensharing connection that is active.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_stopProxyConnection() {
|
||||
if (this._proxyConnection) {
|
||||
this._proxyConnection.stop();
|
||||
}
|
||||
|
||||
this._proxyConnection = null;
|
||||
}
|
||||
};
|
||||
|
||||
48
config.js
48
config.js
@@ -146,7 +146,7 @@ var config = {
|
||||
desktopSharingChromeExtId: null,
|
||||
|
||||
// Whether desktop sharing should be disabled on Chrome.
|
||||
desktopSharingChromeDisabled: true,
|
||||
// desktopSharingChromeDisabled: false,
|
||||
|
||||
// The media sources to use when using screen sharing with the Chrome
|
||||
// extension.
|
||||
@@ -156,7 +156,7 @@ var config = {
|
||||
desktopSharingChromeMinExtVersion: '0.1',
|
||||
|
||||
// Whether desktop sharing should be disabled on Firefox.
|
||||
desktopSharingFirefoxDisabled: false,
|
||||
// desktopSharingFirefoxDisabled: false,
|
||||
|
||||
// Optional desktop sharing frame rate options. Default value: min:5, max:5.
|
||||
// desktopSharingFrameRate: {
|
||||
@@ -174,7 +174,21 @@ var config = {
|
||||
// Enable the dropbox integration.
|
||||
// dropbox: {
|
||||
// appKey: '<APP_KEY>' // Specify your app key here.
|
||||
// // A URL to redirect the user to, after authenticating
|
||||
// // by default uses:
|
||||
// // 'https://jitsi-meet.example.com/static/oauth.html'
|
||||
// redirectURI:
|
||||
// 'https://jitsi-meet.example.com/subfolder/static/oauth.html'
|
||||
// },
|
||||
// When integrations like dropbox are enabled only that will be shown,
|
||||
// by enabling fileRecordingsServiceEnabled, we show both the integrations
|
||||
// and the generic recording service (its configuration and storage type
|
||||
// depends on jibri configuration)
|
||||
// fileRecordingsServiceEnabled: false,
|
||||
// Whether to show the possibility to share file recording with other people
|
||||
// (e.g. meeting participants), based on the actual implementation
|
||||
// on the backend.
|
||||
// fileRecordingsServiceSharingEnabled: false,
|
||||
|
||||
// Whether to enable live streaming or not.
|
||||
// liveStreamingEnabled: false,
|
||||
@@ -252,6 +266,9 @@ var config = {
|
||||
// Whether or not some features are checked based on token.
|
||||
// enableFeaturesBasedOnToken: false,
|
||||
|
||||
// Enable lock room for all moderators, even when userRolesBasedOnToken is enabled and participants are guests.
|
||||
// lockRoomGuestEnabled: false,
|
||||
|
||||
// Message to show the users. Example: 'The service will be down for
|
||||
// maintenance at 01:00 AM GMT,
|
||||
// noticeMessage: '',
|
||||
@@ -334,14 +351,19 @@ 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"
|
||||
// ],
|
||||
analytics: {
|
||||
// The Google Analytics Tracking ID:
|
||||
// googleAnalyticsTrackingId: 'your-tracking-id-UA-123456-1'
|
||||
|
||||
// The Google Analytics Tracking ID
|
||||
// googleAnalyticsTrackingId = 'your-tracking-id-here-UA-123456-1',
|
||||
// The Amplitude APP Key:
|
||||
// amplitudeAPPKey: '<APP_KEY>'
|
||||
|
||||
// Array of script URLs to load as lib-jitsi-meet "analytics handlers".
|
||||
// scriptURLs: [
|
||||
// "libs/analytics-ga.min.js", // google-analytics
|
||||
// "https://example.com/my-custom-analytics.js"
|
||||
// ],
|
||||
},
|
||||
|
||||
// Information about the jitsi-meet instance we are connecting to, including
|
||||
// the user region as seen by the server.
|
||||
@@ -381,6 +403,13 @@ var config = {
|
||||
// analyticsInterval: 60000,
|
||||
// }
|
||||
|
||||
// If set, will attempt to use the provided video input device label when
|
||||
// triggering a screenshare, instead of proceeding through the normal flow
|
||||
// for obtaining a desktop stream.
|
||||
// NOTE: This option is experimental and is currently intended for internal
|
||||
// use only.
|
||||
// _desktopSharingSourceDevice: 'sample-id-or-label'
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
_immediateReloadThreshold
|
||||
@@ -400,7 +429,6 @@ var config = {
|
||||
externalConnectUrl
|
||||
firefox_fake_device
|
||||
googleApiApplicationClientID
|
||||
googleApiIOSClientID
|
||||
iAmRecorder
|
||||
iAmSipGateway
|
||||
microsoftApiApplicationClientID
|
||||
|
||||
@@ -1,57 +1,50 @@
|
||||
/**
|
||||
* Move Atlaskit Flag up a little bit so it does not cover the toolbar with the
|
||||
* first notification.
|
||||
* Move the @atlaskit/flag container up a little bit so it does not cover the
|
||||
* toolbar with the first notification.
|
||||
*/
|
||||
.cxGWJB{
|
||||
.cjMOOK{
|
||||
bottom: calc(#{$newToolbarSizeWithPadding}) !important;
|
||||
}
|
||||
.gXSEsl:nth-child(n+2) {
|
||||
transform: translateX(0) translateY(100%) translateY(16px) !important;
|
||||
-ms-transform: translateX(0) translateY(100%) translateY(16px) !important;
|
||||
-webkit-transform: translateX(0) translateY(100%) translateY(16px) !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Welcome page tab color adjustments.
|
||||
* Disable the slide-in animation for @atlaskit/flag due to the animation
|
||||
* repeating for each queued flag once it becomes the top flag.
|
||||
*/
|
||||
.welcome {
|
||||
/**
|
||||
* The text color of the selected tab and hovered tabs.
|
||||
*/
|
||||
.bVobOt,
|
||||
.bVobOt:hover,
|
||||
.ebveIl:hover {
|
||||
color: #172B4D;
|
||||
}
|
||||
|
||||
/**
|
||||
* The color of the inactive tab text.
|
||||
*/
|
||||
.ebveIl {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* The color of the underline of a selected tab.
|
||||
*/
|
||||
.kByArU {
|
||||
background-color: #172B4D;
|
||||
}
|
||||
.mIBKA:first-child {
|
||||
animation: cbfRuT 0s !important;
|
||||
-webkit-animation: cbfRuT 0s !important;
|
||||
}
|
||||
|
||||
.modal-dialog-form {
|
||||
/**
|
||||
* Update the dropdown trigger wrapper to make sure it looks click-able.
|
||||
* Update the @atlaskit/dropdown-menu trigger wrapper to make sure it looks
|
||||
* click-able.
|
||||
*/
|
||||
.gwEjuO {
|
||||
.cjJUnw {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override Atlaskit dropdown styling when in a modal because the dropdown
|
||||
* backgrounds clash with the modal backgrounds.
|
||||
* Override @atlaskit/dropdown-menu styling when in a modal because the
|
||||
* dropdown backgrounds clash with the modal backgrounds.
|
||||
*/
|
||||
.gBLqhw[data-role=droplistContent] {
|
||||
.cksvax[data-role=droplistContent] {
|
||||
border: 1px solid #455166;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override @atlaskit/theme styling for the top toolbar so it displays over
|
||||
* the video thumbnail while obscuring as little as possible.
|
||||
*/
|
||||
.videocontainer .tOoji {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override @atlaskit/InlineDialog styling for the overflowmenu so it displays
|
||||
* with the correct height.
|
||||
*/
|
||||
.toolbox-button-wth-dialog .eYJELv {
|
||||
max-height: initial;
|
||||
}
|
||||
|
||||
@@ -55,10 +55,6 @@ body, input, textarea, keygen, select, button {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.no-fa-video-camera, .fa-microphone-slash {
|
||||
color: #636363;
|
||||
}
|
||||
|
||||
button, input, select, textarea {
|
||||
margin: 0;
|
||||
vertical-align: baseline;
|
||||
|
||||
212
css/_chat.scss
212
css/_chat.scss
@@ -1,6 +1,9 @@
|
||||
#sideToolbarContainer {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
box-sizing: border-box;
|
||||
color: #FFF;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/**
|
||||
* Make the sidebar flush with the top of the toolbar. Take the size of
|
||||
* the toolbar and subtract from 100%.
|
||||
@@ -21,20 +24,6 @@
|
||||
&.slideInExt {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.sideToolbarContainer__inner {
|
||||
box-sizing: border-box;
|
||||
color: #FFF;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: $sidebarWidth;
|
||||
}
|
||||
}
|
||||
|
||||
#chat_container * {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
#chatconversation {
|
||||
@@ -42,9 +31,8 @@
|
||||
flex: 1;
|
||||
font-size: 10pt;
|
||||
line-height: 20px;
|
||||
margin-top: 15px;
|
||||
overflow: auto;
|
||||
padding: 5px;
|
||||
padding: 16px;
|
||||
text-align: left;
|
||||
width: $sidebarWidth;
|
||||
word-wrap: break-word;
|
||||
@@ -92,26 +80,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-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;
|
||||
.chat-header {
|
||||
background-color: $chatHeaderBackgroundColor;
|
||||
height: 70px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
.chat-close {
|
||||
align-items: center;
|
||||
bottom: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
line-height: 15px;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
width: 40px;
|
||||
|
||||
&:hover {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#chat-input {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border-top: 1px solid $chatInputSeparatorColor;
|
||||
display: flex;
|
||||
|
||||
* {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
@@ -120,20 +123,16 @@
|
||||
|
||||
.usrmsg-form {
|
||||
flex: 1;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border: 0px none;
|
||||
border-radius:0;
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
font-size: 10pt;
|
||||
font-size: 15px;
|
||||
line-height: 30px;
|
||||
padding: 5px 5px 5px 0px;
|
||||
max-height:150px;
|
||||
min-height:35px;
|
||||
padding: 5px;
|
||||
overflow-y: auto;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
@@ -146,61 +145,47 @@
|
||||
}
|
||||
|
||||
#nickname {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
color: #9d9d9d;
|
||||
font-size: 18px;
|
||||
top: 100px;
|
||||
margin-top: 30px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
#chat_container .display-name {
|
||||
float: left;
|
||||
padding-left: 5px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 95%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.sideToolbarContainer {
|
||||
* {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
#chat_container .timestamp {
|
||||
float: right;
|
||||
padding-right: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.usermessage {
|
||||
padding-top: 20px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.chatArrow {
|
||||
height: 15px;
|
||||
left: -10px;
|
||||
position: absolute;
|
||||
.display-name {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
width: 93%;
|
||||
margin-left: 9px;
|
||||
margin-right: auto;
|
||||
border-radius: 5px;
|
||||
border-top-left-radius: 0px;
|
||||
margin-top: 3px;
|
||||
background-color: $chatRemoteMessageBackgroundColor;
|
||||
border-radius: 0px 6px 6px 6px;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
margin-top: 3px;
|
||||
max-width: 100%;
|
||||
padding-bottom: 3px;
|
||||
position: relative;
|
||||
|
||||
&.localuser .display-name {
|
||||
color: #4C9AFF
|
||||
&.localuser {
|
||||
background-color: $chatLocalMessageBackgroundColor;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
&.error {
|
||||
.chatArrow,
|
||||
border-radius: 0px;
|
||||
|
||||
.timestamp,
|
||||
.display-name {
|
||||
display: none;
|
||||
@@ -214,14 +199,11 @@
|
||||
}
|
||||
|
||||
.smiley {
|
||||
height: 26px;
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
#smileys {
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
height: 26px;
|
||||
font-size: 20pt;
|
||||
margin: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -232,33 +214,36 @@
|
||||
}
|
||||
|
||||
#smileysarea {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border: 0px none;
|
||||
display: flex;
|
||||
height: 70px;
|
||||
max-height: 150px;
|
||||
min-height: 35px;
|
||||
min-width: 31px;
|
||||
padding: 0px;
|
||||
overflow: hidden;
|
||||
width: 17%;
|
||||
}
|
||||
|
||||
.smiley-input {
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.smileys-panel {
|
||||
bottom: 100%;
|
||||
box-sizing: border-box;
|
||||
height: 0;
|
||||
height: auto;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
transition: height 0.3s;
|
||||
width: $sidebarWidth;
|
||||
|
||||
/**
|
||||
* CSS transitions do not apply for auto dimensions. So to produce the css
|
||||
* accordion effect for showing and hiding the smiley-panel, while allowing
|
||||
* for variable panel, height, use a very large max-height and animate off
|
||||
* of that.
|
||||
*/
|
||||
transition: max-height 0.3s;
|
||||
|
||||
&.show-smileys {
|
||||
height: 202px;
|
||||
max-height: 500%;
|
||||
}
|
||||
|
||||
#smileysContainer {
|
||||
@@ -269,13 +254,14 @@
|
||||
}
|
||||
|
||||
#smileysContainer .smiley {
|
||||
padding: 7px;
|
||||
font-size: 20pt;
|
||||
}
|
||||
|
||||
.smileyContainer {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
height: 36px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.smileyContainer:hover {
|
||||
@@ -291,3 +277,49 @@
|
||||
#usermsg::-webkit-scrollbar-track-piece {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
|
||||
.chat-message-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.local {
|
||||
align-items: flex-end;
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatLocalMessageBackgroundColor;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.chatmessage {
|
||||
border-radius: 0px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.chatmessage-wrapper {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatRemoteMessageBackgroundColor;
|
||||
border-radius: 0px 6px 6px 6px;
|
||||
display: inline-block;
|
||||
margin-top: 3px;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
321
css/_font.scss
321
css/_font.scss
@@ -1,10 +1,10 @@
|
||||
@font-face {
|
||||
font-family: 'jitsi';
|
||||
src:url('../fonts/jitsi.eot?94d075');
|
||||
src:url('../fonts/jitsi.eot?#iefix94d075') format('embedded-opentype'),
|
||||
url('../fonts/jitsi.woff?94d075') format('woff'),
|
||||
url('../fonts/jitsi.ttf?94d075') format('truetype'),
|
||||
url('../fonts/jitsi.svg?94d075#jitsi') format('svg');
|
||||
src: url('../fonts/jitsi.eot?3vw865');
|
||||
src: url('../fonts/jitsi.eot?3vw865#iefix') format('embedded-opentype'),
|
||||
url('../fonts/jitsi.ttf?3vw865') format('truetype'),
|
||||
url('../fonts/jitsi.woff?3vw865') format('woff'),
|
||||
url('../fonts/jitsi.svg?3vw865#jitsi') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@@ -25,167 +25,98 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-arrow_back:before {
|
||||
content: "\e5c4";
|
||||
.icon-signal_cellular_0:before {
|
||||
content: "\e901";
|
||||
}
|
||||
.icon-signal_cellular_1:before {
|
||||
content: "\e902";
|
||||
}
|
||||
.icon-signal_cellular_2:before {
|
||||
content: "\e907";
|
||||
}
|
||||
.icon-phone:before {
|
||||
content: "\e0cd";
|
||||
}
|
||||
.icon-radio_button_unchecked:before {
|
||||
content: "\e836";
|
||||
}
|
||||
.icon-radio_button_checked:before {
|
||||
content: "\e837";
|
||||
}
|
||||
.icon-search:before {
|
||||
content: "\e8b6";
|
||||
}
|
||||
.icon-chat-unread:before {
|
||||
content: "\e0b7";
|
||||
}
|
||||
.icon-closed_caption:before {
|
||||
content: "\e930";
|
||||
}
|
||||
.icon-tiles-many:before {
|
||||
content: "\e92e";
|
||||
}
|
||||
.icon-close:before {
|
||||
content: "\e5cd";
|
||||
}
|
||||
.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-open_in_new:before {
|
||||
content: "\e89e";
|
||||
}
|
||||
.icon-restore:before {
|
||||
content: "\e8b3";
|
||||
}
|
||||
.icon-timer:before {
|
||||
content: "\e425";
|
||||
.icon-navigate_next:before {
|
||||
content: "\e409";
|
||||
}
|
||||
.icon-menu:before {
|
||||
content: "\e5d2";
|
||||
}
|
||||
.icon-arrow_back:before {
|
||||
content: "\e5c4";
|
||||
}
|
||||
.icon-public:before {
|
||||
content: "\e80b";
|
||||
}
|
||||
.icon-event_note:before {
|
||||
content: "\e616";
|
||||
}
|
||||
.icon-bluetooth:before {
|
||||
content: "\e1aa";
|
||||
}
|
||||
.icon-headset:before {
|
||||
content: "\e310";
|
||||
}
|
||||
.icon-phone-talk:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
.icon-thumb-menu:before {
|
||||
content: "\e5d4";
|
||||
}
|
||||
.icon-mic-camera-combined:before {
|
||||
content: "\e903";
|
||||
}
|
||||
.icon-feedback:before {
|
||||
content: "\e91d";
|
||||
}
|
||||
.icon-toggle-filmstrip:before {
|
||||
content: "\e91c";
|
||||
}
|
||||
.icon-avatar:before {
|
||||
content: "\e901";
|
||||
}
|
||||
.icon-hangup:before {
|
||||
content: "\e905";
|
||||
}
|
||||
.icon-chat:before {
|
||||
content: "\e906";
|
||||
}
|
||||
.icon-download:before {
|
||||
content: "\e902";
|
||||
}
|
||||
.icon-edit:before {
|
||||
content: "\e907";
|
||||
}
|
||||
.icon-share-doc:before {
|
||||
content: "\e908";
|
||||
}
|
||||
.icon-kick:before {
|
||||
content: "\e904";
|
||||
}
|
||||
.icon-menu-up:before {
|
||||
content: "\e91f";
|
||||
}
|
||||
.icon-menu-down:before {
|
||||
content: "\e920";
|
||||
}
|
||||
.icon-full-screen:before {
|
||||
content: "\e90b";
|
||||
}
|
||||
.icon-exit-full-screen:before {
|
||||
content: "\e90c";
|
||||
}
|
||||
.icon-star-full:before {
|
||||
content: "\e90a";
|
||||
}
|
||||
.icon-security:before {
|
||||
content: "\e90d";
|
||||
}
|
||||
.icon-security-locked:before {
|
||||
content: "\e90e";
|
||||
}
|
||||
.icon-reload:before {
|
||||
content: "\e90f";
|
||||
}
|
||||
.icon-microphone:before {
|
||||
content: "\e910";
|
||||
}
|
||||
.icon-mic-empty:before {
|
||||
content: "\e911";
|
||||
}
|
||||
.icon-mic-disabled:before {
|
||||
content: "\e912";
|
||||
content: "\e5d4";
|
||||
}
|
||||
.icon-ninja:before {
|
||||
content: "\e909";
|
||||
content: "\e909";
|
||||
}
|
||||
.icon-raised-hand:before {
|
||||
content: "\e91e";
|
||||
}
|
||||
.icon-contactList:before {
|
||||
content: "\e91b";
|
||||
}
|
||||
.icon-link:before {
|
||||
content: "\e913";
|
||||
}
|
||||
.icon-shared-video:before {
|
||||
content: "\e914";
|
||||
}
|
||||
.icon-settings:before {
|
||||
content: "\e915";
|
||||
}
|
||||
.icon-star:before {
|
||||
content: "\e916";
|
||||
}
|
||||
.icon-switch-camera:before {
|
||||
content: "\e921";
|
||||
}
|
||||
.icon-share-desktop:before {
|
||||
content: "\e917";
|
||||
}
|
||||
.icon-camera:before {
|
||||
content: "\e918";
|
||||
}
|
||||
.icon-camera-disabled:before {
|
||||
content: "\e919";
|
||||
}
|
||||
.icon-volume:before {
|
||||
content: "\e91a";
|
||||
}
|
||||
.icon-recDisable:before {
|
||||
content: "\e613";
|
||||
}
|
||||
.icon-recEnable:before {
|
||||
content: "\e614";
|
||||
}
|
||||
.icon-presentation:before {
|
||||
content: "\e603";
|
||||
}
|
||||
.icon-dialpad:before {
|
||||
content: "\e925";
|
||||
}
|
||||
.icon-visibility:before {
|
||||
content: "\e923";
|
||||
}
|
||||
.icon-visibility-off:before {
|
||||
content: "\e924";
|
||||
}
|
||||
.icon-telephone:before {
|
||||
content: "\e0cd";
|
||||
.icon-invite:before {
|
||||
content: "\e145";
|
||||
}
|
||||
.icon-add:before {
|
||||
content: "\e145";
|
||||
content: "\e146";
|
||||
}
|
||||
.icon-info:before {
|
||||
content: "\e922";
|
||||
.icon-play:before {
|
||||
content: "\f04b";
|
||||
}
|
||||
.icon-gsm-bars:before {
|
||||
content: "\e926";
|
||||
.icon-stop:before {
|
||||
content: "\f04d";
|
||||
}
|
||||
.icon-open_in_new:before {
|
||||
content: "\e89e";
|
||||
.icon-dominant-speaker:before {
|
||||
content: "\f0a1";
|
||||
}
|
||||
.icon-speaker:before {
|
||||
content: "\e92d";
|
||||
}
|
||||
.icon-rec:before {
|
||||
content: "\e92b";
|
||||
}
|
||||
.icon-camera-take-picture:before {
|
||||
content: "\e92a";
|
||||
}
|
||||
.icon-AUD:before {
|
||||
content: "\e900";
|
||||
@@ -199,24 +130,90 @@
|
||||
.icon-SD:before {
|
||||
content: "\e929";
|
||||
}
|
||||
.icon-camera-take-picture:before {
|
||||
content: "\e92a";
|
||||
.icon-gsm-bars:before {
|
||||
content: "\e926";
|
||||
}
|
||||
.icon-rec:before {
|
||||
content: "\e92b";
|
||||
.icon-info:before {
|
||||
content: "\e922";
|
||||
}
|
||||
.icon-live:before {
|
||||
content: "\e92c";
|
||||
.icon-mic-camera-combined:before {
|
||||
content: "\e903";
|
||||
}
|
||||
.icon-speaker:before {
|
||||
content: "\e92d";
|
||||
.icon-feedback:before {
|
||||
content: "\e91d";
|
||||
}
|
||||
.icon-tiles-many:before {
|
||||
content: "\e92e";
|
||||
.icon-hangup:before {
|
||||
content: "\e905";
|
||||
}
|
||||
.icon-tiles-one:before {
|
||||
content: "\e92f";
|
||||
.icon-chat:before {
|
||||
content: "\e906";
|
||||
}
|
||||
.icon-closed_caption:before {
|
||||
content: "\e930";
|
||||
.icon-share-doc:before {
|
||||
content: "\e908";
|
||||
}
|
||||
.icon-kick:before {
|
||||
content: "\e904";
|
||||
}
|
||||
.icon-menu-up:before {
|
||||
content: "\e91f";
|
||||
}
|
||||
.icon-menu-down:before {
|
||||
content: "\e920";
|
||||
}
|
||||
.icon-full-screen:before {
|
||||
content: "\e90b";
|
||||
}
|
||||
.icon-exit-full-screen:before {
|
||||
content: "\e90c";
|
||||
}
|
||||
.icon-security:before {
|
||||
content: "\e90d";
|
||||
}
|
||||
.icon-security-locked:before {
|
||||
content: "\e90e";
|
||||
}
|
||||
.icon-microphone:before {
|
||||
content: "\e910";
|
||||
}
|
||||
.icon-mic-disabled:before {
|
||||
content: "\e912";
|
||||
}
|
||||
.icon-raised-hand:before {
|
||||
content: "\e91e";
|
||||
}
|
||||
.icon-link:before {
|
||||
content: "\e913";
|
||||
}
|
||||
.icon-shared-video:before {
|
||||
content: "\e914";
|
||||
}
|
||||
.icon-settings:before {
|
||||
content: "\e915";
|
||||
}
|
||||
.icon-star:before {
|
||||
content: "\e916";
|
||||
}
|
||||
.icon-switch-camera:before {
|
||||
content: "\e921";
|
||||
}
|
||||
.icon-share-desktop:before {
|
||||
content: "\e917";
|
||||
}
|
||||
.icon-camera:before {
|
||||
content: "\e918";
|
||||
}
|
||||
.icon-camera-disabled:before {
|
||||
content: "\e919";
|
||||
}
|
||||
.icon-volume:before {
|
||||
content: "\e91a";
|
||||
}
|
||||
.icon-presentation:before {
|
||||
content: "\e603";
|
||||
}
|
||||
.icon-visibility:before {
|
||||
content: "\e923";
|
||||
}
|
||||
.icon-visibility-off:before {
|
||||
content: "\e924";
|
||||
}
|
||||
|
||||
@@ -36,6 +36,12 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.calendar-action-buttons {
|
||||
.button {
|
||||
margin: 0px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
background: rgba(255,255,255,0.50);
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -41,3 +41,10 @@
|
||||
top: -25px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.popover {
|
||||
background-color: $popoverBg;
|
||||
border-radius: 3px;
|
||||
margin: -16px -24px;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
@@ -5,16 +5,13 @@
|
||||
.popupmenu {
|
||||
min-width: 75px;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
padding: 0px;
|
||||
width: 150px;
|
||||
white-space: nowrap;
|
||||
|
||||
&__item {
|
||||
list-style-type: none;
|
||||
height: 35px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(9, 30, 66, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
// Link Appearance
|
||||
@@ -28,6 +25,12 @@
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
padding: 0 5px;
|
||||
color: $popupMenuColor;
|
||||
|
||||
&:hover {
|
||||
background-color: $popupMenuHoverBackground;
|
||||
color: $popupMenuHoverColor;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
@@ -62,6 +65,18 @@
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
width: 100%;
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
background-color: $popupSliderColor;
|
||||
}
|
||||
|
||||
&::-moz-range-track {
|
||||
background-color: $popupSliderColor;
|
||||
}
|
||||
|
||||
&::-ms-fill-lower {
|
||||
background-color: $popupSliderColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,7 +93,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.icon-kick {
|
||||
.icon-kick,
|
||||
.icon-play,
|
||||
.icon-stop {
|
||||
font-size: 8pt;
|
||||
}
|
||||
}
|
||||
@@ -89,14 +106,9 @@
|
||||
* InlineDialogs.
|
||||
*/
|
||||
ul.popupmenu {
|
||||
margin: -15px;
|
||||
margin: -16px -24px;
|
||||
}
|
||||
|
||||
span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover {
|
||||
display:block !important;
|
||||
}
|
||||
|
||||
.remote-control-spinner {
|
||||
top: 6px;
|
||||
left: 2px;
|
||||
}
|
||||
|
||||
@@ -11,42 +11,45 @@
|
||||
flex: 0;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 32px;
|
||||
|
||||
.recording-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.recording-header-line {
|
||||
border-top: 1px solid #5e6d7a;
|
||||
}
|
||||
|
||||
.recording-switch-disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.recording-icon-container {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.recording-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.recording-switch {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.authorization-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 10px;
|
||||
margin: 0 40px 10px 40px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.dropbox-sign-in {
|
||||
align-items: center;
|
||||
border: 1px solid #4285f4;
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 10px 0px;
|
||||
color: #4285f4;
|
||||
|
||||
.dropbox-logo {
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
padding-right: 5px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.logged-in-panel {
|
||||
padding: 10px;
|
||||
}
|
||||
@@ -65,6 +68,8 @@
|
||||
}
|
||||
|
||||
.form-footer {
|
||||
display: flex;
|
||||
margin-top: 5px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -93,11 +98,15 @@
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.stream-key-form {
|
||||
.helper-link {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.helper-link {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
color:#FFD740;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
22
css/_subject.scss
Normal file
22
css/_subject.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
.subject {
|
||||
top: -120px;
|
||||
transition: top .3s ease-in;
|
||||
height: 95px;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
padding: 25px 140px 0 140px;
|
||||
text-align: center;
|
||||
font-size: 17px;
|
||||
color: #fff;
|
||||
z-index: $toolbarBackgroundZ;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
|
||||
|
||||
&.visible {
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
@@ -23,192 +23,252 @@
|
||||
* TODO: when the old filmstrip has been removed, remove the "new-" prefix.
|
||||
*/
|
||||
.new-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
bottom: calc((#{$newToolbarSize} * 2) * -1);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
left: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 12px 8px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transition: bottom .3s ease-in;
|
||||
width: 100%;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
|
||||
&.visible {
|
||||
bottom: 0;
|
||||
.toolbox-background {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button-group-center,
|
||||
.button-group-left,
|
||||
.button-group-right {
|
||||
display: flex;
|
||||
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%;
|
||||
line-height: inherit;
|
||||
.toolbox-background {
|
||||
background-image: linear-gradient(to top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0));
|
||||
transition: bottom .3s ease-in;
|
||||
height: 160px;
|
||||
width: 100%;
|
||||
bottom: -160px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
z-index: $toolbarBackgroundZ;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
}
|
||||
.toolbox-content {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 20px 16px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
i.toggled {
|
||||
background: $newToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
i.toggled:hover {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
i.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
|
||||
.disabled i {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
|
||||
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;
|
||||
.button-group-center,
|
||||
.button-group-left,
|
||||
.button-group-right {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
height: 22px;
|
||||
padding: 5px 12px;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
.button-group-center {
|
||||
justify-content: center;
|
||||
.toolbox-icon {
|
||||
margin: 0px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.button-group-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
i {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: inherit;
|
||||
height: 100%;
|
||||
line-height: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
i.toggled {
|
||||
background: $newToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
i.toggled:hover {
|
||||
background: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
.icon-hangup {
|
||||
background-color: #e12d2d;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
&:hover {
|
||||
background: #313D52;
|
||||
background-color: #e54b4b;
|
||||
}
|
||||
}
|
||||
i.disabled, .disabled i {
|
||||
cursor: initial;
|
||||
color: #fff;
|
||||
background-color: #a4b8d1;
|
||||
}
|
||||
|
||||
.icon-mic-disabled, .icon-microphone, .icon-camera-disabled, .icon-camera {
|
||||
background-color: #fff;
|
||||
color: #5e6d7a;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #d1dbe8;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
|
||||
&:hover {
|
||||
background-color: #daebfa;
|
||||
border: 1px solid #daebfa;
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
&.toggled {
|
||||
background: #2a3a4b;
|
||||
color: #fff;
|
||||
border: 1px solid #5e6d7a;
|
||||
|
||||
&:hover {
|
||||
background-color: #5e6d7a;
|
||||
}
|
||||
}
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
&.disabled {
|
||||
|
||||
&.disabled, .disabled & {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
color: #fff;
|
||||
background-color: #a4b8d1;
|
||||
}
|
||||
}
|
||||
|
||||
.beta-tag {
|
||||
background: #B8C7E0;
|
||||
border-radius: 2px;
|
||||
color: $newToolbarBackgroundColor;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
.overflow-menu {
|
||||
font-size: 1.2em;
|
||||
list-style-type: none;
|
||||
background-color: $overflowMenuBG;
|
||||
/**
|
||||
* Undo atlaskit padding by reducing margins.
|
||||
*/
|
||||
margin: -16px -24px;
|
||||
padding: 4px 0;
|
||||
|
||||
.overflow-menu-item-icon {
|
||||
margin-right: 10px;
|
||||
.overflow-menu-item {
|
||||
align-items: center;
|
||||
color: $overflowMenuItemColor;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
height: 22px;
|
||||
padding: 5px 12px;
|
||||
|
||||
i {
|
||||
display: inline;
|
||||
font-size: 24px;
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $overflowMenuItemHoverBG;
|
||||
color: $overflowMenuItemHoverColor;
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
}
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
|
||||
i.toggled {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
i.toggled:hover {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background-color: initial;
|
||||
.beta-tag {
|
||||
background: $overflowMenuItemColor;
|
||||
border-radius: 2px;
|
||||
color: $overflowMenuBG;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-text {
|
||||
max-width: 150px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
.toolbox-button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: $newToolbarSize;
|
||||
margin: 0 8px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbox-button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: $newToolbarSize;
|
||||
margin: 0 4px;
|
||||
text-align: center;
|
||||
}
|
||||
.toolbar-button-with-badge {
|
||||
position: relative;
|
||||
|
||||
.toolbar-button-with-badge {
|
||||
position: relative;
|
||||
|
||||
.badge-round {
|
||||
bottom: -5px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
min-width: 20px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
.badge-round {
|
||||
bottom: -5px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
min-width: 20px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbox-button-wth-dialog {
|
||||
display: inline-block;
|
||||
}
|
||||
.toolbox-button-wth-dialog {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.toolbox-icon {
|
||||
height: $newToolbarSize;
|
||||
font-size: 24px;
|
||||
width: $newToolbarSize;
|
||||
.toolbox-icon {
|
||||
height: $newToolbarSize;
|
||||
font-size: 24px;
|
||||
width: $newToolbarSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,15 +286,15 @@
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
background-color: $AOTToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
i.toggled {
|
||||
background: $newToolbarButtonToggleColor;
|
||||
background: $AOTToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
i.toggled:hover:not(.disabled) {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
background-color: $AOTToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
.icon-hangup {
|
||||
|
||||
@@ -34,13 +34,19 @@ $defaultDarkColor: #2b3d5c;
|
||||
*/
|
||||
$defaultToolbarSize: 50px;
|
||||
$newToolbarBackgroundColor: rgba(22, 38, 55, 0.8);
|
||||
$newToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);
|
||||
$newToolbarButtonToggleColor: rgba(14, 20, 35, 1);
|
||||
$newToolbarButtonHoverColor: rgba(255, 255, 255, 0.15);
|
||||
$newToolbarButtonToggleColor: rgba(255, 255, 255, 0.2);
|
||||
$AOTToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);
|
||||
$AOTToolbarButtonToggleColor: rgba(14, 20, 35, 1);
|
||||
$newToolbarFontSize: 24px;
|
||||
$newToolbarHangupFontSize: 32px;
|
||||
$newToolbarSize: 40px;
|
||||
$newToolbarSizeWithPadding: calc(#{$newToolbarSize} + 24px);
|
||||
$toolbarTitleFontSize: 19px;
|
||||
$overflowMenuBG: initial;
|
||||
$overflowMenuItemHoverBG: #313D52;
|
||||
$overflowMenuItemHoverColor: #B8C7E0;
|
||||
$overflowMenuItemColor: #B8C7E0;
|
||||
|
||||
/**
|
||||
* Video layout
|
||||
@@ -57,6 +63,7 @@ $audioLevelShadow: rgba(9, 36, 77, 0.9);
|
||||
$videoStateIndicatorColor: $defaultColor;
|
||||
$videoStateIndicatorBackground: $toolbarBackground;
|
||||
$videoStateIndicatorSize: 40px;
|
||||
$remoteVideoMenuIconMargin: initial;
|
||||
|
||||
/**
|
||||
* Feedback Modal
|
||||
@@ -76,13 +83,22 @@ $modalMockAKInputBorder: 1px solid #f4f5f7;
|
||||
$modalTextColor: #333;
|
||||
|
||||
/**
|
||||
* Chat
|
||||
*/
|
||||
$chatHeaderBackgroundColor: rgba(42, 58, 75, 0.9);
|
||||
$chatInputSeparatorColor: #A4B8D1;
|
||||
$chatLocalMessageBackgroundColor: rgba(26, 108, 180, 1);
|
||||
$chatRemoteMessageBackgroundColor: rgba(240, 243, 247, 0.15);
|
||||
$sidebarWidth: 375px;
|
||||
|
||||
/**
|
||||
* Misc.
|
||||
*/
|
||||
$borderRadius: 4px;
|
||||
$defaultWatermarkLink: '../images/watermark.png';
|
||||
$sidebarWidth: 220px;
|
||||
$popoverMenuPadding: 13px;
|
||||
$happySoftwareBackground: transparent;
|
||||
$desktopAppDragBarHeight: 25px;
|
||||
|
||||
/**
|
||||
* Z-indexes. TODO: Replace this by a function.
|
||||
@@ -91,6 +107,7 @@ $zindex0: 0;
|
||||
$zindex1: 1;
|
||||
$zindex2: 2;
|
||||
$zindex3: 3;
|
||||
$toolbarBackgroundZ: 4;
|
||||
$filmstripVideosZ: 5;
|
||||
$zindex10: 10;
|
||||
$reloadZ: 20;
|
||||
@@ -148,3 +165,9 @@ $welcomePageDescriptionColor: #fff;
|
||||
$welcomePageFontFamily: inherit;
|
||||
$welcomePageHeaderBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
|
||||
$welcomePageTitleColor: #fff;
|
||||
|
||||
/**
|
||||
* Deep-linking page variables.
|
||||
*/
|
||||
$deepLinkingMobileLogoHeight: 40px;
|
||||
$deepLinkingMobileHeaderBackground: #f1f2f5;
|
||||
|
||||
@@ -80,15 +80,6 @@
|
||||
float: left;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to overwrite the background for the top toolbar dark theme div
|
||||
* wrapper needed before we're able to move all top toolbar indicators
|
||||
* creation to react.
|
||||
*/
|
||||
.sc-ifAKCX {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__toolbar {
|
||||
@@ -418,6 +409,7 @@
|
||||
height: 13px;
|
||||
color: #FFF;
|
||||
font-size: 10pt;
|
||||
margin-right: $remoteVideoMenuIconMargin;
|
||||
|
||||
>i{
|
||||
cursor: hand;
|
||||
@@ -505,18 +497,22 @@
|
||||
display:none !important;
|
||||
}
|
||||
|
||||
#dominantSpeakerAvatarContainer,
|
||||
#dominantSpeakerAvatar,
|
||||
.dynamic-shadow {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
#dominantSpeakerAvatar {
|
||||
#dominantSpeakerAvatarContainer {
|
||||
top: 50px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
border-radius: 100px;
|
||||
overflow: hidden;
|
||||
visibility: inherit;
|
||||
}
|
||||
#dominantSpeakerAvatar {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
background-color: #f1f2f5;
|
||||
background-color: $deepLinkingMobileHeaderBackground;
|
||||
text-align: center;
|
||||
.logo {
|
||||
margin-top: 15px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: 40px;
|
||||
height: $deepLinkingMobileLogoHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
max-width: 40em;
|
||||
padding: 35px 0 40px 0;
|
||||
text-align: center;
|
||||
width: 75%;
|
||||
width: 90%;
|
||||
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
&__text,
|
||||
.deep-linking-dial-in {
|
||||
font-size: 1.2em;
|
||||
font-size: 1em;
|
||||
line-height: em(29px, 21px);
|
||||
margin-bottom: 0.65em;
|
||||
|
||||
@@ -59,6 +59,31 @@
|
||||
font-size: em(21, 18);
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.dial-in-conference-id {
|
||||
margin: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
.dial-in-conference-description {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.toll-free-list {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.numbers-list {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
li.toll-free:empty:before {
|
||||
content: '.';
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&__href {
|
||||
@@ -108,6 +133,7 @@
|
||||
|
||||
.dial-in-numbers-list {
|
||||
color: $unsupportedBrowserTextColor;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.dial-in-numbers-body {
|
||||
|
||||
@@ -44,4 +44,15 @@
|
||||
#largeVideoContainer {
|
||||
background-color: $defaultBackground !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thumbnail popover menus can overlap other thumbnails. Setting an auto
|
||||
* z-index will allow AtlasKit InlineDialog's large z-index to be
|
||||
* respected and thereby display over elements in other thumbnails,
|
||||
* specifically the various status icons.
|
||||
*/
|
||||
.remotevideomenu,
|
||||
.videocontainer__toptoolbar {
|
||||
z-index: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: 100%;
|
||||
padding: 10px 5px;
|
||||
padding: ($desktopAppDragBarHeight - 5px) 5px 10px;
|
||||
/**
|
||||
* fixed positioning is necessary for remote menus and tooltips to pop
|
||||
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
|
||||
.remote-video-menu-trigger {
|
||||
margin-bottom: 7px;
|
||||
margin-left: $remoteVideoMenuIconMargin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,15 @@
|
||||
|
||||
/* Animations END */
|
||||
|
||||
/* Flags BEGIN */
|
||||
$flagsImagePath: "../images/";
|
||||
@import "../node_modules/bc-css-flags/dist/css/bc-css-flags.scss";
|
||||
/* Flags END */
|
||||
|
||||
/* Fonts BEGIN */
|
||||
|
||||
@import 'font';
|
||||
@import 'font-awesome';
|
||||
|
||||
/* Fonts END */
|
||||
|
||||
/* Modules BEGIN */
|
||||
@@ -44,6 +49,7 @@
|
||||
@import 'modals/local-recording/local-recording';
|
||||
@import 'videolayout_default';
|
||||
@import 'notice';
|
||||
@import 'subject';
|
||||
@import 'popup_menu';
|
||||
@import 'recording';
|
||||
@import 'login_menu';
|
||||
|
||||
@@ -12,43 +12,6 @@
|
||||
|
||||
.aui {
|
||||
|
||||
&-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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-iconfont-close-dialog {
|
||||
cursor: pointer;
|
||||
right: 20px;
|
||||
position: absolute;
|
||||
top: -49px;
|
||||
|
||||
&:before {
|
||||
content: "\f00d";
|
||||
}
|
||||
}
|
||||
|
||||
&-dialog2 {
|
||||
&-header, &-footer {
|
||||
background-color: $auiDialogBg;
|
||||
@@ -124,6 +87,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-hide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.input-control {
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
}
|
||||
|
||||
.info-dialog-dial-in {
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
|
||||
.conference-id,
|
||||
.phone-number {
|
||||
@@ -77,6 +77,7 @@
|
||||
.info-dialog-icon {
|
||||
color: #6453C0;
|
||||
font-size: 16px;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.info-dialog-url-text,
|
||||
@@ -124,29 +125,85 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dial-in-numbers-list {
|
||||
margin-top: 20px;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 5px;
|
||||
|
||||
thead {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border-bottom: 1px solid #d1dbe8;
|
||||
}
|
||||
|
||||
.flag-cell {
|
||||
vertical-align: top;
|
||||
width: 30px;
|
||||
}
|
||||
.flag {
|
||||
display: block;
|
||||
margin: 5px 5px 0px 5px;
|
||||
}
|
||||
|
||||
.country {
|
||||
font-weight: bold;
|
||||
vertical-align: top;
|
||||
padding: 0 20px 0 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.numbers-list {
|
||||
list-style: none;
|
||||
padding: 0 20px 0 0;
|
||||
}
|
||||
|
||||
.toll-free-list {
|
||||
font-weight: bold;
|
||||
list-style: none;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
li.toll-free:empty:before {
|
||||
content: '.';
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.dial-in-page {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 24px;
|
||||
font-size: 12px;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
padding: 25px;
|
||||
padding: 15pt;
|
||||
position: absolute;
|
||||
transform: translateY(-50%);
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
|
||||
.dial-in-numbers-list {
|
||||
font-size: 24px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.dial-in-conference-id {
|
||||
text-align: center;
|
||||
min-width: 200px;
|
||||
width: 30%;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.dial-in-conference-name,
|
||||
.dial-in-conference-pin {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.dial-in-conference-description {
|
||||
margin: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,9 +80,12 @@ $errorColor: #c61600;
|
||||
$feedbackCancelFontColor: #333;
|
||||
|
||||
// Popover colors
|
||||
$popoverBg: #000;
|
||||
$popoverFontColor: #ffffff;
|
||||
$popupMenuSelectedItemBackground: rgba(256, 256, 256, .2);
|
||||
$popoverBg: initial;
|
||||
$popoverFontColor: #ffffff !important;
|
||||
$popupMenuColor: #ffffff !important;
|
||||
$popupMenuHoverColor: #ffffff !important;
|
||||
$popupMenuHoverBackground: rgba(255, 255, 255, 0.1);
|
||||
$popupSliderColor: #0376da;
|
||||
|
||||
// Toolbar
|
||||
$secondaryToolbarBg: rgba(0, 0, 0, 0.5);
|
||||
|
||||
6
debian/control
vendored
6
debian/control
vendored
@@ -21,7 +21,7 @@ Description: WebRTC JavaScript video conferences
|
||||
|
||||
Package: jitsi-meet-web-config
|
||||
Architecture: all
|
||||
Depends: openssl, openjdk-8-jre-headless | nginx | apache2
|
||||
Depends: openssl, openjdk-8-jre-headless | nginx | nginx-extras | apache2
|
||||
Description: Configuration for web serving of Jitsi Meet
|
||||
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||
Videobridge to provide high quality, scalable video conferences.
|
||||
@@ -36,7 +36,7 @@ Description: Configuration for web serving of Jitsi Meet
|
||||
|
||||
Package: jitsi-meet-prosody
|
||||
Architecture: all
|
||||
Depends: openssl, prosody | prosody-trunk
|
||||
Depends: openssl, prosody | prosody-trunk | prosody-0.11
|
||||
Description: Prosody configuration for Jitsi Meet
|
||||
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||
Videobridge to provide high quality, scalable video conferences.
|
||||
@@ -50,6 +50,6 @@ Description: Prosody configuration for Jitsi Meet
|
||||
|
||||
Package: jitsi-meet-tokens
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, prosody-trunk (>= 1nightly607), libssl-dev, luarocks, jitsi-meet-prosody
|
||||
Depends: ${misc:Depends}, prosody-trunk (>= 1nightly747) | prosody-0.11 | prosody (>= 0.11.2), libssl-dev, luarocks, jitsi-meet-prosody
|
||||
Description: Prosody token authentication plugin for Jitsi Meet
|
||||
|
||||
|
||||
27
debian/jitsi-meet-prosody.postinst
vendored
27
debian/jitsi-meet-prosody.postinst
vendored
@@ -125,6 +125,33 @@ case "$1" in
|
||||
ln -sf /var/lib/prosody/$JVB_HOSTNAME.crt /etc/prosody/certs/$JVB_HOSTNAME.crt
|
||||
fi
|
||||
|
||||
PR11_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'prosody-0.11' 2>/dev/null | awk '{print $3}' || true)"
|
||||
PR10_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'prosody-0.10' 2>/dev/null | awk '{print $3}' || true)"
|
||||
PR_VER_INSTALLED=$(dpkg-query -f='${Version}\n' --show prosody 2>/dev/null || true)
|
||||
if [ "$PR11_INSTALL_CHECK" = "installed" ] \
|
||||
|| [ "$PR11_INSTALL_CHECK" = "unpacked" ] \
|
||||
|| dpkg --compare-versions "$PR_VER_INSTALLED" gt "0.11" ; then
|
||||
if [ -f $PROSODY_HOST_CONFIG ]; then
|
||||
sed -i 's/storage = \"null\"/storage = \"memory\"/g' $PROSODY_HOST_CONFIG
|
||||
|
||||
# trigger a restart
|
||||
PROSODY_CONFIG_PRESENT="false"
|
||||
fi
|
||||
fi
|
||||
if [ "$PR10_INSTALL_CHECK" = "installed" ] \
|
||||
|| [ "$PR10_INSTALL_CHECK" = "unpacked" ] \
|
||||
|| dpkg --compare-versions "$PR_VER_INSTALLED" gt "0.10" ; then
|
||||
|
||||
# if the version is 0.10.X (>0.10 and <0.11)
|
||||
if [ -f $PROSODY_HOST_CONFIG ] \
|
||||
&& dpkg --compare-versions "$PR_VER_INSTALLED" lt "0.11" ; then
|
||||
sed -i 's/storage = \"null\"/storage = \"none\"/g' $PROSODY_HOST_CONFIG
|
||||
|
||||
# trigger a restart
|
||||
PROSODY_CONFIG_PRESENT="false"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt ]; then
|
||||
# prosodyctl takes care for the permissions
|
||||
# echo for using all default values
|
||||
|
||||
13
debian/jitsi-meet-tokens.postinst
vendored
13
debian/jitsi-meet-tokens.postinst
vendored
@@ -69,14 +69,17 @@ case "$1" in
|
||||
echo "Failed to install basexx - try installing it manually"
|
||||
fi
|
||||
|
||||
PR11_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'prosody-0.11' 2>/dev/null | awk '{print $3}' || true)"
|
||||
PR_VER_INSTALLED=$(dpkg-query -f='${Version}\n' --show prosody 2>/dev/null || true)
|
||||
if [ "$PR11_INSTALL_CHECK" = "installed" ] \
|
||||
|| [ "$PR11_INSTALL_CHECK" = "unpacked" ] \
|
||||
|| dpkg --compare-versions "$PR_VER_INSTALLED" gt "0.11" ; then
|
||||
sed -i 's/module:hook/module:hook_global/g' /usr/share/jitsi-meet/prosody-plugins/mod_auth_token.lua
|
||||
fi
|
||||
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d prosody restart
|
||||
fi
|
||||
|
||||
echo "This package requires BOSH Prosody module to be patched !"
|
||||
echo "Use the following command, after this package has been installed and"
|
||||
echo "after every prosody-trunk upgrade:"
|
||||
echo "sudo patch -N /usr/lib/prosody/modules/mod_bosh.lua /usr/share/jitsi-meet/prosody-plugins/mod_bosh.lua.patch"
|
||||
fi
|
||||
else
|
||||
echo "Prosody config not found at $PROSODY_HOST_CONFIG - unable to auto-configure token authentication"
|
||||
|
||||
7
debian/jitsi-meet-web-config.postinst
vendored
7
debian/jitsi-meet-web-config.postinst
vendored
@@ -52,7 +52,11 @@ case "$1" in
|
||||
db_set jitsi-meet/jvb-hostname $JVB_HOSTNAME
|
||||
|
||||
NGINX_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx' 2>/dev/null | awk '{print $3}' || true)"
|
||||
if [ "$NGINX_INSTALL_CHECK" = "installed" ] || [ "$NGINX_INSTALL_CHECK" = "unpacked" ] ; then
|
||||
NGINX_EXTRAS_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx-extras' 2>/dev/null | awk '{print $3}' || true)"
|
||||
if [ "$NGINX_INSTALL_CHECK" = "installed" ] \
|
||||
|| [ "$NGINX_INSTALL_CHECK" = "unpacked" ] \
|
||||
|| [ "$NGINX_EXTRAS_INSTALL_CHECK" = "installed" ] \
|
||||
|| [ "$NGINX_EXTRAS_INSTALL_CHECK" = "unpacked" ] ; then
|
||||
FORCE_NGINX="true"
|
||||
fi
|
||||
APACHE_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'apache2' 2>/dev/null | awk '{print $3}' || true)"
|
||||
@@ -128,6 +132,7 @@ case "$1" in
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./config.js=/etc/jitsi/meet/$JVB_HOSTNAME-config.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./interface_config.js=/usr/share/jitsi-meet/interface_config.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./logging_config.js=/usr/share/jitsi-meet/logging_config.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./external_api.js=/usr/share/jitsi-meet/libs/external_api.min.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.regex=^/([a-zA-Z0-9]+)$" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.replacement=/" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.SSIResourceHandler.paths=/" >> $JVB_CONFIG
|
||||
|
||||
12
debian/rules
vendored
12
debian/rules
vendored
@@ -3,12 +3,22 @@
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
LANGUAGES := $(shell node -p "Object.keys(require('./lang/languages.json')).join(' ')")
|
||||
COUNTRIES_DIR := node_modules/i18n-iso-countries/langs
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
# we skip making Makefile exists for updating browserify modules when developing
|
||||
override_dh_auto_build:
|
||||
|
||||
override_dh_install:
|
||||
override_dh_install: $(LANGUAGES)
|
||||
dh_installdirs
|
||||
dh_install -X/config.js -X/package.json
|
||||
|
||||
$(LANGUAGES):
|
||||
if [ -f $(COUNTRIES_DIR)/$@.json ] ; \
|
||||
then \
|
||||
dh_install -pjitsi-meet-web $(COUNTRIES_DIR)/$@.json usr/share/jitsi-meet/lang/; \
|
||||
mv debian/jitsi-meet-web/usr/share/jitsi-meet/lang/$@.json debian/jitsi-meet-web/usr/share/jitsi-meet/lang/countries-$@.json; \
|
||||
fi;
|
||||
|
||||
@@ -8,6 +8,5 @@
|
||||
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
|
||||
|
||||
294
doc/api.md
294
doc/api.md
@@ -1,13 +1,13 @@
|
||||
# Jitsi Meet API
|
||||
|
||||
You can use the Jitsi Meet API to embed Jitsi Meet in to your application.
|
||||
You can use the Jitsi Meet API to embed Jitsi Meet in to your application. You are also welcome to use it for embedding the globally distributed and highly available deployment on meet.jit.si itself. The only thing we ask for in that case is that you please DO NOT remove the jitsi.org logo from the top left corner.
|
||||
|
||||
## Installation
|
||||
|
||||
To embed Jitsi Meet in your application you need to add the Jitsi Meet API library:
|
||||
|
||||
```javascript
|
||||
<script src="https://meet.jit.si/external_api.js"></script>
|
||||
<script src='https://meet.jit.si/external_api.js'></script>
|
||||
```
|
||||
## API
|
||||
|
||||
@@ -16,7 +16,7 @@ To embed Jitsi Meet in your application you need to add the Jitsi Meet API libra
|
||||
The next step for embedding Jitsi Meet is to create the Jitsi Meet API object.
|
||||
Its constructor gets a number of options:
|
||||
|
||||
* **domain**: domain used to build the conference URL, "meet.jit.si" for
|
||||
* **domain**: domain used to build the conference URL, 'meet.jit.si' for
|
||||
example.
|
||||
* **options**: object with properties - the optional arguments:
|
||||
* **roomName**: (optional) name of the room to join.
|
||||
@@ -29,46 +29,160 @@ Its constructor gets a number of options:
|
||||
* **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.
|
||||
* **devices**: (optional) A map containing information about the initial devices that will be used in the call.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
var domain = "meet.jit.si";
|
||||
var options = {
|
||||
roomName: "JitsiMeetAPIExample",
|
||||
const domain = 'meet.jit.si';
|
||||
const options = {
|
||||
roomName: 'JitsiMeetAPIExample',
|
||||
width: 700,
|
||||
height: 700,
|
||||
parentNode: document.querySelector('#meet')
|
||||
}
|
||||
var api = new JitsiMeetExternalAPI(domain, options);
|
||||
};
|
||||
const api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
You can set the initial media devices for the call:
|
||||
|
||||
```javascript
|
||||
const domain = 'meet.jit.si';
|
||||
const options = {
|
||||
...
|
||||
devices: {
|
||||
audioInput: '<deviceLabel>',
|
||||
audioOutput: '<deviceLabel>',
|
||||
videoInput: '<deviceLabel>'
|
||||
},
|
||||
...
|
||||
};
|
||||
const api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
You can overwrite options set in [config.js] and [interface_config.js].
|
||||
For example, to enable the filmstrip-only interface mode, you can use:
|
||||
|
||||
```javascript
|
||||
var options = {
|
||||
interfaceConfigOverwrite: {filmStripOnly: true}
|
||||
const options = {
|
||||
...
|
||||
interfaceConfigOverwrite: { filmStripOnly: true },
|
||||
...
|
||||
};
|
||||
var api = new JitsiMeetExternalAPI(domain, options);
|
||||
const api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
You can also pass a jwt token to Jitsi Meet:
|
||||
|
||||
```javascript
|
||||
var options = {
|
||||
jwt: "<jwt_token>",
|
||||
noSsl: false
|
||||
const options = {
|
||||
...
|
||||
jwt: '<jwt_token>',
|
||||
noSsl: false,
|
||||
...
|
||||
};
|
||||
var api = new JitsiMeetExternalAPI(domain, options);
|
||||
const api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
### Controlling the embedded Jitsi Meet Conference
|
||||
|
||||
Device management `JitsiMeetExternalAPI` methods:
|
||||
* **getAvailableDevices** - Retrieve a list of available devices.
|
||||
|
||||
```javascript
|
||||
api.getAvailableDevices().then(devices => {
|
||||
// devices = {
|
||||
// audioInput: [{
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'audioinput'
|
||||
// label: 'label'
|
||||
// },....],
|
||||
// audioOutput: [{
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'audioOutput'
|
||||
// label: 'label'
|
||||
// },....],
|
||||
// videoInput: [{
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'videoInput'
|
||||
// label: 'label'
|
||||
// },....]
|
||||
// }
|
||||
...
|
||||
});
|
||||
```
|
||||
* **getCurrentDevices** - Retrieve a list with the devices that are currently selected.
|
||||
|
||||
```javascript
|
||||
api.getCurrentDevices().then(devices => {
|
||||
// devices = {
|
||||
// audioInput: {
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'videoInput'
|
||||
// label: 'label'
|
||||
// },
|
||||
// audioOutput: {
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'videoInput'
|
||||
// label: 'label'
|
||||
// },
|
||||
// videoInput: {
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'videoInput'
|
||||
// label: 'label'
|
||||
// }
|
||||
// }
|
||||
...
|
||||
});
|
||||
```
|
||||
* **isDeviceChangeAvailable** - Resolves with true if the device change is available and with false if not.
|
||||
|
||||
```javascript
|
||||
// The accepted deviceType values are - 'output', 'input' or undefined.
|
||||
api.isDeviceChangeAvailable(deviceType).then(isDeviceChangeAvailable => {
|
||||
...
|
||||
});
|
||||
```
|
||||
* **isDeviceListAvailable** - Resolves with true if the device list is available and with false if not.
|
||||
|
||||
```javascript
|
||||
api.isDeviceListAvailable().then(isDeviceListAvailable => {
|
||||
...
|
||||
});
|
||||
```
|
||||
* **isMultipleAudioInputSupported** - Resolves with true if multiple audio input is supported and with false if not.
|
||||
|
||||
```javascript
|
||||
api.isMultipleAudioInputSupported().then(isMultipleAudioInputSupported => {
|
||||
...
|
||||
});
|
||||
```
|
||||
* **setAudioInputDevice** - Sets the audio input device to the one with the label or id that is passed.
|
||||
|
||||
```javascript
|
||||
api.setAudioInputDevice(deviceLabel, deviceId);
|
||||
```
|
||||
* **setAudioOutputDevice** - Sets the audio output device to the one with the label or id that is passed.
|
||||
|
||||
```javascript
|
||||
api.setAudioOutputDevice(deviceLabel, deviceId);
|
||||
```
|
||||
* **setVideoInputDevice** - Sets the video input device to the one with the label or id that is passed.
|
||||
|
||||
```javascript
|
||||
api.setVideoInputDevice(deviceLabel, deviceId);
|
||||
```
|
||||
|
||||
You can control the embedded Jitsi Meet conference using the `JitsiMeetExternalAPI` object by using `executeCommand`:
|
||||
|
||||
```javascript
|
||||
api.executeCommand(command, ...arguments)
|
||||
api.executeCommand(command, ...arguments);
|
||||
```
|
||||
|
||||
The `command` parameter is String object with the name of the command. The following commands are currently supported:
|
||||
@@ -78,59 +192,72 @@ The `command` parameter is String object with the name of the command. The follo
|
||||
api.executeCommand('displayName', 'New Nickname');
|
||||
```
|
||||
|
||||
* **subject** - Sets the subject of the conference. This command requires one argument - the new subject to be set.
|
||||
```javascript
|
||||
api.executeCommand('subject', 'New Conference Subject');
|
||||
```
|
||||
|
||||
* **toggleAudio** - Mutes / unmutes the audio for the local participant. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleAudio')
|
||||
api.executeCommand('toggleAudio');
|
||||
```
|
||||
|
||||
* **toggleVideo** - Mutes / unmutes the video for the local participant. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleVideo')
|
||||
api.executeCommand('toggleVideo');
|
||||
```
|
||||
|
||||
* **toggleFilmStrip** - Hides / shows the filmstrip. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleFilmStrip')
|
||||
api.executeCommand('toggleFilmStrip');
|
||||
```
|
||||
|
||||
* **toggleChat** - Hides / shows the chat. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleChat')
|
||||
api.executeCommand('toggleChat');
|
||||
```
|
||||
|
||||
* **toggleShareScreen** - Starts / stops screen sharing. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleShareScreen')
|
||||
api.executeCommand('toggleShareScreen');
|
||||
```
|
||||
|
||||
* **toggleTileView** - Enter / exit tile view layout mode. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleTileView');
|
||||
```
|
||||
|
||||
* **hangup** - Hangups the call. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('hangup')
|
||||
api.executeCommand('hangup');
|
||||
```
|
||||
|
||||
* **email** - Changes the local email address. This command requires one argument - the new email address to be set.
|
||||
```javascript
|
||||
api.executeCommand('email', 'example@example.com')
|
||||
api.executeCommand('email', 'example@example.com');
|
||||
```
|
||||
|
||||
* **avatarUrl** - Changes the local avatar URL. This command requires one argument - the new avatar URL to be set.
|
||||
```javascript
|
||||
api.executeCommand('avatarUrl', 'https://avatars0.githubusercontent.com/u/3671647')
|
||||
api.executeCommand('avatarUrl', 'https://avatars0.githubusercontent.com/u/3671647');
|
||||
```
|
||||
|
||||
You can also execute multiple commands using the `executeCommands` method:
|
||||
```javascript
|
||||
api.executeCommands(commands)
|
||||
api.executeCommands(commands);
|
||||
```
|
||||
The `commands` parameter is an object with the names of the commands as keys and the arguments for the commands as values:
|
||||
```javascript
|
||||
api.executeCommands({displayName: ['nickname'], toggleAudio: []});
|
||||
api.executeCommands({
|
||||
displayName: [ 'nickname' ],
|
||||
toggleAudio: []
|
||||
});
|
||||
```
|
||||
|
||||
You can add event listeners to the embedded Jitsi Meet using the `addEventListener` method.
|
||||
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods (`addListener` or `on`).**
|
||||
```javascript
|
||||
api.addEventListener(event, listener)
|
||||
api.addEventListener(event, listener);
|
||||
```
|
||||
|
||||
The `event` parameter is a String object with the name of the event.
|
||||
@@ -141,29 +268,43 @@ The following events are currently supported:
|
||||
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.
|
||||
id: string, // the id of the participant that changed his avatar.
|
||||
avatarURL: string // 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: boolean // 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: boolean // 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
|
||||
on: boolean, //whether screen sharing is on
|
||||
details: {
|
||||
|
||||
// From where the screen sharing is capturing, if known. Values which are
|
||||
// passed include 'window', 'screen', 'proxy', 'device'. The value undefined
|
||||
// will be passed if the source type is unknown or screen share is off.
|
||||
sourceType: string|undefined
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* **tileViewChanged** - event notifications about tile view layout mode being entered or exited. The listener will receive object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
enabled: boolean, // whether tile view is not displayed or not
|
||||
}
|
||||
```
|
||||
|
||||
@@ -171,9 +312,9 @@ changes. The listener will receive an object with the following structure:
|
||||
messages. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"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
|
||||
from: string, // The id of the user that sent the message
|
||||
nick: string, // the nickname of the user that sent the message
|
||||
message: string // the text of the message
|
||||
}
|
||||
```
|
||||
|
||||
@@ -181,7 +322,7 @@ 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: string // the text of the message
|
||||
}
|
||||
```
|
||||
|
||||
@@ -189,68 +330,90 @@ messages. The listener will receive an object with the following structure:
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id, // the id of the participant that changed his display name
|
||||
"displayname": displayName // the new display name
|
||||
id: string, // the id of the participant that changed his display name
|
||||
displayname: string // the new display name
|
||||
}
|
||||
```
|
||||
|
||||
* **deviceListChanged** - event notifications about device list changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
devices: Object // the new list of available devices.
|
||||
}
|
||||
```
|
||||
NOTE: The devices object has the same format as the getAvailableDevices result format.
|
||||
|
||||
* **emailChange** - event notifications about email
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id, // the id of the participant that changed his email
|
||||
"email": email // the new email
|
||||
id: string, // the id of the participant that changed his email
|
||||
email: string // the new email
|
||||
}
|
||||
```
|
||||
* **filmstripDisplayChanged** - event notifications about the visibility of the filmstrip being updated.
|
||||
```javascript
|
||||
{
|
||||
visible: boolean // Whether or not the filmstrip is displayed or hidden.
|
||||
}
|
||||
```
|
||||
|
||||
* **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id, // the id of the participant
|
||||
"displayName": displayName // the display name of the participant
|
||||
id: string, // the id of the participant
|
||||
displayName: string // 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
|
||||
{
|
||||
"id": id // the id of the participant
|
||||
id: string // 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
|
||||
"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
|
||||
roomName: string, // the room name of the conference
|
||||
id: string, // the id of the local participant
|
||||
displayName: string, // the display name of the local participant
|
||||
avatarURL: string // 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: string // 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: boolean // 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: boolean // new muted status - boolean
|
||||
}
|
||||
```
|
||||
|
||||
* **readyToClose** - event notification fired when Jitsi Meet is ready to be closed (hangup operations are completed).
|
||||
|
||||
* **subjectChange** - event notifications about subject of conference changes.
|
||||
The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
subject: string // the new subject
|
||||
}
|
||||
```
|
||||
|
||||
You can also add multiple event listeners by using `addEventListeners`.
|
||||
This method requires one argument of type Object. The object argument must
|
||||
have the names of the events as keys and the listeners of the events as values.
|
||||
@@ -269,79 +432,80 @@ function outgoingMessageListener(object)
|
||||
|
||||
api.addEventListeners({
|
||||
incomingMessage: incomingMessageListener,
|
||||
outgoingMessage: outgoingMessageListener})
|
||||
outgoingMessage: outgoingMessageListener
|
||||
});
|
||||
```
|
||||
|
||||
If you want to remove a listener you can use `removeEventListener` method with argument the name of the event.
|
||||
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods( `removeListener`).**
|
||||
```javascript
|
||||
api.removeEventListener("incomingMessage");
|
||||
api.removeEventListener('incomingMessage');
|
||||
```
|
||||
|
||||
If you want to remove more than one event you can use `removeEventListeners` method with an Array with the names of the events as an argument.
|
||||
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods.**
|
||||
```javascript
|
||||
api.removeEventListeners(["incomingMessage", "outgoingMessageListener"]);
|
||||
api.removeEventListeners([ 'incomingMessage', 'outgoingMessageListener' ]);
|
||||
```
|
||||
|
||||
You can get the number of participants in the conference with the following API function:
|
||||
```javascript
|
||||
var numberOfParticipants = api.getNumberOfParticipants();
|
||||
const 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);
|
||||
const 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);
|
||||
const displayName = api.getDisplayName(participantId);
|
||||
```
|
||||
|
||||
You can get the email of a participant in the conference with the following API function:
|
||||
```javascript
|
||||
var email = api.getEmail(participantId);
|
||||
const email = api.getEmail(participantId);
|
||||
```
|
||||
|
||||
You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
|
||||
```javascript
|
||||
var iframe = api.getIFrame();
|
||||
const iframe = api.getIFrame();
|
||||
```
|
||||
|
||||
You can check whether the audio is muted with the following API function:
|
||||
```javascript
|
||||
api.isAudioMuted().then(function(muted) {
|
||||
api.isAudioMuted().then(muted => {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can check whether the video is muted with the following API function:
|
||||
```javascript
|
||||
api.isVideoMuted().then(function(muted) {
|
||||
api.isVideoMuted().then(muted => {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can check whether the audio is available with the following API function:
|
||||
```javascript
|
||||
api.isAudioAvailable().then(function(available) {
|
||||
api.isAudioAvailable().then(available => {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can check whether the video is available with the following API function:
|
||||
```javascript
|
||||
api.isVideoAvailable().then(function(available) {
|
||||
api.isVideoAvailable().then(available => {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can invite new participants to the call with the following API function:
|
||||
```javascript
|
||||
api.invite([{...}, {...}, {...}]).then(function() {
|
||||
api.invite([ {...}, {...}, {...} ]).then(() => {
|
||||
// success
|
||||
}).catch(function() {
|
||||
}).catch(() => {
|
||||
// failure
|
||||
});
|
||||
```
|
||||
@@ -349,7 +513,7 @@ api.invite([{...}, {...}, {...}]).then(function() {
|
||||
|
||||
You can remove the embedded Jitsi Meet Conference with the following API function:
|
||||
```javascript
|
||||
api.dispose()
|
||||
api.dispose();
|
||||
```
|
||||
|
||||
NOTE: It's a good practice to remove the conference before the page is unloaded.
|
||||
|
||||
@@ -71,7 +71,7 @@ paths:
|
||||
$ref: "#/definitions/ConferenceMapperDetails"
|
||||
405:
|
||||
description: "Invalid input"
|
||||
|
||||
|
||||
/phoneNumberList:
|
||||
get:
|
||||
tags:
|
||||
@@ -96,7 +96,7 @@ securityDefinitions:
|
||||
name: "Authorization"
|
||||
in: "header"
|
||||
definitions:
|
||||
|
||||
|
||||
ConferenceMapperRequest:
|
||||
description: "Request to create or find a conference mapping"
|
||||
type: "object"
|
||||
@@ -114,7 +114,7 @@ definitions:
|
||||
domain:
|
||||
type: "string"
|
||||
description: "Domain part of the conference. Used if 'conference' is not provided. Defaults to domain of the API endpoint. Used to generate a 'conference' value (search by conference)"
|
||||
|
||||
|
||||
ConferenceMapperDetails:
|
||||
description: "Conference mapping between conference JID and numeric ID"
|
||||
type: "object"
|
||||
@@ -126,22 +126,26 @@ definitions:
|
||||
type: "string"
|
||||
format: "JID"
|
||||
description: "Full JID for the conference OR boolean false if no conference was found (search by ID)"
|
||||
|
||||
|
||||
PhoneNumberList:
|
||||
type: "object"
|
||||
properties:
|
||||
numbersEnabled:
|
||||
type: "boolean"
|
||||
description: "Control flag for Jitsi Meet user interface. Must be set to true for Jitsi Meet to display phone-in UI elements"
|
||||
numbers:
|
||||
description: "List of dial in numbers for the conference."
|
||||
type: "array"
|
||||
items:
|
||||
type: "object"
|
||||
description: "Keys are Country Names, each value is an array of phone numbers"
|
||||
additionalProperties:
|
||||
type: "array"
|
||||
items:
|
||||
properties:
|
||||
countryCode:
|
||||
type: "string"
|
||||
format: "phone"
|
||||
|
||||
description: "ISO 3166-1 country code. Alpha-2 supported."
|
||||
default:
|
||||
type: "boolean"
|
||||
description: "Whether this number is the default one to show. Optional."
|
||||
formattedNumber:
|
||||
type: "string"
|
||||
description: "The formatted telephone number to show."
|
||||
tollFree:
|
||||
type: "boolean"
|
||||
description: "Whether the number is toll free number."
|
||||
|
||||
externalDocs:
|
||||
description: "Find out more about the Jitsi Cloud API"
|
||||
url: "https://jitsi.org/CloudAPI"
|
||||
url: "https://jitsi.org/CloudAPI"
|
||||
|
||||
@@ -26,6 +26,10 @@ server {
|
||||
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
|
||||
}
|
||||
|
||||
location /external_api.js {
|
||||
alias /usr/share/jitsi-meet/libs/external_api.min.js;
|
||||
}
|
||||
|
||||
location ~ ^/([a-zA-Z0-9=\?]+)$ {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
@@ -34,11 +38,6 @@ server {
|
||||
ssi on;
|
||||
}
|
||||
|
||||
# Backward compatibility
|
||||
location ~ /external_api.* {
|
||||
root /usr/share/jitsi-meet/libs;
|
||||
}
|
||||
|
||||
# BOSH
|
||||
location /http-bind {
|
||||
proxy_pass http://localhost:5280/http-bind;
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
Require all granted
|
||||
</Location>
|
||||
|
||||
Alias "/external_api.js" "/usr/share/jitsi-meet/libs/external_api.min.js"
|
||||
<Location /external_api.js>
|
||||
Require all granted
|
||||
</Location>
|
||||
|
||||
ProxyPreserveHost on
|
||||
ProxyPass /http-bind http://localhost:5280/http-bind/
|
||||
ProxyPassReverse /http-bind http://localhost:5280/http-bind/
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
roomName: "JitsiMeetAPIExample",
|
||||
width: 700,
|
||||
height: 180,
|
||||
parent: undefined,
|
||||
parentNode: undefined,
|
||||
configOverwrite: {},
|
||||
interfaceConfigOverwrite: {
|
||||
filmStripOnly: true
|
||||
|
||||
7
doc/faq.md
Normal file
7
doc/faq.md
Normal file
@@ -0,0 +1,7 @@
|
||||
**1. How to tell if my server instance is behind NAT?**
|
||||
|
||||
A. In general, if the tool ifconfig (or ipconfig) shows the assigned IP address to be some local address (10.x.x.x or 192.x.x.x) but you know that its public IP address is different from that, the server is most probably behind NAT
|
||||
|
||||
**2. Clients could communicate well in room created at meet.jit.si . The same clients still could connect to my self-hosted instance but can neither hear nor see one another. What's wrong?**
|
||||
|
||||
A. Most probably, the server is behind NAT. See this [resolved question](https://community.jitsi.org/t/cannot-see-video-or-hear-audio-on-self-hosted-instance/). You need to follow the steps detailed [here](https://github.com/jitsi/ice4j/blob/master/doc/quick-install.md#Advanced-configuration)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user