Compare commits
1225 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d60a0c43f | ||
|
|
e6c7bfc895 | ||
|
|
911730770b | ||
|
|
66b38c483b | ||
|
|
d840b06ca7 | ||
|
|
e0bc66c167 | ||
|
|
237bf51b66 | ||
|
|
6e7a121be0 | ||
|
|
a64d5ac910 | ||
|
|
21e5035c97 | ||
|
|
9dfb3aa474 | ||
|
|
b40d45cbcb | ||
|
|
b89c5b7b4f | ||
|
|
98b7defef6 | ||
|
|
0dc207cc85 | ||
|
|
e5006d95b1 | ||
|
|
4c6aa39ccf | ||
|
|
1f64ee453a | ||
|
|
7bd4779745 | ||
|
|
33fa8a0826 | ||
|
|
642f4ce7aa | ||
|
|
ec9c0db867 | ||
|
|
94fc9b639f | ||
|
|
ff449c8817 | ||
|
|
dbd5b42b86 | ||
|
|
ea4218840c | ||
|
|
86477b594a | ||
|
|
16a9c5c41d | ||
|
|
a2a801d68a | ||
|
|
5fa02d7c53 | ||
|
|
09707af661 | ||
|
|
23984bdfa6 | ||
|
|
83d19f466c | ||
|
|
6f8515f943 | ||
|
|
16ab2461d9 | ||
|
|
da39ca47bc | ||
|
|
25de499931 | ||
|
|
e9d549cebb | ||
|
|
c75f6ae9d0 | ||
|
|
54173cd704 | ||
|
|
02cecaf1ac | ||
|
|
c6c27bf86a | ||
|
|
8d50959abd | ||
|
|
80eb45e775 | ||
|
|
ffe94607d2 | ||
|
|
639d53263d | ||
|
|
2019230dfd | ||
|
|
b2cce94841 | ||
|
|
274d2f7d0a | ||
|
|
02e9aef9f2 | ||
|
|
601194b93f | ||
|
|
879af0a0c2 | ||
|
|
3aff41a4d0 | ||
|
|
ed5bb26a59 | ||
|
|
905c59d23d | ||
|
|
a0f10cacdc | ||
|
|
cf62ae74e6 | ||
|
|
1af5bf1633 | ||
|
|
51706e1478 | ||
|
|
58cb561712 | ||
|
|
c279d43404 | ||
|
|
b699f87b99 | ||
|
|
87931064fa | ||
|
|
d85a0f1899 | ||
|
|
14e5395b34 | ||
|
|
d6b4e788a8 | ||
|
|
b0425b2da7 | ||
|
|
0899aa8a49 | ||
|
|
b996396f17 | ||
|
|
5bbc2abe8b | ||
|
|
fd8a8dfb4a | ||
|
|
15ee553f86 | ||
|
|
84f8fc94fc | ||
|
|
7ca8db1071 | ||
|
|
ea4bd8680b | ||
|
|
8ee0f2ca1b | ||
|
|
9037a09421 | ||
|
|
5391ebda85 | ||
|
|
2eea341578 | ||
|
|
9a4face265 | ||
|
|
9f6a28dbe7 | ||
|
|
3bf8177c58 | ||
|
|
5d8f9efa34 | ||
|
|
7d0f6c3892 | ||
|
|
b9a91d03df | ||
|
|
cadcda27df | ||
|
|
8cd1ec8188 | ||
|
|
005e4b7ea9 | ||
|
|
ca8c503a47 | ||
|
|
e475feae84 | ||
|
|
f653aa9aa7 | ||
|
|
c764faf2a7 | ||
|
|
5b6554ff37 | ||
|
|
47a484fe3d | ||
|
|
9134c3eb95 | ||
|
|
9a4af754f0 | ||
|
|
9d5bbaf3b1 | ||
|
|
d732fa2016 | ||
|
|
91d81d53c1 | ||
|
|
cc528cb188 | ||
|
|
dc23330ce0 | ||
|
|
dda4df16d2 | ||
|
|
dcb5e2d177 | ||
|
|
2888ceb710 | ||
|
|
3e83be2bf0 | ||
|
|
6b566cf69d | ||
|
|
db7cd130b8 | ||
|
|
6bc287d2b3 | ||
|
|
4f2eea4158 | ||
|
|
b152619c03 | ||
|
|
34ee842ec1 | ||
|
|
a1f15c5b10 | ||
|
|
e91cc93289 | ||
|
|
18f49f0853 | ||
|
|
d7c3b3612e | ||
|
|
18b6a51a9e | ||
|
|
d5b3487298 | ||
|
|
231a32de2f | ||
|
|
62994d5825 | ||
|
|
a75de21584 | ||
|
|
b3d4953da2 | ||
|
|
c63af981ab | ||
|
|
fbe0d9fc39 | ||
|
|
d2b4d0d745 | ||
|
|
9b65bc95a0 | ||
|
|
4203ad18be | ||
|
|
39ddd236e6 | ||
|
|
86c528c199 | ||
|
|
ed0b273f47 | ||
|
|
da6ad87370 | ||
|
|
8232a1406f | ||
|
|
de1bf7f746 | ||
|
|
34d8988915 | ||
|
|
24c9090ff1 | ||
|
|
dfb63e9965 | ||
|
|
fe49d12df3 | ||
|
|
234ff23d1f | ||
|
|
7882f66a67 | ||
|
|
f84d88bf4d | ||
|
|
2730a27702 | ||
|
|
f606ef0431 | ||
|
|
b4dc7cdd88 | ||
|
|
74bcc041af | ||
|
|
5f53c683c7 | ||
|
|
8fa4a013d1 | ||
|
|
25f5d4f9ef | ||
|
|
b0263b5cc2 | ||
|
|
5deb859810 | ||
|
|
ac34d0228a | ||
|
|
6cc9bb78e9 | ||
|
|
ba909a4aec | ||
|
|
f1dc4bb555 | ||
|
|
59105e394d | ||
|
|
e6faa7c59f | ||
|
|
4bdc2f5474 | ||
|
|
0b82682dd9 | ||
|
|
ec7103eb28 | ||
|
|
14bbbb0ae9 | ||
|
|
01cf2f2c5b | ||
|
|
61ba3c64a0 | ||
|
|
ec51019632 | ||
|
|
4bd5c230e4 | ||
|
|
687387fb0b | ||
|
|
b0dc9f9fa0 | ||
|
|
9debb0a7c7 | ||
|
|
2c0e3331f6 | ||
|
|
3b40d61cf3 | ||
|
|
fb3fef505f | ||
|
|
0b44c170f8 | ||
|
|
ab7e4e17ca | ||
|
|
d1eb7d0c85 | ||
|
|
66a51ea9cd | ||
|
|
37c8e59d2c | ||
|
|
a8a79bbe3a | ||
|
|
e56685b744 | ||
|
|
9a5eb5aaf7 | ||
|
|
8bfed55f31 | ||
|
|
a683147c80 | ||
|
|
4d578a8cbc | ||
|
|
c88f2d05ed | ||
|
|
31904fc3ae | ||
|
|
32e93ea25d | ||
|
|
81d528e0f6 | ||
|
|
1e982e2bfe | ||
|
|
b93d4ac3bc | ||
|
|
37ad5647f8 | ||
|
|
5a8fa30787 | ||
|
|
65947fabaa | ||
|
|
a7fcf6779c | ||
|
|
55afd3fd56 | ||
|
|
a05a16f67b | ||
|
|
29e5dee001 | ||
|
|
24794a67e9 | ||
|
|
6c6f90de51 | ||
|
|
9b330b8226 | ||
|
|
9cab1f4f1e | ||
|
|
bf5f7c60de | ||
|
|
2a11926cdf | ||
|
|
ee9eb628fd | ||
|
|
5a372c3e82 | ||
|
|
e99d1180c2 | ||
|
|
d7598df136 | ||
|
|
f85ac52ae5 | ||
|
|
046a69d8c8 | ||
|
|
772ff5319c | ||
|
|
8617418fc8 | ||
|
|
71ecb3f6da | ||
|
|
cf59b54f6a | ||
|
|
e359187428 | ||
|
|
50e11cc90e | ||
|
|
f3fbc70167 | ||
|
|
f62e0252aa | ||
|
|
3b40812187 | ||
|
|
290089770f | ||
|
|
691547dd50 | ||
|
|
45233abc35 | ||
|
|
c6968d1094 | ||
|
|
e27f80e5e0 | ||
|
|
693ed68efe | ||
|
|
8b8498ad60 | ||
|
|
8603b1ea86 | ||
|
|
1455488e94 | ||
|
|
7eadee8fd8 | ||
|
|
82546afb25 | ||
|
|
544670d998 | ||
|
|
a57b2bdbdd | ||
|
|
46fe7f7cdf | ||
|
|
f8266d1403 | ||
|
|
53020c5ba7 | ||
|
|
85013928b2 | ||
|
|
575382a629 | ||
|
|
36d93c270a | ||
|
|
b5ae55da9e | ||
|
|
705a257965 | ||
|
|
088bcaf651 | ||
|
|
2f9f41c2b1 | ||
|
|
f1643db448 | ||
|
|
8311156091 | ||
|
|
263de3af98 | ||
|
|
c185c11275 | ||
|
|
f7b24cf393 | ||
|
|
f868fa915f | ||
|
|
e27b6de202 | ||
|
|
89e307e5f7 | ||
|
|
7e023607c3 | ||
|
|
eeafa1065f | ||
|
|
eefd50e9d5 | ||
|
|
81ff3dff10 | ||
|
|
57fa5e1d9c | ||
|
|
e0391bc79c | ||
|
|
fdd0461234 | ||
|
|
f7ebe5e9b6 | ||
|
|
8ab5e8bc70 | ||
|
|
9ff8217f53 | ||
|
|
e6afbc9e4b | ||
|
|
79f8d42e44 | ||
|
|
3f5336a71a | ||
|
|
f10ac01cfc | ||
|
|
2922e1f137 | ||
|
|
251b144409 | ||
|
|
f28290b23d | ||
|
|
d4b82c4291 | ||
|
|
f44f7327f6 | ||
|
|
d0b577648b | ||
|
|
c34e5a5c17 | ||
|
|
f6208c0a02 | ||
|
|
c6f52a2937 | ||
|
|
8f0393bc2a | ||
|
|
3c142a1349 | ||
|
|
831a528131 | ||
|
|
dd2fb6996e | ||
|
|
5bfe3815e4 | ||
|
|
c8f5606a03 | ||
|
|
af55172cb2 | ||
|
|
69eb15942f | ||
|
|
65b7403abc | ||
|
|
ad1d404310 | ||
|
|
720610186c | ||
|
|
48400248c7 | ||
|
|
6d8a857931 | ||
|
|
e366f77dd1 | ||
|
|
d17140e6f8 | ||
|
|
a24b491477 | ||
|
|
e95d39526e | ||
|
|
483b838746 | ||
|
|
93daa44c0d | ||
|
|
6dd1f62cd9 | ||
|
|
760ac969fc | ||
|
|
bd10151702 | ||
|
|
1f2bef724f | ||
|
|
4b700c8efd | ||
|
|
948cccca5e | ||
|
|
ad4aefcdf8 | ||
|
|
b13f107aa2 | ||
|
|
2eb5c607b4 | ||
|
|
db1d34bb37 | ||
|
|
19f4387923 | ||
|
|
5b4800b856 | ||
|
|
7a7cf9847c | ||
|
|
40de18fa7b | ||
|
|
8a4aa64074 | ||
|
|
d3ed746975 | ||
|
|
a0dde2814b | ||
|
|
9ea8007bfc | ||
|
|
5874a78ce0 | ||
|
|
b6a2c9850d | ||
|
|
1a3b9e367b | ||
|
|
682c429f1a | ||
|
|
f90bbea1bb | ||
|
|
9faf4d6337 | ||
|
|
3a8ec97a7b | ||
|
|
275a75b61d | ||
|
|
bfad186efb | ||
|
|
af4d6a6c16 | ||
|
|
63e8c5e5af | ||
|
|
d7d8e54831 | ||
|
|
3f89370578 | ||
|
|
d59727b260 | ||
|
|
2f6d271218 | ||
|
|
322a8a2f5f | ||
|
|
9a15ef94cf | ||
|
|
358459b9dd | ||
|
|
407282e0c9 | ||
|
|
625855c0e9 | ||
|
|
1defc4357a | ||
|
|
c94626bd6c | ||
|
|
b467a315e0 | ||
|
|
295622ad3d | ||
|
|
2332e5e4a2 | ||
|
|
364e5aa09d | ||
|
|
7744549dc0 | ||
|
|
b4297a8848 | ||
|
|
720137c1ef | ||
|
|
383540f64d | ||
|
|
651a14af60 | ||
|
|
1b9e20c97c | ||
|
|
8fbb47852d | ||
|
|
4f93d08ea8 | ||
|
|
bff4374f1a | ||
|
|
c0447acdf2 | ||
|
|
9fa2f0b2b4 | ||
|
|
da07db4098 | ||
|
|
b857256916 | ||
|
|
c225528cd3 | ||
|
|
cb1bc64daa | ||
|
|
f6b13c27c9 | ||
|
|
2394f5d158 | ||
|
|
9f6d456bf3 | ||
|
|
3fe5da1434 | ||
|
|
25e77490c3 | ||
|
|
e978c14da6 | ||
|
|
cea31cb5af | ||
|
|
af4a345fe5 | ||
|
|
63ea389fd7 | ||
|
|
14a73ecab3 | ||
|
|
8530e9fc29 | ||
|
|
7e14caca92 | ||
|
|
c28099eccf | ||
|
|
40bf847c58 | ||
|
|
149c523a68 | ||
|
|
fed0c47b6d | ||
|
|
1dd9345bd0 | ||
|
|
489d63e4de | ||
|
|
20c24abac0 | ||
|
|
140e4e05ad | ||
|
|
e8b237714c | ||
|
|
a34fa5f73e | ||
|
|
4222a44df2 | ||
|
|
1132fc3e1e | ||
|
|
e1f813fc8f | ||
|
|
6de94e813a | ||
|
|
67169771ec | ||
|
|
b8f661a47c | ||
|
|
5aa90afde1 | ||
|
|
cc9e35596c | ||
|
|
fb2bde5304 | ||
|
|
f56eab6a23 | ||
|
|
14d4e52c8c | ||
|
|
5464aa57e3 | ||
|
|
58a263dc96 | ||
|
|
c90b84836a | ||
|
|
fb4f1f84cc | ||
|
|
748b002a47 | ||
|
|
4111aa4b39 | ||
|
|
2f26d54f76 | ||
|
|
a51e426779 | ||
|
|
5c3f38588e | ||
|
|
d7b0e4f9ae | ||
|
|
ba30666e16 | ||
|
|
69fef74d61 | ||
|
|
79d820bc8d | ||
|
|
6dff3833c4 | ||
|
|
c3667e71a4 | ||
|
|
759a8cd557 | ||
|
|
d2e628f174 | ||
|
|
b3c25915c6 | ||
|
|
dbb5337204 | ||
|
|
4c4c58ad3e | ||
|
|
1131ad61fd | ||
|
|
aa9f009f9c | ||
|
|
9c0f462081 | ||
|
|
a621ba9380 | ||
|
|
3ebf18873d | ||
|
|
3c368a63db | ||
|
|
3a2999003d | ||
|
|
b871cd0954 | ||
|
|
6991c233a1 | ||
|
|
681f5580a9 | ||
|
|
f6b637bc3e | ||
|
|
144acbcf0c | ||
|
|
7415d1bf15 | ||
|
|
d1514b9a44 | ||
|
|
8c4ae9ab63 | ||
|
|
6607d5d514 | ||
|
|
c013a8b057 | ||
|
|
984aac52d5 | ||
|
|
c121a0bd30 | ||
|
|
bb229fd959 | ||
|
|
b4bbd1b933 | ||
|
|
59e3454dc8 | ||
|
|
7e343b6e3a | ||
|
|
5e7ac9cc2c | ||
|
|
afa3ac76aa | ||
|
|
05c5e8f8f5 | ||
|
|
8aade987bd | ||
|
|
c70bcacd39 | ||
|
|
095992a1fe | ||
|
|
e5b53fe278 | ||
|
|
465cd8c726 | ||
|
|
b72143ca4c | ||
|
|
007559a3cf | ||
|
|
fafa9d924d | ||
|
|
97e35442b2 | ||
|
|
39de93e509 | ||
|
|
db1dc0c216 | ||
|
|
61024439f9 | ||
|
|
200a5025f0 | ||
|
|
bc1a0e481c | ||
|
|
c7b8bee85e | ||
|
|
fbb20a602e | ||
|
|
a085678049 | ||
|
|
1eb3ce4b09 | ||
|
|
dc9a5e73be | ||
|
|
8bdfe319ac | ||
|
|
bd31a5b061 | ||
|
|
ab2ee7d075 | ||
|
|
054e8b8439 | ||
|
|
eaa9eef289 | ||
|
|
e4bd00c0de | ||
|
|
73742e62e6 | ||
|
|
4ba570f300 | ||
|
|
a39a6ba5e9 | ||
|
|
7186c6ce9f | ||
|
|
5f65c48c02 | ||
|
|
9834d974bc | ||
|
|
957f014d0e | ||
|
|
d5fe93d688 | ||
|
|
ddc3b4e06c | ||
|
|
dfeb7bee79 | ||
|
|
8ccd3a5a9c | ||
|
|
c4687cc3e7 | ||
|
|
98412343e3 | ||
|
|
bcc7ca7226 | ||
|
|
c25a1a934d | ||
|
|
4b1b6830b9 | ||
|
|
7cfcdc9f03 | ||
|
|
3e7c6c3609 | ||
|
|
9d1dc93733 | ||
|
|
10a016409f | ||
|
|
8793807788 | ||
|
|
830fc09774 | ||
|
|
4304ec7b54 | ||
|
|
7112dd9ed0 | ||
|
|
eeccecb542 | ||
|
|
0b473d47c6 | ||
|
|
780a93bb11 | ||
|
|
04d25178b4 | ||
|
|
3098e1ab13 | ||
|
|
209103d6c3 | ||
|
|
72c3d301a0 | ||
|
|
1bad730545 | ||
|
|
339e3e2ad4 | ||
|
|
2720d2231c | ||
|
|
e274989d0b | ||
|
|
c17b09e53d | ||
|
|
a1c16b9bea | ||
|
|
eccb7b76d6 | ||
|
|
de5a17dd9f | ||
|
|
fb2edf6888 | ||
|
|
8e958027e6 | ||
|
|
7d8441fad9 | ||
|
|
22799aa2c1 | ||
|
|
5c05b43708 | ||
|
|
948c1719e9 | ||
|
|
476d3b806c | ||
|
|
911c5ce96c | ||
|
|
0c9ea1c375 | ||
|
|
abf7b40e70 | ||
|
|
f9bccef201 | ||
|
|
fb67d908c8 | ||
|
|
528f0dcbff | ||
|
|
52bfa24a20 | ||
|
|
85ac2e810f | ||
|
|
31d3d4bec8 | ||
|
|
11b05aa534 | ||
|
|
9109c6050c | ||
|
|
9d4caf43e5 | ||
|
|
1add810f8f | ||
|
|
420d496965 | ||
|
|
51c65bca4c | ||
|
|
4f0ce62a2d | ||
|
|
ca8eabd14b | ||
|
|
6ad54ce8da | ||
|
|
5da830a0e5 | ||
|
|
cd308475b6 | ||
|
|
0bd9c397cc | ||
|
|
8aa004d474 | ||
|
|
c6a53d5852 | ||
|
|
68621a8ca0 | ||
|
|
e9d71d99d1 | ||
|
|
f96be99d75 | ||
|
|
8680b4bab8 | ||
|
|
4fb3f04ca1 | ||
|
|
31858088f5 | ||
|
|
e4fb45fab4 | ||
|
|
febab48943 | ||
|
|
e255587335 | ||
|
|
77b8b59734 | ||
|
|
4c9bcbf64e | ||
|
|
23ffc9977b | ||
|
|
1b9010435f | ||
|
|
454a6f387f | ||
|
|
457dc9f8e2 | ||
|
|
682d3f2eb3 | ||
|
|
f8bac1aa56 | ||
|
|
c43e6d879f | ||
|
|
fc56980399 | ||
|
|
520f8595aa | ||
|
|
d6c3e81e5c | ||
|
|
2e6c67e1b6 | ||
|
|
4449a924a3 | ||
|
|
045353bc1b | ||
|
|
8d9fed3f7f | ||
|
|
28216b475f | ||
|
|
58a2dec385 | ||
|
|
33bb4c86b9 | ||
|
|
c2060b1d93 | ||
|
|
1f0dba4294 | ||
|
|
bff70c8d6b | ||
|
|
385fb8b96d | ||
|
|
b9cf9ac6c8 | ||
|
|
a1945b239f | ||
|
|
c8b5a00e57 | ||
|
|
b69bacc202 | ||
|
|
c867e137b1 | ||
|
|
104d9d7b53 | ||
|
|
c1efcf8e92 | ||
|
|
c821b560f2 | ||
|
|
6b11c70647 | ||
|
|
4a6bc7ecdb | ||
|
|
ba20f0853a | ||
|
|
62415550df | ||
|
|
93f951af60 | ||
|
|
f73e418b36 | ||
|
|
82a60e8c5f | ||
|
|
ec7525668b | ||
|
|
5fa793e1f5 | ||
|
|
504a34df24 | ||
|
|
6f0db7303b | ||
|
|
fc2ff910e6 | ||
|
|
983f33556f | ||
|
|
13c4313703 | ||
|
|
c6b45340ba | ||
|
|
de576915d5 | ||
|
|
fd9cc1ac52 | ||
|
|
a318bd350b | ||
|
|
8b267fb6d7 | ||
|
|
a8f10fbcd8 | ||
|
|
2417bf548a | ||
|
|
aeb49232dd | ||
|
|
c36210e9c2 | ||
|
|
f4bd44d3e0 | ||
|
|
4ea96b477f | ||
|
|
a6b1310cf3 | ||
|
|
77f5cb2b80 | ||
|
|
86f6bc4ae2 | ||
|
|
df6006c221 | ||
|
|
d09625ebbd | ||
|
|
36b2375db0 | ||
|
|
4f6a150592 | ||
|
|
fd7372a2e9 | ||
|
|
671ed871e3 | ||
|
|
83bb91e1d1 | ||
|
|
e5fb18fe5c | ||
|
|
916ad188bc | ||
|
|
828644cce4 | ||
|
|
b24cd9ca66 | ||
|
|
181de15b93 | ||
|
|
fefb0cda01 | ||
|
|
68dd37cc1d | ||
|
|
4547e153ac | ||
|
|
ef1504ad5b | ||
|
|
1931bbc558 | ||
|
|
a042cf5ca6 | ||
|
|
154d568aa3 | ||
|
|
b33fde8c9e | ||
|
|
afdac8c1e9 | ||
|
|
9d0163b4eb | ||
|
|
a4548c69e9 | ||
|
|
0ee748cd10 | ||
|
|
b817ff37b5 | ||
|
|
2314d1db86 | ||
|
|
674dbcf818 | ||
|
|
b3765f13da | ||
|
|
effab63a70 | ||
|
|
f008654ac7 | ||
|
|
335edb214a | ||
|
|
d7dc0ea8ae | ||
|
|
34a3c52aed | ||
|
|
34d809c510 | ||
|
|
00cf7e696d | ||
|
|
6f15e104ef | ||
|
|
3e30048c03 | ||
|
|
4a61136690 | ||
|
|
ea2debb9dd | ||
|
|
25e1102832 | ||
|
|
bfba45e74a | ||
|
|
44f9aa5c35 | ||
|
|
301560b21a | ||
|
|
696d37dd08 | ||
|
|
a6fe84b07a | ||
|
|
fd864c0cc0 | ||
|
|
a8867df4e6 | ||
|
|
950fc5cf90 | ||
|
|
eef72d8b70 | ||
|
|
54b51391ad | ||
|
|
7ed65d59d3 | ||
|
|
41adf5913f | ||
|
|
0cd94da7ce | ||
|
|
5ff8ebca92 | ||
|
|
61a69799d4 | ||
|
|
0ddc78587b | ||
|
|
f552958f03 | ||
|
|
f9c043ba32 | ||
|
|
15af3e1dc2 | ||
|
|
7bf3a344f3 | ||
|
|
14e8e90ee3 | ||
|
|
d52c202518 | ||
|
|
c728f4ea8d | ||
|
|
c20c2713d0 | ||
|
|
d398c73214 | ||
|
|
9e6b455323 | ||
|
|
5a2271c64e | ||
|
|
eb6fcf639f | ||
|
|
a85cc93026 | ||
|
|
56fd18a8e9 | ||
|
|
82d0ebb714 | ||
|
|
df5772d40b | ||
|
|
3030d5961d | ||
|
|
6974768457 | ||
|
|
d31cd2fcdc | ||
|
|
b916bdbcca | ||
|
|
67d53cf5ce | ||
|
|
175d6a173e | ||
|
|
b7140e15a5 | ||
|
|
76310dae1b | ||
|
|
01a041aebf | ||
|
|
031c0c8772 | ||
|
|
fd1e303403 | ||
|
|
45b63cb33f | ||
|
|
c9dfecb68c | ||
|
|
aa6406eae0 | ||
|
|
26e4c6db88 | ||
|
|
2439bd1cd8 | ||
|
|
1fdcdd02bf | ||
|
|
bb94a49662 | ||
|
|
2ebec55bbc | ||
|
|
5fe23c7ad1 | ||
|
|
b6a5c108de | ||
|
|
83ce7cf44d | ||
|
|
28632d192f | ||
|
|
2a265bf716 | ||
|
|
b06800860c | ||
|
|
75702d823f | ||
|
|
f865b737e6 | ||
|
|
2169354f0d | ||
|
|
8dc1217c36 | ||
|
|
0a1511f09f | ||
|
|
73030f150a | ||
|
|
a1f4702647 | ||
|
|
2ddfe63fa4 | ||
|
|
79ffe92864 | ||
|
|
e5178c9414 | ||
|
|
f779febc32 | ||
|
|
5afd3d6b08 | ||
|
|
6005574019 | ||
|
|
645dcecaca | ||
|
|
1686138499 | ||
|
|
cd1ed27f1e | ||
|
|
3b210b06d5 | ||
|
|
0f9c1b03a5 | ||
|
|
217244c367 | ||
|
|
852868cf89 | ||
|
|
a52a2ccc31 | ||
|
|
210ae6b0ee | ||
|
|
c6e55429e4 | ||
|
|
07b27dd485 | ||
|
|
5166dbd446 | ||
|
|
0722923a78 | ||
|
|
a85698b009 | ||
|
|
9b2b619121 | ||
|
|
ac097f6513 | ||
|
|
a383289457 | ||
|
|
e76b44cff1 | ||
|
|
0f9f6bbe5d | ||
|
|
c48670fa74 | ||
|
|
c530619039 | ||
|
|
5e221e7e97 | ||
|
|
65909a5f2e | ||
|
|
bbdd4c0504 | ||
|
|
9924d26ff6 | ||
|
|
b10aab6057 | ||
|
|
ccad48fbb4 | ||
|
|
91e9549ec6 | ||
|
|
066bf6f15d | ||
|
|
56df30a4da | ||
|
|
27ce25f5c5 | ||
|
|
334d0b1863 | ||
|
|
437645d5fd | ||
|
|
280536e93c | ||
|
|
611b37c847 | ||
|
|
5e3198c9c6 | ||
|
|
6ef047db3c | ||
|
|
cdab715463 | ||
|
|
96ac361c8e | ||
|
|
ed4950cd1f | ||
|
|
afddf4bf2d | ||
|
|
9c37ad8b94 | ||
|
|
9877f513e2 | ||
|
|
f4b5082827 | ||
|
|
1627928fb2 | ||
|
|
6ff5aa9e02 | ||
|
|
20601cd7ba | ||
|
|
2d6b4afa2d | ||
|
|
4ce14ec4cc | ||
|
|
0f1d736716 | ||
|
|
edeb6ebe3c | ||
|
|
ab7f008bbb | ||
|
|
1e60af1ffb | ||
|
|
4dd1fca0a7 | ||
|
|
81b97da75e | ||
|
|
6a7d6a1458 | ||
|
|
2835ede747 | ||
|
|
59f77ac831 | ||
|
|
3e63efc178 | ||
|
|
4aa22cc1c3 | ||
|
|
4fdda9a184 | ||
|
|
5bd9f41104 | ||
|
|
486ea63a8a | ||
|
|
0919a40c75 | ||
|
|
3de2fb4809 | ||
|
|
3d5a9ebf42 | ||
|
|
be3e111e63 | ||
|
|
8a0bed7238 | ||
|
|
d2556b6c36 | ||
|
|
506ca4f95c | ||
|
|
5b2c57d5c7 | ||
|
|
7c2b1ac73d | ||
|
|
a55669d16f | ||
|
|
656062bc25 | ||
|
|
b42401a909 | ||
|
|
2c6c110265 | ||
|
|
e7b3458f34 | ||
|
|
e0ad949141 | ||
|
|
28d27128d1 | ||
|
|
ebbe715581 | ||
|
|
af138944b5 | ||
|
|
4603d1dc2a | ||
|
|
e323906083 | ||
|
|
6cb115ed74 | ||
|
|
0149068126 | ||
|
|
7894258a26 | ||
|
|
775242255a | ||
|
|
faa4f341e6 | ||
|
|
a079649563 | ||
|
|
63359532a3 | ||
|
|
5d42a828d2 | ||
|
|
2da03d4931 | ||
|
|
4235e327fc | ||
|
|
0d3454cd24 | ||
|
|
5850650713 | ||
|
|
47f3cb4b71 | ||
|
|
2b802079a0 | ||
|
|
0d416f724c | ||
|
|
a6a0a8b1b1 | ||
|
|
dd1a3ed496 | ||
|
|
9f3831e733 | ||
|
|
fd1107a5a3 | ||
|
|
a9a78d5565 | ||
|
|
a794e27235 | ||
|
|
9bb9d6548a | ||
|
|
3625ca9edc | ||
|
|
3293b57537 | ||
|
|
867458b52f | ||
|
|
8daca53be3 | ||
|
|
4174fd2add | ||
|
|
d3041f99cc | ||
|
|
6acfa2971b | ||
|
|
7271ed39a0 | ||
|
|
639e84bb88 | ||
|
|
36ad45cfbc | ||
|
|
c0540db282 | ||
|
|
7d6008b0a9 | ||
|
|
af96d30122 | ||
|
|
bf75cca438 | ||
|
|
3285ecbe04 | ||
|
|
6906ad99b7 | ||
|
|
f3c5289013 | ||
|
|
be9473adf7 | ||
|
|
ec28f73df9 | ||
|
|
d19f4713a2 | ||
|
|
2e79c34068 | ||
|
|
c7e5696974 | ||
|
|
154d034e8f | ||
|
|
916eeaef4c | ||
|
|
ad18e30de7 | ||
|
|
d4917fefc9 | ||
|
|
67b16c27f3 | ||
|
|
f15fe80d3a | ||
|
|
fbb44b14dd | ||
|
|
103bcdd4cc | ||
|
|
5e04ebca18 | ||
|
|
b00812333a | ||
|
|
9012bfdea9 | ||
|
|
9e5877173e | ||
|
|
b165c3223d | ||
|
|
79213ad573 | ||
|
|
7df42903c6 | ||
|
|
42d4290acd | ||
|
|
8c449215a6 | ||
|
|
183d30c720 | ||
|
|
53a572ecac | ||
|
|
4173786b12 | ||
|
|
44abb9f635 | ||
|
|
fd6e29a8ec | ||
|
|
832e5368be | ||
|
|
e543d72879 | ||
|
|
b4b8648e25 | ||
|
|
ff2bca18a5 | ||
|
|
cf5cc2d8f9 | ||
|
|
a30fa604d7 | ||
|
|
18e90397d9 | ||
|
|
97f558faf4 | ||
|
|
69dacb0ede | ||
|
|
95e7d44035 | ||
|
|
d39a485d24 | ||
|
|
4f0a673f87 | ||
|
|
8618dcda74 | ||
|
|
cc8be32cac | ||
|
|
f65eb8fe7e | ||
|
|
7c1a69d136 | ||
|
|
7fdd4c4383 | ||
|
|
43cd14a025 | ||
|
|
5662ee908d | ||
|
|
de7fcb4d4d | ||
|
|
f6dea03c05 | ||
|
|
6e2ddd2dda | ||
|
|
2d97cad1dc | ||
|
|
1d9a3b645b | ||
|
|
a89c7f5c5c | ||
|
|
91af3a4021 | ||
|
|
7a5ecd3009 | ||
|
|
aed4fed56f | ||
|
|
7885df341e | ||
|
|
2921b3eb1f | ||
|
|
579b0a77b3 | ||
|
|
063f8a8c1b | ||
|
|
985d01d5a9 | ||
|
|
9f461f7091 | ||
|
|
0b4db84f30 | ||
|
|
7e7804b6d4 | ||
|
|
eee6f81fca | ||
|
|
9375fab06c | ||
|
|
d2987ce0cc | ||
|
|
a61c1e6456 | ||
|
|
0a1e008d5f | ||
|
|
7f9e9ff5db | ||
|
|
39498f78b7 | ||
|
|
8588aca948 | ||
|
|
3f63461d45 | ||
|
|
f5e9a3977b | ||
|
|
aa62cc78f0 | ||
|
|
d6bb2869c5 | ||
|
|
74764bbbe0 | ||
|
|
441e8d8656 | ||
|
|
52dd075b6a | ||
|
|
1821bf1230 | ||
|
|
ab720ddae7 | ||
|
|
b4cecee191 | ||
|
|
614597d1bd | ||
|
|
0165b1f987 | ||
|
|
8b3a1b57bf | ||
|
|
c421e46724 | ||
|
|
1f3b9413cd | ||
|
|
6855a89792 | ||
|
|
811701ebae | ||
|
|
18fa11fd88 | ||
|
|
767fd2ff87 | ||
|
|
68c5b09e3a | ||
|
|
d2f9b7586a | ||
|
|
4753b2a57a | ||
|
|
5119822c31 | ||
|
|
09d0483ee3 | ||
|
|
034fd8a9aa | ||
|
|
b3ec4e0c01 | ||
|
|
f0f71afd67 | ||
|
|
3cea291901 | ||
|
|
9504d48607 | ||
|
|
19438ff1d5 | ||
|
|
967b90b98e | ||
|
|
0d6d69e0a8 | ||
|
|
a50f8de913 | ||
|
|
32df7d79ad | ||
|
|
759215da8c | ||
|
|
894469ae0e | ||
|
|
79b0cfc990 | ||
|
|
a8dfe30546 | ||
|
|
dda34b6982 | ||
|
|
599e31c11f | ||
|
|
ffc4200b96 | ||
|
|
d23c48cc0c | ||
|
|
aaf0b498f8 | ||
|
|
bda3732a83 | ||
|
|
b2a9708856 | ||
|
|
6e3f79a231 | ||
|
|
395b01d22b | ||
|
|
d9aaa045fd | ||
|
|
d6f2cb7592 | ||
|
|
5804959ddf | ||
|
|
af572f8b29 | ||
|
|
104d0f4516 | ||
|
|
b061c1f756 | ||
|
|
89f99dba85 | ||
|
|
ea25526ded | ||
|
|
2d5b1263b5 | ||
|
|
371aacd734 | ||
|
|
af1b0b03d8 | ||
|
|
baee619d73 | ||
|
|
b3f5ba4260 | ||
|
|
7ec9c3591e | ||
|
|
407514301b | ||
|
|
35abf985a9 | ||
|
|
374bc8e2d3 | ||
|
|
48300bf767 | ||
|
|
78553d8323 | ||
|
|
8c0b47975c | ||
|
|
601a4a0f1d | ||
|
|
59cb902658 | ||
|
|
d876b7618c | ||
|
|
60cdf9fe86 | ||
|
|
619333c328 | ||
|
|
80d75a528f | ||
|
|
4ac45a041a | ||
|
|
51064a65b2 | ||
|
|
2e61617f83 | ||
|
|
dbbee06a55 | ||
|
|
181cdaecb5 | ||
|
|
c3fbe31fd4 | ||
|
|
f05366ae45 | ||
|
|
1c53e65b26 | ||
|
|
c01528b309 | ||
|
|
53dd480231 | ||
|
|
3c35b99759 | ||
|
|
8a34bf6678 | ||
|
|
8b5a707daa | ||
|
|
bf22e559c5 | ||
|
|
9e7543df02 | ||
|
|
52065a1462 | ||
|
|
01953af578 | ||
|
|
e160bf6fe0 | ||
|
|
ff9d2c52be | ||
|
|
3801e80dd9 | ||
|
|
1307a71b4c | ||
|
|
2c9072299e | ||
|
|
6bdc998496 | ||
|
|
1b990aa6ec | ||
|
|
057c709b41 | ||
|
|
d457db5053 | ||
|
|
22d8fe311a | ||
|
|
e9796ee966 | ||
|
|
6c201e285a | ||
|
|
6c326e1378 | ||
|
|
725c8aa9b7 | ||
|
|
7a2f340c22 | ||
|
|
c4af2e8eea | ||
|
|
63a7051b86 | ||
|
|
9f75bfdeed | ||
|
|
083da01463 | ||
|
|
14980f2bfd | ||
|
|
6c08b459bf | ||
|
|
6d1ba14ab4 | ||
|
|
bbf035ebae | ||
|
|
03aa45a0b0 | ||
|
|
8d918cdf5e | ||
|
|
2494d45e8f | ||
|
|
011f480fc1 | ||
|
|
771765f32b | ||
|
|
e9a331dbd5 | ||
|
|
348cbf86d8 | ||
|
|
ecaaaa66ed | ||
|
|
0ab85ec870 | ||
|
|
a27ee4ac56 | ||
|
|
56234daf82 | ||
|
|
668099b542 | ||
|
|
a4725c24bc | ||
|
|
1a1fdb6fdf | ||
|
|
0bb0df2a60 | ||
|
|
5163031869 | ||
|
|
13523fbbe4 | ||
|
|
e9b38db8b4 | ||
|
|
eeed1c7492 | ||
|
|
655ddbeb42 | ||
|
|
69ac2d64ad | ||
|
|
34f110f16a | ||
|
|
09e35d5a0c | ||
|
|
41081118ef | ||
|
|
1ca4e275de | ||
|
|
c9c4a80387 | ||
|
|
3d0ac0f9f4 | ||
|
|
ffaac958c4 | ||
|
|
f2f503b9ab | ||
|
|
117306bd66 | ||
|
|
76b28d2a2c | ||
|
|
41ba4ac12c | ||
|
|
54f85e9689 | ||
|
|
aabf3e18ef | ||
|
|
daafdeedc2 | ||
|
|
426e936740 | ||
|
|
aa0c338c05 | ||
|
|
ea7f6bf7d7 | ||
|
|
2c524020af | ||
|
|
6331c43f68 | ||
|
|
a6168a7d64 | ||
|
|
1c083f836b | ||
|
|
1756c28ed9 | ||
|
|
91a42e6e29 | ||
|
|
0e14d257ad | ||
|
|
ba72d8bca2 | ||
|
|
19420e901e | ||
|
|
7aa66aff74 | ||
|
|
5c3c5717ab | ||
|
|
a5a66a5e8c | ||
|
|
b17b492741 | ||
|
|
5fe1db24c1 | ||
|
|
2ba7c3c057 | ||
|
|
8e34fbb124 | ||
|
|
d971d9f71f | ||
|
|
e033352752 | ||
|
|
96f79f7c72 | ||
|
|
80cdc98902 | ||
|
|
d6af98c6d7 | ||
|
|
5a3ebe6c01 | ||
|
|
84b8cb7817 | ||
|
|
33d13c74d3 | ||
|
|
6fa53151fb | ||
|
|
4261891a35 | ||
|
|
a0a138081d | ||
|
|
f13266b1fc | ||
|
|
1287d7f6a0 | ||
|
|
e437a23029 | ||
|
|
b71314b8f6 | ||
|
|
d9fcc085a6 | ||
|
|
dd542091e1 | ||
|
|
85cb950fa8 | ||
|
|
09e25738a8 | ||
|
|
9452b01e9c | ||
|
|
f24d889ee7 | ||
|
|
d8bf8dd2ae | ||
|
|
90233cd299 | ||
|
|
fdbfb81d25 | ||
|
|
e1634195be | ||
|
|
c317e8562e | ||
|
|
49b814a36d | ||
|
|
e5e77508b8 | ||
|
|
fff8e9e8cd | ||
|
|
814ee3eef8 | ||
|
|
be2435db27 | ||
|
|
c54ea1d591 | ||
|
|
24c0bb5247 | ||
|
|
e84a8a58c7 | ||
|
|
94e165f0b0 | ||
|
|
ed3aef88ff | ||
|
|
83df75915e | ||
|
|
7c4dec9e7a | ||
|
|
a0eb6ca6dc | ||
|
|
d6216d95db | ||
|
|
a0e5332897 | ||
|
|
23bdd9a5ca | ||
|
|
2b2b838745 | ||
|
|
a5feeb40a7 | ||
|
|
1ba0a49778 | ||
|
|
665ff06ad1 | ||
|
|
b81435be29 | ||
|
|
4daab74e27 | ||
|
|
a7fbd567fd | ||
|
|
f90752bdb7 | ||
|
|
c5590639b1 | ||
|
|
098ab73ba1 | ||
|
|
060b0b927b | ||
|
|
786e06b27f | ||
|
|
ef1c34a9c0 | ||
|
|
b97481f2d9 | ||
|
|
8d74b7e7ce | ||
|
|
635c97b1ad | ||
|
|
673c96c917 | ||
|
|
15fc49d84d | ||
|
|
54cfd194f1 | ||
|
|
be561c6d9f | ||
|
|
dc8c27dfc4 | ||
|
|
ce7f92b540 | ||
|
|
f0271cd395 | ||
|
|
2a4c076854 | ||
|
|
c852c56231 | ||
|
|
614ffc12c0 | ||
|
|
d9b2545cdd | ||
|
|
1ae6b4f108 | ||
|
|
373efa0564 | ||
|
|
6c464abdd4 | ||
|
|
5d5d77ae1b | ||
|
|
b2cfc19e96 | ||
|
|
e40e8bb7c5 | ||
|
|
a3158be2bd | ||
|
|
e305dee777 | ||
|
|
415efd4e03 | ||
|
|
f027788266 | ||
|
|
23acda3167 | ||
|
|
a8b4dc5a01 | ||
|
|
fd4efe6c7f | ||
|
|
68340eac9e | ||
|
|
175d47f71f | ||
|
|
0b764412b2 | ||
|
|
f7c0c670d7 | ||
|
|
18bd6ff3ca | ||
|
|
ac5326ba3f | ||
|
|
c5af56537b | ||
|
|
17cdd503e9 | ||
|
|
688950d0c2 | ||
|
|
eb3082cddb | ||
|
|
32bec08f30 | ||
|
|
a7f850d577 | ||
|
|
08f356cfa4 | ||
|
|
6e975ffe26 | ||
|
|
64981dd110 | ||
|
|
b5156bcc69 | ||
|
|
9ab99a1225 | ||
|
|
d5edec025c | ||
|
|
ef43463b99 | ||
|
|
ca2e95e6f4 | ||
|
|
559c50fa87 | ||
|
|
2ca0a05636 | ||
|
|
363de47313 | ||
|
|
bdb2803371 | ||
|
|
6eae438300 | ||
|
|
707ceb711a | ||
|
|
024646579e | ||
|
|
f895428e3d | ||
|
|
698456c205 | ||
|
|
43cf907a2e | ||
|
|
0a04ba5743 | ||
|
|
8b1a40d2e2 | ||
|
|
51ae0784cf | ||
|
|
efa5d3f629 | ||
|
|
fd5861026d | ||
|
|
1535ef9aac | ||
|
|
bb6f1f32a0 | ||
|
|
3effdee5c0 | ||
|
|
bf15c5fb45 | ||
|
|
5c3ba9e0d8 | ||
|
|
ce0b39d48b | ||
|
|
71e3498876 | ||
|
|
e8ec05bd51 | ||
|
|
2303c32940 | ||
|
|
e9d54bf0d6 | ||
|
|
e435a68aea | ||
|
|
d55143e6fb | ||
|
|
e2719c373d | ||
|
|
b1e9f9b3f8 | ||
|
|
2d512053a8 | ||
|
|
6cfddb68bb | ||
|
|
12d04e84d8 | ||
|
|
829e569ccd | ||
|
|
7e300e8789 | ||
|
|
6a6a93aec4 | ||
|
|
13cba764cc | ||
|
|
5263005e92 | ||
|
|
1a12a6c10c | ||
|
|
0b134f9266 | ||
|
|
07233ba9ae | ||
|
|
92aaae40f6 | ||
|
|
faeeaf5ecf | ||
|
|
edfbc2d937 | ||
|
|
ab8f870e73 | ||
|
|
2a5798c107 | ||
|
|
92441110bf | ||
|
|
1a9d59e804 | ||
|
|
9e7b9c5fe4 | ||
|
|
615172d29c | ||
|
|
5671323bc1 | ||
|
|
c0263eb3c3 | ||
|
|
a5a5358f7b | ||
|
|
2599fd85d7 | ||
|
|
84a308e5dc | ||
|
|
569d50f25b | ||
|
|
93221b9760 | ||
|
|
81a7f63782 | ||
|
|
1c7fd533c7 | ||
|
|
a0d9420be2 | ||
|
|
544dc8b639 | ||
|
|
68f02bbc80 | ||
|
|
1660b0a75b | ||
|
|
d691189973 | ||
|
|
03da6c9a0c | ||
|
|
46b0ed44bd | ||
|
|
362000d6df | ||
|
|
fb8b65e61b |
11
.env.example
|
|
@ -3,6 +3,12 @@
|
||||||
EXPO_PUBLIC_SUPABASE_URL=your_supabase_project_url
|
EXPO_PUBLIC_SUPABASE_URL=your_supabase_project_url
|
||||||
EXPO_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
|
EXPO_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
|
||||||
|
|
||||||
|
# Remote cache for TMDB (optional)
|
||||||
|
# Set to true to use local/remote cache server, and provide URL
|
||||||
|
EXPO_PUBLIC_USE_REMOTE_CACHE=false
|
||||||
|
EXPO_PUBLIC_CACHE_SERVER_URL=http://localhost:5173
|
||||||
|
EXPO_PUBLIC_DISABLE_LOCAL_CACHE=false
|
||||||
|
|
||||||
# MovieBox (MoviesMod) Keys
|
# MovieBox (MoviesMod) Keys
|
||||||
EXPO_PUBLIC_MOVIEBOX_PRIMARY_KEY=your_moviebox_primary_key
|
EXPO_PUBLIC_MOVIEBOX_PRIMARY_KEY=your_moviebox_primary_key
|
||||||
EXPO_PUBLIC_MOVIEBOX_TMDB_API_KEY=your_tmdb_api_key_for_moviebox
|
EXPO_PUBLIC_MOVIEBOX_TMDB_API_KEY=your_tmdb_api_key_for_moviebox
|
||||||
|
|
@ -11,3 +17,8 @@ EXPO_PUBLIC_MOVIEBOX_TMDB_API_KEY=your_tmdb_api_key_for_moviebox
|
||||||
EXPO_PUBLIC_TRAKT_CLIENT_ID=your_trakt_client_id
|
EXPO_PUBLIC_TRAKT_CLIENT_ID=your_trakt_client_id
|
||||||
EXPO_PUBLIC_TRAKT_CLIENT_SECRET=your_trakt_client_secret
|
EXPO_PUBLIC_TRAKT_CLIENT_SECRET=your_trakt_client_secret
|
||||||
EXPO_PUBLIC_TRAKT_REDIRECT_URI=stremioexpo://auth/trakt
|
EXPO_PUBLIC_TRAKT_REDIRECT_URI=stremioexpo://auth/trakt
|
||||||
|
|
||||||
|
# Skip Intro API (IntroDB)
|
||||||
|
# Fetches intro timestamps for TV shows to enable skip intro functionality
|
||||||
|
EXPO_PUBLIC_INTRODB_API_URL=https://api.introdb.app
|
||||||
|
EXPO_PUBLIC_DISCORD_USER_API=
|
||||||
4
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [tapframe]
|
||||||
|
ko_fi: tapframe
|
||||||
217
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
name: Bug report
|
||||||
|
description: Report a reproducible bug (one per issue).
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for reporting a bug.
|
||||||
|
|
||||||
|
If we can reproduce it, we can usually fix it. This form is just to get the basics in one place.
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Quick checks
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: checks
|
||||||
|
attributes:
|
||||||
|
label: Pre-flight checks
|
||||||
|
options:
|
||||||
|
- label: I searched existing issues and this is not a duplicate.
|
||||||
|
required: true
|
||||||
|
- label: I can reproduce this on the latest release or latest main build.
|
||||||
|
required: false
|
||||||
|
- label: This issue is limited to a single bug (not multiple unrelated problems).
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Version & device
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: app_version
|
||||||
|
attributes:
|
||||||
|
label: App version / OTA update ID
|
||||||
|
description: Release version, commit hash, or OTA update ID. You can find your OTA update ID in Settings > App updates > Current version (hold to copy).
|
||||||
|
placeholder: "e.g. 1.2.3, main@abc1234, or an OTA ID"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: install_method
|
||||||
|
attributes:
|
||||||
|
label: Install method
|
||||||
|
options:
|
||||||
|
- GitHub Release APK / IPA
|
||||||
|
- Expo Go
|
||||||
|
- Built from source
|
||||||
|
- Other (please describe below)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: platform
|
||||||
|
attributes:
|
||||||
|
label: Platform
|
||||||
|
options:
|
||||||
|
- Android phone/tablet
|
||||||
|
- iOS (iPhone/iPad)
|
||||||
|
- Android emulator
|
||||||
|
- iOS Simulator
|
||||||
|
- Other (please describe below)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: device_model
|
||||||
|
attributes:
|
||||||
|
label: Device model
|
||||||
|
description: "Example: iPhone 15 Pro, Pixel 8, Galaxy S23 Ultra, iPad Pro, etc."
|
||||||
|
placeholder: "e.g. iPhone 15 Pro"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: os_version
|
||||||
|
attributes:
|
||||||
|
label: OS version
|
||||||
|
placeholder: "e.g. Android 14, iOS 17.2"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: player_mode
|
||||||
|
attributes:
|
||||||
|
label: Player mode
|
||||||
|
description: If you are using an external player, most playback issues must be reported to that player instead.
|
||||||
|
options:
|
||||||
|
- Internal player (iOS: KSPlayer)
|
||||||
|
- Internal player (Android: ExoPlayer)
|
||||||
|
- Internal player (Android: MPV)
|
||||||
|
- External player
|
||||||
|
- Ask every time
|
||||||
|
- Not sure
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## What happened?
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: area
|
||||||
|
attributes:
|
||||||
|
label: Area (tag)
|
||||||
|
description: Pick the closest match. It helps triage.
|
||||||
|
options:
|
||||||
|
- Playback (start/stop/buffering)
|
||||||
|
- Streams / Sources (selection, loading, errors)
|
||||||
|
- Next Episode / Auto-play
|
||||||
|
- Watch Progress (resume, watched state, history)
|
||||||
|
- Subtitles (styling, sync)
|
||||||
|
- Audio tracks
|
||||||
|
- UI / Layout / Animations
|
||||||
|
- Settings
|
||||||
|
- Sync (Trakt / SIMKL / remote)
|
||||||
|
- Downloads
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: steps
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce
|
||||||
|
description: Exact steps. If it depends on specific content, describe it (movie/series, season/episode, source/addon name) without sharing private links.
|
||||||
|
placeholder: |
|
||||||
|
1. Open ...
|
||||||
|
2. Navigate to ...
|
||||||
|
3. Press ...
|
||||||
|
4. Observe ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
placeholder: "What you expected to happen."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: actual
|
||||||
|
attributes:
|
||||||
|
label: Actual behavior
|
||||||
|
placeholder: "What actually happened (include any on-screen error text/codes)."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: frequency
|
||||||
|
attributes:
|
||||||
|
label: Frequency
|
||||||
|
options:
|
||||||
|
- Always
|
||||||
|
- Often (more than 50%)
|
||||||
|
- Sometimes
|
||||||
|
- Rarely
|
||||||
|
- Once
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: regression
|
||||||
|
attributes:
|
||||||
|
label: Did this work before?
|
||||||
|
options:
|
||||||
|
- Not sure
|
||||||
|
- Yes, it used to work
|
||||||
|
- No, it never worked
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Extra context (optional)
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: media_details
|
||||||
|
attributes:
|
||||||
|
label: Media details (optional)
|
||||||
|
description: Only include what you can safely share.
|
||||||
|
placeholder: |
|
||||||
|
- Content type: series/movie
|
||||||
|
- Season/Episode: S1E2
|
||||||
|
- Stream/source: (addon name / source label)
|
||||||
|
- Video format: (if known)
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Logs (optional but helpful)
|
||||||
|
description: |
|
||||||
|
Not required, but super helpful for playback/crash issues.
|
||||||
|
If you can, include a short snippet from Metro bundler, Xcode, or `adb logcat`.
|
||||||
|
render: shell
|
||||||
|
placeholder: |
|
||||||
|
adb logcat -d | tail -n 300
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: extra
|
||||||
|
attributes:
|
||||||
|
label: Anything else? (optional)
|
||||||
|
description: Screenshots/recordings, related issues, workarounds, etc.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Downloads / Releases
|
||||||
|
url: https://github.com/tapframe/NuvioMobile/releases
|
||||||
|
about: Grab the latest GitHub Release APK/IPA here.
|
||||||
|
- name: Documentation
|
||||||
|
url: https://github.com/tapframe/NuvioMobile/blob/main/README.md
|
||||||
|
about: Read the README for setup and usage details.
|
||||||
78
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
name: Feature request
|
||||||
|
description: Suggest an improvement or new feature.
|
||||||
|
title: "[Feature]: "
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
One feature request per issue, please. The more real-world your use case is, the easier it is to evaluate.
|
||||||
|
|
||||||
|
Feature requests are reviewed as product proposals first.
|
||||||
|
Please do not open a pull request for a new feature, major UX change, or broad cosmetic update unless a maintainer has explicitly approved it first.
|
||||||
|
Unapproved feature PRs will usually be closed.
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: area
|
||||||
|
attributes:
|
||||||
|
label: Area (tag)
|
||||||
|
options:
|
||||||
|
- Playback
|
||||||
|
- Streams / Sources
|
||||||
|
- Next Episode / Auto-play
|
||||||
|
- Watch Progress
|
||||||
|
- Subtitles
|
||||||
|
- Audio
|
||||||
|
- UI / Layout / Animations
|
||||||
|
- Settings
|
||||||
|
- Sync (Trakt / SIMKL / remote)
|
||||||
|
- Downloads
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Problem statement
|
||||||
|
description: What problem are you trying to solve?
|
||||||
|
placeholder: "I want to be able to..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: proposed
|
||||||
|
attributes:
|
||||||
|
label: Proposed solution
|
||||||
|
description: What would you like the app to do?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: contribution_plan
|
||||||
|
attributes:
|
||||||
|
label: Are you planning to implement this yourself?
|
||||||
|
description: Major features are usually implemented in-house unless approved first.
|
||||||
|
options:
|
||||||
|
- No, this is only a proposal
|
||||||
|
- Maybe, but only if approved first
|
||||||
|
- Yes, but I understand implementation still needs maintainer approval
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternatives considered (optional)
|
||||||
|
description: Any workarounds or other approaches you considered.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: extra
|
||||||
|
attributes:
|
||||||
|
label: Additional context (optional)
|
||||||
|
description: Mockups, examples from other apps, etc.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
43
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
<!-- What changed in this PR? -->
|
||||||
|
|
||||||
|
## PR type
|
||||||
|
|
||||||
|
<!-- Pick one and delete the others -->
|
||||||
|
- Bug fix
|
||||||
|
- Small maintenance improvement
|
||||||
|
- Docs fix
|
||||||
|
- Approved larger change (link approval below)
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
<!-- Why this change is needed. Link bug/issue/context. -->
|
||||||
|
|
||||||
|
## Policy check
|
||||||
|
|
||||||
|
<!-- Confirm these before requesting review -->
|
||||||
|
- [ ] This PR is not cosmetic only.
|
||||||
|
- [ ] This PR does not add a new major feature without prior approval.
|
||||||
|
- [ ] This PR is small in scope and focused on one problem.
|
||||||
|
- [ ] If this is a larger or directional change, I linked the issue where it was approved.
|
||||||
|
|
||||||
|
<!-- PRs that do not match this policy will usually be closed without merge. -->
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
<!-- What you tested and how (manual + automated). -->
|
||||||
|
- [ ] iOS tested
|
||||||
|
- [ ] Android tested
|
||||||
|
|
||||||
|
## Screenshots / Video (UI changes only)
|
||||||
|
|
||||||
|
<!-- If UI changed, add before/after screenshots or a short clip. -->
|
||||||
|
|
||||||
|
## Breaking changes
|
||||||
|
|
||||||
|
<!-- Any breaking behavior/config/schema changes? If none, write: None -->
|
||||||
|
|
||||||
|
## Linked issues
|
||||||
|
|
||||||
|
<!-- Example: Fixes #123 -->
|
||||||
44
.gitignore
vendored
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
|
# Un-ignore specific react-native-video source files we patch
|
||||||
|
!node_modules/react-native-video/android/src/main/java/com/brentvatne/common/api/SubtitleStyle.kt
|
||||||
|
!node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt
|
||||||
!node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
|
!node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
|
||||||
|
|
||||||
# Expo
|
# Expo
|
||||||
|
|
@ -31,8 +34,12 @@ yarn-error.*
|
||||||
*.pem
|
*.pem
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env
|
|
||||||
.env*.local
|
.env*.local
|
||||||
|
.env
|
||||||
|
# Sentry
|
||||||
|
ios/sentry.properties
|
||||||
|
android/sentry.properties
|
||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
@ -47,6 +54,7 @@ android/build/
|
||||||
android/.gradle/
|
android/.gradle/
|
||||||
android/app/libs/*.aar
|
android/app/libs/*.aar
|
||||||
!android/app/libs/lib-decoder-ffmpeg-release.aar
|
!android/app/libs/lib-decoder-ffmpeg-release.aar
|
||||||
|
!android/app/libs/libmpv-release.aar
|
||||||
HEATING_OPTIMIZATIONS.md
|
HEATING_OPTIMIZATIONS.md
|
||||||
# sliderreadme.md
|
# sliderreadme.md
|
||||||
.cursor/mcp.json
|
.cursor/mcp.json
|
||||||
|
|
@ -65,3 +73,37 @@ ffmpegreadme.md
|
||||||
sliderreadme.md
|
sliderreadme.md
|
||||||
bottomsheet.md
|
bottomsheet.md
|
||||||
fastimage.md
|
fastimage.md
|
||||||
|
|
||||||
|
## Backup directories
|
||||||
|
backup_sdk54_upgrade/
|
||||||
|
SDK54_UPGRADE_SUMMARY.md
|
||||||
|
SDK54_UPGRADE_SUMMARY.md
|
||||||
|
build-and-publish-app-releases.sh
|
||||||
|
bottomnav.md
|
||||||
|
/TrailerServices
|
||||||
|
mmkv.md
|
||||||
|
fix-android-scroll-lag-summary.md
|
||||||
|
server/cache-server
|
||||||
|
server/campaign-manager
|
||||||
|
server/sync-service
|
||||||
|
carousal.md
|
||||||
|
node_modules
|
||||||
|
expofs.md
|
||||||
|
ios/sentry.properties
|
||||||
|
android/sentry.properties
|
||||||
|
Stremio addons refer
|
||||||
|
trakt-docs
|
||||||
|
trakt-docss
|
||||||
|
|
||||||
|
# Removed submodules (kept locally)
|
||||||
|
libmpv-android/
|
||||||
|
mpv-android/
|
||||||
|
mpvKt/
|
||||||
|
|
||||||
|
# Torrent libraries
|
||||||
|
LibTorrent/
|
||||||
|
iTorrent/
|
||||||
|
simkl-docss
|
||||||
|
downloader.md
|
||||||
|
server
|
||||||
|
Deliverables 2
|
||||||
1
.vscode/settings.json
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
{
|
{
|
||||||
|
"java.compile.nullAnalysis.mode": "automatic"
|
||||||
}
|
}
|
||||||
10
ALPHA_BUILD_2_ANNOUNCEMENT.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Nuvio Alpha Build 2
|
||||||
|
|
||||||
|
This is the second alpha release of Nuvio!
|
||||||
|
|
||||||
|
## What's New
|
||||||
|
- **Intro Submission:** You can now submit intro timestamps directly to IntroDB!
|
||||||
|
- **Bug Fixes:** Various improvements and stability fixes.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Download the attached APK and install it on your Android device.
|
||||||
211
App.tsx
|
|
@ -11,21 +11,26 @@ import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
I18nManager,
|
I18nManager,
|
||||||
Platform,
|
Platform,
|
||||||
LogBox
|
LogBox,
|
||||||
|
Linking
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import './src/i18n'; // Initialize i18n
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { Provider as PaperProvider } from 'react-native-paper';
|
import { Provider as PaperProvider } from 'react-native-paper';
|
||||||
import { enableScreens } from 'react-native-screens';
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
import AppNavigator, {
|
import { enableScreens, enableFreeze } from 'react-native-screens';
|
||||||
|
import AppNavigator, {
|
||||||
CustomNavigationDarkTheme,
|
CustomNavigationDarkTheme,
|
||||||
CustomDarkTheme
|
CustomDarkTheme
|
||||||
} from './src/navigation/AppNavigator';
|
} from './src/navigation/AppNavigator';
|
||||||
|
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
||||||
import 'react-native-reanimated';
|
import 'react-native-reanimated';
|
||||||
import { CatalogProvider } from './src/contexts/CatalogContext';
|
import { CatalogProvider } from './src/contexts/CatalogContext';
|
||||||
import { GenreProvider } from './src/contexts/GenreContext';
|
import { GenreProvider } from './src/contexts/GenreContext';
|
||||||
import { TraktProvider } from './src/contexts/TraktContext';
|
import { TraktProvider } from './src/contexts/TraktContext';
|
||||||
|
import { SimklProvider } from './src/contexts/SimklContext';
|
||||||
import { ThemeProvider, useTheme } from './src/contexts/ThemeContext';
|
import { ThemeProvider, useTheme } from './src/contexts/ThemeContext';
|
||||||
import { TrailerProvider } from './src/contexts/TrailerContext';
|
import { TrailerProvider } from './src/contexts/TrailerContext';
|
||||||
import { DownloadsProvider } from './src/contexts/DownloadsContext';
|
import { DownloadsProvider } from './src/contexts/DownloadsContext';
|
||||||
|
|
@ -34,25 +39,54 @@ import UpdatePopup from './src/components/UpdatePopup';
|
||||||
import MajorUpdateOverlay from './src/components/MajorUpdateOverlay';
|
import MajorUpdateOverlay from './src/components/MajorUpdateOverlay';
|
||||||
import { useGithubMajorUpdate } from './src/hooks/useGithubMajorUpdate';
|
import { useGithubMajorUpdate } from './src/hooks/useGithubMajorUpdate';
|
||||||
import { useUpdatePopup } from './src/hooks/useUpdatePopup';
|
import { useUpdatePopup } from './src/hooks/useUpdatePopup';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
||||||
import * as Sentry from '@sentry/react-native';
|
import * as Sentry from '@sentry/react-native';
|
||||||
import UpdateService from './src/services/updateService';
|
import UpdateService from './src/services/updateService';
|
||||||
import { memoryMonitorService } from './src/services/memoryMonitorService';
|
import { memoryMonitorService } from './src/services/memoryMonitorService';
|
||||||
import { aiService } from './src/services/aiService';
|
import { aiService } from './src/services/aiService';
|
||||||
import { AccountProvider, useAccount } from './src/contexts/AccountContext';
|
import { AccountProvider, useAccount } from './src/contexts/AccountContext';
|
||||||
|
import { ToastProvider } from './src/contexts/ToastContext';
|
||||||
|
import { mmkvStorage } from './src/services/mmkvStorage';
|
||||||
|
import { CampaignManager } from './src/components/promotions/CampaignManager';
|
||||||
|
import { isErrorReportingEnabledSync } from './src/services/telemetryService';
|
||||||
|
import { supabaseSyncService } from './src/services/supabaseSyncService';
|
||||||
|
|
||||||
|
// Initialize Sentry with privacy-first defaults
|
||||||
|
// Settings are loaded from telemetryService and can be controlled by user
|
||||||
|
// Note: Full dynamic control requires app restart as Sentry initializes at startup
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
|
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
|
||||||
|
|
||||||
// Adds more context data to events (IP address, cookies, user, etc.)
|
// Privacy-first: Disable PII by default (IP address, cookies, user data)
|
||||||
// For more information, visit: https://docs.sentry.io/platforms/react-native/data-management/data-collected/
|
// Users can opt-in via Privacy Settings if they choose
|
||||||
sendDefaultPii: true,
|
sendDefaultPii: false,
|
||||||
|
|
||||||
// Configure Session Replay conservatively to avoid startup overhead in production
|
// Session Replay completely disabled by default for privacy
|
||||||
replaysSessionSampleRate: __DEV__ ? 0.1 : 0,
|
// This prevents screen recording without explicit user consent
|
||||||
replaysOnErrorSampleRate: __DEV__ ? 1 : 0,
|
replaysSessionSampleRate: 0,
|
||||||
|
replaysOnErrorSampleRate: 0,
|
||||||
|
|
||||||
|
// Only include feedback integration (user-initiated, not automatic)
|
||||||
integrations: [Sentry.feedbackIntegration()],
|
integrations: [Sentry.feedbackIntegration()],
|
||||||
|
|
||||||
|
// beforeSend hook to respect user's telemetry preferences
|
||||||
|
// Uses synchronous MMKV read to check preference immediately
|
||||||
|
beforeSend: (event) => {
|
||||||
|
// Check if error reporting is disabled (synchronous check)
|
||||||
|
if (!isErrorReportingEnabledSync()) {
|
||||||
|
// Drop the event - user has opted out
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
},
|
||||||
|
|
||||||
|
// beforeSendTransaction hook for performance monitoring
|
||||||
|
beforeSendTransaction: (event) => {
|
||||||
|
if (!isErrorReportingEnabledSync()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
},
|
||||||
|
|
||||||
// uncomment the line below to enable Spotlight (https://spotlightjs.com)
|
// uncomment the line below to enable Spotlight (https://spotlightjs.com)
|
||||||
// spotlight: __DEV__,
|
// spotlight: __DEV__,
|
||||||
});
|
});
|
||||||
|
|
@ -70,6 +104,8 @@ LogBox.ignoreLogs([
|
||||||
|
|
||||||
// This fixes many navigation layout issues by using native screen containers
|
// This fixes many navigation layout issues by using native screen containers
|
||||||
enableScreens(true);
|
enableScreens(true);
|
||||||
|
// Freeze non-focused screens to stop background re-renders
|
||||||
|
enableFreeze(true);
|
||||||
|
|
||||||
// Inner app component that uses the theme context
|
// Inner app component that uses the theme context
|
||||||
const ThemedApp = () => {
|
const ThemedApp = () => {
|
||||||
|
|
@ -79,12 +115,12 @@ const ThemedApp = () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const engine = (global as any).HermesInternal ? 'Hermes' : 'JSC';
|
const engine = (global as any).HermesInternal ? 'Hermes' : 'JSC';
|
||||||
console.log('JS Engine:', engine);
|
console.log('JS Engine:', engine);
|
||||||
} catch {}
|
} catch { }
|
||||||
}, []);
|
}, []);
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const [isAppReady, setIsAppReady] = useState(false);
|
const [isAppReady, setIsAppReady] = useState(false);
|
||||||
const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState<boolean | null>(null);
|
const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState<boolean | null>(null);
|
||||||
|
|
||||||
// Update popup functionality
|
// Update popup functionality
|
||||||
const {
|
const {
|
||||||
showUpdatePopup,
|
showUpdatePopup,
|
||||||
|
|
@ -97,38 +133,84 @@ const ThemedApp = () => {
|
||||||
|
|
||||||
// GitHub major/minor release overlay
|
// GitHub major/minor release overlay
|
||||||
const githubUpdate = useGithubMajorUpdate();
|
const githubUpdate = useGithubMajorUpdate();
|
||||||
|
const [isDownloadingGitHub, setIsDownloadingGitHub] = useState(false);
|
||||||
|
const [downloadProgress, setDownloadProgress] = useState(0);
|
||||||
|
|
||||||
|
const handleGithubUpdateAction = async () => {
|
||||||
|
console.log('handleGithubUpdateAction triggered. Release data exists:', !!githubUpdate.releaseData);
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
setIsDownloadingGitHub(true);
|
||||||
|
setDownloadProgress(0);
|
||||||
|
try {
|
||||||
|
const { default: AndroidUpdateService } = await import('./src/services/androidUpdateService');
|
||||||
|
if (githubUpdate.releaseData) {
|
||||||
|
console.log('Calling AndroidUpdateService with:', githubUpdate.releaseData.tag_name);
|
||||||
|
const success = await AndroidUpdateService.downloadAndInstallUpdate(
|
||||||
|
githubUpdate.releaseData,
|
||||||
|
(progress) => {
|
||||||
|
setDownloadProgress(progress);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log('AndroidUpdateService result:', success);
|
||||||
|
if (!success) {
|
||||||
|
console.log('Update failed, falling back to browser');
|
||||||
|
// If download fails or no APK found, fallback to browser
|
||||||
|
if (githubUpdate.releaseUrl) Linking.openURL(githubUpdate.releaseUrl);
|
||||||
|
}
|
||||||
|
} else if (githubUpdate.releaseUrl) {
|
||||||
|
console.log('No release data, falling back to browser');
|
||||||
|
Linking.openURL(githubUpdate.releaseUrl);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update via Android service', error);
|
||||||
|
if (githubUpdate.releaseUrl) Linking.openURL(githubUpdate.releaseUrl);
|
||||||
|
} finally {
|
||||||
|
setIsDownloadingGitHub(false);
|
||||||
|
setDownloadProgress(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (githubUpdate.releaseUrl) Linking.openURL(githubUpdate.releaseUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Check onboarding status and initialize services
|
// Check onboarding status and initialize services
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeApp = async () => {
|
const initializeApp = async () => {
|
||||||
try {
|
try {
|
||||||
// Check onboarding status
|
// Check onboarding status
|
||||||
const onboardingCompleted = await AsyncStorage.getItem('hasCompletedOnboarding');
|
const onboardingCompleted = await mmkvStorage.getItem('hasCompletedOnboarding');
|
||||||
setHasCompletedOnboarding(onboardingCompleted === 'true');
|
setHasCompletedOnboarding(onboardingCompleted === 'true');
|
||||||
|
|
||||||
// Initialize update service (skip on Android to prevent update checks)
|
// Initialize Supabase auth/session and start background sync.
|
||||||
if (Platform.OS !== 'android') {
|
// This is intentionally non-blocking for app startup UX.
|
||||||
await UpdateService.initialize();
|
supabaseSyncService
|
||||||
}
|
.initialize()
|
||||||
|
.then(() => supabaseSyncService.startupSync())
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn('[App] Supabase sync bootstrap failed:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize update service
|
||||||
|
await UpdateService.initialize();
|
||||||
|
|
||||||
// Initialize memory monitoring service to prevent OutOfMemoryError
|
// Initialize memory monitoring service to prevent OutOfMemoryError
|
||||||
memoryMonitorService; // Just accessing it starts the monitoring
|
memoryMonitorService; // Just accessing it starts the monitoring
|
||||||
console.log('Memory monitoring service initialized');
|
console.log('Memory monitoring service initialized');
|
||||||
|
|
||||||
// Initialize AI service
|
// Initialize AI service
|
||||||
await aiService.initialize();
|
await aiService.initialize();
|
||||||
console.log('AI service initialized');
|
console.log('AI service initialized');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error initializing app:', error);
|
console.error('Error initializing app:', error);
|
||||||
// Default to showing onboarding if we can't check
|
// Default to showing onboarding if we can't check
|
||||||
setHasCompletedOnboarding(false);
|
setHasCompletedOnboarding(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
initializeApp();
|
initializeApp();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Create custom themes based on current theme
|
// Create custom themes based on current theme
|
||||||
const customDarkTheme = {
|
const customDarkTheme = {
|
||||||
...CustomDarkTheme,
|
...CustomDarkTheme,
|
||||||
|
|
@ -137,7 +219,7 @@ const ThemedApp = () => {
|
||||||
primary: currentTheme.colors.primary,
|
primary: currentTheme.colors.primary,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const customNavigationTheme = {
|
const customNavigationTheme = {
|
||||||
...CustomNavigationDarkTheme,
|
...CustomNavigationDarkTheme,
|
||||||
colors: {
|
colors: {
|
||||||
|
|
@ -152,33 +234,44 @@ const ThemedApp = () => {
|
||||||
const handleSplashComplete = () => {
|
const handleSplashComplete = () => {
|
||||||
setIsAppReady(true);
|
setIsAppReady(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Navigation reference
|
||||||
|
const navigationRef = React.useRef<any>(null);
|
||||||
|
|
||||||
// Don't render anything until we know the onboarding status
|
// Don't render anything until we know the onboarding status
|
||||||
const shouldShowApp = isAppReady && hasCompletedOnboarding !== null;
|
const shouldShowApp = isAppReady && hasCompletedOnboarding !== null;
|
||||||
const initialRouteName = hasCompletedOnboarding ? 'MainTabs' : 'Onboarding';
|
const initialRouteName = hasCompletedOnboarding ? 'MainTabs' : 'Onboarding';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountProvider>
|
<AccountProvider>
|
||||||
<PaperProvider theme={customDarkTheme}>
|
<PaperProvider theme={customDarkTheme}>
|
||||||
<NavigationContainer
|
<NavigationContainer
|
||||||
|
ref={navigationRef}
|
||||||
theme={customNavigationTheme}
|
theme={customNavigationTheme}
|
||||||
linking={undefined}
|
linking={{
|
||||||
|
prefixes: ['nuvio://'],
|
||||||
|
config: {
|
||||||
|
screens: {
|
||||||
|
ScraperSettings: {
|
||||||
|
path: 'repo',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<DownloadsProvider>
|
<DownloadsProvider>
|
||||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||||
<StatusBar style="light" />
|
<StatusBar style="light" />
|
||||||
{!isAppReady && <SplashScreen onFinish={handleSplashComplete} />}
|
{!isAppReady && <SplashScreen onFinish={handleSplashComplete} />}
|
||||||
{shouldShowApp && <AppNavigator initialRouteName={initialRouteName} />}
|
{shouldShowApp && <AppNavigator initialRouteName={initialRouteName} />}
|
||||||
{Platform.OS === 'ios' && (
|
<UpdatePopup
|
||||||
<UpdatePopup
|
visible={showUpdatePopup}
|
||||||
visible={showUpdatePopup}
|
updateInfo={updateInfo}
|
||||||
updateInfo={updateInfo}
|
onUpdateNow={handleUpdateNow}
|
||||||
onUpdateNow={handleUpdateNow}
|
onUpdateLater={handleUpdateLater}
|
||||||
onUpdateLater={handleUpdateLater}
|
onDismiss={handleDismiss}
|
||||||
onDismiss={handleDismiss}
|
isInstalling={isInstalling}
|
||||||
isInstalling={isInstalling}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<MajorUpdateOverlay
|
<MajorUpdateOverlay
|
||||||
visible={githubUpdate.visible}
|
visible={githubUpdate.visible}
|
||||||
latestTag={githubUpdate.latestTag}
|
latestTag={githubUpdate.latestTag}
|
||||||
|
|
@ -186,7 +279,11 @@ const ThemedApp = () => {
|
||||||
releaseUrl={githubUpdate.releaseUrl}
|
releaseUrl={githubUpdate.releaseUrl}
|
||||||
onDismiss={githubUpdate.onDismiss}
|
onDismiss={githubUpdate.onDismiss}
|
||||||
onLater={githubUpdate.onLater}
|
onLater={githubUpdate.onLater}
|
||||||
|
onUpdateAction={handleGithubUpdateAction}
|
||||||
|
isDownloading={isDownloadingGitHub}
|
||||||
|
downloadProgress={downloadProgress}
|
||||||
/>
|
/>
|
||||||
|
<CampaignManager />
|
||||||
</View>
|
</View>
|
||||||
</DownloadsProvider>
|
</DownloadsProvider>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
|
|
@ -197,19 +294,27 @@ const ThemedApp = () => {
|
||||||
|
|
||||||
function App(): React.JSX.Element {
|
function App(): React.JSX.Element {
|
||||||
return (
|
return (
|
||||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
<SafeAreaProvider>
|
||||||
<GenreProvider>
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||||
<CatalogProvider>
|
<BottomSheetModalProvider>
|
||||||
<TraktProvider>
|
<GenreProvider>
|
||||||
<ThemeProvider>
|
<CatalogProvider>
|
||||||
<TrailerProvider>
|
<TraktProvider>
|
||||||
<ThemedApp />
|
<SimklProvider>
|
||||||
</TrailerProvider>
|
<ThemeProvider>
|
||||||
</ThemeProvider>
|
<TrailerProvider>
|
||||||
</TraktProvider>
|
<ToastProvider>
|
||||||
</CatalogProvider>
|
<ThemedApp />
|
||||||
</GenreProvider>
|
</ToastProvider>
|
||||||
</GestureHandlerRootView>
|
</TrailerProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</SimklProvider>
|
||||||
|
</TraktProvider>
|
||||||
|
</CatalogProvider>
|
||||||
|
</GenreProvider>
|
||||||
|
</BottomSheetModalProvider>
|
||||||
|
</GestureHandlerRootView>
|
||||||
|
</SafeAreaProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,4 +324,4 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Sentry.wrap(App);
|
export default Sentry.wrap(App);
|
||||||
|
|
|
||||||
80
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Thanks for helping improve NuvioMobile.
|
||||||
|
|
||||||
|
## PR policy
|
||||||
|
|
||||||
|
Pull requests are currently intended for:
|
||||||
|
|
||||||
|
- Reproducible bug fixes
|
||||||
|
- Small stability improvements
|
||||||
|
- Minor maintenance work
|
||||||
|
- Small documentation fixes that improve accuracy
|
||||||
|
|
||||||
|
Pull requests are generally **not** accepted for:
|
||||||
|
|
||||||
|
- New major features
|
||||||
|
- Product direction changes
|
||||||
|
- Large UX / UI redesigns
|
||||||
|
- Cosmetic-only changes
|
||||||
|
- Refactors without a clear user-facing or maintenance benefit
|
||||||
|
|
||||||
|
For feature ideas and bigger changes, please open an issue first. Feature implementation is usually kept in-house unless it has been discussed and explicitly approved beforehand.
|
||||||
|
|
||||||
|
## Where to ask questions
|
||||||
|
|
||||||
|
- Use **Issues** for bugs, feature requests, setup help, and general support.
|
||||||
|
|
||||||
|
## Bug reports (rules)
|
||||||
|
|
||||||
|
To keep issues fixable, bug reports should include:
|
||||||
|
|
||||||
|
- App version or OTA update ID (Settings > App updates > Current version, hold to copy)
|
||||||
|
- Platform + device model + OS version (Android/iOS)
|
||||||
|
- Install method (release APK/IPA / Expo Go / built from source)
|
||||||
|
- Steps to reproduce (exact steps)
|
||||||
|
- Expected vs actual behavior
|
||||||
|
- Frequency (always/sometimes/once)
|
||||||
|
|
||||||
|
Logs are **optional**, but they help a lot for playback/crash issues.
|
||||||
|
|
||||||
|
### How to capture logs (optional)
|
||||||
|
|
||||||
|
If you can, reproduce the issue once, then attach a short log snippet from around the time it happened:
|
||||||
|
|
||||||
|
For Android:
|
||||||
|
```sh
|
||||||
|
adb logcat -d | tail -n 300
|
||||||
|
```
|
||||||
|
For iOS/Metro:
|
||||||
|
```sh
|
||||||
|
# Copy from your Metro bundler output or Xcode console
|
||||||
|
```
|
||||||
|
|
||||||
|
If the issue is a crash, also include any stack trace shown by Android Studio, Xcode, or `adb logcat`.
|
||||||
|
|
||||||
|
## Feature requests (rules)
|
||||||
|
|
||||||
|
Please include:
|
||||||
|
|
||||||
|
- The problem you are solving (use case)
|
||||||
|
- Your proposed solution
|
||||||
|
- Alternatives considered (if any)
|
||||||
|
|
||||||
|
Opening a feature request does **not** mean a pull request will be accepted for it. If the feature affects product scope, UX direction, or adds a significant new surface area, do not start implementation unless a maintainer explicitly approves it first.
|
||||||
|
|
||||||
|
## Before opening a PR
|
||||||
|
|
||||||
|
Please make sure your PR is all of the following:
|
||||||
|
|
||||||
|
- Small in scope
|
||||||
|
- Focused on one problem
|
||||||
|
- Clearly aligned with the current direction of the project
|
||||||
|
- Not cosmetic-only
|
||||||
|
- Not a new major feature unless it was discussed and approved first
|
||||||
|
|
||||||
|
PRs that do not fit this policy will usually be closed without merge so review time can stay focused on bugs, regressions, and small improvements.
|
||||||
|
|
||||||
|
## One issue per problem
|
||||||
|
|
||||||
|
Please open separate issues for separate bugs/features. It makes tracking, fixing, and closing issues much faster.
|
||||||
119
README.md
|
|
@ -1,86 +1,85 @@
|
||||||
# Nuvio Media Hub
|
<div align="center">
|
||||||
|
|
||||||
<p align="center">
|
<img src="https://github.com/tapframe/NuvioTV/blob/main/assets/brand/app_logo_wordmark.png" alt="Nuvio" width="300" />
|
||||||
<img src="assets/titlelogo.png" alt="Nuvio Logo" width="300"/>
|
<br />
|
||||||
</p>
|
<br />
|
||||||
|
|
||||||
<p align="center">
|
[![Contributors][contributors-shield]][contributors-url]
|
||||||
A modern media hub built with React Native and Expo, featuring comprehensive addon integration and content synchronization.
|
[![Forks][forks-shield]][forks-url]
|
||||||
</p>
|
[![Stargazers][stars-shield]][stars-url]
|
||||||
|
[![Issues][issues-shield]][issues-url]
|
||||||
|
[![License][license-shield]][license-url]
|
||||||
|
|
||||||
---
|
<p>
|
||||||
|
A modern media hub for Android and iOS built with React Native and Expo.
|
||||||
|
<br />
|
||||||
|
Stremio Addon ecosystem • Cross-platform
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
Nuvio Media Hub is a cross-platform app for managing and discovering media, with a playback-focused interface that can integrate with the Stremio addon ecosystem through user-installed extensions.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### AltStore
|
### Android
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/2/20/AltStore_logo.png" width="32" height="32" align="left"> [](https://tinyurl.com/NuvioAltstore)
|
|
||||||
|
|
||||||
### SideStore
|
Download the latest APK from [GitHub Releases](https://github.com/tapframe/NuvioStreaming/releases/latest).
|
||||||
<img src="https://github.com/SideStore/assets/blob/main/icon.png?raw=true" width="32" height="32" align="left"> [](https://tinyurl.com/NuvioSidestore)
|
|
||||||
|
|
||||||
**Manual URL:** `https://raw.githubusercontent.com/tapframe/NuvioStreaming/main/nuvio-source.json`
|
### iOS
|
||||||
|
|
||||||
---
|
- [TestFlight](https://testflight.apple.com/join/QkKMGRqp)
|
||||||
|
- [AltStore](https://tinyurl.com/NuvioAltstore)
|
||||||
|
- [SideStore](https://tinyurl.com/NuvioSidestore)
|
||||||
|
|
||||||
## Screenshots
|
**Manual source:** `https://raw.githubusercontent.com/tapframe/NuvioStreaming/main/nuvio-source.json`
|
||||||
|
|
||||||
| Home | Details |
|
|
||||||
|:----:|:-------:|
|
|
||||||
|  |  |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
<p align="left">
|
|
||||||
<a href="https://skillicons.dev">
|
|
||||||
<img src="https://skillicons.dev/icons?i=react,typescript,nodejs,expo,github,githubactions&theme=light&perline=6" />
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
### Setup
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/tapframe/NuvioStreaming.git
|
git clone https://github.com/tapframe/NuvioStreaming.git
|
||||||
cd NuvioStreaming
|
cd NuvioStreaming
|
||||||
npm install
|
npm install --legacy-peer-deps
|
||||||
npx expo start
|
npx expo prebuild
|
||||||
|
npx expo run:android
|
||||||
|
# or
|
||||||
|
npx expo run:ios
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build
|
## Legal & DMCA
|
||||||
```bash
|
|
||||||
npx expo run:android # Android
|
|
||||||
npx expo run:ios # iOS
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
Nuvio functions solely as a client-side interface for browsing metadata and playing media provided by user-installed extensions and/or user-provided sources. It is intended for content the user owns or is otherwise authorized to access.
|
||||||
|
|
||||||
## Contributing
|
Nuvio is not affiliated with any third-party extensions, catalogs, sources, or content providers. It does not host, store, or distribute any media content.
|
||||||
|
|
||||||
1. Fork the repository
|
For comprehensive legal information, including our full disclaimer, third-party extension policy, and DMCA/Copyright information, please visit our **[Legal & Disclaimer Page](https://nuvioapp.space/legal)**.
|
||||||
2. Create a feature branch
|
|
||||||
3. Make your changes
|
|
||||||
4. Submit a pull request
|
|
||||||
|
|
||||||
---
|
## Built With
|
||||||
|
|
||||||
## Issues
|
- React Native
|
||||||
|
- Expo
|
||||||
|
- TypeScript
|
||||||
|
|
||||||
Report bugs and request features via [GitHub Issues](https://github.com/tapframe/NuvioStreaming/issues)
|
## Star History
|
||||||
|
|
||||||
---
|
<a href="https://www.star-history.com/#tapframe/NuvioStreaming&type=date&legend=top-left">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=tapframe/NuvioStreaming&type=date&theme=dark&legend=top-left" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=tapframe/NuvioStreaming&type=date&legend=top-left" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=tapframe/NuvioStreaming&type=date&legend=top-left" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
## License
|
<!-- MARKDOWN LINKS & IMAGES -->
|
||||||
|
[contributors-shield]: https://img.shields.io/github/contributors/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
[contributors-url]: https://github.com/tapframe/NuvioStreaming/graphs/contributors
|
||||||
|
[forks-shield]: https://img.shields.io/github/forks/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||||
This project is licensed under the GNU General Public License v3.0.
|
[forks-url]: https://github.com/tapframe/NuvioStreaming/network/members
|
||||||
|
[stars-shield]: https://img.shields.io/github/stars/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||||
---
|
[stars-url]: https://github.com/tapframe/NuvioStreaming/stargazers
|
||||||
|
[issues-shield]: https://img.shields.io/github/issues/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||||
## Disclaimer
|
[issues-url]: https://github.com/tapframe/NuvioStreaming/issues
|
||||||
|
[license-shield]: https://img.shields.io/github/license/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||||
This application functions as a media hub with addon/plugin support. It does not contain any built-in content or host media content. Content access is only available through user-installed plugins and addons. Any legal concerns should be directed to the specific websites providing the content.
|
[license-url]: http://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
apply plugin: "com.android.application"
|
apply plugin: "com.android.application"
|
||||||
apply plugin: "org.jetbrains.kotlin.android"
|
apply plugin: "org.jetbrains.kotlin.android"
|
||||||
apply plugin: "com.facebook.react"
|
apply plugin: "com.facebook.react"
|
||||||
|
apply plugin: "io.sentry.android.gradle"
|
||||||
|
|
||||||
def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
|
def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
|
||||||
|
|
||||||
|
|
@ -14,6 +15,7 @@ react {
|
||||||
hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
|
hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
|
||||||
codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
|
codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
|
||||||
|
|
||||||
|
enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean()
|
||||||
// Use Expo CLI to bundle the app, this ensures the Metro config
|
// Use Expo CLI to bundle the app, this ensures the Metro config
|
||||||
// works correctly with Expo projects.
|
// works correctly with Expo projects.
|
||||||
cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
|
cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
|
||||||
|
|
@ -63,9 +65,9 @@ react {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
|
* Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization).
|
||||||
*/
|
*/
|
||||||
def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean()
|
def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The preferred build flavor of JavaScriptCore (JSC)
|
* The preferred build flavor of JavaScriptCore (JSC)
|
||||||
|
|
@ -78,7 +80,7 @@ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInRelea
|
||||||
* give correct results when using with locales other than en-US. Note that
|
* give correct results when using with locales other than en-US. Note that
|
||||||
* this variant is about 6MiB larger per architecture than default.
|
* this variant is about 6MiB larger per architecture than default.
|
||||||
*/
|
*/
|
||||||
def jscFlavor = 'org.webkit:android-jsc:+'
|
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
|
||||||
|
|
||||||
apply from: new File(["node", "--print", "require('path').dirname(require.resolve('@sentry/react-native/package.json'))"].execute().text.trim(), "sentry.gradle")
|
apply from: new File(["node", "--print", "require('path').dirname(require.resolve('@sentry/react-native/package.json'))"].execute().text.trim(), "sentry.gradle")
|
||||||
|
|
||||||
|
|
@ -93,16 +95,39 @@ android {
|
||||||
applicationId 'com.nuvio.app'
|
applicationId 'com.nuvio.app'
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 19
|
versionCode 37
|
||||||
versionName "1.2.4"
|
versionName "1.4.1"
|
||||||
|
|
||||||
|
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split APKs by architecture only for smaller downloads
|
||||||
splits {
|
splits {
|
||||||
abi {
|
abi {
|
||||||
reset()
|
|
||||||
enable true
|
enable true
|
||||||
universalApk false // If true, also generate a universal APK
|
reset()
|
||||||
include "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
|
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||||
|
universalApk true
|
||||||
|
}
|
||||||
|
density {
|
||||||
|
enable false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate unique version codes for each split APK
|
||||||
|
def abiVersionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
|
||||||
|
applicationVariants.all { variant ->
|
||||||
|
variant.outputs.each { output ->
|
||||||
|
def baseVersionCode = 37 // Current versionCode 37 from defaultConfig
|
||||||
|
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
|
||||||
|
|
||||||
|
def versionCode = baseVersionCode * 100 // Base multiplier
|
||||||
|
|
||||||
|
if (abiName != null) {
|
||||||
|
versionCode += abiVersionCodes.get(abiName)
|
||||||
|
}
|
||||||
|
|
||||||
|
output.versionCodeOverride = versionCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|
@ -121,15 +146,18 @@ android {
|
||||||
// Caution! In production, you need to generate your own keystore file.
|
// Caution! In production, you need to generate your own keystore file.
|
||||||
// see https://reactnative.dev/docs/signed-apk-android.
|
// see https://reactnative.dev/docs/signed-apk-android.
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
|
def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
|
||||||
minifyEnabled enableProguardInReleaseBuilds
|
shrinkResources enableShrinkResources.toBoolean()
|
||||||
|
minifyEnabled enableMinifyInReleaseBuilds
|
||||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||||
crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true)
|
def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true'
|
||||||
|
crunchPngs enablePngCrunchInRelease.toBoolean()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
jniLibs {
|
jniLibs {
|
||||||
useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false)
|
def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false'
|
||||||
|
useLegacyPackaging enableLegacyPackaging.toBoolean()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
androidResources {
|
androidResources {
|
||||||
|
|
@ -157,7 +185,38 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sentry {
|
||||||
|
// Enables or disables the automatic configuration of Native Symbols
|
||||||
|
// for Sentry. This executes sentry-cli automatically so
|
||||||
|
// you don't need to do it manually.
|
||||||
|
// Default is disabled.
|
||||||
|
uploadNativeSymbols = true
|
||||||
|
|
||||||
|
// Enables or disables the automatic upload of the app's native source code to Sentry.
|
||||||
|
// This executes sentry-cli with the --include-sources param automatically so
|
||||||
|
// you don't need to do it manually.
|
||||||
|
// This option has an effect only when [uploadNativeSymbols] is enabled.
|
||||||
|
// Default is disabled.
|
||||||
|
includeNativeSources = true
|
||||||
|
|
||||||
|
// `@sentry/react-native` ships with compatible `sentry-android`
|
||||||
|
// This option would install the latest version that ships with the SDK or SAGP (Sentry Android Gradle Plugin)
|
||||||
|
// which might be incompatible with the React Native SDK
|
||||||
|
// Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber and fragment integrations).
|
||||||
|
// Default is enabled.
|
||||||
|
autoInstallation {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations.all {
|
||||||
|
exclude group: 'com.caverock', module: 'androidsvg'
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// @generated begin react-native-google-cast-dependencies - expo prebuild (DO NOT MODIFY) sync-3822a3c86222e7aca74039b551612aab7e75365d
|
||||||
|
implementation "com.google.android.gms:play-services-cast-framework:${safeExtGet('castFrameworkVersion', '+')}"
|
||||||
|
// @generated end react-native-google-cast-dependencies
|
||||||
// The version of react-native is set by the React Native Gradle Plugin
|
// The version of react-native is set by the React Native Gradle Plugin
|
||||||
implementation("com.facebook.react:react-android")
|
implementation("com.facebook.react:react-android")
|
||||||
|
|
||||||
|
|
@ -167,15 +226,15 @@ dependencies {
|
||||||
|
|
||||||
if (isGifEnabled) {
|
if (isGifEnabled) {
|
||||||
// For animated gif support
|
// For animated gif support
|
||||||
implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}")
|
implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWebpEnabled) {
|
if (isWebpEnabled) {
|
||||||
// For webp support
|
// For webp support
|
||||||
implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}")
|
implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}")
|
||||||
if (isWebpAnimatedEnabled) {
|
if (isWebpAnimatedEnabled) {
|
||||||
// Animated webp support
|
// Animated webp support
|
||||||
implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}")
|
implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,4 +246,14 @@ dependencies {
|
||||||
|
|
||||||
// Include only FFmpeg decoder AAR to avoid duplicates with Maven Media3
|
// Include only FFmpeg decoder AAR to avoid duplicates with Maven Media3
|
||||||
implementation files("libs/lib-decoder-ffmpeg-release.aar")
|
implementation files("libs/lib-decoder-ffmpeg-release.aar")
|
||||||
|
|
||||||
|
// MPV Player library
|
||||||
|
implementation files("libs/libmpv-release.aar")
|
||||||
|
|
||||||
|
// Google Cast Framework
|
||||||
|
implementation "com.google.android.gms:play-services-cast-framework:${safeExtGet('castFrameworkVersion', '+')}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def safeExtGet(prop, fallback) {
|
||||||
|
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
android/app/libs/libmpv-release.aar
Normal file
7
android/app/src/debugOptimized/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
|
|
||||||
|
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
|
||||||
|
</manifest>
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<uses-sdk tools:overrideLibrary="dev.jdtech.mpv"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
|
|
@ -6,6 +7,7 @@
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
|
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
|
@ -13,12 +15,14 @@
|
||||||
<data android:scheme="https"/>
|
<data android:scheme="https"/>
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true">
|
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:enableOnBackInvokedCallback="false">
|
||||||
|
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:value="com.reactnative.googlecast.GoogleCastOptionsProvider"/>
|
||||||
|
<meta-data android:name="com.reactnative.googlecast.RECEIVER_APPLICATION_ID" android:value="CC1AD845"/>
|
||||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
|
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_RUNTIME_VERSION" android:value="@string/expo_runtime_version"/>
|
<meta-data android:name="expo.modules.updates.EXPO_RUNTIME_VERSION" android:value="@string/expo_runtime_version"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ERROR_RECOVERY_ONLY"/>
|
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ERROR_RECOVERY_ONLY"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="30000"/>
|
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="5000"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"/>
|
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://ota.nuvioapp.space/api/manifest"/>
|
||||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="unspecified">
|
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="unspecified">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
@ -29,9 +33,8 @@
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
<data android:scheme="nuvio"/>
|
<data android:scheme="nuvio"/>
|
||||||
<data android:scheme="com.nuvio.app"/>
|
|
||||||
<data android:scheme="exp+nuvio"/>
|
<data android:scheme="exp+nuvio"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnable
|
||||||
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
||||||
|
|
||||||
import expo.modules.ReactActivityDelegateWrapper
|
import expo.modules.ReactActivityDelegateWrapper
|
||||||
|
import com.reactnative.googlecast.api.RNGCCastContext
|
||||||
|
|
||||||
class MainActivity : ReactActivity() {
|
class MainActivity : ReactActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
@ -17,6 +18,12 @@ class MainActivity : ReactActivity() {
|
||||||
// This is required for expo-splash-screen.
|
// This is required for expo-splash-screen.
|
||||||
setTheme(R.style.AppTheme);
|
setTheme(R.style.AppTheme);
|
||||||
super.onCreate(null)
|
super.onCreate(null)
|
||||||
|
// @generated begin react-native-google-cast-onCreate - expo prebuild (DO NOT MODIFY) sync-489050f2bf9933a98bbd9d93137016ae14c22faa
|
||||||
|
RNGCCastContext.getSharedInstance(this)
|
||||||
|
// @generated end react-native-google-cast-onCreate
|
||||||
|
|
||||||
|
// Initialize Google Cast context
|
||||||
|
RNGCCastContext.getSharedInstance(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,35 +5,34 @@ import android.content.res.Configuration
|
||||||
|
|
||||||
import com.facebook.react.PackageList
|
import com.facebook.react.PackageList
|
||||||
import com.facebook.react.ReactApplication
|
import com.facebook.react.ReactApplication
|
||||||
|
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
||||||
import com.facebook.react.ReactNativeHost
|
import com.facebook.react.ReactNativeHost
|
||||||
import com.facebook.react.ReactPackage
|
import com.facebook.react.ReactPackage
|
||||||
import com.facebook.react.ReactHost
|
import com.facebook.react.ReactHost
|
||||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
import com.facebook.react.common.ReleaseLevel
|
||||||
|
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
|
||||||
import com.facebook.react.defaults.DefaultReactNativeHost
|
import com.facebook.react.defaults.DefaultReactNativeHost
|
||||||
import com.facebook.react.soloader.OpenSourceMergedSoMapping
|
|
||||||
import com.facebook.soloader.SoLoader
|
|
||||||
|
|
||||||
import expo.modules.ApplicationLifecycleDispatcher
|
import expo.modules.ApplicationLifecycleDispatcher
|
||||||
import expo.modules.ReactNativeHostWrapper
|
import expo.modules.ReactNativeHostWrapper
|
||||||
|
import com.nuvio.app.mpv.MpvPackage
|
||||||
|
|
||||||
class MainApplication : Application(), ReactApplication {
|
class MainApplication : Application(), ReactApplication {
|
||||||
|
|
||||||
override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
|
override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
|
||||||
this,
|
this,
|
||||||
object : DefaultReactNativeHost(this) {
|
object : DefaultReactNativeHost(this) {
|
||||||
override fun getPackages(): List<ReactPackage> {
|
override fun getPackages(): List<ReactPackage> =
|
||||||
val packages = PackageList(this).packages
|
PackageList(this).packages.apply {
|
||||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||||
// packages.add(new MyReactNativePackage());
|
add(com.nuvio.app.mpv.MpvPackage())
|
||||||
return packages
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
|
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
|
||||||
|
|
||||||
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
|
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
|
||||||
|
|
||||||
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
||||||
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -42,11 +41,12 @@ class MainApplication : Application(), ReactApplication {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
SoLoader.init(this, OpenSourceMergedSoMapping)
|
DefaultNewArchitectureEntryPoint.releaseLevel = try {
|
||||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase())
|
||||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
} catch (e: IllegalArgumentException) {
|
||||||
load()
|
ReleaseLevel.STABLE
|
||||||
}
|
}
|
||||||
|
loadReactNative(this)
|
||||||
ApplicationLifecycleDispatcher.onApplicationCreate(this)
|
ApplicationLifecycleDispatcher.onApplicationCreate(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
615
android/app/src/main/java/com/nuvio/app/mpv/MPVView.kt
Normal file
|
|
@ -0,0 +1,615 @@
|
||||||
|
package com.nuvio.app.mpv
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.SurfaceTexture
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.Surface
|
||||||
|
import android.view.TextureView
|
||||||
|
import dev.jdtech.mpv.MPVLib
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.LifecycleEventListener
|
||||||
|
import com.facebook.react.bridge.ReactContext
|
||||||
|
|
||||||
|
class MPVView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : TextureView(context, attrs, defStyleAttr), TextureView.SurfaceTextureListener, MPVLib.EventObserver {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "MPVView"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isMpvInitialized = false
|
||||||
|
private var pendingDataSource: String? = null
|
||||||
|
private var isPaused: Boolean = true
|
||||||
|
private var surface: Surface? = null
|
||||||
|
private var httpHeaders: Map<String, String>? = null
|
||||||
|
|
||||||
|
// Decoder mode setting: 'auto', 'sw', 'hw', 'hw+' (default: auto)
|
||||||
|
var decoderMode: String = "auto"
|
||||||
|
|
||||||
|
// GPU mode setting: 'gpu', 'gpu-next' (default: gpu)
|
||||||
|
var gpuMode: String = "gpu"
|
||||||
|
|
||||||
|
// Flag to track if onLoad has been fired (prevents multiple fires for HLS streams)
|
||||||
|
private var hasLoadEventFired: Boolean = false
|
||||||
|
|
||||||
|
// Event listener for React Native
|
||||||
|
var onLoadCallback: ((duration: Double, width: Int, height: Int) -> Unit)? = null
|
||||||
|
var onProgressCallback: ((position: Double, duration: Double) -> Unit)? = null
|
||||||
|
var onEndCallback: (() -> Unit)? = null
|
||||||
|
var onErrorCallback: ((message: String) -> Unit)? = null
|
||||||
|
var onTracksChangedCallback: ((audioTracks: List<Map<String, Any>>, subtitleTracks: List<Map<String, Any>>) -> Unit)? = null
|
||||||
|
|
||||||
|
private var resumeOnForeground = false
|
||||||
|
private val lifeCycleListener = object : LifecycleEventListener {
|
||||||
|
override fun onHostPause() {
|
||||||
|
resumeOnForeground = !isPaused;
|
||||||
|
if(resumeOnForeground) {
|
||||||
|
Log.d(TAG, "App backgrounded — pausing MPV")
|
||||||
|
setPaused(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onHostResume() {
|
||||||
|
if(resumeOnForeground) {
|
||||||
|
setPaused(false)
|
||||||
|
resumeOnForeground = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onHostDestroy() {}
|
||||||
|
}
|
||||||
|
init {
|
||||||
|
surfaceTextureListener = this
|
||||||
|
isOpaque = false
|
||||||
|
(context as? ReactContext)?.addLifecycleEventListener(lifeCycleListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) {
|
||||||
|
Log.d(TAG, "Surface texture available: ${width}x${height}")
|
||||||
|
try {
|
||||||
|
surface = Surface(surfaceTexture)
|
||||||
|
|
||||||
|
MPVLib.create(context.applicationContext)
|
||||||
|
initOptions()
|
||||||
|
MPVLib.init()
|
||||||
|
MPVLib.attachSurface(surface!!)
|
||||||
|
MPVLib.addObserver(this)
|
||||||
|
MPVLib.setPropertyString("android-surface-size", "${width}x${height}")
|
||||||
|
observeProperties()
|
||||||
|
isMpvInitialized = true
|
||||||
|
|
||||||
|
// If a data source was set before surface was ready, load it now
|
||||||
|
// Headers are already applied in initOptions() before init()
|
||||||
|
pendingDataSource?.let { url ->
|
||||||
|
loadFile(url)
|
||||||
|
pendingDataSource = null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to initialize MPV", e)
|
||||||
|
onErrorCallback?.invoke("MPV initialization failed: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSurfaceTextureSizeChanged(surfaceTexture: SurfaceTexture, width: Int, height: Int) {
|
||||||
|
Log.d(TAG, "Surface texture size changed: ${width}x${height}")
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
MPVLib.setPropertyString("android-surface-size", "${width}x${height}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean {
|
||||||
|
Log.d(TAG, "Surface texture destroyed")
|
||||||
|
(context as? ReactContext)?.removeLifecycleEventListener(lifeCycleListener)
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
MPVLib.removeObserver(this)
|
||||||
|
MPVLib.detachSurface()
|
||||||
|
MPVLib.destroy()
|
||||||
|
isMpvInitialized = false
|
||||||
|
}
|
||||||
|
surface?.release()
|
||||||
|
surface = null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) {
|
||||||
|
// Called when the SurfaceTexture is updated via updateTexImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initOptions() {
|
||||||
|
MPVLib.setOptionString("profile", "fast")
|
||||||
|
|
||||||
|
// GPU rendering mode (gpu or gpu-next)
|
||||||
|
MPVLib.setOptionString("vo", gpuMode)
|
||||||
|
MPVLib.setOptionString("gpu-context", "android")
|
||||||
|
MPVLib.setOptionString("opengl-es", "yes")
|
||||||
|
|
||||||
|
// Decoder mode mapping (same as mpvKt)
|
||||||
|
val hwdecValue = when (decoderMode) {
|
||||||
|
"auto" -> "auto-copy" // Best balance: HW decode, copy to CPU for filters
|
||||||
|
"sw" -> "no" // Software decoding only
|
||||||
|
"hw" -> "mediacodec-copy" // HW decode with copy (safer)
|
||||||
|
"hw+" -> "mediacodec" // Full HW decode (fastest, may have issues)
|
||||||
|
else -> "auto-copy"
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Decoder mode: $decoderMode, hwdec value: $hwdecValue, GPU mode: $gpuMode")
|
||||||
|
MPVLib.setOptionString("hwdec", hwdecValue)
|
||||||
|
// Note: Not setting hwdec-codecs explicitly - let mpv use defaults
|
||||||
|
|
||||||
|
MPVLib.setOptionString("target-colorspace-hint", "yes")
|
||||||
|
|
||||||
|
// HDR and Dolby Vision support
|
||||||
|
// target-prim: Signal target display primaries (auto = passthrough when display supports)
|
||||||
|
MPVLib.setOptionString("target-prim", "auto")
|
||||||
|
// target-trc: Signal target transfer characteristics (auto = passthrough when display supports)
|
||||||
|
MPVLib.setOptionString("target-trc", "auto")
|
||||||
|
// tone-mapping: How to handle HDR/DV content on SDR displays (auto = best automatic choice)
|
||||||
|
MPVLib.setOptionString("tone-mapping", "auto")
|
||||||
|
// hdr-compute-peak: Compute peak brightness for better tone mapping
|
||||||
|
MPVLib.setOptionString("hdr-compute-peak", "auto")
|
||||||
|
// Allow DV Profile 5 (HEVC with RPU) to be decoded by hardware decoder
|
||||||
|
MPVLib.setOptionString("vd-lavc-o", "strict=-2")
|
||||||
|
|
||||||
|
// Workaround for https://github.com/mpv-player/mpv/issues/14651
|
||||||
|
MPVLib.setOptionString("vd-lavc-film-grain", "cpu")
|
||||||
|
|
||||||
|
MPVLib.setOptionString("ao", "audiotrack,opensles")
|
||||||
|
|
||||||
|
// Limit demuxer cache based on Android version (like mpvKt)
|
||||||
|
val cacheMegs = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O_MR1) 64 else 32
|
||||||
|
MPVLib.setOptionString("demuxer-max-bytes", "${cacheMegs * 1024 * 1024}")
|
||||||
|
MPVLib.setOptionString("demuxer-max-back-bytes", "${cacheMegs * 1024 * 1024}")
|
||||||
|
MPVLib.setOptionString("cache", "yes")
|
||||||
|
MPVLib.setOptionString("cache-secs", "30")
|
||||||
|
|
||||||
|
MPVLib.setOptionString("network-timeout", "60")
|
||||||
|
MPVLib.setOptionString("ytdl", "no")
|
||||||
|
|
||||||
|
applyHttpHeadersAsOptions()
|
||||||
|
|
||||||
|
MPVLib.setOptionString("tls-verify", "no")
|
||||||
|
MPVLib.setOptionString("http-reconnect", "yes")
|
||||||
|
MPVLib.setOptionString("stream-reconnect", "yes")
|
||||||
|
|
||||||
|
MPVLib.setOptionString("demuxer-lavf-o", "live_start_index=0,prefer_x_start=1,http_persistent=0")
|
||||||
|
MPVLib.setOptionString("demuxer-seekable-cache", "yes")
|
||||||
|
MPVLib.setOptionString("force-seekable", "yes")
|
||||||
|
|
||||||
|
MPVLib.setOptionString("sub-auto", "fuzzy")
|
||||||
|
MPVLib.setOptionString("sub-visibility", "yes")
|
||||||
|
MPVLib.setOptionString("sub-font-size", "48")
|
||||||
|
MPVLib.setOptionString("sub-pos", "100")
|
||||||
|
MPVLib.setOptionString("sub-color", "#FFFFFFFF")
|
||||||
|
MPVLib.setOptionString("sub-border-size", "3")
|
||||||
|
MPVLib.setOptionString("sub-border-color", "#FF000000")
|
||||||
|
MPVLib.setOptionString("sub-shadow-offset", "2")
|
||||||
|
MPVLib.setOptionString("sub-shadow-color", "#80000000")
|
||||||
|
|
||||||
|
MPVLib.setOptionString("osd-fonts-dir", "/system/fonts")
|
||||||
|
MPVLib.setOptionString("sub-fonts-dir", "/system/fonts")
|
||||||
|
MPVLib.setOptionString("sub-font", "Roboto")
|
||||||
|
MPVLib.setOptionString("embeddedfonts", "yes")
|
||||||
|
|
||||||
|
MPVLib.setOptionString("sub-codepage", "auto")
|
||||||
|
|
||||||
|
MPVLib.setOptionString("blend-subtitles", "no")
|
||||||
|
MPVLib.setOptionString("sub-use-margins", "yes")
|
||||||
|
MPVLib.setOptionString("sub-ass-override", "force")
|
||||||
|
MPVLib.setOptionString("sub-scale", "1.0")
|
||||||
|
MPVLib.setOptionString("sub-fix-timing", "yes")
|
||||||
|
|
||||||
|
MPVLib.setOptionString("osc", "no")
|
||||||
|
MPVLib.setOptionString("osd-level", "1")
|
||||||
|
|
||||||
|
MPVLib.setOptionString("sid", "auto")
|
||||||
|
|
||||||
|
MPVLib.setOptionString("terminal", "no")
|
||||||
|
MPVLib.setOptionString("input-default-bindings", "no")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeProperties() {
|
||||||
|
// MPV format constants (from MPVLib source)
|
||||||
|
val MPV_FORMAT_NONE = 0
|
||||||
|
val MPV_FORMAT_FLAG = 3
|
||||||
|
val MPV_FORMAT_INT64 = 4
|
||||||
|
val MPV_FORMAT_DOUBLE = 5
|
||||||
|
|
||||||
|
MPVLib.observeProperty("time-pos", MPV_FORMAT_DOUBLE)
|
||||||
|
MPVLib.observeProperty("duration/full", MPV_FORMAT_DOUBLE) // Use /full for complete HLS duration
|
||||||
|
MPVLib.observeProperty("pause", MPV_FORMAT_FLAG)
|
||||||
|
MPVLib.observeProperty("paused-for-cache", MPV_FORMAT_FLAG)
|
||||||
|
MPVLib.observeProperty("eof-reached", MPV_FORMAT_FLAG)
|
||||||
|
MPVLib.observeProperty("video-params/aspect", MPV_FORMAT_DOUBLE)
|
||||||
|
MPVLib.observeProperty("width", MPV_FORMAT_INT64)
|
||||||
|
MPVLib.observeProperty("height", MPV_FORMAT_INT64)
|
||||||
|
MPVLib.observeProperty("track-list", MPV_FORMAT_NONE)
|
||||||
|
|
||||||
|
// Observe subtitle properties for debugging
|
||||||
|
MPVLib.observeProperty("sid", MPV_FORMAT_INT64)
|
||||||
|
MPVLib.observeProperty("sub-visibility", MPV_FORMAT_FLAG)
|
||||||
|
MPVLib.observeProperty("sub-text", MPV_FORMAT_NONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadFile(url: String) {
|
||||||
|
Log.d(TAG, "Loading file: $url")
|
||||||
|
// Reset load event flag for new file
|
||||||
|
hasLoadEventFired = false
|
||||||
|
MPVLib.command(arrayOf("loadfile", url))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
|
||||||
|
fun setDataSource(url: String) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
// Headers were already set during initialization in initOptions()
|
||||||
|
loadFile(url)
|
||||||
|
} else {
|
||||||
|
pendingDataSource = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setHeaders(headers: Map<String, String>?) {
|
||||||
|
httpHeaders = headers
|
||||||
|
Log.d(TAG, "Headers set: $headers")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyHttpHeadersAsOptions() {
|
||||||
|
// Always set user-agent (this works reliably)
|
||||||
|
val userAgent = httpHeaders?.get("User-Agent")
|
||||||
|
?: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||||
|
|
||||||
|
Log.d(TAG, "Setting User-Agent: $userAgent")
|
||||||
|
MPVLib.setOptionString("user-agent", userAgent)
|
||||||
|
|
||||||
|
// Additionally, set other headers via http-header-fields if present
|
||||||
|
// This is needed for streams that require Referer, Origin, Cookie, etc.
|
||||||
|
httpHeaders?.let { headers ->
|
||||||
|
val otherHeaders = headers.filterKeys { it != "User-Agent" }
|
||||||
|
if (otherHeaders.isNotEmpty()) {
|
||||||
|
// Format as comma-separated "Key: Value" pairs
|
||||||
|
val headerString = otherHeaders.map { (key, value) -> "$key: $value" }.joinToString(",")
|
||||||
|
Log.d(TAG, "Setting additional headers: $headerString")
|
||||||
|
MPVLib.setOptionString("http-header-fields", headerString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPaused(paused: Boolean) {
|
||||||
|
isPaused = paused
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
MPVLib.setPropertyBoolean("pause", paused)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun seekTo(positionSeconds: Double) {
|
||||||
|
Log.d(TAG, "seekTo called: positionSeconds=$positionSeconds, isMpvInitialized=$isMpvInitialized")
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
Log.d(TAG, "Executing MPV seek command: seek $positionSeconds absolute")
|
||||||
|
MPVLib.command(arrayOf("seek", positionSeconds.toString(), "absolute"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSpeed(speed: Double) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
MPVLib.setPropertyDouble("speed", speed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setVolume(volume: Double) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
// MPV volume is 0-100
|
||||||
|
MPVLib.setPropertyDouble("volume", volume * 100.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAudioTrack(trackId: Int) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
if (trackId == -1) {
|
||||||
|
MPVLib.setPropertyString("aid", "no")
|
||||||
|
} else {
|
||||||
|
MPVLib.setPropertyInt("aid", trackId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleTrack(trackId: Int) {
|
||||||
|
Log.d(TAG, "setSubtitleTrack called: trackId=$trackId, isMpvInitialized=$isMpvInitialized")
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
if (trackId == -1) {
|
||||||
|
Log.d(TAG, "Disabling subtitles (sid=no)")
|
||||||
|
MPVLib.setPropertyString("sid", "no")
|
||||||
|
MPVLib.setPropertyString("sub-visibility", "no")
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Setting subtitle track to: $trackId")
|
||||||
|
MPVLib.setPropertyInt("sid", trackId)
|
||||||
|
// Ensure subtitles are visible
|
||||||
|
MPVLib.setPropertyString("sub-visibility", "yes")
|
||||||
|
|
||||||
|
// Debug: Verify the subtitle was set correctly
|
||||||
|
val currentSid = MPVLib.getPropertyInt("sid")
|
||||||
|
val subVisibility = MPVLib.getPropertyString("sub-visibility")
|
||||||
|
val subDelay = MPVLib.getPropertyDouble("sub-delay")
|
||||||
|
val subScale = MPVLib.getPropertyDouble("sub-scale")
|
||||||
|
Log.d(TAG, "After setting - sid=$currentSid, sub-visibility=$subVisibility, sub-delay=$subDelay, sub-scale=$subScale")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setResizeMode(mode: String) {
|
||||||
|
Log.d(TAG, "setResizeMode called: mode=$mode, isMpvInitialized=$isMpvInitialized")
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
when (mode) {
|
||||||
|
"contain" -> {
|
||||||
|
// Letterbox - show entire video with black bars
|
||||||
|
MPVLib.setPropertyDouble("panscan", 0.0)
|
||||||
|
MPVLib.setPropertyString("keepaspect", "yes")
|
||||||
|
}
|
||||||
|
"cover" -> {
|
||||||
|
// Fill/crop - zoom to fill, cropping edges
|
||||||
|
MPVLib.setPropertyDouble("panscan", 1.0)
|
||||||
|
MPVLib.setPropertyString("keepaspect", "yes")
|
||||||
|
}
|
||||||
|
"stretch" -> {
|
||||||
|
// Stretch - disable aspect ratio
|
||||||
|
MPVLib.setPropertyDouble("panscan", 0.0)
|
||||||
|
MPVLib.setPropertyString("keepaspect", "no")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// Default to contain
|
||||||
|
MPVLib.setPropertyDouble("panscan", 0.0)
|
||||||
|
MPVLib.setPropertyString("keepaspect", "yes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtitle Styling Methods
|
||||||
|
|
||||||
|
fun setSubtitleSize(size: Int) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
Log.d(TAG, "Setting subtitle size: $size")
|
||||||
|
MPVLib.setPropertyInt("sub-font-size", size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleColor(color: String) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
// MPV expects color in #AARRGGBB format, but we receive #RRGGBB
|
||||||
|
// Convert to MPV format with full opacity
|
||||||
|
val mpvColor = if (color.length == 7) "#FF${color.substring(1)}" else color
|
||||||
|
Log.d(TAG, "Setting subtitle color: $mpvColor")
|
||||||
|
MPVLib.setPropertyString("sub-color", mpvColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleBackgroundColor(color: String, opacity: Float) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
// Convert opacity (0-1) to hex (00-FF)
|
||||||
|
val alphaHex = (opacity * 255).toInt().coerceIn(0, 255).let {
|
||||||
|
String.format("%02X", it)
|
||||||
|
}
|
||||||
|
// MPV format: #AARRGGBB
|
||||||
|
val baseColor = if (color.startsWith("#")) color.substring(1) else color
|
||||||
|
val mpvColor = "#${alphaHex}${baseColor.takeLast(6)}"
|
||||||
|
Log.d(TAG, "Setting subtitle background: $mpvColor (opacity: $opacity)")
|
||||||
|
MPVLib.setPropertyString("sub-back-color", mpvColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleBorderSize(size: Int) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
Log.d(TAG, "Setting subtitle border size: $size")
|
||||||
|
MPVLib.setPropertyInt("sub-border-size", size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleBorderColor(color: String) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
val mpvColor = if (color.length == 7) "#FF${color.substring(1)}" else color
|
||||||
|
Log.d(TAG, "Setting subtitle border color: $mpvColor")
|
||||||
|
MPVLib.setPropertyString("sub-border-color", mpvColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleShadow(enabled: Boolean, offset: Int) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
Log.d(TAG, "Setting subtitle shadow: enabled=$enabled, offset=$offset")
|
||||||
|
if (enabled) {
|
||||||
|
MPVLib.setPropertyInt("sub-shadow-offset", offset)
|
||||||
|
MPVLib.setPropertyString("sub-shadow-color", "#80000000")
|
||||||
|
} else {
|
||||||
|
MPVLib.setPropertyInt("sub-shadow-offset", 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitlePosition(pos: Int) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
// sub-pos: 0=top, 100=bottom, can go beyond 100 for more offset
|
||||||
|
// UI sends bottomOffset (0=at bottom, higher=more up from bottom)
|
||||||
|
// Convert: MPV pos = 100 - (bottomOffset / screenHeightFactor)
|
||||||
|
// Simplified: just pass pos directly, UI should convert
|
||||||
|
Log.d(TAG, "Setting subtitle position: $pos")
|
||||||
|
MPVLib.setPropertyInt("sub-pos", pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleDelay(delaySec: Double) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
Log.d(TAG, "Setting subtitle delay: $delaySec seconds")
|
||||||
|
MPVLib.setPropertyDouble("sub-delay", delaySec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleScale(scale: Double) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
Log.d(TAG, "Setting subtitle scale: $scale")
|
||||||
|
MPVLib.setPropertyDouble("sub-scale", scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleAlignment(align: String) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
// MPV sub-justify values: left, center, right, auto
|
||||||
|
val mpvAlign = when (align) {
|
||||||
|
"left" -> "left"
|
||||||
|
"right" -> "right"
|
||||||
|
"center" -> "center"
|
||||||
|
else -> "center"
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Setting subtitle alignment: $mpvAlign")
|
||||||
|
MPVLib.setPropertyString("sub-justify", mpvAlign)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleBold(bold: Boolean) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
Log.d(TAG, "Setting subtitle bold: $bold")
|
||||||
|
MPVLib.setPropertyString("sub-bold", if (bold) "yes" else "no")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleItalic(italic: Boolean) {
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
Log.d(TAG, "Setting subtitle italic: $italic")
|
||||||
|
MPVLib.setPropertyString("sub-italic", if (italic) "yes" else "no")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MPVLib.EventObserver implementation
|
||||||
|
|
||||||
|
override fun eventProperty(property: String) {
|
||||||
|
Log.d(TAG, "Property changed: $property")
|
||||||
|
when (property) {
|
||||||
|
"track-list" -> {
|
||||||
|
// Parse track list and notify React Native
|
||||||
|
parseAndSendTracks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseAndSendTracks() {
|
||||||
|
try {
|
||||||
|
val trackCount = MPVLib.getPropertyInt("track-list/count") ?: 0
|
||||||
|
Log.d(TAG, "Track count: $trackCount")
|
||||||
|
|
||||||
|
val audioTracks = mutableListOf<Map<String, Any>>()
|
||||||
|
val subtitleTracks = mutableListOf<Map<String, Any>>()
|
||||||
|
|
||||||
|
for (i in 0 until trackCount) {
|
||||||
|
val type = MPVLib.getPropertyString("track-list/$i/type") ?: continue
|
||||||
|
val id = MPVLib.getPropertyInt("track-list/$i/id") ?: continue
|
||||||
|
val title = MPVLib.getPropertyString("track-list/$i/title") ?: ""
|
||||||
|
val lang = MPVLib.getPropertyString("track-list/$i/lang") ?: ""
|
||||||
|
val codec = MPVLib.getPropertyString("track-list/$i/codec") ?: ""
|
||||||
|
|
||||||
|
val trackName = when {
|
||||||
|
title.isNotEmpty() -> title
|
||||||
|
lang.isNotEmpty() -> lang.uppercase()
|
||||||
|
else -> "Track $id"
|
||||||
|
}
|
||||||
|
|
||||||
|
val track = mapOf(
|
||||||
|
"id" to id,
|
||||||
|
"name" to trackName,
|
||||||
|
"language" to lang,
|
||||||
|
"codec" to codec
|
||||||
|
)
|
||||||
|
|
||||||
|
when (type) {
|
||||||
|
"audio" -> {
|
||||||
|
Log.d(TAG, "Found audio track: $track")
|
||||||
|
audioTracks.add(track)
|
||||||
|
}
|
||||||
|
"sub" -> {
|
||||||
|
Log.d(TAG, "Found subtitle track: $track")
|
||||||
|
subtitleTracks.add(track)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Sending tracks - Audio: ${audioTracks.size}, Subtitles: ${subtitleTracks.size}")
|
||||||
|
onTracksChangedCallback?.invoke(audioTracks, subtitleTracks)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error parsing tracks", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun eventProperty(property: String, value: Long) {
|
||||||
|
Log.d(TAG, "Property $property = $value (Long)")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun eventProperty(property: String, value: Double) {
|
||||||
|
Log.d(TAG, "Property $property = $value (Double)")
|
||||||
|
when (property) {
|
||||||
|
"time-pos" -> {
|
||||||
|
val duration = MPVLib.getPropertyDouble("duration/full") ?: MPVLib.getPropertyDouble("duration") ?: 0.0
|
||||||
|
onProgressCallback?.invoke(value, duration)
|
||||||
|
}
|
||||||
|
"duration/full", "duration" -> {
|
||||||
|
// Only fire onLoad once when video dimensions are available
|
||||||
|
// For HLS streams, duration updates incrementally as segments are fetched
|
||||||
|
if (!hasLoadEventFired) {
|
||||||
|
val width = MPVLib.getPropertyInt("width") ?: 0
|
||||||
|
val height = MPVLib.getPropertyInt("height") ?: 0
|
||||||
|
// Wait until we have valid dimensions before firing onLoad
|
||||||
|
if (width > 0 && height > 0 && value > 0) {
|
||||||
|
hasLoadEventFired = true
|
||||||
|
Log.d(TAG, "Firing onLoad event: duration=$value, width=$width, height=$height")
|
||||||
|
onLoadCallback?.invoke(value, width, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun eventProperty(property: String, value: Boolean) {
|
||||||
|
Log.d(TAG, "Property $property = $value (Boolean)")
|
||||||
|
when (property) {
|
||||||
|
"eof-reached" -> {
|
||||||
|
if (value) {
|
||||||
|
onEndCallback?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun eventProperty(property: String, value: String) {
|
||||||
|
Log.d(TAG, "Property $property = $value (String)")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun event(eventId: Int) {
|
||||||
|
Log.d(TAG, "Event: $eventId")
|
||||||
|
// MPV event constants (from MPVLib source)
|
||||||
|
val MPV_EVENT_FILE_LOADED = 8
|
||||||
|
val MPV_EVENT_END_FILE = 7
|
||||||
|
|
||||||
|
when (eventId) {
|
||||||
|
MPV_EVENT_FILE_LOADED -> {
|
||||||
|
// File is loaded, start playback if not paused
|
||||||
|
if (!isPaused) {
|
||||||
|
MPVLib.setPropertyBoolean("pause", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MPV_EVENT_END_FILE -> {
|
||||||
|
Log.d(TAG, "MPV_EVENT_END_FILE")
|
||||||
|
|
||||||
|
// Heuristic: If duration is effectively 0 at end of file, it's a load error
|
||||||
|
val duration = MPVLib.getPropertyDouble("duration/full") ?: MPVLib.getPropertyDouble("duration") ?: 0.0
|
||||||
|
val timePos = MPVLib.getPropertyDouble("time-pos") ?: 0.0
|
||||||
|
val eofReached = MPVLib.getPropertyBoolean("eof-reached") ?: false
|
||||||
|
|
||||||
|
Log.d(TAG, "End stats - Duration: $duration, Time: $timePos, EOF: $eofReached")
|
||||||
|
|
||||||
|
if (duration < 1.0 && !eofReached) {
|
||||||
|
val customError = "Unable to play media. Source may be unreachable."
|
||||||
|
Log.e(TAG, "Playback error detected (heuristic): $customError")
|
||||||
|
onErrorCallback?.invoke(customError)
|
||||||
|
} else {
|
||||||
|
onEndCallback?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
android/app/src/main/java/com/nuvio/app/mpv/MpvPackage.kt
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.nuvio.app.mpv
|
||||||
|
|
||||||
|
import com.facebook.react.ReactPackage
|
||||||
|
import com.facebook.react.bridge.NativeModule
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext
|
||||||
|
import com.facebook.react.uimanager.ViewManager
|
||||||
|
|
||||||
|
class MpvPackage : ReactPackage {
|
||||||
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
||||||
|
return listOf(MpvPlayerViewManager(reactContext))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,241 @@
|
||||||
|
package com.nuvio.app.mpv
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import com.facebook.react.bridge.Arguments
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext
|
||||||
|
import com.facebook.react.bridge.ReadableArray
|
||||||
|
import com.facebook.react.common.MapBuilder
|
||||||
|
import com.facebook.react.uimanager.SimpleViewManager
|
||||||
|
import com.facebook.react.uimanager.ThemedReactContext
|
||||||
|
import com.facebook.react.uimanager.annotations.ReactProp
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter
|
||||||
|
|
||||||
|
class MpvPlayerViewManager(
|
||||||
|
private val reactContext: ReactApplicationContext
|
||||||
|
) : SimpleViewManager<MPVView>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val REACT_CLASS = "MpvPlayer"
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
const val COMMAND_SEEK = 1
|
||||||
|
const val COMMAND_SET_AUDIO_TRACK = 2
|
||||||
|
const val COMMAND_SET_SUBTITLE_TRACK = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getName(): String = REACT_CLASS
|
||||||
|
|
||||||
|
override fun createViewInstance(context: ThemedReactContext): MPVView {
|
||||||
|
val view = MPVView(context)
|
||||||
|
// Note: Do NOT set background color - it will block the SurfaceView content
|
||||||
|
|
||||||
|
// Set up event callbacks
|
||||||
|
view.onLoadCallback = { duration, width, height ->
|
||||||
|
val event = Arguments.createMap().apply {
|
||||||
|
putDouble("duration", duration)
|
||||||
|
putInt("width", width)
|
||||||
|
putInt("height", height)
|
||||||
|
}
|
||||||
|
sendEvent(context, view.id, "onLoad", event)
|
||||||
|
}
|
||||||
|
|
||||||
|
view.onProgressCallback = { position, duration ->
|
||||||
|
val event = Arguments.createMap().apply {
|
||||||
|
putDouble("currentTime", position)
|
||||||
|
putDouble("duration", duration)
|
||||||
|
}
|
||||||
|
sendEvent(context, view.id, "onProgress", event)
|
||||||
|
}
|
||||||
|
|
||||||
|
view.onEndCallback = {
|
||||||
|
sendEvent(context, view.id, "onEnd", Arguments.createMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
view.onErrorCallback = { message ->
|
||||||
|
val event = Arguments.createMap().apply {
|
||||||
|
putString("error", message)
|
||||||
|
}
|
||||||
|
sendEvent(context, view.id, "onError", event)
|
||||||
|
}
|
||||||
|
|
||||||
|
view.onTracksChangedCallback = { audioTracks, subtitleTracks ->
|
||||||
|
val event = Arguments.createMap().apply {
|
||||||
|
val audioArray = Arguments.createArray()
|
||||||
|
audioTracks.forEach { track ->
|
||||||
|
val trackMap = Arguments.createMap().apply {
|
||||||
|
putInt("id", track["id"] as Int)
|
||||||
|
putString("name", track["name"] as String)
|
||||||
|
putString("language", track["language"] as String)
|
||||||
|
putString("codec", track["codec"] as String)
|
||||||
|
}
|
||||||
|
audioArray.pushMap(trackMap)
|
||||||
|
}
|
||||||
|
putArray("audioTracks", audioArray)
|
||||||
|
|
||||||
|
val subtitleArray = Arguments.createArray()
|
||||||
|
subtitleTracks.forEach { track ->
|
||||||
|
val trackMap = Arguments.createMap().apply {
|
||||||
|
putInt("id", track["id"] as Int)
|
||||||
|
putString("name", track["name"] as String)
|
||||||
|
putString("language", track["language"] as String)
|
||||||
|
putString("codec", track["codec"] as String)
|
||||||
|
}
|
||||||
|
subtitleArray.pushMap(trackMap)
|
||||||
|
}
|
||||||
|
putArray("subtitleTracks", subtitleArray)
|
||||||
|
}
|
||||||
|
sendEvent(context, view.id, "onTracksChanged", event)
|
||||||
|
}
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendEvent(context: ThemedReactContext, viewId: Int, eventName: String, params: com.facebook.react.bridge.WritableMap) {
|
||||||
|
context.getJSModule(RCTEventEmitter::class.java)
|
||||||
|
.receiveEvent(viewId, eventName, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
|
||||||
|
return MapBuilder.builder<String, Any>()
|
||||||
|
.put("onLoad", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onLoad")))
|
||||||
|
.put("onProgress", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onProgress")))
|
||||||
|
.put("onEnd", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onEnd")))
|
||||||
|
.put("onError", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onError")))
|
||||||
|
.put("onTracksChanged", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onTracksChanged")))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCommandsMap(): Map<String, Int> {
|
||||||
|
return MapBuilder.of(
|
||||||
|
"seek", COMMAND_SEEK,
|
||||||
|
"setAudioTrack", COMMAND_SET_AUDIO_TRACK,
|
||||||
|
"setSubtitleTrack", COMMAND_SET_SUBTITLE_TRACK
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun receiveCommand(view: MPVView, commandId: String?, args: ReadableArray?) {
|
||||||
|
android.util.Log.d("MpvPlayerViewManager", "receiveCommand: $commandId, args: $args")
|
||||||
|
when (commandId) {
|
||||||
|
"seek" -> {
|
||||||
|
val position = args?.getDouble(0)
|
||||||
|
android.util.Log.d("MpvPlayerViewManager", "Seek command received: position=$position")
|
||||||
|
position?.let { view.seekTo(it) }
|
||||||
|
}
|
||||||
|
"setAudioTrack" -> {
|
||||||
|
args?.getInt(0)?.let { view.setAudioTrack(it) }
|
||||||
|
}
|
||||||
|
"setSubtitleTrack" -> {
|
||||||
|
args?.getInt(0)?.let { view.setSubtitleTrack(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// React Props
|
||||||
|
|
||||||
|
@ReactProp(name = "source")
|
||||||
|
fun setSource(view: MPVView, source: String?) {
|
||||||
|
source?.let { view.setDataSource(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "paused")
|
||||||
|
fun setPaused(view: MPVView, paused: Boolean) {
|
||||||
|
view.setPaused(paused)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "volume", defaultFloat = 1.0f)
|
||||||
|
fun setVolume(view: MPVView, volume: Float) {
|
||||||
|
view.setVolume(volume.toDouble())
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "rate", defaultFloat = 1.0f)
|
||||||
|
fun setRate(view: MPVView, rate: Float) {
|
||||||
|
view.setSpeed(rate.toDouble())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle backgroundColor prop to prevent crash from React Native style system
|
||||||
|
@ReactProp(name = "backgroundColor", customType = "Color")
|
||||||
|
fun setBackgroundColor(view: MPVView, color: Int?) {
|
||||||
|
// Intentionally ignoring - background color would block the TextureView content
|
||||||
|
// Leave the view transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "resizeMode")
|
||||||
|
fun setResizeMode(view: MPVView, resizeMode: String?) {
|
||||||
|
view.setResizeMode(resizeMode ?: "contain")
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "headers")
|
||||||
|
fun setHeaders(view: MPVView, headers: com.facebook.react.bridge.ReadableMap?) {
|
||||||
|
if (headers != null) {
|
||||||
|
val headerMap = mutableMapOf<String, String>()
|
||||||
|
val iterator = headers.keySetIterator()
|
||||||
|
while (iterator.hasNextKey()) {
|
||||||
|
val key = iterator.nextKey()
|
||||||
|
headers.getString(key)?.let { value ->
|
||||||
|
headerMap[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.setHeaders(headerMap)
|
||||||
|
} else {
|
||||||
|
view.setHeaders(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "decoderMode")
|
||||||
|
fun setDecoderMode(view: MPVView, decoderMode: String?) {
|
||||||
|
view.decoderMode = decoderMode ?: "auto"
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "gpuMode")
|
||||||
|
fun setGpuMode(view: MPVView, gpuMode: String?) {
|
||||||
|
view.gpuMode = gpuMode ?: "gpu"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtitle Styling Props
|
||||||
|
|
||||||
|
@ReactProp(name = "subtitleSize", defaultInt = 48)
|
||||||
|
fun setSubtitleSize(view: MPVView, size: Int) {
|
||||||
|
view.setSubtitleSize(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "subtitleColor")
|
||||||
|
fun setSubtitleColor(view: MPVView, color: String?) {
|
||||||
|
view.setSubtitleColor(color ?: "#FFFFFF")
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "subtitleBackgroundOpacity", defaultFloat = 0.0f)
|
||||||
|
fun setSubtitleBackgroundOpacity(view: MPVView, opacity: Float) {
|
||||||
|
// Black background with user-specified opacity
|
||||||
|
view.setSubtitleBackgroundColor("#000000", opacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "subtitleBorderSize", defaultInt = 3)
|
||||||
|
fun setSubtitleBorderSize(view: MPVView, size: Int) {
|
||||||
|
view.setSubtitleBorderSize(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "subtitleBorderColor")
|
||||||
|
fun setSubtitleBorderColor(view: MPVView, color: String?) {
|
||||||
|
view.setSubtitleBorderColor(color ?: "#000000")
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "subtitleShadowEnabled", defaultBoolean = true)
|
||||||
|
fun setSubtitleShadowEnabled(view: MPVView, enabled: Boolean) {
|
||||||
|
view.setSubtitleShadow(enabled, if (enabled) 2 else 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "subtitlePosition", defaultInt = 100)
|
||||||
|
fun setSubtitlePosition(view: MPVView, pos: Int) {
|
||||||
|
view.setSubtitlePosition(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "subtitleDelay", defaultFloat = 0.0f)
|
||||||
|
fun setSubtitleDelay(view: MPVView, delay: Float) {
|
||||||
|
view.setSubtitleDelay(delay.toDouble())
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "subtitleAlignment")
|
||||||
|
fun setSubtitleAlignment(view: MPVView, align: String?) {
|
||||||
|
view.setSubtitleAlignment(align ?: "center")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 14 KiB |
|
|
@ -3,5 +3,5 @@
|
||||||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||||
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
||||||
<string name="expo_system_ui_user_interface_style" translatable="false">dark</string>
|
<string name="expo_system_ui_user_interface_style" translatable="false">dark</string>
|
||||||
<string name="expo_runtime_version">1.2.4</string>
|
<string name="expo_runtime_version">1.4.1</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -1,17 +1,11 @@
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
<item name="android:textColor">@android:color/black</item>
|
<item name="android:enforceNavigationBarContrast" tools:targetApi="29">true</item>
|
||||||
<item name="android:editTextStyle">@style/ResetEditText</item>
|
|
||||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="android:statusBarColor">#020404</item>
|
<item name="android:statusBarColor">#020404</item>
|
||||||
<item name="android:windowBackground">@color/activityBackground</item>
|
<item name="android:windowBackground">@color/activityBackground</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="ResetEditText" parent="@android:style/Widget.EditText">
|
|
||||||
<item name="android:padding">0dp</item>
|
|
||||||
<item name="android:textColorHint">#c8c8c8</item>
|
|
||||||
<item name="android:textColor">@android:color/black</item>
|
|
||||||
</style>
|
|
||||||
<style name="Theme.App.SplashScreen" parent="AppTheme">
|
<style name="Theme.App.SplashScreen" parent="AppTheme">
|
||||||
<item name="android:windowBackground">@drawable/ic_launcher_background</item>
|
<item name="android:windowBackground">@drawable/ic_launcher_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,33 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
buildToolsVersion = findProperty('android.buildToolsVersion') ?: '35.0.0'
|
buildToolsVersion = "35.0.0"
|
||||||
minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '24')
|
minSdkVersion = 24
|
||||||
compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '35')
|
compileSdkVersion = 35
|
||||||
targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '34')
|
targetSdkVersion = 35
|
||||||
kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.25'
|
castFrameworkVersion = "22.1.0"
|
||||||
|
ndkVersion = "29.0.14206865" // Required for libmpv AAR built with NDK r29
|
||||||
ndkVersion = "26.1.10909125"
|
}
|
||||||
}
|
repositories {
|
||||||
repositories {
|
google()
|
||||||
google()
|
mavenCentral()
|
||||||
mavenCentral()
|
}
|
||||||
}
|
dependencies {
|
||||||
dependencies {
|
classpath('com.android.tools.build:gradle')
|
||||||
classpath('com.android.tools.build:gradle')
|
classpath('com.facebook.react:react-native-gradle-plugin')
|
||||||
classpath('com.facebook.react:react-native-gradle-plugin')
|
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
|
||||||
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
|
classpath("io.sentry:sentry-android-gradle-plugin:5.12.2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: "com.facebook.react.rootproject"
|
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
google()
|
||||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
mavenCentral()
|
||||||
url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android'))
|
maven { url 'https://www.jitpack.io' }
|
||||||
}
|
}
|
||||||
maven {
|
|
||||||
// Android JSC is installed from npm
|
|
||||||
url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist'))
|
|
||||||
}
|
|
||||||
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
maven { url 'https://www.jitpack.io' }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply plugin: "expo-root-project"
|
||||||
|
apply plugin: "com.facebook.react.rootproject"
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
||||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
|
||||||
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
# org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
|
|
||||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
# Android operating system, and which are packaged with your app's APK
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
|
@ -41,6 +41,11 @@ newArchEnabled=true
|
||||||
# If set to false, you will be using JSC instead.
|
# If set to false, you will be using JSC instead.
|
||||||
hermesEnabled=true
|
hermesEnabled=true
|
||||||
|
|
||||||
|
# Use this property to enable edge-to-edge display support.
|
||||||
|
# This allows your app to draw behind system bars for an immersive UI.
|
||||||
|
# Note: Only works with ReactActivity and should not be used with custom Activity.
|
||||||
|
edgeToEdgeEnabled=true
|
||||||
|
|
||||||
# Enable GIF support in React Native images (~200 B increase)
|
# Enable GIF support in React Native images (~200 B increase)
|
||||||
expo.gif.enabled=true
|
expo.gif.enabled=true
|
||||||
# Enable webp support in React Native images (~85 KB increase)
|
# Enable webp support in React Native images (~85 KB increase)
|
||||||
|
|
@ -54,8 +59,7 @@ EX_DEV_CLIENT_NETWORK_INSPECTOR=true
|
||||||
|
|
||||||
# Use legacy packaging to compress native libraries in the resulting APK.
|
# Use legacy packaging to compress native libraries in the resulting APK.
|
||||||
expo.useLegacyPackaging=false
|
expo.useLegacyPackaging=false
|
||||||
android.minSdkVersion=26
|
|
||||||
RNVideo_media3Version=1.8.0
|
|
||||||
|
|
||||||
# Whether the app is configured to use edge-to-edge via the app config or `react-native-edge-to-edge` plugin
|
# Specifies whether the app is configured to use edge-to-edge via the app config or plugin
|
||||||
expo.edgeToEdgeEnabled=false
|
# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge.
|
||||||
|
expo.edgeToEdgeEnabled=true
|
||||||
|
|
|
||||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
||||||
7
android/gradlew
vendored
|
|
@ -86,8 +86,7 @@ done
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
' "$PWD" ) || exit
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|
@ -115,7 +114,7 @@ case "$( uname )" in #(
|
||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH="\\\"\\\""
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
|
|
@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
-classpath "$CLASSPATH" \
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
|
|
|
||||||
4
android/gradlew.bat
vendored
|
|
@ -70,11 +70,11 @@ goto fail
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
defaults.url=https://sentry.io/
|
defaults.url=https://sentry.io/
|
||||||
defaults.org=tapframe
|
defaults.org=tapframe
|
||||||
defaults.project=react-native
|
defaults.project=react-native
|
||||||
# Using SENTRY_AUTH_TOKEN environment variable
|
auth.token=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
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,39 @@
|
||||||
pluginManagement {
|
pluginManagement {
|
||||||
includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().toString())
|
def reactNativeGradlePlugin = new File(
|
||||||
|
providers.exec {
|
||||||
|
workingDir(rootDir)
|
||||||
|
commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
|
||||||
|
}.standardOutput.asText.get().trim()
|
||||||
|
).getParentFile().absolutePath
|
||||||
|
includeBuild(reactNativeGradlePlugin)
|
||||||
|
|
||||||
|
def expoPluginsPath = new File(
|
||||||
|
providers.exec {
|
||||||
|
workingDir(rootDir)
|
||||||
|
commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })")
|
||||||
|
}.standardOutput.asText.get().trim(),
|
||||||
|
"../android/expo-gradle-plugin"
|
||||||
|
).absolutePath
|
||||||
|
includeBuild(expoPluginsPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("com.facebook.react.settings")
|
||||||
|
id("expo-autolinking-settings")
|
||||||
}
|
}
|
||||||
plugins { id("com.facebook.react.settings") }
|
|
||||||
|
|
||||||
extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
|
extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
|
||||||
if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') {
|
if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') {
|
||||||
ex.autolinkLibrariesFromCommand()
|
ex.autolinkLibrariesFromCommand()
|
||||||
} else {
|
} else {
|
||||||
def command = [
|
ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand)
|
||||||
'node',
|
|
||||||
'--no-warnings',
|
|
||||||
'--eval',
|
|
||||||
'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
|
|
||||||
'react-native-config',
|
|
||||||
'--json',
|
|
||||||
'--platform',
|
|
||||||
'android'
|
|
||||||
].toList()
|
|
||||||
ex.autolinkLibrariesFromCommand(command)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
expoAutolinking.useExpoModules()
|
||||||
|
|
||||||
rootProject.name = 'Nuvio'
|
rootProject.name = 'Nuvio'
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
expoAutolinking.useExpoVersionCatalog()
|
||||||
versionCatalogs {
|
|
||||||
reactAndroidLibs {
|
|
||||||
from(files(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../gradle/libs.versions.toml")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle");
|
|
||||||
useExpoModules()
|
|
||||||
|
|
||||||
include ':app'
|
include ':app'
|
||||||
includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile())
|
includeBuild(expoAutolinking.reactNativeGradlePlugin)
|
||||||
|
|
|
||||||
41
app.json
|
|
@ -2,7 +2,7 @@
|
||||||
"expo": {
|
"expo": {
|
||||||
"name": "Nuvio",
|
"name": "Nuvio",
|
||||||
"slug": "nuvio",
|
"slug": "nuvio",
|
||||||
"version": "1.2.4",
|
"version": "1.4.1",
|
||||||
"orientation": "default",
|
"orientation": "default",
|
||||||
"backgroundColor": "#020404",
|
"backgroundColor": "#020404",
|
||||||
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
|
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
|
||||||
|
|
@ -10,22 +10,24 @@
|
||||||
"scheme": "nuvio",
|
"scheme": "nuvio",
|
||||||
"newArchEnabled": true,
|
"newArchEnabled": true,
|
||||||
"splash": {
|
"splash": {
|
||||||
"image": "./assets/splash-icon.png",
|
"image": "./src/assets/splash-icon-new.png",
|
||||||
"resizeMode": "contain",
|
"resizeMode": "contain",
|
||||||
"backgroundColor": "#020404"
|
"backgroundColor": "#020404"
|
||||||
},
|
},
|
||||||
"ios": {
|
"ios": {
|
||||||
"supportsTablet": true,
|
"supportsTablet": true,
|
||||||
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
|
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
|
||||||
"buildNumber": "19",
|
"buildNumber": "37",
|
||||||
"infoPlist": {
|
"infoPlist": {
|
||||||
"NSAppTransportSecurity": {
|
"NSAppTransportSecurity": {
|
||||||
"NSAllowsArbitraryLoads": true
|
"NSAllowsArbitraryLoads": true
|
||||||
},
|
},
|
||||||
"NSBonjourServices": [
|
"NSBonjourServices": [
|
||||||
"_http._tcp"
|
"_http._tcp",
|
||||||
|
"_googlecast._tcp",
|
||||||
|
"_CC1AD845._googlecast._tcp"
|
||||||
],
|
],
|
||||||
"NSLocalNetworkUsageDescription": "App uses the local network to discover and connect to devices.",
|
"NSLocalNetworkUsageDescription": "Nuvio uses the local network to discover Cast-enabled devices on your WiFi network and to connect to local media servers.",
|
||||||
"NSMicrophoneUsageDescription": "This app does not require microphone access.",
|
"NSMicrophoneUsageDescription": "This app does not require microphone access.",
|
||||||
"UIBackgroundModes": [
|
"UIBackgroundModes": [
|
||||||
"audio"
|
"audio"
|
||||||
|
|
@ -33,9 +35,10 @@
|
||||||
"LSSupportsOpeningDocumentsInPlace": true,
|
"LSSupportsOpeningDocumentsInPlace": true,
|
||||||
"UIFileSharingEnabled": true
|
"UIFileSharingEnabled": true
|
||||||
},
|
},
|
||||||
"bundleIdentifier": "com.nuvio.app",
|
"bundleIdentifier": "com.nuvio.hub",
|
||||||
"associatedDomains": [],
|
"associatedDomains": [],
|
||||||
"jsEngine": "hermes"
|
"jsEngine": "hermes",
|
||||||
|
"appleTeamId": "8QBDZ766S3"
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
|
|
@ -45,10 +48,11 @@
|
||||||
"icon": "./assets/android/mipmap-xxxhdpi/ic_launcher.png",
|
"icon": "./assets/android/mipmap-xxxhdpi/ic_launcher.png",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"INTERNET",
|
"INTERNET",
|
||||||
"WAKE_LOCK"
|
"WAKE_LOCK",
|
||||||
|
"android.permission.WRITE_SETTINGS"
|
||||||
],
|
],
|
||||||
"package": "com.nuvio.app",
|
"package": "com.nuvio.app",
|
||||||
"versionCode": 19,
|
"versionCode": 37,
|
||||||
"architectures": [
|
"architectures": [
|
||||||
"arm64-v8a",
|
"arm64-v8a",
|
||||||
"armeabi-v7a",
|
"armeabi-v7a",
|
||||||
|
|
@ -57,7 +61,6 @@
|
||||||
],
|
],
|
||||||
"jsEngine": "hermes"
|
"jsEngine": "hermes"
|
||||||
},
|
},
|
||||||
|
|
||||||
"extra": {
|
"extra": {
|
||||||
"eas": {
|
"eas": {
|
||||||
"projectId": "909107b8-fe61-45ce-b02f-b02510d306a6"
|
"projectId": "909107b8-fe61-45ce-b02f-b02510d306a6"
|
||||||
|
|
@ -65,6 +68,7 @@
|
||||||
},
|
},
|
||||||
"owner": "nayifleo",
|
"owner": "nayifleo",
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"expo-live-activity",
|
||||||
[
|
[
|
||||||
"@sentry/react-native/expo",
|
"@sentry/react-native/expo",
|
||||||
{
|
{
|
||||||
|
|
@ -73,6 +77,12 @@
|
||||||
"organization": "tapframe"
|
"organization": "tapframe"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"@kesha-antonov/react-native-background-downloader",
|
||||||
|
{
|
||||||
|
"skipMmkvDependency": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"expo-localization",
|
"expo-localization",
|
||||||
[
|
[
|
||||||
"expo-updates",
|
"expo-updates",
|
||||||
|
|
@ -80,11 +90,12 @@
|
||||||
"username": "nayifleo"
|
"username": "nayifleo"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"react-native-bottom-tabs",
|
||||||
[
|
[
|
||||||
"expo-libvlc-player",
|
"react-native-google-cast",
|
||||||
{
|
{
|
||||||
"localNetworkPermission": "Allow $(PRODUCT_NAME) to access your local network",
|
"receiverAppId": "CC1AD845",
|
||||||
"supportsBackgroundPlayback": true
|
"iosStartDiscoveryAfterFirstTapOnCastButton": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
@ -92,8 +103,8 @@
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"checkAutomatically": "ON_ERROR_RECOVERY",
|
"checkAutomatically": "ON_ERROR_RECOVERY",
|
||||||
"fallbackToCacheTimeout": 30000,
|
"fallbackToCacheTimeout": 30000,
|
||||||
"url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"
|
"url": "https://ota.nuvioapp.space/api/manifest"
|
||||||
},
|
},
|
||||||
"runtimeVersion": "1.2.4"
|
"runtimeVersion": "1.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
app/Streams.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 49 KiB |
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
assets/android/mipmap-ldpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
assets/android/mipmap-ldpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 49 KiB |
|
|
@ -1,4 +1,3 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#151515</color>
|
<color name="ic_launcher_background">#000000</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 111 KiB |
BIN
assets/icon.png
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 111 KiB |
|
|
@ -1,128 +1,128 @@
|
||||||
{
|
{
|
||||||
"images":[
|
"images": [
|
||||||
{
|
{
|
||||||
"idiom":"iphone",
|
"idiom": "iphone",
|
||||||
"size":"20x20",
|
"size": "20x20",
|
||||||
"scale":"2x",
|
"scale": "2x",
|
||||||
"filename":"Icon-App-20x20@2x.png"
|
"filename": "Icon-App-20x20@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"iphone",
|
"idiom": "iphone",
|
||||||
"size":"20x20",
|
"size": "20x20",
|
||||||
"scale":"3x",
|
"scale": "3x",
|
||||||
"filename":"Icon-App-20x20@3x.png"
|
"filename": "Icon-App-20x20@3x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"iphone",
|
"idiom": "iphone",
|
||||||
"size":"29x29",
|
"size": "29x29",
|
||||||
"scale":"1x",
|
"scale": "1x",
|
||||||
"filename":"Icon-App-29x29@1x.png"
|
"filename": "Icon-App-29x29@1x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"iphone",
|
"idiom": "iphone",
|
||||||
"size":"29x29",
|
"size": "29x29",
|
||||||
"scale":"2x",
|
"scale": "2x",
|
||||||
"filename":"Icon-App-29x29@2x.png"
|
"filename": "Icon-App-29x29@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"iphone",
|
"idiom": "iphone",
|
||||||
"size":"29x29",
|
"size": "29x29",
|
||||||
"scale":"3x",
|
"scale": "3x",
|
||||||
"filename":"Icon-App-29x29@3x.png"
|
"filename": "Icon-App-29x29@3x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"iphone",
|
"idiom": "iphone",
|
||||||
"size":"40x40",
|
"size": "40x40",
|
||||||
"scale":"2x",
|
"scale": "2x",
|
||||||
"filename":"Icon-App-40x40@2x.png"
|
"filename": "Icon-App-40x40@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"iphone",
|
"idiom": "iphone",
|
||||||
"size":"40x40",
|
"size": "40x40",
|
||||||
"scale":"3x",
|
"scale": "3x",
|
||||||
"filename":"Icon-App-40x40@3x.png"
|
"filename": "Icon-App-40x40@3x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"iphone",
|
"idiom": "iphone",
|
||||||
"size":"60x60",
|
"size": "60x60",
|
||||||
"scale":"2x",
|
"scale": "2x",
|
||||||
"filename":"Icon-App-60x60@2x.png"
|
"filename": "Icon-App-60x60@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"iphone",
|
"idiom": "iphone",
|
||||||
"size":"60x60",
|
"size": "60x60",
|
||||||
"scale":"3x",
|
"scale": "3x",
|
||||||
"filename":"Icon-App-60x60@3x.png"
|
"filename": "Icon-App-60x60@3x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"iphone",
|
"idiom": "iphone",
|
||||||
"size":"76x76",
|
"size": "76x76",
|
||||||
"scale":"2x",
|
"scale": "2x",
|
||||||
"filename":"Icon-App-76x76@2x.png"
|
"filename": "Icon-App-76x76@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"ipad",
|
"idiom": "ipad",
|
||||||
"size":"20x20",
|
"size": "20x20",
|
||||||
"scale":"1x",
|
"scale": "1x",
|
||||||
"filename":"Icon-App-20x20@1x.png"
|
"filename": "Icon-App-20x20@1x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"ipad",
|
"idiom": "ipad",
|
||||||
"size":"20x20",
|
"size": "20x20",
|
||||||
"scale":"2x",
|
"scale": "2x",
|
||||||
"filename":"Icon-App-20x20@2x.png"
|
"filename": "Icon-App-20x20@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"ipad",
|
"idiom": "ipad",
|
||||||
"size":"29x29",
|
"size": "29x29",
|
||||||
"scale":"1x",
|
"scale": "1x",
|
||||||
"filename":"Icon-App-29x29@1x.png"
|
"filename": "Icon-App-29x29@1x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"ipad",
|
"idiom": "ipad",
|
||||||
"size":"29x29",
|
"size": "29x29",
|
||||||
"scale":"2x",
|
"scale": "2x",
|
||||||
"filename":"Icon-App-29x29@2x.png"
|
"filename": "Icon-App-29x29@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"ipad",
|
"idiom": "ipad",
|
||||||
"size":"40x40",
|
"size": "40x40",
|
||||||
"scale":"1x",
|
"scale": "1x",
|
||||||
"filename":"Icon-App-40x40@1x.png"
|
"filename": "Icon-App-40x40@1x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"ipad",
|
"idiom": "ipad",
|
||||||
"size":"40x40",
|
"size": "40x40",
|
||||||
"scale":"2x",
|
"scale": "2x",
|
||||||
"filename":"Icon-App-40x40@2x.png"
|
"filename": "Icon-App-40x40@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"ipad",
|
"idiom": "ipad",
|
||||||
"size":"76x76",
|
"size": "76x76",
|
||||||
"scale":"1x",
|
"scale": "1x",
|
||||||
"filename":"Icon-App-76x76@1x.png"
|
"filename": "Icon-App-76x76@1x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"ipad",
|
"idiom": "ipad",
|
||||||
"size":"76x76",
|
"size": "76x76",
|
||||||
"scale":"2x",
|
"scale": "2x",
|
||||||
"filename":"Icon-App-76x76@2x.png"
|
"filename": "Icon-App-76x76@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom":"ipad",
|
"idiom": "ipad",
|
||||||
"size":"83.5x83.5",
|
"size": "83.5x83.5",
|
||||||
"scale":"2x",
|
"scale": "2x",
|
||||||
"filename":"Icon-App-83.5x83.5@2x.png"
|
"filename": "Icon-App-83.5x83.5@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "1024x1024",
|
"size": "1024x1024",
|
||||||
"idiom" : "ios-marketing",
|
"idiom": "ios-marketing",
|
||||||
"scale" : "1x",
|
"scale": "1x",
|
||||||
"filename" : "ItunesArtwork@2x.png"
|
"filename": "ItunesArtwork@2x.png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info":{
|
"info": {
|
||||||
"version":1,
|
"version": 1,
|
||||||
"author":"easyappicon"
|
"author": "easyappicon"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 718 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |