Compare commits
598 Commits
android-sd
...
android-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f32f8013c | ||
|
|
77987d7f48 | ||
|
|
baa30f63ee | ||
|
|
fd409e4570 | ||
|
|
344244fa46 | ||
|
|
eb58f0e9de | ||
|
|
f1fbd60bbf | ||
|
|
27a59529c8 | ||
|
|
98c6b825fb | ||
|
|
54efd35081 | ||
|
|
7d09088186 | ||
|
|
34be081d87 | ||
|
|
3c36eece5e | ||
|
|
8eb0acada6 | ||
|
|
8307a8be2d | ||
|
|
d62190c644 | ||
|
|
58c4248da0 | ||
|
|
74af70a1d5 | ||
|
|
7b25b847ba | ||
|
|
18536cb14c | ||
|
|
01e36e1c56 | ||
|
|
5d96a226ed | ||
|
|
9855fac805 | ||
|
|
ed5351d250 | ||
|
|
b64260e554 | ||
|
|
7d67cb583e | ||
|
|
8e3c301d03 | ||
|
|
f9071b8b6b | ||
|
|
01abc4e8a8 | ||
|
|
c5bddda781 | ||
|
|
c2bc92ae05 | ||
|
|
a425e9c92e | ||
|
|
3bfa4744c8 | ||
|
|
bd5901d59c | ||
|
|
306c8ba8c2 | ||
|
|
5a6335207f | ||
|
|
97e8a6c3f3 | ||
|
|
523926d598 | ||
|
|
ab5627212d | ||
|
|
1b6c5a7141 | ||
|
|
a9767eda72 | ||
|
|
6c3a5793b4 | ||
|
|
c05b4a43e8 | ||
|
|
385669cbb8 | ||
|
|
5cfae2c419 | ||
|
|
92740707f0 | ||
|
|
775c531fe7 | ||
|
|
b8eda2b68d | ||
|
|
470c115e7c | ||
|
|
d9cf7aef3a | ||
|
|
c20c4bd5a3 | ||
|
|
74a5eb2d81 | ||
|
|
b25db3ce2e | ||
|
|
31d9fb12c8 | ||
|
|
ca9ca04d0f | ||
|
|
5a027f1585 | ||
|
|
5d86d202bd | ||
|
|
7ea7afebfb | ||
|
|
710307725b | ||
|
|
1cde7e63c7 | ||
|
|
a53d284bbe | ||
|
|
579e650a1d | ||
|
|
ee525ae569 | ||
|
|
63a411168e | ||
|
|
ad68a87dba | ||
|
|
45aafe5432 | ||
|
|
ac0f3979b0 | ||
|
|
d2c2919aef | ||
|
|
a18ed3a779 | ||
|
|
22871f15d0 | ||
|
|
91fb4665d6 | ||
|
|
f6c94fffc4 | ||
|
|
c8939a133d | ||
|
|
f810483ff6 | ||
|
|
c2cf09a2ca | ||
|
|
c73ba37202 | ||
|
|
659eb6b789 | ||
|
|
87821eb2c0 | ||
|
|
8291f4597c | ||
|
|
fddaf7c8a8 | ||
|
|
960ffa7e78 | ||
|
|
779ed6bb5e | ||
|
|
47984d3ec1 | ||
|
|
d9282f873c | ||
|
|
9344138303 | ||
|
|
6c3968a434 | ||
|
|
e8e70d9d27 | ||
|
|
0be68b59b7 | ||
|
|
d5ab3da9f2 | ||
|
|
5ef0f527f9 | ||
|
|
e60a14d5af | ||
|
|
03abb244df | ||
|
|
f9ea8b034f | ||
|
|
ee551ea0a0 | ||
|
|
0b22f8e449 | ||
|
|
e21f99d9d6 | ||
|
|
edb56200d5 | ||
|
|
04313a4aa3 | ||
|
|
cdb606e7ee | ||
|
|
28781965ed | ||
|
|
cbe396be55 | ||
|
|
a3befe336f | ||
|
|
63ea496bcd | ||
|
|
db6547f48d | ||
|
|
34e329a4f2 | ||
|
|
42b71fc4b9 | ||
|
|
dd99268461 | ||
|
|
692c6a47b4 | ||
|
|
b3983aa766 | ||
|
|
a79ae9b576 | ||
|
|
e69da98348 | ||
|
|
e01d891bba | ||
|
|
f904626f5e | ||
|
|
1333fd1975 | ||
|
|
af918f8dc5 | ||
|
|
b7baf8fe98 | ||
|
|
4ebab6f9c0 | ||
|
|
8f93acb464 | ||
|
|
9b60537e0f | ||
|
|
7b9abd34a0 | ||
|
|
ed82443ffa | ||
|
|
a3cd331369 | ||
|
|
9c77ab7f4d | ||
|
|
55983ff62a | ||
|
|
b4be1bcd05 | ||
|
|
2420a68be9 | ||
|
|
ebfc5a95ff | ||
|
|
68cad276bd | ||
|
|
e683d70a18 | ||
|
|
9645391180 | ||
|
|
851b1a76a9 | ||
|
|
4890390ea2 | ||
|
|
7828bf8d46 | ||
|
|
191da551e3 | ||
|
|
55f35933e8 | ||
|
|
b125bff7c7 | ||
|
|
c1d261445e | ||
|
|
c494d6c48b | ||
|
|
4134d47f6e | ||
|
|
0b25e62c5c | ||
|
|
4d0cbff5a1 | ||
|
|
c79463aaee | ||
|
|
339e1c5fab | ||
|
|
36455c24c8 | ||
|
|
a622a4c713 | ||
|
|
1aaaae24ee | ||
|
|
9191000da4 | ||
|
|
8eb93086bd | ||
|
|
b64294af6d | ||
|
|
bbf33a8895 | ||
|
|
bcc1289a23 | ||
|
|
58bd48c1ae | ||
|
|
1a3736bf98 | ||
|
|
0eec182df4 | ||
|
|
c526844eb2 | ||
|
|
d856c1f328 | ||
|
|
15e47a9eb3 | ||
|
|
da98d39b61 | ||
|
|
411bafb5a6 | ||
|
|
0a64bf2068 | ||
|
|
db6a2673de | ||
|
|
e11d4d3101 | ||
|
|
8fd3bb2302 | ||
|
|
fb3a832a52 | ||
|
|
9c146c1245 | ||
|
|
792f506425 | ||
|
|
6121e9fc65 | ||
|
|
955fa1f49f | ||
|
|
2544d0a084 | ||
|
|
8f0a12016a | ||
|
|
34ccd3524f | ||
|
|
563e99ecd3 | ||
|
|
70f14be50f | ||
|
|
8bd0da886e | ||
|
|
1fd326f980 | ||
|
|
d9cc664ea6 | ||
|
|
d65e241056 | ||
|
|
fe2b1f3d9f | ||
|
|
17c1f50fc3 | ||
|
|
5c1c022291 | ||
|
|
72435dee56 | ||
|
|
42f2eff02a | ||
|
|
0b68bef0be | ||
|
|
676e943d81 | ||
|
|
2b4307dee9 | ||
|
|
f3f936c196 | ||
|
|
eb900ddbe1 | ||
|
|
c2c323347a | ||
|
|
af6642b91b | ||
|
|
ffded8d82a | ||
|
|
00b57c7983 | ||
|
|
5d40a8992a | ||
|
|
e543625295 | ||
|
|
0b25ff649e | ||
|
|
63344ac62d | ||
|
|
2e60aafebf | ||
|
|
131e8f4aea | ||
|
|
53f01a39c9 | ||
|
|
50f4796144 | ||
|
|
5bdfae377f | ||
|
|
44970648ea | ||
|
|
3cd7f0b77d | ||
|
|
4d243f9b92 | ||
|
|
6b716f8f56 | ||
|
|
5b99219f29 | ||
|
|
f0dcb51915 | ||
|
|
3ff658a13b | ||
|
|
3a46513d4b | ||
|
|
ad0064993d | ||
|
|
458d4acd22 | ||
|
|
8ebc99175c | ||
|
|
9889cb2b69 | ||
|
|
191e530071 | ||
|
|
ae30d39b4d | ||
|
|
c354e46846 | ||
|
|
5da4e43e50 | ||
|
|
eae6f7760f | ||
|
|
00161212c8 | ||
|
|
8976b92842 | ||
|
|
c3a6a8fb17 | ||
|
|
391e5ca483 | ||
|
|
36654cb808 | ||
|
|
6d16e087d9 | ||
|
|
fe90e5aa8f | ||
|
|
3c22cd8ef4 | ||
|
|
5429b8568e | ||
|
|
0eccaf9a21 | ||
|
|
be0950c1ec | ||
|
|
6ecd150f75 | ||
|
|
02fb37189b | ||
|
|
8fe2536996 | ||
|
|
4b9e156c5d | ||
|
|
9265e1ffec | ||
|
|
d11735b04c | ||
|
|
d33b700477 | ||
|
|
97d75c2cb9 | ||
|
|
287115f4c3 | ||
|
|
63a221212b | ||
|
|
6a916fd0e1 | ||
|
|
220691d61d | ||
|
|
5cafc4bcbd | ||
|
|
b3a78dc2e6 | ||
|
|
bebc6eabe5 | ||
|
|
26dc6a4ac2 | ||
|
|
ff2626723a | ||
|
|
72bb897269 | ||
|
|
f46387a226 | ||
|
|
a4cbbccb2a | ||
|
|
3e1a008399 | ||
|
|
7e70a8c1de | ||
|
|
8efee04a10 | ||
|
|
a35099f949 | ||
|
|
8be02f9ca1 | ||
|
|
3c25a4c08c | ||
|
|
5ade0cad8b | ||
|
|
0fa6ffc439 | ||
|
|
c2ed296178 | ||
|
|
febd12b871 | ||
|
|
0a06e256b7 | ||
|
|
f295f60bea | ||
|
|
4a8f787519 | ||
|
|
d85b869934 | ||
|
|
35130f0736 | ||
|
|
1feff9709c | ||
|
|
1010f53a84 | ||
|
|
f7a526f488 | ||
|
|
245eb89b85 | ||
|
|
99de9d0bfa | ||
|
|
98698ba89a | ||
|
|
19d1e3829d | ||
|
|
612586ed1f | ||
|
|
2609e43f29 | ||
|
|
c5cd4f534c | ||
|
|
6e10ca5dd2 | ||
|
|
0fff1c3534 | ||
|
|
42271b1b89 | ||
|
|
f270b50972 | ||
|
|
ab4b6be9d7 | ||
|
|
c45ee0230f | ||
|
|
d210f2f2e7 | ||
|
|
13d78d6b49 | ||
|
|
34a71042c6 | ||
|
|
47ecf7d035 | ||
|
|
bbe8c52778 | ||
|
|
2011421e9d | ||
|
|
ce55952ca9 | ||
|
|
f93482e815 | ||
|
|
761ac6a730 | ||
|
|
11d3a343e5 | ||
|
|
9666bf836e | ||
|
|
a6d3b09796 | ||
|
|
bb0036fdab | ||
|
|
8dc0f30a49 | ||
|
|
1aed08f460 | ||
|
|
afccf6f06d | ||
|
|
5cd351a46f | ||
|
|
51f257e894 | ||
|
|
ac06892bb4 | ||
|
|
fd8473cb52 | ||
|
|
3f40257f89 | ||
|
|
14509adff2 | ||
|
|
c472537ecf | ||
|
|
d40fce741a | ||
|
|
944e8f8353 | ||
|
|
7d972a50f2 | ||
|
|
92e7be34e3 | ||
|
|
f5dba929a5 | ||
|
|
64d2885233 | ||
|
|
4ce65ae7a7 | ||
|
|
07bf95f838 | ||
|
|
3469d5dc4f | ||
|
|
8c0f942ae1 | ||
|
|
b8aa74f212 | ||
|
|
2a7c6681ad | ||
|
|
324a9eba91 | ||
|
|
fb1ed22c6c | ||
|
|
00c8409e31 | ||
|
|
1b43c22940 | ||
|
|
efddb36164 | ||
|
|
385e1c1047 | ||
|
|
3cc181a2e5 | ||
|
|
bcc1be675f | ||
|
|
d1be5742ba | ||
|
|
1b27e331da | ||
|
|
c1f7bf75c1 | ||
|
|
382ec011eb | ||
|
|
8a3ddd8596 | ||
|
|
738a199b4c | ||
|
|
40a1af5302 | ||
|
|
be5dba7eea | ||
|
|
eb15f73e59 | ||
|
|
2f7b485b8f | ||
|
|
3a885c893a | ||
|
|
6c4901a826 | ||
|
|
91c1c91950 | ||
|
|
1091ac7e7d | ||
|
|
0c0bd001e5 | ||
|
|
256994e1f8 | ||
|
|
f6fb859531 | ||
|
|
7deb2006c3 | ||
|
|
2aea24ffad | ||
|
|
b5aae0b58d | ||
|
|
d436825a45 | ||
|
|
5276cb6bc8 | ||
|
|
f863733dd3 | ||
|
|
2ccd4968a4 | ||
|
|
c233433243 | ||
|
|
6861f463b3 | ||
|
|
ac065f0225 | ||
|
|
b4b33c94dd | ||
|
|
c8753230a4 | ||
|
|
dbf569e29e | ||
|
|
27205e3119 | ||
|
|
f2fdef8361 | ||
|
|
d4dd5ab46a | ||
|
|
902da8cc4f | ||
|
|
c0a5e4f203 | ||
|
|
55ff9dbe80 | ||
|
|
bd99108e8e | ||
|
|
0c042b4078 | ||
|
|
743bbcb846 | ||
|
|
4ef2f0211a | ||
|
|
86f765a01f | ||
|
|
9e8b8313e0 | ||
|
|
f4a8115b00 | ||
|
|
a93bd422d3 | ||
|
|
c1598b7376 | ||
|
|
bc403adb46 | ||
|
|
1941275f93 | ||
|
|
6ae9bbe0c5 | ||
|
|
2c70388a9e | ||
|
|
e0815de2ad | ||
|
|
02e058370e | ||
|
|
8a7b795d37 | ||
|
|
d24ca796ad | ||
|
|
af2c61fd96 | ||
|
|
5a934c071a | ||
|
|
bfe4237430 | ||
|
|
84c60a5fdf | ||
|
|
cfc7210ac8 | ||
|
|
57ac951192 | ||
|
|
36fee03d5e | ||
|
|
14b747e0a4 | ||
|
|
c113b2e765 | ||
|
|
560e65da37 | ||
|
|
861cf7d842 | ||
|
|
dd23ed09ad | ||
|
|
64897b9c91 | ||
|
|
0dc8c687f2 | ||
|
|
c65d29d1a7 | ||
|
|
6aa895b679 | ||
|
|
dae451e6fa | ||
|
|
3c1f056d2a | ||
|
|
663e0a6693 | ||
|
|
53f98df8f3 | ||
|
|
6616f728f6 | ||
|
|
1c1e8a942b | ||
|
|
9721d99918 | ||
|
|
69c21cbb11 | ||
|
|
abefc56750 | ||
|
|
a4a1685224 | ||
|
|
21fb225726 | ||
|
|
75ab890707 | ||
|
|
2ded8363ad | ||
|
|
c0a8a386a5 | ||
|
|
7af081ea99 | ||
|
|
8800cb4580 | ||
|
|
b658f20a30 | ||
|
|
c3e52f32f9 | ||
|
|
ab73d808fe | ||
|
|
e110460793 | ||
|
|
c0ee451ca1 | ||
|
|
67dca97d1d | ||
|
|
fd0ca76255 | ||
|
|
149e53af76 | ||
|
|
f3e7952e51 | ||
|
|
0f77cf9e0c | ||
|
|
66eb09fb63 | ||
|
|
b7f2c7d487 | ||
|
|
bed3f62536 | ||
|
|
e8eb7e1e1f | ||
|
|
1409d6fb5e | ||
|
|
a2cbd9229a | ||
|
|
bf50a110c7 | ||
|
|
467c9d36cf | ||
|
|
6ddd17c769 | ||
|
|
732f2c1963 | ||
|
|
e7144eb674 | ||
|
|
b5489b7c81 | ||
|
|
4d5332cadf | ||
|
|
5261f61110 | ||
|
|
8c7d6da5d5 | ||
|
|
6d91728a74 | ||
|
|
9b32fc95eb | ||
|
|
6bc1d87753 | ||
|
|
a5fc62b920 | ||
|
|
49f6010905 | ||
|
|
1c27f567ee | ||
|
|
7684b2bf98 | ||
|
|
8886bcdb73 | ||
|
|
437bdb92be | ||
|
|
6943659dc9 | ||
|
|
35f934e197 | ||
|
|
aca0469bd0 | ||
|
|
11f2e7291e | ||
|
|
7a985dd843 | ||
|
|
e09ea36055 | ||
|
|
ad3a087091 | ||
|
|
d379b4d53b | ||
|
|
7a99d57274 | ||
|
|
85bc0ed5fc | ||
|
|
f4dda6e7ce | ||
|
|
5c8dfabf7c | ||
|
|
20d03f3228 | ||
|
|
268137bb97 | ||
|
|
f87463780c | ||
|
|
6b2ced63c7 | ||
|
|
d6fbe55360 | ||
|
|
0feeb94fc7 | ||
|
|
c9c509ec51 | ||
|
|
f7ff457a3b | ||
|
|
72127aaa0b | ||
|
|
b8854a3b45 | ||
|
|
e6e7001857 | ||
|
|
6d4601fe66 | ||
|
|
8ecb01ec21 | ||
|
|
14ccb41015 | ||
|
|
348cde54e7 | ||
|
|
430bd3f141 | ||
|
|
b36f419e86 | ||
|
|
ed1d03db71 | ||
|
|
5d837571c0 | ||
|
|
f23532d7f4 | ||
|
|
0280294a8f | ||
|
|
fdd1418236 | ||
|
|
bae47434b1 | ||
|
|
3c98cfc136 | ||
|
|
17e5bc7b73 | ||
|
|
6092655099 | ||
|
|
721cca669f | ||
|
|
c62ff0939d | ||
|
|
ecf050c2d9 | ||
|
|
069137b02a | ||
|
|
f053cad66c | ||
|
|
4be3042cb0 | ||
|
|
593235d683 | ||
|
|
7bd8ddbba2 | ||
|
|
14232b1d93 | ||
|
|
ff4887a3c8 | ||
|
|
0289aacd4f | ||
|
|
99c13eaec0 | ||
|
|
1a713dfa27 | ||
|
|
7e84b51ee0 | ||
|
|
86fa442d0e | ||
|
|
93b88f1a16 | ||
|
|
2041d4fffb | ||
|
|
83c3e7c725 | ||
|
|
a34331891a | ||
|
|
56dd83740c | ||
|
|
debe50c3f4 | ||
|
|
d9985c786c | ||
|
|
ed910947d4 | ||
|
|
c4c5eace25 | ||
|
|
c6bf646fe8 | ||
|
|
ab2bb7686e | ||
|
|
12b53c38b9 | ||
|
|
ca31c8b199 | ||
|
|
1b331ce090 | ||
|
|
edd55e7798 | ||
|
|
9f4da84701 | ||
|
|
0097360396 | ||
|
|
b5cef05941 | ||
|
|
ddab9224b2 | ||
|
|
a5693c6e96 | ||
|
|
8dd345b192 | ||
|
|
130ab886ee | ||
|
|
c0e9f2b95a | ||
|
|
011972872e | ||
|
|
ef5720a3f0 | ||
|
|
ec30af2844 | ||
|
|
e08aeca28c | ||
|
|
ad7892ebce | ||
|
|
cbc7e1b6be | ||
|
|
da7d959b9a | ||
|
|
ada57ebcd0 | ||
|
|
1993ad10eb | ||
|
|
d305caf910 | ||
|
|
a25a504a59 | ||
|
|
250c61279b | ||
|
|
1e18af2d1a | ||
|
|
eb1fd4fd88 | ||
|
|
e0c8b6b3c0 | ||
|
|
9071cd4813 | ||
|
|
9bf650c700 | ||
|
|
49e3b03885 | ||
|
|
31e996ac3f | ||
|
|
d1c447e97b | ||
|
|
8a34c0a80f | ||
|
|
2f626ea474 | ||
|
|
0a76eebca7 | ||
|
|
0e9e695251 | ||
|
|
b86df7a8e3 | ||
|
|
5b25e02e26 | ||
|
|
0e6f14bb7c | ||
|
|
57b9954d9c | ||
|
|
249dd7b8b8 | ||
|
|
b31d7b4451 | ||
|
|
8dea3389ee | ||
|
|
e7f9e8e7f7 | ||
|
|
a53a86ee7a | ||
|
|
598b6f0598 | ||
|
|
b245945c4a | ||
|
|
301a8e319a | ||
|
|
820abfd059 | ||
|
|
1d42420a36 | ||
|
|
1b4bdb5142 | ||
|
|
f030a3f1fb | ||
|
|
8f79779ca7 | ||
|
|
c5111bb359 | ||
|
|
42814eac7d | ||
|
|
74d0013acc | ||
|
|
88e4850c4d | ||
|
|
87f9b1cf92 | ||
|
|
fe1187d7b7 | ||
|
|
a35b36d6df | ||
|
|
a04982fd96 | ||
|
|
658679f89e | ||
|
|
f90fc665f8 | ||
|
|
0c8130af41 | ||
|
|
e4c5968459 | ||
|
|
a148cd41a4 | ||
|
|
f8a049759d | ||
|
|
4b4225e14f | ||
|
|
3b0c5d0b6a | ||
|
|
3b750ddd5a | ||
|
|
6383d000a9 | ||
|
|
a48d67bdc7 | ||
|
|
2f92e72858 | ||
|
|
d2c85ada1b | ||
|
|
55b95c52d6 | ||
|
|
52362c4675 | ||
|
|
8da0552541 | ||
|
|
7ce0def995 | ||
|
|
48285e8a2d | ||
|
|
21dcc41d31 | ||
|
|
625d268373 | ||
|
|
681782ed20 | ||
|
|
1baa85b649 | ||
|
|
72137a2811 | ||
|
|
0734ce7ae3 | ||
|
|
2dc06c28e3 | ||
|
|
5848669552 | ||
|
|
c0376d238a | ||
|
|
979b773c3c | ||
|
|
3195a449ca | ||
|
|
d7483f07e3 | ||
|
|
9c1b802997 | ||
|
|
bb3a10b0fc |
79
.flowconfig
@@ -2,22 +2,30 @@
|
||||
; We fork some components by platform
|
||||
.*/*[.]android.js
|
||||
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; Ignore unexpected extra "@providesModule"
|
||||
.*/node_modules/.*/node_modules/fbjs/.*
|
||||
node_modules/react-native/Libraries/react-native/React.js
|
||||
|
||||
; Ignore duplicate module providers
|
||||
; For RN Apps installed via npm, "Libraries" folder is inside
|
||||
; "node_modules/react-native" but in the source repo it is in the root
|
||||
.*/Libraries/react-native/React.js
|
||||
node_modules/react-native/Libraries/react-native/React.js
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/Libraries/Utilities/LoadingView.js
|
||||
|
||||
; Ignore polyfills
|
||||
.*/Libraries/polyfills/.*
|
||||
node_modules/react-native/Libraries/polyfills/.*
|
||||
|
||||
; Ignore metro
|
||||
.*/node_modules/metro/.*
|
||||
; These should not be required directly
|
||||
; require from fbjs/lib instead: require('fbjs/lib/warning')
|
||||
node_modules/warning/.*
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/Libraries/Utilities/HMRLoadingView.js
|
||||
|
||||
[untyped]
|
||||
.*/node_modules/@react-native-community/cli/.*/.*
|
||||
|
||||
; Ignore packages in node_modules which we (i.e. the jitsi-meet project) have
|
||||
; seen to cause errors and we have chosen not to fix.
|
||||
@@ -40,6 +48,18 @@ emoji=true
|
||||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
; We (i.e. the jitsi-meet project) are using the haste module system on Web as
|
||||
; well, not only on React Native. Unfortunately, Flow does not support .web.js
|
||||
; by default. Override Flow's defaults to include .web.js as well. Technically,
|
||||
; we have .native.js as well so the choice of .web.js may lead to errors.
|
||||
; Practically though, it is a potential future problem that we do not have at
|
||||
; the time of this writing.
|
||||
module.file_ext=.web.js
|
||||
; Flow's defaults:
|
||||
module.file_ext=.js
|
||||
module.file_ext=.json
|
||||
module.file_ext=.ios.js
|
||||
|
||||
module.system=haste
|
||||
module.system.haste.use_name_reducers=true
|
||||
# get basename
|
||||
@@ -52,8 +72,11 @@ module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
|
||||
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
|
||||
module.system.haste.paths.blacklist=.*/__tests__/.*
|
||||
module.system.haste.paths.blacklist=.*/__mocks__/.*
|
||||
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
|
||||
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
|
||||
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/RNTester/.*
|
||||
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/IntegrationTests/.*
|
||||
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/react-native/react-native-implementation.js
|
||||
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
@@ -64,22 +87,32 @@ suppress_type=$FlowFixMe
|
||||
suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
; We (i.e. the jitsi-meet project) are using the haste module system on Web as
|
||||
; well, not only on React Native. Unfortunately, Flow does not support .web.js
|
||||
; by default. Override Flow's defaults to include .web.js as well. Technically,
|
||||
; we have .native.js as well so the choice of .web.js may lead to errors.
|
||||
; Practically though, it is a potential future problem that we do not have at
|
||||
; the time of this writing.
|
||||
module.file_ext=.web.js
|
||||
; Flow's defaults:
|
||||
module.file_ext=.js
|
||||
module.file_ext=.jsx
|
||||
module.file_ext=.json
|
||||
[lints]
|
||||
sketchy-null-number=warn
|
||||
sketchy-null-mixed=warn
|
||||
sketchy-number=warn
|
||||
untyped-type-import=warn
|
||||
nonstrict-import=warn
|
||||
deprecated-type=warn
|
||||
unsafe-getters-setters=warn
|
||||
inexact-spread=warn
|
||||
unnecessary-invariant=warn
|
||||
signature-verification-failure=warn
|
||||
deprecated-utility=error
|
||||
|
||||
[strict]
|
||||
deprecated-type
|
||||
nonstrict-import
|
||||
sketchy-null
|
||||
unclear-type
|
||||
unsafe-getters-setters
|
||||
untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.92.0
|
||||
^0.104.0
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/4-security-issues.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Security issues
|
||||
about: Please email security@jitsi.org
|
||||
|
||||
---
|
||||
|
||||
We take security very seriously and develop all Jitsi projects to be secure and safe.
|
||||
|
||||
If you find (or simply suspect) a security issue in any of the Jitsi projects, please send us an email to security@jitsi.org.
|
||||
|
||||
We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.
|
||||
6
.gitignore
vendored
@@ -61,14 +61,8 @@ buck-out/
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/
|
||||
|
||||
*/fastlane/report.xml
|
||||
*/fastlane/Preview.html
|
||||
*/fastlane/screenshots
|
||||
|
||||
# Build artifacts
|
||||
*.jsbundle
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
osx_image: xcode10.2
|
||||
osx_image: xcode11.1
|
||||
language: objective-c
|
||||
script:
|
||||
- "./ios/travis-ci/build-ipa.sh"
|
||||
after_script:
|
||||
- sleep 10
|
||||
|
||||
14
Makefile
@@ -3,6 +3,7 @@ CLEANCSS = ./node_modules/.bin/cleancss
|
||||
DEPLOY_DIR = libs
|
||||
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
|
||||
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
|
||||
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
|
||||
NODE_SASS = ./node_modules/.bin/node-sass
|
||||
NPM = npm
|
||||
OUTPUT_DIR = .
|
||||
@@ -20,7 +21,7 @@ compile:
|
||||
clean:
|
||||
rm -fr $(BUILD_DIR)
|
||||
|
||||
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
|
||||
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
|
||||
|
||||
deploy-init:
|
||||
rm -fr $(DEPLOY_DIR)
|
||||
@@ -45,6 +46,10 @@ deploy-appbundle:
|
||||
$(OUTPUT_DIR)/analytics-ga.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.map \
|
||||
$(BUILD_DIR)/video-blur-effect.min.js \
|
||||
$(BUILD_DIR)/video-blur-effect.min.map \
|
||||
$(BUILD_DIR)/rnnoise-processor.min.js \
|
||||
$(BUILD_DIR)/rnnoise-processor.min.map \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-lib-jitsi-meet:
|
||||
@@ -61,6 +66,11 @@ deploy-libflac:
|
||||
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js.mem \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-rnnoise-binary:
|
||||
cp \
|
||||
$(RNNOISE_WASM_DIR)/rnnoise.wasm \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-css:
|
||||
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
|
||||
$(CLEANCSS) $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
|
||||
@@ -69,7 +79,7 @@ deploy-css:
|
||||
deploy-local:
|
||||
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
|
||||
|
||||
dev: deploy-init deploy-css deploy-lib-jitsi-meet deploy-libflac
|
||||
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac
|
||||
$(WEBPACK_DEV_SERVER)
|
||||
|
||||
source-package:
|
||||
|
||||
101
README.md
@@ -27,86 +27,28 @@ You can download Debian/Ubuntu binaries:
|
||||
You can download source archives (produced by ```make source-package```):
|
||||
* [source builds](https://download.jitsi.org/jitsi-meet/src/)
|
||||
|
||||
You can get our mobile versions from here:
|
||||
### Mobile apps
|
||||
|
||||
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
|
||||
|
||||
[<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet)
|
||||
|
||||
* [Android (F-Droid)](https://f-droid.org/en/packages/org.jitsi.meet/)
|
||||
|
||||
[<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/en/packages/org.jitsi.meet/)
|
||||
|
||||
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
|
||||
|
||||
## Building the sources
|
||||
[<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
|
||||
|
||||
Node.js >= 10 and npm >= 6 are required.
|
||||
You can also sign up for our open beta testing here:
|
||||
|
||||
On Debian/Ubuntu systems, the required packages can be installed with:
|
||||
```
|
||||
sudo apt-get install npm nodejs
|
||||
cd jitsi-meet
|
||||
npm install
|
||||
```
|
||||
* [Android](https://play.google.com/apps/testing/org.jitsi.meet)
|
||||
* [iOS](https://testflight.apple.com/join/isy6ja7S)
|
||||
|
||||
To build the Jitsi Meet application, just type
|
||||
```
|
||||
make
|
||||
```
|
||||
## Development
|
||||
|
||||
### Working with the library sources (lib-jitsi-meet)
|
||||
|
||||
By default the library is build from its git repository sources. The default dependency path in package.json is :
|
||||
```json
|
||||
"lib-jitsi-meet": "jitsi/lib-jitsi-meet",
|
||||
```
|
||||
|
||||
To work with local copy you must change the path to:
|
||||
```json
|
||||
"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':
|
||||
```
|
||||
npm install lib-jitsi-meet --force && make
|
||||
```
|
||||
|
||||
Or if you are making only changes to the library:
|
||||
```
|
||||
npm install lib-jitsi-meet --force && make deploy-lib-jitsi-meet
|
||||
```
|
||||
|
||||
Alternative way is to use [npm link](https://docs.npmjs.com/cli/link).
|
||||
It allows to link `lib-jitsi-meet` dependency to local source in few steps:
|
||||
|
||||
```bash
|
||||
cd lib-jitsi-meet
|
||||
|
||||
#### create global symlink for lib-jitsi-meet package
|
||||
npm link
|
||||
|
||||
cd ../jitsi-meet
|
||||
|
||||
#### create symlink from the local node_modules folder to the global lib-jitsi-meet symlink
|
||||
npm link lib-jitsi-meet
|
||||
```
|
||||
|
||||
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
|
||||
cd jitsi-meet
|
||||
npm unlink lib-jitsi-meet
|
||||
npm install
|
||||
```
|
||||
### Running with webpack-dev-server for development
|
||||
|
||||
Use it at the CLI, type
|
||||
```
|
||||
make dev
|
||||
```
|
||||
|
||||
By default the backend deployment used is `beta.meet.jit.si`. You can point the Jitsi-Meet app at a different backend by using a proxy server. To do this, set the WEBPACK_DEV_SERVER_PROXY_TARGET variable:
|
||||
```
|
||||
export WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com
|
||||
make dev
|
||||
```
|
||||
|
||||
The app should be running at https://localhost:8080/
|
||||
For web development see [here](doc/development.md), and for mobile see [here](doc/mobile.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -118,7 +60,8 @@ see our [guidelines for contributing](CONTRIBUTING.md).
|
||||
Jitsi Meet provides a very flexible way of embedding in external applications by using the [Jitsi Meet API](doc/api.md).
|
||||
|
||||
## Security
|
||||
WebRTC does not provide a way of conducting multi-party conversations with end-to-end encryption.
|
||||
|
||||
WebRTC does not (yet) 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.
|
||||
|
||||
@@ -130,9 +73,13 @@ Jitsi Meet in terms of security.
|
||||
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
|
||||
at [8x8](https://8x8.com).
|
||||
|
||||
## Mobile app
|
||||
Jitsi Meet is also available as a React Native app for Android and iOS.
|
||||
Instructions on how to build it can be found [here](doc/mobile.md).
|
||||
## Security issues
|
||||
|
||||
We take security very seriously and develop all Jitsi projects to be secure and safe.
|
||||
|
||||
If you find (or simply suspect) a security issue in any of the Jitsi projects, please send us an email to security@jitsi.org.
|
||||
|
||||
**We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.**
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
|
||||
@@ -207,24 +207,6 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI
|
||||
|
||||
</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 `FragmentActivity`
|
||||
|
||||
6
android/app/.classpath
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
||||
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||
<classpathentry kind="output" path="bin/default"/>
|
||||
</classpath>
|
||||
23
android/app/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>app</name>
|
||||
<comment>Project app created by Buildship.</comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
2
android/app/.settings/org.eclipse.buildship.core.prefs
Normal file
@@ -0,0 +1,2 @@
|
||||
connection.project.dir=..
|
||||
eclipse.preferences.version=1
|
||||
@@ -32,14 +32,23 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('debug.keystore')
|
||||
storePassword 'android'
|
||||
keyAlias 'androiddebugkey'
|
||||
keyPassword 'android'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
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 {
|
||||
// Uncomment the following line for singing a test release build.
|
||||
//signingConfig signingConfigs.debug
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.pro'
|
||||
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
|
||||
@@ -70,8 +79,10 @@ repositories {
|
||||
|
||||
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 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-5'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||
@@ -85,18 +96,6 @@ dependencies {
|
||||
}
|
||||
|
||||
implementation project(':sdk')
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
|
||||
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
|
||||
|
||||
// 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 {
|
||||
|
||||
BIN
android/app/debug.keystore
Normal file
@@ -1,5 +0,0 @@
|
||||
-include proguard-rules.pro
|
||||
|
||||
# Disabling obfuscation is useful if you collect stack traces from production crashes
|
||||
# (unless you are using a system that supports de-obfuscate the stack traces).
|
||||
-dontobfuscate
|
||||
26
android/app/proguard-rules.pro
vendored
@@ -9,13 +9,6 @@
|
||||
|
||||
# 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 *;
|
||||
#}
|
||||
|
||||
# React Native
|
||||
|
||||
# Keep our interfaces so they can be used by other ProGuard rules.
|
||||
@@ -60,19 +53,9 @@
|
||||
-keep class sun.misc.Unsafe { *; }
|
||||
-dontwarn java.nio.file.*
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-keep class okio.** { *; }
|
||||
-dontwarn okio.**
|
||||
|
||||
# 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.** { *; }
|
||||
@@ -100,3 +83,10 @@
|
||||
-dontwarn javax.servlet.**
|
||||
|
||||
# ^^^ We added the above when we switched minifyEnabled on.
|
||||
|
||||
# Rule to avoid build errors related to SVGs.
|
||||
-keep public class com.horcrux.svg.** {*;}
|
||||
|
||||
# Hermes
|
||||
-keep class com.facebook.hermes.unicode.** { *; }
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:name=".MainApplication"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
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,6 +1,5 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
* Copyright @ 2017-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,14 +20,13 @@ 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 androidx.annotation.Nullable;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeet;
|
||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
|
||||
import org.jitsi.meet.sdk.JitsiMeetUserInfo;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
@@ -92,6 +90,7 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
= new JitsiMeetConferenceOptions.Builder()
|
||||
.setWelcomePageEnabled(true)
|
||||
.setServerURL(buildURL("https://meet.jit.si"))
|
||||
.setFeatureFlag("call-integration.enabled", false)
|
||||
.build();
|
||||
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 2.3 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#66A8DD</color>
|
||||
</resources>
|
||||
@@ -13,8 +13,8 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
classpath 'io.fabric.tools:gradle:1.27.0'
|
||||
classpath 'com.google.gms:google-services:4.3.3'
|
||||
classpath 'io.fabric.tools:gradle:1.28.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files.
|
||||
@@ -25,8 +25,7 @@ allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from
|
||||
// npm.
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from npm.
|
||||
maven { url "$rootDir/../node_modules/react-native/android" }
|
||||
}
|
||||
|
||||
@@ -74,6 +73,13 @@ allprojects {
|
||||
def versionQualifierNumber = (int)(((new Date().getTime()/1000) - 1546297200) / 10)
|
||||
|
||||
afterEvaluate { project ->
|
||||
if (project.plugins.hasPlugin('android') || project.plugins.hasPlugin('android-library')) {
|
||||
project.android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
}
|
||||
}
|
||||
|
||||
if (project.name.startsWith('react-native-')) {
|
||||
def npmManifest = project.file('../package.json')
|
||||
def json = new JsonSlurper().parseText(npmManifest.text)
|
||||
@@ -83,17 +89,6 @@ allprojects {
|
||||
|
||||
project.version = "${json.version}-jitsi-${versionQualifierNumber}"
|
||||
|
||||
project.android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
if (rootProject.ext.has('buildToolsVersion')) {
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
}
|
||||
}
|
||||
|
||||
task androidSourcesJar(type: Jar) {
|
||||
classifier = 'sources'
|
||||
from android.sourceSets.main.java.source
|
||||
@@ -164,58 +159,10 @@ ext {
|
||||
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 ?: "false").toBoolean()
|
||||
}
|
||||
|
||||
// If Android SDK is not installed, accept its license so that it
|
||||
// is automatically downloaded.
|
||||
afterEvaluate { project ->
|
||||
// Either the environment variable ANDROID_HOME or the property sdk.dir in
|
||||
// local.properties identifies where Android SDK is installed.
|
||||
def androidHome = System.env.ANDROID_HOME
|
||||
if (!androidHome) {
|
||||
// ANDROID_HOME is not set. Is sdk.dir set?
|
||||
def file = file("${project.rootDir}/local.properties")
|
||||
def props = new Properties()
|
||||
if (file.canRead()) {
|
||||
file.withInputStream {
|
||||
props.load(it)
|
||||
androidHome = props.'sdk.dir'
|
||||
}
|
||||
}
|
||||
if (!androidHome && (!file.exists() || file.canWrite())) {
|
||||
// Neither ANDROID_HOME nor sdk.dir is set. Set sdk.dir (because
|
||||
// environment variables cannot be set).
|
||||
props.'sdk.dir' = "${project.buildDir}/android-sdk".toString()
|
||||
file.withOutputStream {
|
||||
props.store(it, null)
|
||||
androidHome = props.'sdk.dir'
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the license is not accepted, accept it so that automatic downloading
|
||||
// kicks in.
|
||||
// The license hash can be taken from the accepted licenses, by doing this
|
||||
// on your local machine the file is
|
||||
// ${androidHome}/licenses/android-sdk-license
|
||||
if (androidHome) {
|
||||
def dir = file("${androidHome}/licenses")
|
||||
dir.mkdirs()
|
||||
def file = file("${dir.path}/android-sdk-license")
|
||||
if (!file.exists()) {
|
||||
file.withWriter {
|
||||
def hash = 'd56f5187479451eabf01fb78af6dfcb131a6481e'
|
||||
it.write(hash, 0, hash.length())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force the version of the Android build tools we have chosen on all
|
||||
// subprojects. The forcing was introduced for react-native and the third-party
|
||||
// modules that we utilize such as react-native-background-timer.
|
||||
|
||||
@@ -24,7 +24,7 @@ platform :android do
|
||||
|
||||
# Upload built artifact to the Closed Beta track
|
||||
upload_to_play_store(
|
||||
track: "Closed Beta",
|
||||
track: "beta",
|
||||
json_key: ENV["JITSI_JSON_KEY_FILE"],
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_images: true,
|
||||
|
||||
BIN
android/fastlane/screenshots/Feature-Graphic-1024x500-1-1.png
Normal file
|
After Width: | Height: | Size: 512 KiB |
BIN
android/fastlane/screenshots/Feature-Graphic-1024x500-1.png
Normal file
|
After Width: | Height: | Size: 396 KiB |
BIN
android/fastlane/screenshots/Feature-Graphic-1024x500-2.png
Normal file
|
After Width: | Height: | Size: 265 KiB |
BIN
android/fastlane/screenshots/Feature-Graphic-1024x500-3.png
Normal file
|
After Width: | Height: | Size: 279 KiB |
BIN
android/fastlane/screenshots/GroupCall.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
android/fastlane/screenshots/GroupCall_framed.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
android/fastlane/screenshots/More Menu.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
android/fastlane/screenshots/More Menu_framed.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
android/fastlane/screenshots/Nexus 9 Body.png
Normal file
|
After Width: | Height: | Size: 6.0 MiB |
BIN
android/fastlane/screenshots/Nexus-9-Landscape.png
Normal file
|
After Width: | Height: | Size: 4.0 MiB |
BIN
android/fastlane/screenshots/Nexus-9-Portrait.png
Normal file
|
After Width: | Height: | Size: 6.0 MiB |
BIN
android/fastlane/screenshots/Video-Call-1-1024x768.png
Normal file
|
After Width: | Height: | Size: 948 KiB |
BIN
android/fastlane/screenshots/Video-Call-1-1280x720.png
Normal file
|
After Width: | Height: | Size: 983 KiB |
BIN
android/fastlane/screenshots/Video-Call-2-1024x768.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
android/fastlane/screenshots/Video-Call-2-1280x720.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
android/fastlane/screenshots/WelcomePage-Calendar.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
android/fastlane/screenshots/WelcomePage-Calendar_framed.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
android/fastlane/screenshots/WelcomeScreen-1024x768.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
android/fastlane/screenshots/WelcomeScreen-1280x720.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
@@ -17,5 +17,8 @@
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
appVersion=19.2.0
|
||||
sdkVersion=2.2.0
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
appVersion=20.0.2
|
||||
sdkVersion=2.6.1
|
||||
|
||||
@@ -9,7 +9,8 @@ THE_MVN_REPO=${MVN_REPO:-${1:-$DEFAULT_MVN_REPO}}
|
||||
MVN_HTTP=0
|
||||
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
RN_VERSION=$(jq -r '.dependencies."react-native"' ${THIS_DIR}/../../package.json)
|
||||
RN_VERSION=$(jq -r '.version' ${THIS_DIR}/../../node_modules/react-native/package.json)
|
||||
HERMES_VERSION=$(jq -r '.dependencies."hermes-engine"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -c 2-)
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
if [[ $THE_MVN_REPO == http* ]]; then
|
||||
@@ -37,14 +38,22 @@ if [[ $MVN_HTTP == 1 ]]; then
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=react-native-${RN_VERSION}.pom || true
|
||||
popd
|
||||
# Push Hermes
|
||||
echo "Pushing Hermes ${HERMES_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/hermes-engine/android/
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-DrepositoryId=${MVN_REPO_ID} \
|
||||
-Dfile=hermes-release.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgroupId=com.facebook \
|
||||
-DartifactId=hermes \
|
||||
-Dversion=${HERMES_VERSION} \
|
||||
-DgeneratePom=true || 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
|
||||
# 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}
|
||||
@@ -57,6 +66,28 @@ else
|
||||
-DpomFile=react-native-${RN_VERSION}.pom
|
||||
popd
|
||||
fi
|
||||
|
||||
# Push Hermes, if necessary
|
||||
if [[ ! -d ${MVN_REPO}/com/facebook/hermes/${HERMES_VERSION} ]]; then
|
||||
echo "Pushing Hermes ${HERMES_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/hermes-engine/android/
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-Dfile=hermes-release.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgroupId=com.facebook \
|
||||
-DartifactId=hermes \
|
||||
-Dversion=${HERMES_VERSION} \
|
||||
-DgeneratePom=true
|
||||
popd
|
||||
fi
|
||||
|
||||
# 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
|
||||
fi
|
||||
|
||||
# Now build and publish the Jitsi Meet SDK and its dependencies
|
||||
|
||||
@@ -8,13 +8,14 @@ THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOUR
|
||||
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"
|
||||
|
||||
adb reverse tcp:8081 tcp:8081
|
||||
|
||||
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
|
||||
adb reverse tcp:8081 tcp:8081
|
||||
CMD="${THIS_DIR}/../../node_modules/react-native/scripts/launchPackager.command"
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
open -g "${CMD}" || echo "Can't start packager automatically"
|
||||
|
||||
6
android/sdk/.classpath
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
||||
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||
<classpathentry kind="output" path="bin/default"/>
|
||||
</classpath>
|
||||
23
android/sdk/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>sdk</name>
|
||||
<comment>Project sdk created by Buildship.</comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
2
android/sdk/.settings/org.eclipse.buildship.core.prefs
Normal file
@@ -0,0 +1,2 @@
|
||||
connection.project.dir=..
|
||||
eclipse.preferences.version=1
|
||||
@@ -1,3 +1,5 @@
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
@@ -31,37 +33,48 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst '**/libc++_shared.so'
|
||||
}
|
||||
}
|
||||
|
||||
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 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.fragment:fragment:1.2.0'
|
||||
|
||||
//noinspection GradleDynamicVersion
|
||||
api 'com.facebook.react:react-native:+'
|
||||
|
||||
// Hermes JS engine
|
||||
def hermesPath = "../../node_modules/hermes-engine/android/"
|
||||
debugImplementation files(hermesPath + "hermes-debug.aar")
|
||||
releaseImplementation files(hermesPath + "hermes-release.aar")
|
||||
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.squareup.duktape:duktape-android:1.3.0'
|
||||
|
||||
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'
|
||||
exclude group: 'androidx'
|
||||
}
|
||||
}
|
||||
|
||||
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-community_netinfo')
|
||||
implementation project(':react-native-immersive')
|
||||
implementation project(':react-native-keep-awake')
|
||||
implementation project(':react-native-linear-gradient')
|
||||
implementation project(':react-native-sound')
|
||||
implementation project(':react-native-vector-icons')
|
||||
implementation project(':react-native-svg')
|
||||
implementation project(':react-native-webrtc')
|
||||
implementation project(':react-native-webview')
|
||||
|
||||
@@ -139,19 +152,14 @@ android.libraryVariants.all { def variant ->
|
||||
mergeAssetsTask.doLast {
|
||||
def assetsDir = mergeAssetsTask.outputDir
|
||||
|
||||
// Bundle fonts
|
||||
//
|
||||
copy {
|
||||
from("${projectDir}/../../fonts/jitsi.ttf")
|
||||
into("${assetsDir}/fonts")
|
||||
}
|
||||
|
||||
// Bundle sounds
|
||||
//
|
||||
copy {
|
||||
from("${projectDir}/../../sounds/incomingMessage.wav")
|
||||
from("${projectDir}/../../sounds/joined.wav")
|
||||
from("${projectDir}/../../sounds/left.wav")
|
||||
from("${projectDir}/../../sounds/liveStreamingOn.mp3")
|
||||
from("${projectDir}/../../sounds/liveStreamingOff.mp3")
|
||||
from("${projectDir}/../../sounds/outgoingRinging.wav")
|
||||
from("${projectDir}/../../sounds/outgoingStart.wav")
|
||||
from("${projectDir}/../../sounds/recordingOn.mp3")
|
||||
@@ -216,6 +224,14 @@ publishing {
|
||||
dependency.appendNode('artifactId', artifactId)
|
||||
dependency.appendNode('version', it.moduleVersion)
|
||||
}
|
||||
|
||||
// Add Hermes dependency.
|
||||
def hermesPkg = new File("$rootDir/../node_modules/hermes-engine/package.json")
|
||||
def hermesVersion = new JsonSlurper().parseText(hermesPkg.text).version
|
||||
def hermesDependency = dependencies.appendNode('dependency')
|
||||
hermesDependency.appendNode('groupId', "com.facebook")
|
||||
hermesDependency.appendNode('artifactId', "hermes")
|
||||
hermesDependency.appendNode('version', hermesVersion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
@@ -24,6 +28,7 @@ import com.facebook.react.bridge.ReadableMap;
|
||||
import com.amplitude.api.Amplitude;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@@ -48,8 +53,16 @@ class AmplitudeModule
|
||||
* @param apiKey The API_KEY of the Amplitude project.
|
||||
*/
|
||||
@ReactMethod
|
||||
@SuppressLint("HardwareIds")
|
||||
public void init(String instanceName, String apiKey) {
|
||||
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
|
||||
|
||||
// Set the device ID to something consistent.
|
||||
String android_id
|
||||
= Settings.Secure.getString(getReactApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
if (!TextUtils.isEmpty(android_id)) {
|
||||
Amplitude.getInstance(instanceName).setDeviceId(android_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +103,7 @@ class AmplitudeModule
|
||||
JSONObject eventProps = new JSONObject(eventPropsString);
|
||||
Amplitude.getInstance(instanceName).logEvent(eventType, eventProps);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
JitsiMeetLogger.e(e, "Error logging event");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright @ 2017-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.telecom.CallAudioState;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
|
||||
/**
|
||||
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
|
||||
* Android versions >= O when ConnectionService is enabled.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class AudioDeviceHandlerConnectionService implements
|
||||
AudioModeModule.AudioDeviceHandlerInterface,
|
||||
RNConnectionService.CallAudioStateListener {
|
||||
|
||||
private final static String TAG = AudioDeviceHandlerConnectionService.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* {@link AudioManager} instance used to interact with the Android audio subsystem.
|
||||
*/
|
||||
private AudioManager audioManager;
|
||||
|
||||
/**
|
||||
* Reference to the main {@code AudioModeModule}.
|
||||
*/
|
||||
private AudioModeModule module;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private static int audioDeviceToRouteInt(String audioDevice) {
|
||||
if (audioDevice == null) {
|
||||
return CallAudioState.ROUTE_SPEAKER;
|
||||
}
|
||||
switch (audioDevice) {
|
||||
case AudioModeModule.DEVICE_BLUETOOTH:
|
||||
return CallAudioState.ROUTE_BLUETOOTH;
|
||||
case AudioModeModule.DEVICE_EARPIECE:
|
||||
return CallAudioState.ROUTE_EARPIECE;
|
||||
case AudioModeModule.DEVICE_HEADPHONES:
|
||||
return CallAudioState.ROUTE_WIRED_HEADSET;
|
||||
case AudioModeModule.DEVICE_SPEAKER:
|
||||
return CallAudioState.ROUTE_SPEAKER;
|
||||
default:
|
||||
JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
|
||||
return CallAudioState.ROUTE_SPEAKER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private static Set<String> routesToDeviceNames(int supportedRouteMask) {
|
||||
Set<String> devices = new HashSet<>();
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_EARPIECE) == CallAudioState.ROUTE_EARPIECE) {
|
||||
devices.add(AudioModeModule.DEVICE_EARPIECE);
|
||||
}
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_BLUETOOTH) == CallAudioState.ROUTE_BLUETOOTH) {
|
||||
devices.add(AudioModeModule.DEVICE_BLUETOOTH);
|
||||
}
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_SPEAKER) == CallAudioState.ROUTE_SPEAKER) {
|
||||
devices.add(AudioModeModule.DEVICE_SPEAKER);
|
||||
}
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_WIRED_HEADSET) == CallAudioState.ROUTE_WIRED_HEADSET) {
|
||||
devices.add(AudioModeModule.DEVICE_HEADPHONES);
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used 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 {@code availableDevices} on each update.
|
||||
*/
|
||||
private int supportedRouteMask = -1;
|
||||
|
||||
public AudioDeviceHandlerConnectionService(AudioManager audioManager) {
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallAudioStateChange(final CallAudioState state) {
|
||||
module.runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean audioRouteChanged
|
||||
= audioDeviceToRouteInt(module.getSelectedDevice()) != state.getRoute();
|
||||
int newSupportedRoutes = state.getSupportedRouteMask();
|
||||
boolean audioDevicesChanged = supportedRouteMask != newSupportedRoutes;
|
||||
if (audioDevicesChanged) {
|
||||
supportedRouteMask = newSupportedRoutes;
|
||||
Set<String> devices = routesToDeviceNames(supportedRouteMask);
|
||||
module.replaceDevices(devices);
|
||||
JitsiMeetLogger.i(TAG + " Available audio devices: " + devices.toString());
|
||||
}
|
||||
|
||||
if (audioRouteChanged || audioDevicesChanged) {
|
||||
module.resetSelectedDevice();
|
||||
module.updateAudioRoute();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(AudioModeModule audioModeModule) {
|
||||
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
|
||||
|
||||
module = audioModeModule;
|
||||
|
||||
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (rcs != null) {
|
||||
rcs.setCallAudioStateListener(this);
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (rcs != null) {
|
||||
rcs.setCallAudioStateListener(null);
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
|
||||
}
|
||||
}
|
||||
|
||||
public void setAudioRoute(String audioDevice) {
|
||||
int newAudioRoute = audioDeviceToRouteInt(audioDevice);
|
||||
|
||||
RNConnectionService.setAudioRoute(newAudioRoute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setMode(int mode) {
|
||||
if (mode != AudioModeModule.DEFAULT) {
|
||||
// This shouldn't be needed when using ConnectionService, but some devices have been
|
||||
// observed not doing it.
|
||||
try {
|
||||
audioManager.setMicrophoneMute(false);
|
||||
} catch (Throwable tr) {
|
||||
JitsiMeetLogger.w(tr, TAG + " Failed to unmute the microphone");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright @ 2017-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
|
||||
/**
|
||||
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
|
||||
* all post-M Android versions. This handler can be used on any Android versions >= M, but by
|
||||
* default it's only used on versions < O, since versions >= O use ConnectionService, but it
|
||||
* can be disabled.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
class AudioDeviceHandlerGeneric implements
|
||||
AudioModeModule.AudioDeviceHandlerInterface,
|
||||
AudioManager.OnAudioFocusChangeListener {
|
||||
|
||||
private final static String TAG = AudioDeviceHandlerGeneric.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Reference to the main {@code AudioModeModule}.
|
||||
*/
|
||||
private AudioModeModule module;
|
||||
|
||||
/**
|
||||
* Constant defining a USB headset. Only available on API level >= 26.
|
||||
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
|
||||
*/
|
||||
private static final int TYPE_USB_HEADSET = 22;
|
||||
|
||||
/**
|
||||
* Indicator that we have lost audio focus.
|
||||
*/
|
||||
private boolean audioFocusLost = false;
|
||||
|
||||
/**
|
||||
* {@link AudioManager} instance used to interact with the Android audio
|
||||
* subsystem.
|
||||
*/
|
||||
private AudioManager audioManager;
|
||||
|
||||
/**
|
||||
* {@link Runnable} for running audio device detection the main thread.
|
||||
* This is only used on Android >= M.
|
||||
*/
|
||||
private final Runnable onAudioDeviceChangeRunner = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Set<String> devices = new HashSet<>();
|
||||
AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
|
||||
|
||||
for (AudioDeviceInfo info: deviceInfos) {
|
||||
switch (info.getType()) {
|
||||
case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
|
||||
devices.add(AudioModeModule.DEVICE_BLUETOOTH);
|
||||
break;
|
||||
case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
|
||||
devices.add(AudioModeModule.DEVICE_EARPIECE);
|
||||
break;
|
||||
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
|
||||
devices.add(AudioModeModule.DEVICE_SPEAKER);
|
||||
break;
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
|
||||
case TYPE_USB_HEADSET:
|
||||
devices.add(AudioModeModule.DEVICE_HEADPHONES);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
module.replaceDevices(devices);
|
||||
|
||||
JitsiMeetLogger.i(TAG + " Available audio devices: " + devices.toString());
|
||||
|
||||
module.updateAudioRoute();
|
||||
}
|
||||
};
|
||||
|
||||
private final android.media.AudioDeviceCallback audioDeviceCallback =
|
||||
new android.media.AudioDeviceCallback() {
|
||||
@Override
|
||||
public void onAudioDevicesAdded(
|
||||
AudioDeviceInfo[] addedDevices) {
|
||||
JitsiMeetLogger.d(TAG + " Audio devices added");
|
||||
onAudioDeviceChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDevicesRemoved(
|
||||
AudioDeviceInfo[] removedDevices) {
|
||||
JitsiMeetLogger.d(TAG + " Audio devices removed");
|
||||
onAudioDeviceChange();
|
||||
}
|
||||
};
|
||||
|
||||
public AudioDeviceHandlerGeneric(AudioManager audioManager) {
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to trigger an audio route update when devices change. It
|
||||
* makes sure the operation is performed on the audio thread.
|
||||
*/
|
||||
private void onAudioDeviceChange() {
|
||||
module.runInAudioThread(onAudioDeviceChangeRunner);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
|
||||
* when the audio focus of the system is updated.
|
||||
*
|
||||
* @param focusChange - The type of focus change.
|
||||
*/
|
||||
@Override
|
||||
public void onAudioFocusChange(final int focusChange) {
|
||||
module.runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
switch (focusChange) {
|
||||
case AudioManager.AUDIOFOCUS_GAIN: {
|
||||
JitsiMeetLogger.d(TAG + " Audio focus gained");
|
||||
// Some other application potentially stole our audio focus
|
||||
// temporarily. Restore our mode.
|
||||
if (audioFocusLost) {
|
||||
module.updateAudioRoute();
|
||||
}
|
||||
audioFocusLost = false;
|
||||
break;
|
||||
}
|
||||
case AudioManager.AUDIOFOCUS_LOSS:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
|
||||
JitsiMeetLogger.d(TAG + " Audio focus lost");
|
||||
audioFocusLost = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to set the output route to a Bluetooth device.
|
||||
*
|
||||
* @param enabled true if Bluetooth should use used, false otherwise.
|
||||
*/
|
||||
private void setBluetoothAudioRoute(boolean enabled) {
|
||||
if (enabled) {
|
||||
audioManager.startBluetoothSco();
|
||||
audioManager.setBluetoothScoOn(true);
|
||||
} else {
|
||||
audioManager.setBluetoothScoOn(false);
|
||||
audioManager.stopBluetoothSco();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(AudioModeModule audioModeModule) {
|
||||
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
|
||||
|
||||
module = audioModeModule;
|
||||
|
||||
// Setup runtime device change detection.
|
||||
audioManager.registerAudioDeviceCallback(audioDeviceCallback, null);
|
||||
|
||||
// Do an initial detection.
|
||||
onAudioDeviceChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
audioManager.unregisterAudioDeviceCallback(audioDeviceCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAudioRoute(String device) {
|
||||
// Turn speaker on / off
|
||||
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
|
||||
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setMode(int mode) {
|
||||
if (mode == AudioModeModule.DEFAULT) {
|
||||
audioFocusLost = false;
|
||||
audioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
audioManager.abandonAudioFocus(this);
|
||||
audioManager.setSpeakerphoneOn(false);
|
||||
setBluetoothAudioRoute(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
audioManager.setMicrophoneMute(false);
|
||||
|
||||
if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
|
||||
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
JitsiMeetLogger.w(TAG + " Audio focus request failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright @ 2017-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.AudioManager;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
|
||||
/**
|
||||
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
|
||||
* legacy (pre-M) Android versions.
|
||||
*/
|
||||
class AudioDeviceHandlerLegacy implements
|
||||
AudioModeModule.AudioDeviceHandlerInterface,
|
||||
AudioManager.OnAudioFocusChangeListener,
|
||||
BluetoothHeadsetMonitor.Listener {
|
||||
|
||||
private final static String TAG = AudioDeviceHandlerLegacy.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Reference to the main {@code AudioModeModule}.
|
||||
*/
|
||||
private AudioModeModule module;
|
||||
|
||||
/**
|
||||
* Indicator that we have lost audio focus.
|
||||
*/
|
||||
private boolean audioFocusLost = false;
|
||||
|
||||
/**
|
||||
* {@link AudioManager} instance used to interact with the Android audio
|
||||
* subsystem.
|
||||
*/
|
||||
private AudioManager audioManager;
|
||||
|
||||
/**
|
||||
* {@link BluetoothHeadsetMonitor} for detecting Bluetooth device changes in
|
||||
* old (< M) Android versions.
|
||||
*/
|
||||
private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
|
||||
|
||||
public AudioDeviceHandlerLegacy(AudioManager audioManager) {
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to trigger an audio route update when Bluetooth devices are
|
||||
* connected / disconnected.
|
||||
*/
|
||||
@Override
|
||||
public void onBluetoothDeviceChange(final boolean deviceAvailable) {
|
||||
module.runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (deviceAvailable) {
|
||||
module.addDevice(AudioModeModule.DEVICE_BLUETOOTH);
|
||||
} else {
|
||||
module.removeDevice(AudioModeModule.DEVICE_BLUETOOTH);
|
||||
}
|
||||
|
||||
module.updateAudioRoute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to trigger an audio route update when a headset is plugged
|
||||
* or unplugged.
|
||||
*/
|
||||
private void onHeadsetDeviceChange() {
|
||||
module.runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// XXX: isWiredHeadsetOn is not deprecated when used just for
|
||||
// knowing if there is a wired headset connected, regardless of
|
||||
// audio being routed to it.
|
||||
//noinspection deprecation
|
||||
if (audioManager.isWiredHeadsetOn()) {
|
||||
module.addDevice(AudioModeModule.DEVICE_HEADPHONES);
|
||||
} else {
|
||||
module.removeDevice(AudioModeModule.DEVICE_HEADPHONES);
|
||||
}
|
||||
|
||||
module.updateAudioRoute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
|
||||
* when the audio focus of the system is updated.
|
||||
*
|
||||
* @param focusChange - The type of focus change.
|
||||
*/
|
||||
@Override
|
||||
public void onAudioFocusChange(final int focusChange) {
|
||||
module.runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
switch (focusChange) {
|
||||
case AudioManager.AUDIOFOCUS_GAIN: {
|
||||
JitsiMeetLogger.d(TAG + " Audio focus gained");
|
||||
// Some other application potentially stole our audio focus
|
||||
// temporarily. Restore our mode.
|
||||
if (audioFocusLost) {
|
||||
module.updateAudioRoute();
|
||||
}
|
||||
audioFocusLost = false;
|
||||
break;
|
||||
}
|
||||
case AudioManager.AUDIOFOCUS_LOSS:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
|
||||
JitsiMeetLogger.d(TAG + " Audio focus lost");
|
||||
audioFocusLost = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to set the output route to a Bluetooth device.
|
||||
*
|
||||
* @param enabled true if Bluetooth should use used, false otherwise.
|
||||
*/
|
||||
private void setBluetoothAudioRoute(boolean enabled) {
|
||||
if (enabled) {
|
||||
audioManager.startBluetoothSco();
|
||||
audioManager.setBluetoothScoOn(true);
|
||||
} else {
|
||||
audioManager.setBluetoothScoOn(false);
|
||||
audioManager.stopBluetoothSco();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(AudioModeModule audioModeModule) {
|
||||
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
|
||||
|
||||
module = audioModeModule;
|
||||
Context context = module.getContext();
|
||||
|
||||
// Setup runtime device change detection.
|
||||
//
|
||||
|
||||
// Detect changes in wired headset connections.
|
||||
IntentFilter wiredHeadSetFilter = new IntentFilter(AudioManager.ACTION_HEADSET_PLUG);
|
||||
BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
JitsiMeetLogger.d(TAG + " Wired headset added / removed");
|
||||
onHeadsetDeviceChange();
|
||||
}
|
||||
};
|
||||
context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
|
||||
|
||||
// Detect Bluetooth device changes.
|
||||
bluetoothHeadsetMonitor = new BluetoothHeadsetMonitor(context, this);
|
||||
|
||||
// On Android < M, detect if we have an earpiece.
|
||||
PackageManager pm = context.getPackageManager();
|
||||
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
|
||||
module.addDevice(AudioModeModule.DEVICE_EARPIECE);
|
||||
}
|
||||
|
||||
// Always assume there is a speaker.
|
||||
module.addDevice(AudioModeModule.DEVICE_SPEAKER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
bluetoothHeadsetMonitor.stop();
|
||||
bluetoothHeadsetMonitor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAudioRoute(String device) {
|
||||
// Turn speaker on / off
|
||||
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
|
||||
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setMode(int mode) {
|
||||
if (mode == AudioModeModule.DEFAULT) {
|
||||
audioFocusLost = false;
|
||||
audioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
audioManager.abandonAudioFocus(this);
|
||||
audioManager.setSpeakerphoneOn(false);
|
||||
setBluetoothAudioRoute(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
audioManager.setMicrophoneMute(false);
|
||||
|
||||
if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
|
||||
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
JitsiMeetLogger.w(TAG + " Audio focus request failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
* Copyright @ 2017-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,17 +16,9 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
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;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
@@ -37,6 +29,8 @@ import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@@ -60,9 +54,7 @@ import java.util.concurrent.Executors;
|
||||
* {@code AudioModeModule.DEFAULT} mode should be used.
|
||||
*/
|
||||
@ReactModule(name = AudioModeModule.NAME)
|
||||
class AudioModeModule extends ReactContextBaseJavaModule
|
||||
implements AudioManager.OnAudioFocusChangeListener {
|
||||
|
||||
class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
public static final String NAME = "AudioMode";
|
||||
|
||||
/**
|
||||
@@ -74,174 +66,39 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
* - VIDEO_CALL: Used for video calls. It will use the speaker by default,
|
||||
* unless a wired or Bluetooth headset is connected.
|
||||
*/
|
||||
private static final int DEFAULT = 0;
|
||||
private static final int AUDIO_CALL = 1;
|
||||
private static final int VIDEO_CALL = 2;
|
||||
|
||||
/**
|
||||
* Constant defining the action for plugging in a headset. This is used on
|
||||
* our device detection system for API < 23.
|
||||
*/
|
||||
private static final String ACTION_HEADSET_PLUG
|
||||
= (Build.VERSION.SDK_INT >= 21)
|
||||
? AudioManager.ACTION_HEADSET_PLUG
|
||||
: Intent.ACTION_HEADSET_PLUG;
|
||||
|
||||
/**
|
||||
* Constant defining a USB headset. Only available on API level >= 26.
|
||||
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
|
||||
*/
|
||||
private static final int TYPE_USB_HEADSET = 22;
|
||||
static final int DEFAULT = 0;
|
||||
static final int AUDIO_CALL = 1;
|
||||
static final int VIDEO_CALL = 2;
|
||||
|
||||
/**
|
||||
* The {@code Log} tag {@code AudioModeModule} is to log messages with.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private boolean audioFocusLost = false;
|
||||
private static final boolean supportsConnectionService = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
private static boolean useConnectionService_ = supportsConnectionService;
|
||||
|
||||
static boolean useConnectionService() {
|
||||
return supportsConnectionService && useConnectionService_;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AudioManager} instance used to interact with the Android audio
|
||||
* subsystem.
|
||||
*/
|
||||
private final AudioManager audioManager;
|
||||
private AudioManager audioManager;
|
||||
|
||||
/**
|
||||
* {@link BluetoothHeadsetMonitor} for detecting Bluetooth device changes in
|
||||
* old (< M) Android versions.
|
||||
*/
|
||||
private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
|
||||
private AudioDeviceHandlerInterface audioDeviceHandler;
|
||||
|
||||
/**
|
||||
* {@link ExecutorService} for running all audio operations on a dedicated
|
||||
* thread.
|
||||
*/
|
||||
private static final ExecutorService executor
|
||||
= Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
* {@link Runnable} for running audio device detection the main thread.
|
||||
* This is only used on Android >= M.
|
||||
*/
|
||||
private final Runnable onAudioDeviceChangeRunner = new Runnable() {
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void run() {
|
||||
Set<String> devices = new HashSet<>();
|
||||
AudioDeviceInfo[] deviceInfos
|
||||
= audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
|
||||
|
||||
for (AudioDeviceInfo info: deviceInfos) {
|
||||
switch (info.getType()) {
|
||||
case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
|
||||
devices.add(DEVICE_BLUETOOTH);
|
||||
break;
|
||||
case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
|
||||
devices.add(DEVICE_EARPIECE);
|
||||
break;
|
||||
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
|
||||
devices.add(DEVICE_SPEAKER);
|
||||
break;
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
|
||||
case TYPE_USB_HEADSET:
|
||||
devices.add(DEVICE_HEADPHONES);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
availableDevices = devices;
|
||||
Log.d(TAG, "Available audio devices: " +
|
||||
availableDevices.toString());
|
||||
|
||||
// Reset user selection
|
||||
userSelectedDevice = null;
|
||||
|
||||
if (mode != -1) {
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* {@link Runnable} for running update operation on the main thread.
|
||||
*/
|
||||
private final Runnable updateAudioRouteRunner
|
||||
= new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mode != -1) {
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
}
|
||||
};
|
||||
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
* Audio mode currently in use.
|
||||
@@ -251,10 +108,15 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
/**
|
||||
* Audio device types.
|
||||
*/
|
||||
private static final String DEVICE_BLUETOOTH = "BLUETOOTH";
|
||||
private static final String DEVICE_EARPIECE = "EARPIECE";
|
||||
private static final String DEVICE_HEADPHONES = "HEADPHONES";
|
||||
private static final String DEVICE_SPEAKER = "SPEAKER";
|
||||
static final String DEVICE_BLUETOOTH = "BLUETOOTH";
|
||||
static final String DEVICE_EARPIECE = "EARPIECE";
|
||||
static final String DEVICE_HEADPHONES = "HEADPHONES";
|
||||
static final String DEVICE_SPEAKER = "SPEAKER";
|
||||
|
||||
/**
|
||||
* Device change event.
|
||||
*/
|
||||
private static final String DEVICE_CHANGE_EVENT = "org.jitsi.meet:features/audio-mode#devices-update";
|
||||
|
||||
/**
|
||||
* List of currently available audio devices.
|
||||
@@ -266,15 +128,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
*/
|
||||
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.
|
||||
@@ -291,30 +144,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
public AudioModeModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
audioManager
|
||||
= (AudioManager)
|
||||
reactContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
// Starting Oreo the ConnectionImpl from ConnectionService is used to
|
||||
// detect the available devices.
|
||||
if (!useConnectionService()) {
|
||||
// Setup runtime device change detection.
|
||||
setupAudioRouteChangeDetection();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
audioManager = (AudioManager)reactContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -327,6 +157,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
public Map<String, Object> getConstants() {
|
||||
Map<String, Object> constants = new HashMap<>();
|
||||
|
||||
constants.put("DEVICE_CHANGE_EVENT", DEVICE_CHANGE_EVENT);
|
||||
constants.put("AUDIO_CALL", AUDIO_CALL);
|
||||
constants.put("DEFAULT", DEFAULT);
|
||||
constants.put("VIDEO_CALL", VIDEO_CALL);
|
||||
@@ -335,31 +166,26 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of available audio device categories, i.e. 'bluetooth',
|
||||
* 'earpiece ', 'speaker', 'headphones'.
|
||||
*
|
||||
* @param promise a {@link Promise} which will be resolved with an object
|
||||
* containing a 'devices' key with a list of devices, plus a
|
||||
* 'selected' key with the selected one.
|
||||
* Notifies JS land that the devices list has changed.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getAudioDevices(final Promise promise) {
|
||||
private void notifyDevicesChanged() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putString("selected", selectedDevice);
|
||||
WritableArray devices = Arguments.createArray();
|
||||
WritableArray data = Arguments.createArray();
|
||||
final boolean hasHeadphones = availableDevices.contains(DEVICE_HEADPHONES);
|
||||
for (String device : availableDevices) {
|
||||
if (mode == VIDEO_CALL && device.equals(DEVICE_EARPIECE)) {
|
||||
// Skip earpiece when in video call mode.
|
||||
if (hasHeadphones && device.equals(DEVICE_EARPIECE)) {
|
||||
// Skip earpiece when headphones are plugged in.
|
||||
continue;
|
||||
}
|
||||
devices.pushString(device);
|
||||
WritableMap deviceInfo = Arguments.createMap();
|
||||
deviceInfo.putString("type", device);
|
||||
deviceInfo.putBoolean("selected", device.equals(selectedDevice));
|
||||
data.pushMap(deviceInfo);
|
||||
}
|
||||
map.putArray("devices", devices);
|
||||
|
||||
promise.resolve(map);
|
||||
ReactInstanceManagerHolder.emitEvent(DEVICE_CHANGE_EVENT, data);
|
||||
JitsiMeetLogger.i(TAG + " Updating audio device list");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -375,133 +201,41 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to trigger an audio route update when devices change. It
|
||||
* makes sure the operation is performed on the main thread.
|
||||
*
|
||||
* Only used on Android >= M.
|
||||
*/
|
||||
void onAudioDeviceChange() {
|
||||
runInAudioThread(onAudioDeviceChangeRunner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to trigger an audio route update when Bluetooth devices are
|
||||
* connected / disconnected.
|
||||
*
|
||||
* Only used on Android < M. Runs on the main thread.
|
||||
*/
|
||||
void onBluetoothDeviceChange() {
|
||||
if (bluetoothHeadsetMonitor != null && bluetoothHeadsetMonitor.isHeadsetAvailable()) {
|
||||
availableDevices.add(DEVICE_BLUETOOTH);
|
||||
} else {
|
||||
availableDevices.remove(DEVICE_BLUETOOTH);
|
||||
}
|
||||
|
||||
if (mode != -1) {
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to trigger an audio route update when a headset is plugged
|
||||
* or unplugged.
|
||||
*
|
||||
* Only used on Android < M.
|
||||
*/
|
||||
void onHeadsetDeviceChange() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// XXX: isWiredHeadsetOn is not deprecated when used just for
|
||||
// knowing if there is a wired headset connected, regardless of
|
||||
// audio being routed to it.
|
||||
//noinspection deprecation
|
||||
if (audioManager.isWiredHeadsetOn()) {
|
||||
availableDevices.add(DEVICE_HEADPHONES);
|
||||
} else {
|
||||
availableDevices.remove(DEVICE_HEADPHONES);
|
||||
}
|
||||
|
||||
if (mode != -1) {
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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.
|
||||
*
|
||||
* @param focusChange - The type of focus change.
|
||||
* Initializes the audio device handler module. This function is called *after* all Catalyst
|
||||
* modules have been created, and that's why we use it, because {@link AudioDeviceHandlerConnectionService}
|
||||
* needs access to another Catalyst module, so doing this in the constructor would be too early.
|
||||
*/
|
||||
@Override
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
switch (focusChange) {
|
||||
case AudioManager.AUDIOFOCUS_GAIN: {
|
||||
Log.d(TAG, "Audio focus gained");
|
||||
// Some other application potentially stole our audio focus
|
||||
// temporarily. Restore our mode.
|
||||
if (audioFocusLost) {
|
||||
updateAudioRoute(mode);
|
||||
public void initialize() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setAudioDeviceHandler();
|
||||
}
|
||||
audioFocusLost = false;
|
||||
break;
|
||||
}
|
||||
case AudioManager.AUDIOFOCUS_LOSS:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
|
||||
Log.d(TAG, "Audio focus lost");
|
||||
audioFocusLost = true;
|
||||
break;
|
||||
});
|
||||
}
|
||||
|
||||
private void setAudioDeviceHandler() {
|
||||
if (audioDeviceHandler != null) {
|
||||
audioDeviceHandler.stop();
|
||||
}
|
||||
|
||||
if (useConnectionService()) {
|
||||
audioDeviceHandler = new AudioDeviceHandlerConnectionService(audioManager);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
audioDeviceHandler = new AudioDeviceHandlerGeneric(audioManager);
|
||||
} else {
|
||||
audioDeviceHandler = new AudioDeviceHandlerLegacy(audioManager);
|
||||
}
|
||||
|
||||
audioDeviceHandler.start(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to run operations on a dedicated thread.
|
||||
* @param runnable
|
||||
*/
|
||||
public void runInAudioThread(Runnable runnable) {
|
||||
void runInAudioThread(Runnable runnable) {
|
||||
executor.execute(runnable);
|
||||
}
|
||||
|
||||
@@ -516,13 +250,13 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
@Override
|
||||
public void run() {
|
||||
if (!availableDevices.contains(device)) {
|
||||
Log.d(TAG, "Audio device not available: " + device);
|
||||
JitsiMeetLogger.w(TAG + " Audio device not available: " + device);
|
||||
userSelectedDevice = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode != -1) {
|
||||
Log.d(TAG, "User selected device set to: " + device);
|
||||
JitsiMeetLogger.i(TAG + " User selected device set to: " + device);
|
||||
userSelectedDevice = device;
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
@@ -530,46 +264,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param enabled true if Bluetooth should use used, false otherwise.
|
||||
*/
|
||||
private void setBluetoothAudioRoute(boolean enabled) {
|
||||
if (enabled) {
|
||||
audioManager.startBluetoothSco();
|
||||
audioManager.setBluetoothScoOn(true);
|
||||
} else {
|
||||
audioManager.setBluetoothScoOn(false);
|
||||
audioManager.stopBluetoothSco();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method to set the current audio mode.
|
||||
*
|
||||
@@ -584,7 +278,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean success;
|
||||
@@ -593,80 +287,33 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
success = updateAudioRoute(mode);
|
||||
} catch (Throwable e) {
|
||||
success = false;
|
||||
Log.e(
|
||||
TAG,
|
||||
"Failed to update audio route for mode: " + mode,
|
||||
e);
|
||||
JitsiMeetLogger.e(e, TAG + " Failed to update audio route for mode: " + mode);
|
||||
}
|
||||
if (success) {
|
||||
AudioModeModule.this.mode = mode;
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
promise.reject(
|
||||
"setMode",
|
||||
"Failed to set audio mode to " + mode);
|
||||
promise.reject("setMode", "Failed to set audio mode to " + mode);
|
||||
}
|
||||
}
|
||||
};
|
||||
runInAudioThread(r);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the audio route change detection mechanism. We use the
|
||||
* {@link android.media.AudioDeviceCallback} on 23 >= Android API < 26.
|
||||
* Sets whether ConnectionService should be used (if available) for setting the audio mode
|
||||
* or not.
|
||||
*
|
||||
* @param use Boolean indicator of where it should be used or not.
|
||||
*/
|
||||
private void setupAudioRouteChangeDetection() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
setupAudioRouteChangeDetectionM();
|
||||
} else {
|
||||
setupAudioRouteChangeDetectionPreM();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio route change detection mechanism for 23 >= Android API < 26.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private void setupAudioRouteChangeDetectionM() {
|
||||
android.media.AudioDeviceCallback audioDeviceCallback =
|
||||
new android.media.AudioDeviceCallback() {
|
||||
@Override
|
||||
public void onAudioDevicesAdded(
|
||||
AudioDeviceInfo[] addedDevices) {
|
||||
Log.d(TAG, "Audio devices added");
|
||||
onAudioDeviceChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDevicesRemoved(
|
||||
AudioDeviceInfo[] removedDevices) {
|
||||
Log.d(TAG, "Audio devices removed");
|
||||
onAudioDeviceChange();
|
||||
}
|
||||
};
|
||||
|
||||
audioManager.registerAudioDeviceCallback(audioDeviceCallback, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio route change detection mechanism for Android API < 23.
|
||||
*/
|
||||
private void setupAudioRouteChangeDetectionPreM() {
|
||||
Context context = getReactApplicationContext();
|
||||
|
||||
// Detect changes in wired headset connections.
|
||||
IntentFilter wiredHeadSetFilter = new IntentFilter(ACTION_HEADSET_PLUG);
|
||||
BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
|
||||
@ReactMethod
|
||||
public void setUseConnectionService(final boolean use) {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "Wired headset added / removed");
|
||||
onHeadsetDeviceChange();
|
||||
public void run() {
|
||||
useConnectionService_ = use;
|
||||
setAudioDeviceHandler();
|
||||
}
|
||||
};
|
||||
context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
|
||||
|
||||
// Detect Bluetooth device changes.
|
||||
bluetoothHeadsetMonitor = new BluetoothHeadsetMonitor(this, context);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -677,38 +324,21 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
private boolean updateAudioRoute(int mode) {
|
||||
Log.d(TAG, "Update audio route for mode: " + mode);
|
||||
JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
|
||||
|
||||
if (!audioDeviceHandler.setMode(mode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode == DEFAULT) {
|
||||
if (!useConnectionService()) {
|
||||
audioFocusLost = false;
|
||||
audioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
audioManager.abandonAudioFocus(this);
|
||||
audioManager.setSpeakerphoneOn(false);
|
||||
setBluetoothAudioRoute(false);
|
||||
}
|
||||
selectedDevice = null;
|
||||
userSelectedDevice = null;
|
||||
|
||||
notifyDevicesChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!useConnectionService()) {
|
||||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
audioManager.setMicrophoneMute(false);
|
||||
|
||||
if (audioManager.requestAudioFocus(
|
||||
this,
|
||||
AudioManager.STREAM_VOICE_CALL,
|
||||
AudioManager.AUDIOFOCUS_GAIN)
|
||||
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
Log.d(TAG, "Audio focus request failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
|
||||
boolean earpieceAvailable = availableDevices.contains(DEVICE_EARPIECE);
|
||||
boolean headsetAvailable = availableDevices.contains(DEVICE_HEADPHONES);
|
||||
|
||||
// Pick the desired device based on what's available and the mode.
|
||||
@@ -717,15 +347,12 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
audioDevice = DEVICE_BLUETOOTH;
|
||||
} else if (headsetAvailable) {
|
||||
audioDevice = DEVICE_HEADPHONES;
|
||||
} else if (mode == AUDIO_CALL && earpieceAvailable) {
|
||||
audioDevice = DEVICE_EARPIECE;
|
||||
} else {
|
||||
audioDevice = DEVICE_SPEAKER;
|
||||
}
|
||||
|
||||
// Consider the user's selection
|
||||
if (userSelectedDevice != null
|
||||
&& availableDevices.contains(userSelectedDevice)) {
|
||||
if (userSelectedDevice != null && availableDevices.contains(userSelectedDevice)) {
|
||||
audioDevice = userSelectedDevice;
|
||||
}
|
||||
|
||||
@@ -736,14 +363,107 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
|
||||
selectedDevice = audioDevice;
|
||||
Log.d(TAG, "Selected audio device: " + audioDevice);
|
||||
JitsiMeetLogger.i(TAG + " Selected audio device: " + audioDevice);
|
||||
|
||||
if (useConnectionService()) {
|
||||
setAudioRoute(audioDevice);
|
||||
} else {
|
||||
setAudioRoutePreO(audioDevice);
|
||||
}
|
||||
audioDeviceHandler.setAudioRoute(audioDevice);
|
||||
|
||||
notifyDevicesChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently selected audio device.
|
||||
*
|
||||
* @return The selected audio device.
|
||||
*/
|
||||
String getSelectedDevice() {
|
||||
return selectedDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the current device selection.
|
||||
*/
|
||||
void resetSelectedDevice() {
|
||||
selectedDevice = null;
|
||||
userSelectedDevice = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new device to the list of available devices.
|
||||
*
|
||||
* @param device The new device.
|
||||
*/
|
||||
void addDevice(String device) {
|
||||
availableDevices.add(device);
|
||||
resetSelectedDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a device from the list of available devices.
|
||||
*
|
||||
* @param device The old device to the removed.
|
||||
*/
|
||||
void removeDevice(String device) {
|
||||
availableDevices.remove(device);
|
||||
resetSelectedDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current list of available devices with a new one.
|
||||
*
|
||||
* @param devices The new devices list.
|
||||
*/
|
||||
void replaceDevices(Set<String> devices) {
|
||||
availableDevices = devices;
|
||||
resetSelectedDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-sets the current audio route. Needed when devices changes have happened.
|
||||
*/
|
||||
void updateAudioRoute() {
|
||||
if (mode != -1) {
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed on the legacy handler...
|
||||
*
|
||||
* @return Context for the application.
|
||||
*/
|
||||
Context getContext() {
|
||||
return getReactApplicationContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for the modules implementing the actual audio device management.
|
||||
*/
|
||||
interface AudioDeviceHandlerInterface {
|
||||
/**
|
||||
* Start detecting audio device changes.
|
||||
* @param audioModeModule Reference to the main {@link AudioModeModule}.
|
||||
*/
|
||||
void start(AudioModeModule audioModeModule);
|
||||
|
||||
/**
|
||||
* Stop audio device detection.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Set the appropriate route for the given audio device.
|
||||
*
|
||||
* @param device Audio device for which the route must be set.
|
||||
*/
|
||||
void setAudioRoute(String device);
|
||||
|
||||
/**
|
||||
* Set the given audio mode.
|
||||
*
|
||||
* @param mode The new audio mode to be used.
|
||||
* @return Whether the operation was successful or not.
|
||||
*/
|
||||
boolean setMode(int mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ package org.jitsi.meet.sdk;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
@@ -111,8 +111,7 @@ public abstract class BaseReactView<ListenerT>
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
ReactInstanceManagerHolder.initReactInstanceManager(
|
||||
((Activity) context).getApplication());
|
||||
ReactInstanceManagerHolder.initReactInstanceManager((Activity)context);
|
||||
|
||||
// Hook this BaseReactView into ExternalAPI.
|
||||
externalAPIScope = UUID.randomUUID().toString();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
* Copyright @ 2017-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,8 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
/**
|
||||
* Helper class to detect and handle Bluetooth device changes. It monitors
|
||||
@@ -32,68 +33,43 @@ import android.util.Log;
|
||||
* about device changes when this occurs.
|
||||
*/
|
||||
class BluetoothHeadsetMonitor {
|
||||
/**
|
||||
* {@link AudioModeModule} where this monitor reports.
|
||||
*/
|
||||
private final AudioModeModule audioModeModule;
|
||||
private final static String TAG = BluetoothHeadsetMonitor.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* The {@link Context} in which {@link #audioModeModule} executes.
|
||||
* The {@link Context} in which this module executes.
|
||||
*/
|
||||
private final Context context;
|
||||
|
||||
/**
|
||||
* Reference to the {@link BluetoothAdapter} object, used to access Bluetooth functionality.
|
||||
*/
|
||||
private BluetoothAdapter adapter;
|
||||
|
||||
/**
|
||||
* Reference to a proxy object which allows us to query connected devices.
|
||||
*/
|
||||
private BluetoothHeadset headset;
|
||||
|
||||
/**
|
||||
* Flag indicating if there are any Bluetooth headset devices currently
|
||||
* available.
|
||||
* receiver registered for receiving Bluetooth connection state changes.
|
||||
*/
|
||||
private boolean headsetAvailable = false;
|
||||
private BroadcastReceiver receiver;
|
||||
|
||||
/**
|
||||
* Helper for running Bluetooth operations on the main thread.
|
||||
* Listener for receiving Bluetooth device change events.
|
||||
*/
|
||||
private final Runnable updateDevicesRunnable
|
||||
= new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
headsetAvailable
|
||||
= (headset != null)
|
||||
&& !headset.getConnectedDevices().isEmpty();
|
||||
audioModeModule.onBluetoothDeviceChange();
|
||||
}
|
||||
};
|
||||
private Listener listener;
|
||||
|
||||
public BluetoothHeadsetMonitor(
|
||||
AudioModeModule audioModeModule,
|
||||
Context context) {
|
||||
this.audioModeModule = audioModeModule;
|
||||
public BluetoothHeadsetMonitor(Context context, Listener listener) {
|
||||
this.context = context;
|
||||
|
||||
AudioManager audioManager
|
||||
= (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
if (!audioManager.isBluetoothScoAvailableOffCall()) {
|
||||
Log.w(AudioModeModule.TAG, "Bluetooth SCO is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (getBluetoothHeadsetProfileProxy()) {
|
||||
registerBluetoothReceiver();
|
||||
|
||||
// Initial detection.
|
||||
updateDevices();
|
||||
}
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private boolean getBluetoothHeadsetProfileProxy() {
|
||||
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
||||
adapter = BluetoothAdapter.getDefaultAdapter();
|
||||
|
||||
if (adapter == null) {
|
||||
Log.w(AudioModeModule.TAG, "Device doesn't support Bluetooth");
|
||||
JitsiMeetLogger.w(TAG + " Device doesn't support Bluetooth");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -103,9 +79,7 @@ class BluetoothHeadsetMonitor {
|
||||
BluetoothProfile.ServiceListener listener
|
||||
= new BluetoothProfile.ServiceListener() {
|
||||
@Override
|
||||
public void onServiceConnected(
|
||||
int profile,
|
||||
BluetoothProfile proxy) {
|
||||
public void onServiceConnected(int profile, BluetoothProfile proxy) {
|
||||
if (profile == BluetoothProfile.HEADSET) {
|
||||
headset = (BluetoothHeadset) proxy;
|
||||
updateDevices();
|
||||
@@ -119,21 +93,7 @@ class BluetoothHeadsetMonitor {
|
||||
}
|
||||
};
|
||||
|
||||
return
|
||||
adapter.getProfileProxy(
|
||||
context,
|
||||
listener,
|
||||
BluetoothProfile.HEADSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current headset availability.
|
||||
*
|
||||
* @return {@code true} if there is a Bluetooth headset connected;
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
public boolean isHeadsetAvailable() {
|
||||
return headsetAvailable;
|
||||
return adapter.getProfileProxy(context, listener, BluetoothProfile.HEADSET);
|
||||
}
|
||||
|
||||
private void onBluetoothReceiverReceive(Context context, Intent intent) {
|
||||
@@ -148,9 +108,7 @@ class BluetoothHeadsetMonitor {
|
||||
switch (state) {
|
||||
case BluetoothHeadset.STATE_CONNECTED:
|
||||
case BluetoothHeadset.STATE_DISCONNECTED:
|
||||
Log.d(
|
||||
AudioModeModule.TAG,
|
||||
"BT headset connection state changed: " + state);
|
||||
JitsiMeetLogger.d(TAG + " BT headset connection state changed: " + state);
|
||||
updateDevices();
|
||||
break;
|
||||
}
|
||||
@@ -158,15 +116,12 @@ class BluetoothHeadsetMonitor {
|
||||
// XXX: This action will be fired when the connection established
|
||||
// with a Bluetooth headset (called a SCO connection) changes state.
|
||||
// When the SCO connection is active we route audio to it.
|
||||
int state
|
||||
= intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -99);
|
||||
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -99);
|
||||
|
||||
switch (state) {
|
||||
case AudioManager.SCO_AUDIO_STATE_CONNECTED:
|
||||
case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
|
||||
Log.d(
|
||||
AudioModeModule.TAG,
|
||||
"BT SCO connection state changed: " + state);
|
||||
JitsiMeetLogger.d(TAG + " BT SCO connection state changed: " + state);
|
||||
updateDevices();
|
||||
break;
|
||||
}
|
||||
@@ -174,24 +129,63 @@ class BluetoothHeadsetMonitor {
|
||||
}
|
||||
|
||||
private void registerBluetoothReceiver() {
|
||||
BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
onBluetoothReceiverReceive(context, intent);
|
||||
}
|
||||
};
|
||||
IntentFilter filter = new IntentFilter();
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
|
||||
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
|
||||
|
||||
context.registerReceiver(receiver, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if there are new devices connected / disconnected and fires the
|
||||
* {@link AudioModeModule#onAudioDeviceChange()} callback.
|
||||
* {@link Listener} registered event.
|
||||
*/
|
||||
private void updateDevices() {
|
||||
audioModeModule.runInAudioThread(updateDevicesRunnable);
|
||||
boolean headsetAvailable = (headset != null) && !headset.getConnectedDevices().isEmpty();
|
||||
listener.onBluetoothDeviceChange(headsetAvailable);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
if (!audioManager.isBluetoothScoAvailableOffCall()) {
|
||||
JitsiMeetLogger.w(TAG + " Bluetooth SCO is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getBluetoothHeadsetProfileProxy()) {
|
||||
JitsiMeetLogger.w(TAG + " Couldn't get BT profile proxy");
|
||||
return;
|
||||
}
|
||||
|
||||
registerBluetoothReceiver();
|
||||
|
||||
// Initial detection.
|
||||
updateDevices();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (receiver != null) {
|
||||
context.unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
if (adapter != null && headset != null) {
|
||||
adapter.closeProfileProxy(BluetoothProfile.HEADSET, headset);
|
||||
}
|
||||
|
||||
receiver = null;
|
||||
headset = null;
|
||||
adapter = null;
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
void onBluetoothDeviceChange(boolean deviceAvailable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -14,12 +13,14 @@ import android.telecom.PhoneAccount;
|
||||
import android.telecom.PhoneAccountHandle;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.telecom.VideoProfile;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -122,16 +123,17 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
* {@link android.telecom.Connection#STATE_ACTIVE}.
|
||||
*
|
||||
* @param callUUID the call UUID which identifies the connection.
|
||||
* @return Whether the connection was set as active or not.
|
||||
*/
|
||||
static void setConnectionActive(String callUUID) {
|
||||
static boolean setConnectionActive(String callUUID) {
|
||||
ConnectionImpl connection = connections.get(callUUID);
|
||||
|
||||
if (connection != null) {
|
||||
connection.setActive();
|
||||
return true;
|
||||
} else {
|
||||
Log.e(TAG, String.format(
|
||||
"setConnectionActive - no connection for UUID: %s",
|
||||
callUUID));
|
||||
JitsiMeetLogger.w("%s setConnectionActive - no connection for UUID: %s", TAG, callUUID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +164,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
connection.setDisconnected(cause);
|
||||
connection.destroy();
|
||||
} else {
|
||||
Log.e(TAG, "endCall no connection for UUID: " + callUUID);
|
||||
JitsiMeetLogger.e(TAG + " endCall no connection for UUID: " + callUUID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,15 +196,14 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
boolean hasVideo
|
||||
= callState.getBoolean(ConnectionImpl.KEY_HAS_VIDEO);
|
||||
|
||||
Log.d(TAG, String.format(
|
||||
"updateCall: %s hasVideo: %s", callUUID, hasVideo));
|
||||
JitsiMeetLogger.i(" %s updateCall: %s hasVideo: %s", TAG, callUUID, hasVideo);
|
||||
connection.setVideoState(
|
||||
hasVideo
|
||||
? VideoProfile.STATE_BIDIRECTIONAL
|
||||
: VideoProfile.STATE_AUDIO_ONLY);
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "updateCall no connection for UUID: " + callUUID);
|
||||
JitsiMeetLogger.e(TAG + " updateCall no connection for UUID: " + callUUID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,13 +239,11 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
= unregisterStartCallPromise(connection.getCallUUID());
|
||||
|
||||
if (startCallPromise != null) {
|
||||
Log.d(TAG,
|
||||
"onCreateOutgoingConnection " + connection.getCallUUID());
|
||||
JitsiMeetLogger.d(TAG + " onCreateOutgoingConnection " + connection.getCallUUID());
|
||||
startCallPromise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, String.format(
|
||||
"onCreateOutgoingConnection: no start call Promise for %s",
|
||||
connection.getCallUUID()));
|
||||
JitsiMeetLogger.e(
|
||||
TAG + " onCreateOutgoingConnection: no start call Promise for " + connection.getCallUUID());
|
||||
}
|
||||
|
||||
return connection;
|
||||
@@ -268,7 +267,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
PhoneAccountHandle theAccountHandle = request.getAccountHandle();
|
||||
String callUUID = theAccountHandle.getId();
|
||||
|
||||
Log.e(TAG, "onCreateOutgoingConnectionFailed " + callUUID);
|
||||
JitsiMeetLogger.e(TAG + " onCreateOutgoingConnectionFailed " + callUUID);
|
||||
|
||||
if (callUUID != null) {
|
||||
Promise startCallPromise = unregisterStartCallPromise(callUUID);
|
||||
@@ -278,12 +277,10 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
"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));
|
||||
JitsiMeetLogger.e(TAG + " startCallFailed - no start call Promise for UUID: " + callUUID);
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "onCreateOutgoingConnectionFailed - no call UUID");
|
||||
JitsiMeetLogger.e(TAG + " onCreateOutgoingConnectionFailed - no call UUID");
|
||||
}
|
||||
|
||||
unregisterPhoneAccount(theAccountHandle);
|
||||
@@ -295,10 +292,10 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
if (phoneAccountHandle != null) {
|
||||
telecom.unregisterPhoneAccount(phoneAccountHandle);
|
||||
} else {
|
||||
Log.e(TAG, "unregisterPhoneAccount - account handle is null");
|
||||
JitsiMeetLogger.e(TAG + " unregisterPhoneAccount - account handle is null");
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "unregisterPhoneAccount - telecom is null");
|
||||
JitsiMeetLogger.e(TAG + " unregisterPhoneAccount - telecom is null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +354,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
*/
|
||||
@Override
|
||||
public void onDisconnect() {
|
||||
Log.d(TAG, "onDisconnect " + getCallUUID());
|
||||
JitsiMeetLogger.i(TAG + " onDisconnect " + getCallUUID());
|
||||
WritableNativeMap data = new WritableNativeMap();
|
||||
data.putString("callUUID", getCallUUID());
|
||||
ReactInstanceManagerHolder.emitEvent(
|
||||
@@ -377,7 +374,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
*/
|
||||
@Override
|
||||
public void onAbort() {
|
||||
Log.d(TAG, "onAbort " + getCallUUID());
|
||||
JitsiMeetLogger.i(TAG + " onAbort " + getCallUUID());
|
||||
WritableNativeMap data = new WritableNativeMap();
|
||||
data.putString("callUUID", getCallUUID());
|
||||
ReactInstanceManagerHolder.emitEvent(
|
||||
@@ -395,9 +392,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
// 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()));
|
||||
JitsiMeetLogger.w(TAG + " onHold %s - HOLD is not supported, aborting the call...", getCallUUID());
|
||||
this.onAbort();
|
||||
}
|
||||
|
||||
@@ -410,12 +405,10 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
*/
|
||||
@Override
|
||||
public void onCallAudioStateChanged(CallAudioState state) {
|
||||
Log.d(TAG, "onCallAudioStateChanged: " + state);
|
||||
AudioModeModule audioModeModule
|
||||
= ReactInstanceManagerHolder
|
||||
.getNativeModule(AudioModeModule.class);
|
||||
if (audioModeModule != null) {
|
||||
audioModeModule.onCallAudioStateChange(state);
|
||||
JitsiMeetLogger.d(TAG + " onCallAudioStateChanged: " + state);
|
||||
RNConnectionService module = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (module != null) {
|
||||
module.onCallAudioStateChange(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,10 +419,8 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
*/
|
||||
@Override
|
||||
public void onStateChanged(int state) {
|
||||
Log.d(TAG,
|
||||
String.format("onStateChanged: %s %s",
|
||||
Connection.stateToString(state),
|
||||
getCallUUID()));
|
||||
JitsiMeetLogger.d(
|
||||
"%s onStateChanged: %s %s", TAG, Connection.stateToString(state), getCallUUID());
|
||||
|
||||
if (state == STATE_DISCONNECTED) {
|
||||
removeConnection(this);
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
/**
|
||||
* Module implementing an API for sending events from JavaScript to native code.
|
||||
*/
|
||||
@@ -76,11 +76,11 @@ class ExternalAPIModule
|
||||
BaseReactView view = BaseReactView.findViewByExternalAPIScope(scope);
|
||||
|
||||
if (view != null) {
|
||||
Log.d(TAG, "Sending event: " + name + " with data: " + data);
|
||||
JitsiMeetLogger.d(TAG + " Sending event: " + name + " with data: " + data);
|
||||
try {
|
||||
view.onExternalAPIEvent(name, data);
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "onExternalAPIEvent: error sending event", e);
|
||||
JitsiMeetLogger.e(e, TAG + " onExternalAPIEvent: error sending event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.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 com.squareup.duktape.Duktape;
|
||||
|
||||
@ReactModule(name = JavaScriptSandboxModule.NAME)
|
||||
class JavaScriptSandboxModule extends ReactContextBaseJavaModule {
|
||||
public static final String NAME = "JavaScriptSandbox";
|
||||
|
||||
public JavaScriptSandboxModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given code in a Duktape VM.
|
||||
* @param code - The code that needs to evaluated.
|
||||
* @param promise - Resolved with the output in case of success or rejected with an exception
|
||||
* in case of failure.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void evaluate(String code, Promise promise) {
|
||||
Duktape vm = Duktape.create();
|
||||
try {
|
||||
Object res = vm.evaluate(code);
|
||||
promise.resolve(res.toString());
|
||||
} catch (Throwable tr) {
|
||||
promise.reject(tr);
|
||||
} finally {
|
||||
vm.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,9 @@ public class JitsiMeet {
|
||||
}
|
||||
|
||||
public static void setDefaultConferenceOptions(JitsiMeetConferenceOptions options) {
|
||||
if (options != null && options.getRoom() != null) {
|
||||
throw new RuntimeException("'room' must be null in the default conference options");
|
||||
}
|
||||
defaultConferenceOptions = options;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,12 +20,13 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@@ -208,19 +209,19 @@ public class JitsiMeetActivity extends FragmentActivity
|
||||
|
||||
@Override
|
||||
public void onConferenceJoined(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference joined: " + data);
|
||||
JitsiMeetLogger.i("Conference joined: " + data);
|
||||
// Launch the service for the ongoing notification.
|
||||
JitsiMeetOngoingConferenceService.launch(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceTerminated(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference terminated: " + data);
|
||||
JitsiMeetLogger.i("Conference terminated: " + data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceWillJoin(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference will join: " + data);
|
||||
JitsiMeetLogger.i("Conference will join: " + data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.os.Build;
|
||||
import com.calendarevents.CalendarEventsPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
/**
|
||||
@@ -117,7 +118,13 @@ public class JitsiMeetActivityDelegate {
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostPause(activity);
|
||||
// Try to avoid a crash because some devices trip on this assert:
|
||||
// https://github.com/facebook/react-native/blob/df4e67fe75d781d1eb264128cadf079989542755/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java#L512
|
||||
// Why this happens is a mystery wrapped in an enigma.
|
||||
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
|
||||
if (reactContext != null && activity == reactContext.getCurrentActivity()) {
|
||||
reactInstanceManager.onHostPause(activity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||
|
||||
|
||||
@@ -72,6 +72,46 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
*/
|
||||
private JitsiMeetUserInfo userInfo;
|
||||
|
||||
public URL getServerURL() {
|
||||
return serverURL;
|
||||
}
|
||||
|
||||
public String getRoom() {
|
||||
return room;
|
||||
}
|
||||
|
||||
public String getSubject() {
|
||||
return subject;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public Bundle getColorScheme() {
|
||||
return colorScheme;
|
||||
}
|
||||
|
||||
public Bundle getFeatureFlags() {
|
||||
return featureFlags;
|
||||
}
|
||||
|
||||
public boolean getAudioMuted() {
|
||||
return audioMuted;
|
||||
}
|
||||
|
||||
public boolean getAudioOnly() {
|
||||
return audioOnly;
|
||||
}
|
||||
|
||||
public boolean getVideoMuted() {
|
||||
return videoMuted;
|
||||
}
|
||||
|
||||
public JitsiMeetUserInfo getUserInfo() {
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class used to build the immutable {@link JitsiMeetConferenceOptions} object.
|
||||
*/
|
||||
@@ -309,7 +349,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
urlProps.putString("jwt", token);
|
||||
}
|
||||
|
||||
if (token == null && userInfo != null) {
|
||||
if (userInfo != null) {
|
||||
props.putBundle("userInfo", userInfo.asBundle());
|
||||
}
|
||||
|
||||
@@ -341,7 +381,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
dest.writeString(token);
|
||||
dest.writeBundle(colorScheme);
|
||||
dest.writeBundle(featureFlags);
|
||||
dest.writeBundle(userInfo.asBundle());
|
||||
dest.writeBundle(userInfo != null ? userInfo.asBundle() : new Bundle());
|
||||
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));
|
||||
|
||||
@@ -19,9 +19,10 @@ 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 androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@@ -21,8 +21,10 @@ import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
|
||||
/**
|
||||
@@ -47,9 +49,14 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
intent.setAction(Actions.START);
|
||||
|
||||
ComponentName componentName = context.startService(intent);
|
||||
ComponentName componentName;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
componentName = context.startForegroundService(intent);
|
||||
} else {
|
||||
componentName = context.startService(intent);
|
||||
}
|
||||
if (componentName == null) {
|
||||
Log.w(TAG, "Ongoing conference service not started");
|
||||
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,19 +87,24 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
final String action = intent.getAction();
|
||||
if (action.equals(Actions.START)) {
|
||||
if (Actions.START.equals(action)) {
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification();
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
Log.i(TAG, "Service started");
|
||||
} else if (action.equals(Actions.HANGUP)) {
|
||||
Log.i(TAG, "Hangup requested");
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
JitsiMeetLogger.i(TAG + " Service started");
|
||||
}
|
||||
} else if (Actions.HANGUP.equals(action)) {
|
||||
JitsiMeetLogger.i(TAG + " Hangup requested");
|
||||
// Abort all ongoing calls
|
||||
if (AudioModeModule.useConnectionService()) {
|
||||
ConnectionService.abortConnections();
|
||||
}
|
||||
stopSelf();
|
||||
} else {
|
||||
Log.w(TAG, "Unknown action received: " + action);
|
||||
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
@@ -103,7 +115,7 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
public void onCurrentConferenceChanged(String conferenceUrl) {
|
||||
if (conferenceUrl == null) {
|
||||
stopSelf();
|
||||
Log.i(TAG, "Service stopped");
|
||||
JitsiMeetLogger.i(TAG + "Service stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.util.Log;
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
class JitsiMeetUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
|
||||
private final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;
|
||||
@@ -37,14 +37,7 @@ class JitsiMeetUncaughtExceptionHandler implements Thread.UncaughtExceptionHandl
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
Log.e(this.getClass().getSimpleName(), "FATAL ERROR", e);
|
||||
|
||||
// Terminate all conferences
|
||||
for (BaseReactView view: BaseReactView.getViews()) {
|
||||
if (view instanceof JitsiMeetView) {
|
||||
((JitsiMeetView) view).leave();
|
||||
}
|
||||
}
|
||||
JitsiMeetLogger.e(e, this.getClass().getSimpleName() + " FATAL ERROR");
|
||||
|
||||
// Abort all ConnectionService ongoing calls
|
||||
if (AudioModeModule.useConnectionService()) {
|
||||
|
||||
@@ -19,12 +19,13 @@ package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -39,12 +40,6 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
|
||||
private static final Map<String, Method> LISTENER_METHODS
|
||||
= ListenerUtils.mapListenerMethods(JitsiMeetViewListener.class);
|
||||
|
||||
/**
|
||||
* The {@link Log} tag which identifies the source of the log messages of
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
private static final String TAG = JitsiMeetView.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* The URL of the current conference.
|
||||
*/
|
||||
@@ -137,7 +132,7 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
|
||||
try {
|
||||
pipModule.enterPictureInPicture();
|
||||
} catch (RuntimeException re) {
|
||||
Log.e(TAG, "failed to enter PiP mode", re);
|
||||
JitsiMeetLogger.e(re, "Failed to enter PiP mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Module implementing a "bridge" between the JS loggers and the native one.
|
||||
*/
|
||||
@ReactModule(name = LogBridgeModule.NAME)
|
||||
class LogBridgeModule extends ReactContextBaseJavaModule {
|
||||
public static final String NAME = "LogBridge";
|
||||
|
||||
public LogBridgeModule(@Nonnull ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void trace(final String message) {
|
||||
JitsiMeetLogger.v(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void debug(final String message) {
|
||||
JitsiMeetLogger.d(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void info(final String message) {
|
||||
JitsiMeetLogger.i(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void log(final String message) {
|
||||
JitsiMeetLogger.i(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void warn(final String message) {
|
||||
JitsiMeetLogger.w(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void error(final String message) {
|
||||
JitsiMeetLogger.e(message);
|
||||
}
|
||||
}
|
||||
@@ -86,8 +86,10 @@ class OngoingConferenceTracker {
|
||||
}
|
||||
|
||||
private void updateListeners() {
|
||||
for (OngoingConferenceListener listener : listeners) {
|
||||
listener.onCurrentConferenceChanged(currentConference);
|
||||
synchronized (listeners) {
|
||||
for (OngoingConferenceListener listener : listeners) {
|
||||
listener.onCurrentConferenceChanged(currentConference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,9 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
@@ -50,7 +51,7 @@ class OngoingNotification {
|
||||
|
||||
Context context = ReactInstanceManagerHolder.getCurrentActivity();
|
||||
if (context == null) {
|
||||
Log.w(TAG, "Cannot create notification channel: no current context");
|
||||
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -75,7 +76,7 @@ class OngoingNotification {
|
||||
static Notification buildOngoingConferenceNotification() {
|
||||
Context context = ReactInstanceManagerHolder.getCurrentActivity();
|
||||
if (context == null) {
|
||||
Log.w(TAG, "Cannot create notification: no current context");
|
||||
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.PictureInPictureParams;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.util.Rational;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
@@ -29,6 +28,8 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
@ReactModule(name = PictureInPictureModule.NAME)
|
||||
class PictureInPictureModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
@@ -70,7 +71,7 @@ class PictureInPictureModule
|
||||
throw new IllegalStateException("No current Activity!");
|
||||
}
|
||||
|
||||
Log.d(TAG, "Entering Picture-in-Picture");
|
||||
JitsiMeetLogger.i(TAG + " Entering Picture-in-Picture");
|
||||
|
||||
PictureInPictureParams.Builder builder
|
||||
= new PictureInPictureParams.Builder()
|
||||
|
||||
@@ -5,13 +5,12 @@ 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 androidx.annotation.RequiresApi;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
@@ -20,6 +19,8 @@ import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
/**
|
||||
* The react-native side of Jitsi Meet's {@link ConnectionService}. Exposes
|
||||
* the Java Script API.
|
||||
@@ -28,13 +29,18 @@ import com.facebook.react.module.annotations.ReactModule;
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@ReactModule(name = RNConnectionService.NAME)
|
||||
class RNConnectionService
|
||||
extends ReactContextBaseJavaModule {
|
||||
class RNConnectionService extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "ConnectionService";
|
||||
|
||||
private static final String TAG = ConnectionService.TAG;
|
||||
|
||||
/**
|
||||
* Handler for dealing with call state changes. We are acting as a proxy between ConnectionService
|
||||
* and other modules such as {@link AudioModeModule}.
|
||||
*/
|
||||
private CallAudioStateListener callAudioStateListener;
|
||||
|
||||
/**
|
||||
* Sets the audio route on all existing {@link android.telecom.Connection}s
|
||||
*
|
||||
@@ -74,18 +80,26 @@ class RNConnectionService
|
||||
String handle,
|
||||
boolean hasVideo,
|
||||
Promise promise) {
|
||||
Log.d(TAG,
|
||||
String.format("startCall UUID=%s, h=%s, v=%s",
|
||||
JitsiMeetLogger.d("%s startCall UUID=%s, h=%s, v=%s",
|
||||
TAG,
|
||||
callUUID,
|
||||
handle,
|
||||
hasVideo));
|
||||
hasVideo);
|
||||
|
||||
ReactApplicationContext ctx = getReactApplicationContext();
|
||||
|
||||
Uri address = Uri.fromParts(PhoneAccount.SCHEME_SIP, handle, null);
|
||||
PhoneAccountHandle accountHandle
|
||||
= ConnectionService.registerPhoneAccount(
|
||||
getReactApplicationContext(), address, callUUID);
|
||||
PhoneAccountHandle accountHandle;
|
||||
|
||||
try {
|
||||
accountHandle
|
||||
= ConnectionService.registerPhoneAccount(getReactApplicationContext(), address, callUUID);
|
||||
} catch (Throwable tr) {
|
||||
JitsiMeetLogger.e(tr, TAG + " error in startCall");
|
||||
|
||||
promise.reject(tr);
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putParcelable(
|
||||
@@ -99,15 +113,23 @@ class RNConnectionService
|
||||
|
||||
ConnectionService.registerStartCallPromise(callUUID, promise);
|
||||
|
||||
try {
|
||||
TelecomManager tm
|
||||
= (TelecomManager) ctx.getSystemService(
|
||||
Context.TELECOM_SERVICE);
|
||||
TelecomManager tm = null;
|
||||
|
||||
try {
|
||||
tm = (TelecomManager) ctx.getSystemService(Context.TELECOM_SERVICE);
|
||||
tm.placeCall(address, extras);
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable tr) {
|
||||
JitsiMeetLogger.e(tr, TAG + " error in startCall");
|
||||
if (tm != null) {
|
||||
try {
|
||||
tm.unregisterPhoneAccount(accountHandle);
|
||||
} catch (Throwable tr1) {
|
||||
// UnsupportedOperationException: System does not support feature android.software.connectionservice
|
||||
// was observed here. Ignore.
|
||||
}
|
||||
}
|
||||
ConnectionService.unregisterStartCallPromise(callUUID);
|
||||
promise.reject(e);
|
||||
promise.reject(tr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +140,7 @@ class RNConnectionService
|
||||
*/
|
||||
@ReactMethod
|
||||
public void reportCallFailed(String callUUID) {
|
||||
Log.d(TAG, "reportCallFailed " + callUUID);
|
||||
JitsiMeetLogger.d(TAG + " reportCallFailed " + callUUID);
|
||||
ConnectionService.setConnectionDisconnected(
|
||||
callUUID,
|
||||
new DisconnectCause(DisconnectCause.ERROR));
|
||||
@@ -131,7 +153,7 @@ class RNConnectionService
|
||||
*/
|
||||
@ReactMethod
|
||||
public void endCall(String callUUID) {
|
||||
Log.d(TAG, "endCall " + callUUID);
|
||||
JitsiMeetLogger.d(TAG + " endCall " + callUUID);
|
||||
ConnectionService.setConnectionDisconnected(
|
||||
callUUID,
|
||||
new DisconnectCause(DisconnectCause.LOCAL));
|
||||
@@ -143,9 +165,13 @@ class RNConnectionService
|
||||
* @param callUUID - the call's UUID.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void reportConnectedOutgoingCall(String callUUID) {
|
||||
Log.d(TAG, "reportConnectedOutgoingCall " + callUUID);
|
||||
ConnectionService.setConnectionActive(callUUID);
|
||||
public void reportConnectedOutgoingCall(String callUUID, Promise promise) {
|
||||
JitsiMeetLogger.d(TAG + " reportConnectedOutgoingCall " + callUUID);
|
||||
if (ConnectionService.setConnectionActive(callUUID)) {
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
promise.reject("CONNECTION_NOT_FOUND_ERROR", "Connection wasn't found.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -166,4 +192,28 @@ class RNConnectionService
|
||||
public void updateCall(String callUUID, ReadableMap callState) {
|
||||
ConnectionService.updateCall(callUUID, callState);
|
||||
}
|
||||
|
||||
public CallAudioStateListener getCallAudioStateListener() {
|
||||
return callAudioStateListener;
|
||||
}
|
||||
|
||||
public void setCallAudioStateListener(CallAudioStateListener callAudioStateListener) {
|
||||
this.callAudioStateListener = callAudioStateListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for call state changes. {@code ConnectionServiceImpl} will call this handler when the
|
||||
* call audio state changes.
|
||||
*
|
||||
* @param callAudioState The current call's audio state.
|
||||
*/
|
||||
void onCallAudioStateChange(android.telecom.CallAudioState callAudioState) {
|
||||
if (callAudioStateListener != null) {
|
||||
callAudioStateListener.onCallAudioStateChange(callAudioState);
|
||||
}
|
||||
}
|
||||
|
||||
interface CallAudioStateListener {
|
||||
void onCallAudioStateChange(android.telecom.CallAudioState callAudioState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
* Copyright @ 2017-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,9 +17,11 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
@@ -29,6 +30,21 @@ 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 com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
import com.oney.WebRTCModule.EglUtils;
|
||||
import com.oney.WebRTCModule.RTCVideoViewManager;
|
||||
import com.oney.WebRTCModule.WebRTCModule;
|
||||
|
||||
import org.webrtc.DefaultVideoDecoderFactory;
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.SoftwareVideoDecoderFactory;
|
||||
import org.webrtc.SoftwareVideoEncoderFactory;
|
||||
import org.webrtc.VideoDecoderFactory;
|
||||
import org.webrtc.VideoEncoderFactory;
|
||||
import org.webrtc.audio.AudioDeviceModule;
|
||||
import org.webrtc.audio.JavaAudioDeviceModule;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
@@ -47,8 +63,7 @@ class ReactInstanceManagerHolder {
|
||||
*/
|
||||
private static ReactInstanceManager reactInstanceManager;
|
||||
|
||||
private static List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
private static List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> nativeModules
|
||||
= new ArrayList<>(Arrays.<NativeModule>asList(
|
||||
new AndroidSettingsModule(reactContext),
|
||||
@@ -56,7 +71,9 @@ class ReactInstanceManagerHolder {
|
||||
new AudioModeModule(reactContext),
|
||||
new DropboxModule(reactContext),
|
||||
new ExternalAPIModule(reactContext),
|
||||
new JavaScriptSandboxModule(reactContext),
|
||||
new LocaleDetector(reactContext),
|
||||
new LogBridgeModule(reactContext),
|
||||
new PictureInPictureModule(reactContext),
|
||||
new ProximityModule(reactContext),
|
||||
new WiFiStatsModule(reactContext),
|
||||
@@ -66,6 +83,33 @@ class ReactInstanceManagerHolder {
|
||||
nativeModules.add(new RNConnectionService(reactContext));
|
||||
}
|
||||
|
||||
// Initialize the WebRTC module by hand, since we want to override some
|
||||
// initialization options.
|
||||
WebRTCModule.Options options = new WebRTCModule.Options();
|
||||
|
||||
AudioDeviceModule adm = JavaAudioDeviceModule.builder(reactContext)
|
||||
.createAudioDeviceModule();
|
||||
options.setAudioDeviceModule(adm);
|
||||
|
||||
VideoEncoderFactory videoEncoderFactory = new SoftwareVideoEncoderFactory();
|
||||
VideoDecoderFactory videoDecoderFactory = new SoftwareVideoDecoderFactory();
|
||||
|
||||
// Initialize EGL context required for HW acceleration. We are only going to use it for
|
||||
// decoding.
|
||||
// NOTE: We are explicitly skipping Samsung devices because we have observed a high crash
|
||||
// count on them.
|
||||
if (!Build.MANUFACTURER.toLowerCase().contains("samsung")) {
|
||||
EglBase.Context eglContext = EglUtils.getRootEglBaseContext();
|
||||
if (eglContext != null) {
|
||||
videoDecoderFactory = new DefaultVideoDecoderFactory(eglContext);
|
||||
}
|
||||
}
|
||||
|
||||
options.setVideoDecoderFactory(videoDecoderFactory);
|
||||
options.setVideoEncoderFactory(videoEncoderFactory);
|
||||
|
||||
nativeModules.add(new WebRTCModule(reactContext, options));
|
||||
|
||||
try {
|
||||
Class<?> amplitudeModuleClass = Class.forName("org.jitsi.meet.sdk.AmplitudeModule");
|
||||
Constructor constructor = amplitudeModuleClass.getConstructor(ReactApplicationContext.class);
|
||||
@@ -77,6 +121,13 @@ class ReactInstanceManagerHolder {
|
||||
return nativeModules;
|
||||
}
|
||||
|
||||
private static List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Arrays.<ViewManager>asList(
|
||||
// WebRTC, see createNativeModules for details.
|
||||
new RTCVideoViewManager()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to send an event to JavaScript.
|
||||
*
|
||||
@@ -143,24 +194,25 @@ class ReactInstanceManagerHolder {
|
||||
* time. All {@code ReactRootView} instances will be tied to the one and
|
||||
* only {@code ReactInstanceManager}.
|
||||
*
|
||||
* @param application {@code Application} instance which is running.
|
||||
* @param activity {@code Activity} current running Activity.
|
||||
*/
|
||||
static void initReactInstanceManager(Application application) {
|
||||
static void initReactInstanceManager(Activity activity) {
|
||||
if (reactInstanceManager != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SoLoader.init(activity, /* native exopackage */ false);
|
||||
|
||||
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.horcrux.svg.SvgPackage(),
|
||||
new com.ocetnik.timer.BackgroundTimerPackage(),
|
||||
new com.oney.WebRTCModule.WebRTCModulePackage(),
|
||||
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
|
||||
new com.reactnativecommunity.netinfo.NetInfoPackage(),
|
||||
new com.reactnativecommunity.webview.RNCWebViewPackage(),
|
||||
new com.rnimmersive.RNImmersivePackage(),
|
||||
new com.zmxv.RNSound.RNSoundPackage(),
|
||||
@@ -169,6 +221,10 @@ class ReactInstanceManagerHolder {
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
return ReactInstanceManagerHolder.createNativeModules(reactContext);
|
||||
}
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return ReactInstanceManagerHolder.createViewManagers(reactContext);
|
||||
}
|
||||
}));
|
||||
|
||||
try {
|
||||
@@ -179,11 +235,16 @@ class ReactInstanceManagerHolder {
|
||||
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
||||
}
|
||||
|
||||
// Use the Hermes JavaScript engine.
|
||||
HermesExecutorFactory jsFactory = new HermesExecutorFactory();
|
||||
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
.setApplication(application)
|
||||
.setApplication(activity.getApplication())
|
||||
.setCurrentActivity(activity)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.setJavaScriptExecutorFactory(jsFactory)
|
||||
.addPackages(packages)
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.jitsi.meet.sdk;
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
@@ -27,6 +26,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@@ -144,8 +144,7 @@ class WiFiStatsModule
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("rssi", rssi)
|
||||
.put("signal", signalLevel)
|
||||
.put("timestamp",
|
||||
String.valueOf(System.currentTimeMillis()));
|
||||
.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
JSONArray addresses = new JSONArray();
|
||||
|
||||
@@ -185,17 +184,15 @@ class WiFiStatsModule
|
||||
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
Log.wtf(TAG,
|
||||
"Unable to NetworkInterface.getNetworkInterfaces()"
|
||||
);
|
||||
JitsiMeetLogger.e(e, TAG + " Unable to NetworkInterface.getNetworkInterfaces()");
|
||||
}
|
||||
|
||||
result.put("addresses", addresses);
|
||||
promise.resolve(result.toString());
|
||||
|
||||
Log.d(TAG, "WiFi stats: " + result.toString());
|
||||
JitsiMeetLogger.d(TAG + " WiFi stats: " + result.toString());
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Failed to obtain wifi stats", e);
|
||||
JitsiMeetLogger.e(e, TAG + " Failed to obtain wifi stats");
|
||||
promise.reject(
|
||||
new Exception("Failed to obtain wifi stats"));
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.jitsi.meet.sdk.incoming_call;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class IncomingCallInfo {
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.jitsi.meet.sdk.incoming_call;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.log;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Base class for all custom log handlers. Implementations must inherit from this class and
|
||||
* implement a `doLog` method which does the actual logging, in addition with a `getTag` method
|
||||
* with which to tag all logs coming into this logger.
|
||||
*
|
||||
* See {@link JitsiMeetDefaultLogHandler} for an example.
|
||||
*/
|
||||
public abstract class JitsiMeetBaseLogHandler extends Timber.Tree {
|
||||
@Override
|
||||
protected void log(int priority, @Nullable String tag, @NotNull String msg, @Nullable Throwable t) {
|
||||
String errmsg = Log.getStackTraceString(t);
|
||||
if (errmsg.isEmpty()) {
|
||||
doLog(priority, getDefaultTag(), msg);
|
||||
} else {
|
||||
doLog(priority, getDefaultTag(), MessageFormat.format("{0}\n{1}", msg, errmsg));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void doLog(int priority, @NotNull String tag, @NotNull String msg);
|
||||
|
||||
protected abstract String getDefaultTag();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.log;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link JitsiMeetBaseLogHandler}. This is the main SDK logger, which
|
||||
* logs using the Android util.Log module.
|
||||
*/
|
||||
public class JitsiMeetDefaultLogHandler extends JitsiMeetBaseLogHandler {
|
||||
private static final String TAG = "JitsiMeetSDK";
|
||||
|
||||
@Override
|
||||
protected void doLog(int priority, @NotNull String tag, @NotNull String msg) {
|
||||
Log.println(priority, tag, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultTag() {
|
||||
return TAG;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.log;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class JitsiMeetLogger {
|
||||
static {
|
||||
addHandler(new JitsiMeetDefaultLogHandler());
|
||||
}
|
||||
|
||||
public static void addHandler(JitsiMeetBaseLogHandler handler) {
|
||||
if (!Timber.forest().contains(handler)) {
|
||||
try {
|
||||
Timber.plant(handler);
|
||||
} catch (Throwable t) {
|
||||
Timber.w(t, "Couldn't add log handler");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeHandler(JitsiMeetBaseLogHandler handler) {
|
||||
if (Timber.forest().contains(handler)) {
|
||||
try {
|
||||
Timber.uproot(handler);
|
||||
} catch (Throwable t) {
|
||||
Timber.w(t, "Couldn't remove log handler");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void v(String message, Object... args) {
|
||||
Timber.v(message, args);
|
||||
}
|
||||
|
||||
public static void v(Throwable t, String message, Object... args) {
|
||||
Timber.v(t, message, args);
|
||||
}
|
||||
|
||||
public static void v(Throwable t) {
|
||||
Timber.v(t);
|
||||
}
|
||||
|
||||
public static void d(String message, Object... args) {
|
||||
Timber.d(message, args);
|
||||
}
|
||||
|
||||
public static void d(Throwable t, String message, Object... args) {
|
||||
Timber.d(t, message, args);
|
||||
}
|
||||
|
||||
public static void d(Throwable t) {
|
||||
Timber.d(t);
|
||||
}
|
||||
|
||||
public static void i(String message, Object... args) {
|
||||
Timber.i(message, args);
|
||||
}
|
||||
|
||||
public static void i(Throwable t, String message, Object... args) {
|
||||
Timber.i(t, message, args);
|
||||
}
|
||||
|
||||
public static void i(Throwable t) {
|
||||
Timber.i(t);
|
||||
}
|
||||
|
||||
public static void w(String message, Object... args) {
|
||||
Timber.w(message, args);
|
||||
}
|
||||
|
||||
public static void w(Throwable t, String message, Object... args) {
|
||||
Timber.w(t, message, args);
|
||||
}
|
||||
|
||||
public static void w(Throwable t) {
|
||||
Timber.w(t);
|
||||
}
|
||||
|
||||
public static void e(String message, Object... args) {
|
||||
Timber.e(message, args);
|
||||
}
|
||||
|
||||
public static void e(Throwable t, String message, Object... args) {
|
||||
Timber.e(t, message, args);
|
||||
}
|
||||
|
||||
public static void e(Throwable t) {
|
||||
Timber.e(t);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
package org.jitsi.meet.sdk.net;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
@@ -43,7 +43,7 @@ public class NAT64AddrInfoModule
|
||||
* The host for which the module wil try to resolve both IPv4 and IPv6
|
||||
* addresses in order to figure out the NAT64 prefix.
|
||||
*/
|
||||
private final static String HOST = "nat64.jitsi.net";
|
||||
private final static String HOST = "ipv4only.arpa";
|
||||
|
||||
/**
|
||||
* How long is the {@link NAT64AddrInfo} instance valid.
|
||||
@@ -97,7 +97,7 @@ public class NAT64AddrInfoModule
|
||||
try {
|
||||
info = NAT64AddrInfo.discover(host);
|
||||
} catch (UnknownHostException e) {
|
||||
Log.e(TAG, "NAT64AddrInfo.discover: " + host, e);
|
||||
JitsiMeetLogger.e(e, TAG + " NAT64AddrInfo.discover: " + host);
|
||||
}
|
||||
infoTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public class NAT64AddrInfoModule
|
||||
try {
|
||||
result = info == null ? null : info.getIPv6Address(ipv4Address);
|
||||
} catch (IllegalArgumentException exc) {
|
||||
Log.e(TAG, "Failed to get IPv6 address for: " + ipv4Address, exc);
|
||||
JitsiMeetLogger.e(exc, TAG + " Failed to get IPv6 address for: " + ipv4Address);
|
||||
|
||||
// We don't want to reject. It's not a big deal if there's no IPv6
|
||||
// address resolved.
|
||||
|
||||
@@ -7,10 +7,10 @@ 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-community_netinfo'
|
||||
project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android')
|
||||
include ':react-native-google-signin'
|
||||
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-signin/android')
|
||||
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/google-signin/android')
|
||||
include ':react-native-immersive'
|
||||
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
|
||||
include ':react-native-keep-awake'
|
||||
@@ -19,8 +19,8 @@ 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-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-svg'
|
||||
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
|
||||
include ':react-native-webrtc'
|
||||
project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android')
|
||||
include ':react-native-webview'
|
||||
|
||||
650
conference.js
71
config.js
@@ -1,16 +1,6 @@
|
||||
/* eslint-disable no-unused-vars, no-var */
|
||||
|
||||
var config = {
|
||||
// Configuration
|
||||
//
|
||||
|
||||
// Alternative location for the configuration.
|
||||
// configLocation: './config.json',
|
||||
|
||||
// Custom function which given the URL path should return a room name.
|
||||
// getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
|
||||
|
||||
|
||||
// Connection
|
||||
//
|
||||
|
||||
@@ -40,6 +30,9 @@ var config = {
|
||||
// BOSH URL. FIXME: use XEP-0156 to discover it.
|
||||
bosh: '//jitsi-meet.example.com/http-bind',
|
||||
|
||||
// Websocket URL
|
||||
// websocket: 'wss://jitsi-meet.example.com/xmpp-websocket',
|
||||
|
||||
// The name of client node advertised in XEP-0115 'c' stanza
|
||||
clientNode: 'http://jitsi.org/jitsimeet',
|
||||
|
||||
@@ -60,6 +53,10 @@ var config = {
|
||||
|
||||
// Enables the test specific features consumed by jitsi-meet-torture
|
||||
// testMode: false
|
||||
|
||||
// Disables the auto-play behavior of *all* newly created video element.
|
||||
// This is useful when the client runs on a host with limited resources.
|
||||
// noAutoPlayVideo: false
|
||||
},
|
||||
|
||||
// Disables ICE/UDP by filtering out local and remote UDP candidates in
|
||||
@@ -79,6 +76,17 @@ var config = {
|
||||
// Disable measuring of audio levels.
|
||||
// disableAudioLevels: false,
|
||||
|
||||
// Enabling this will run the lib-jitsi-meet no audio detection module which
|
||||
// will notify the user if the current selected microphone has no audio
|
||||
// input and will suggest another valid device if one is present.
|
||||
enableNoAudioDetection: true,
|
||||
|
||||
// Enabling this will run the lib-jitsi-meet noise detection module which will
|
||||
// notify the user if there is noise, other than voice, coming from the current
|
||||
// selected microphone. The purpose it to let the user know that the input could
|
||||
// be potentially unpleasant for other meeting participants.
|
||||
enableNoisyMicDetection: true,
|
||||
|
||||
// Start the conference in audio only mode (no video is being received nor
|
||||
// sent).
|
||||
// startAudioOnly: false,
|
||||
@@ -123,10 +131,6 @@ var config = {
|
||||
// are requested again.
|
||||
// enableLayerSuspension: false,
|
||||
|
||||
// Suspend sending video if bandwidth estimation is too low. This may cause
|
||||
// problems with audio playback. Disabled until these are fixed.
|
||||
disableSuspendVideo: true,
|
||||
|
||||
// Every participant after the Nth will start video muted.
|
||||
// startVideoMuted: 10,
|
||||
|
||||
@@ -201,6 +205,9 @@ var config = {
|
||||
// subtitles and buttons can be configured)
|
||||
// transcribingEnabled: false,
|
||||
|
||||
// Enables automatic turning on captions when recording is started
|
||||
// autoCaptionOnRecord: false,
|
||||
|
||||
// Misc
|
||||
|
||||
// Default value for the channel "last N" attribute. -1 for unlimited.
|
||||
@@ -299,13 +306,11 @@ var config = {
|
||||
// callStatsID: '',
|
||||
// callStatsSecret: '',
|
||||
|
||||
// enables callstatsUsername to be reported as statsId and used
|
||||
// by callstats as repoted remote id
|
||||
// enableStatsID: false
|
||||
|
||||
// enables sending participants display name to callstats
|
||||
// enableDisplayNameInStats: false
|
||||
|
||||
// enables sending participants email if available to callstats and other analytics
|
||||
// enableEmailInStats: false
|
||||
|
||||
// Privacy
|
||||
//
|
||||
@@ -333,6 +338,8 @@ var config = {
|
||||
|
||||
// The STUN servers that will be used in the peer to peer connections
|
||||
stunServers: [
|
||||
|
||||
// { urls: 'stun:jitsi-meet.example.com:443' },
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' },
|
||||
{ urls: 'stun:stun2.l.google.com:19302' }
|
||||
@@ -381,6 +388,20 @@ var config = {
|
||||
// userRegion: "asia"
|
||||
}
|
||||
|
||||
// Information for the chrome extension banner
|
||||
// chromeExtensionBanner: {
|
||||
// // The chrome extension to be installed address
|
||||
// url: 'https://chrome.google.com/webstore/detail/jitsi-meetings/kglhbbefdnlheedjiejgomgmfplipfeb',
|
||||
|
||||
// // Extensions info which allows checking if they are installed or not
|
||||
// chromeExtensionsInfo: [
|
||||
// {
|
||||
// id: 'kglhbbefdnlheedjiejgomgmfplipfeb',
|
||||
// path: 'jitsi-logo-48x48.png'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
// Local Recording
|
||||
//
|
||||
|
||||
@@ -418,10 +439,24 @@ var config = {
|
||||
// use only.
|
||||
// _desktopSharingSourceDevice: 'sample-id-or-label'
|
||||
|
||||
// If true, any checks to handoff to another application will be prevented
|
||||
// and instead the app will continue to display in the current browser.
|
||||
// disableDeepLinking: false
|
||||
|
||||
// A property to disable the right click context menu for localVideo
|
||||
// the menu has option to flip the locally seen video for local presentations
|
||||
// disableLocalVideoFlip: false
|
||||
|
||||
// Deployment specific URLs.
|
||||
// deploymentUrls: {
|
||||
// // If specified a 'Help' button will be displayed in the overflow menu with a link to the specified URL for
|
||||
// // user documentation.
|
||||
// userDocumentationURL: 'https://docs.example.com/video-meetings.html',
|
||||
// // If specified a 'Download our apps' button will be displayed in the overflow menu with a link
|
||||
// // to the specified URL for an app download page.
|
||||
// downloadAppsUrl: 'https://docs.example.com/our-apps.html'
|
||||
// }
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
_immediateReloadThreshold
|
||||
|
||||
@@ -75,7 +75,15 @@ function connect(id, password, roomName) {
|
||||
const connectionConfig = Object.assign({}, config);
|
||||
const { issuer, jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
connectionConfig.bosh += `?room=${roomName}`;
|
||||
// Use Websocket URL for the web app if configured. Note that there is no 'isWeb' check, because there's assumption
|
||||
// that this code executes only on web browsers/electron. This needs to be changed when mobile and web are unified.
|
||||
let serviceUrl = connectionConfig.websocket || connectionConfig.bosh;
|
||||
|
||||
serviceUrl += `?room=${roomName}`;
|
||||
|
||||
// FIXME Remove deprecated 'bosh' option assignment at some point(LJM will be accepting only 'serviceUrl' option
|
||||
// in future). It's included for the time being for Jitsi Meet and lib-jitsi-meet versions interoperability.
|
||||
connectionConfig.serviceUrl = connectionConfig.bosh = serviceUrl;
|
||||
|
||||
const connection
|
||||
= new JitsiMeetJS.JitsiConnection(
|
||||
|
||||