Compare commits
1187 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 |
11
.env.example
|
|
@ -3,6 +3,12 @@
|
|||
EXPO_PUBLIC_SUPABASE_URL=your_supabase_project_url
|
||||
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
|
||||
EXPO_PUBLIC_MOVIEBOX_PRIMARY_KEY=your_moviebox_primary_key
|
||||
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_SECRET=your_trakt_client_secret
|
||||
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 -->
|
||||
39
.gitignore
vendored
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
# dependencies
|
||||
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
|
||||
|
||||
# Expo
|
||||
|
|
@ -31,8 +34,12 @@ yarn-error.*
|
|||
*.pem
|
||||
|
||||
# local env files
|
||||
.env
|
||||
|
||||
.env*.local
|
||||
.env
|
||||
# Sentry
|
||||
ios/sentry.properties
|
||||
android/sentry.properties
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
|
@ -47,6 +54,7 @@ android/build/
|
|||
android/.gradle/
|
||||
android/app/libs/*.aar
|
||||
!android/app/libs/lib-decoder-ffmpeg-release.aar
|
||||
!android/app/libs/libmpv-release.aar
|
||||
HEATING_OPTIMIZATIONS.md
|
||||
# sliderreadme.md
|
||||
.cursor/mcp.json
|
||||
|
|
@ -66,9 +74,36 @@ sliderreadme.md
|
|||
bottomsheet.md
|
||||
fastimage.md
|
||||
|
||||
# Backup directories
|
||||
## 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,
|
||||
I18nManager,
|
||||
Platform,
|
||||
LogBox
|
||||
LogBox,
|
||||
Linking
|
||||
} from 'react-native';
|
||||
import './src/i18n'; // Initialize i18n
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { Provider as PaperProvider } from 'react-native-paper';
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
import AppNavigator, {
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { enableScreens, enableFreeze } from 'react-native-screens';
|
||||
import AppNavigator, {
|
||||
CustomNavigationDarkTheme,
|
||||
CustomDarkTheme
|
||||
} from './src/navigation/AppNavigator';
|
||||
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
||||
import 'react-native-reanimated';
|
||||
import { CatalogProvider } from './src/contexts/CatalogContext';
|
||||
import { GenreProvider } from './src/contexts/GenreContext';
|
||||
import { TraktProvider } from './src/contexts/TraktContext';
|
||||
import { SimklProvider } from './src/contexts/SimklContext';
|
||||
import { ThemeProvider, useTheme } from './src/contexts/ThemeContext';
|
||||
import { TrailerProvider } from './src/contexts/TrailerContext';
|
||||
import { DownloadsProvider } from './src/contexts/DownloadsContext';
|
||||
|
|
@ -34,25 +39,54 @@ import UpdatePopup from './src/components/UpdatePopup';
|
|||
import MajorUpdateOverlay from './src/components/MajorUpdateOverlay';
|
||||
import { useGithubMajorUpdate } from './src/hooks/useGithubMajorUpdate';
|
||||
import { useUpdatePopup } from './src/hooks/useUpdatePopup';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import * as Sentry from '@sentry/react-native';
|
||||
import UpdateService from './src/services/updateService';
|
||||
import { memoryMonitorService } from './src/services/memoryMonitorService';
|
||||
import { aiService } from './src/services/aiService';
|
||||
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({
|
||||
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
|
||||
|
||||
// Adds more context data to events (IP address, cookies, user, etc.)
|
||||
// For more information, visit: https://docs.sentry.io/platforms/react-native/data-management/data-collected/
|
||||
sendDefaultPii: true,
|
||||
// Privacy-first: Disable PII by default (IP address, cookies, user data)
|
||||
// Users can opt-in via Privacy Settings if they choose
|
||||
sendDefaultPii: false,
|
||||
|
||||
// Configure Session Replay conservatively to avoid startup overhead in production
|
||||
replaysSessionSampleRate: __DEV__ ? 0.1 : 0,
|
||||
replaysOnErrorSampleRate: __DEV__ ? 1 : 0,
|
||||
// Session Replay completely disabled by default for privacy
|
||||
// This prevents screen recording without explicit user consent
|
||||
replaysSessionSampleRate: 0,
|
||||
replaysOnErrorSampleRate: 0,
|
||||
|
||||
// Only include feedback integration (user-initiated, not automatic)
|
||||
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)
|
||||
// spotlight: __DEV__,
|
||||
});
|
||||
|
|
@ -70,6 +104,8 @@ LogBox.ignoreLogs([
|
|||
|
||||
// This fixes many navigation layout issues by using native screen containers
|
||||
enableScreens(true);
|
||||
// Freeze non-focused screens to stop background re-renders
|
||||
enableFreeze(true);
|
||||
|
||||
// Inner app component that uses the theme context
|
||||
const ThemedApp = () => {
|
||||
|
|
@ -79,12 +115,12 @@ const ThemedApp = () => {
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const engine = (global as any).HermesInternal ? 'Hermes' : 'JSC';
|
||||
console.log('JS Engine:', engine);
|
||||
} catch {}
|
||||
} catch { }
|
||||
}, []);
|
||||
const { currentTheme } = useTheme();
|
||||
const [isAppReady, setIsAppReady] = useState(false);
|
||||
const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState<boolean | null>(null);
|
||||
|
||||
|
||||
// Update popup functionality
|
||||
const {
|
||||
showUpdatePopup,
|
||||
|
|
@ -97,38 +133,84 @@ const ThemedApp = () => {
|
|||
|
||||
// GitHub major/minor release overlay
|
||||
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
|
||||
useEffect(() => {
|
||||
const initializeApp = async () => {
|
||||
try {
|
||||
// Check onboarding status
|
||||
const onboardingCompleted = await AsyncStorage.getItem('hasCompletedOnboarding');
|
||||
const onboardingCompleted = await mmkvStorage.getItem('hasCompletedOnboarding');
|
||||
setHasCompletedOnboarding(onboardingCompleted === 'true');
|
||||
|
||||
// Initialize update service (skip on Android to prevent update checks)
|
||||
if (Platform.OS !== 'android') {
|
||||
await UpdateService.initialize();
|
||||
}
|
||||
|
||||
|
||||
// Initialize Supabase auth/session and start background sync.
|
||||
// This is intentionally non-blocking for app startup UX.
|
||||
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
|
||||
memoryMonitorService; // Just accessing it starts the monitoring
|
||||
console.log('Memory monitoring service initialized');
|
||||
|
||||
|
||||
// Initialize AI service
|
||||
await aiService.initialize();
|
||||
console.log('AI service initialized');
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error initializing app:', error);
|
||||
// Default to showing onboarding if we can't check
|
||||
setHasCompletedOnboarding(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
initializeApp();
|
||||
}, []);
|
||||
|
||||
|
||||
// Create custom themes based on current theme
|
||||
const customDarkTheme = {
|
||||
...CustomDarkTheme,
|
||||
|
|
@ -137,7 +219,7 @@ const ThemedApp = () => {
|
|||
primary: currentTheme.colors.primary,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const customNavigationTheme = {
|
||||
...CustomNavigationDarkTheme,
|
||||
colors: {
|
||||
|
|
@ -152,33 +234,44 @@ const ThemedApp = () => {
|
|||
const handleSplashComplete = () => {
|
||||
setIsAppReady(true);
|
||||
};
|
||||
|
||||
|
||||
// Navigation reference
|
||||
const navigationRef = React.useRef<any>(null);
|
||||
|
||||
// Don't render anything until we know the onboarding status
|
||||
const shouldShowApp = isAppReady && hasCompletedOnboarding !== null;
|
||||
const initialRouteName = hasCompletedOnboarding ? 'MainTabs' : 'Onboarding';
|
||||
|
||||
|
||||
return (
|
||||
<AccountProvider>
|
||||
<PaperProvider theme={customDarkTheme}>
|
||||
<NavigationContainer
|
||||
<NavigationContainer
|
||||
ref={navigationRef}
|
||||
theme={customNavigationTheme}
|
||||
linking={undefined}
|
||||
linking={{
|
||||
prefixes: ['nuvio://'],
|
||||
config: {
|
||||
screens: {
|
||||
ScraperSettings: {
|
||||
path: 'repo',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DownloadsProvider>
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar style="light" />
|
||||
{!isAppReady && <SplashScreen onFinish={handleSplashComplete} />}
|
||||
{shouldShowApp && <AppNavigator initialRouteName={initialRouteName} />}
|
||||
{Platform.OS === 'ios' && (
|
||||
<UpdatePopup
|
||||
visible={showUpdatePopup}
|
||||
updateInfo={updateInfo}
|
||||
onUpdateNow={handleUpdateNow}
|
||||
onUpdateLater={handleUpdateLater}
|
||||
onDismiss={handleDismiss}
|
||||
isInstalling={isInstalling}
|
||||
/>
|
||||
)}
|
||||
<UpdatePopup
|
||||
visible={showUpdatePopup}
|
||||
updateInfo={updateInfo}
|
||||
onUpdateNow={handleUpdateNow}
|
||||
onUpdateLater={handleUpdateLater}
|
||||
onDismiss={handleDismiss}
|
||||
isInstalling={isInstalling}
|
||||
/>
|
||||
<MajorUpdateOverlay
|
||||
visible={githubUpdate.visible}
|
||||
latestTag={githubUpdate.latestTag}
|
||||
|
|
@ -186,7 +279,11 @@ const ThemedApp = () => {
|
|||
releaseUrl={githubUpdate.releaseUrl}
|
||||
onDismiss={githubUpdate.onDismiss}
|
||||
onLater={githubUpdate.onLater}
|
||||
onUpdateAction={handleGithubUpdateAction}
|
||||
isDownloading={isDownloadingGitHub}
|
||||
downloadProgress={downloadProgress}
|
||||
/>
|
||||
<CampaignManager />
|
||||
</View>
|
||||
</DownloadsProvider>
|
||||
</NavigationContainer>
|
||||
|
|
@ -197,19 +294,27 @@ const ThemedApp = () => {
|
|||
|
||||
function App(): React.JSX.Element {
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<GenreProvider>
|
||||
<CatalogProvider>
|
||||
<TraktProvider>
|
||||
<ThemeProvider>
|
||||
<TrailerProvider>
|
||||
<ThemedApp />
|
||||
</TrailerProvider>
|
||||
</ThemeProvider>
|
||||
</TraktProvider>
|
||||
</CatalogProvider>
|
||||
</GenreProvider>
|
||||
</GestureHandlerRootView>
|
||||
<SafeAreaProvider>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<BottomSheetModalProvider>
|
||||
<GenreProvider>
|
||||
<CatalogProvider>
|
||||
<TraktProvider>
|
||||
<SimklProvider>
|
||||
<ThemeProvider>
|
||||
<TrailerProvider>
|
||||
<ToastProvider>
|
||||
<ThemedApp />
|
||||
</ToastProvider>
|
||||
</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.
|
||||
193
README.md
|
|
@ -1,175 +1,76 @@
|
|||
<!-- Improved compatibility of back to top link -->
|
||||
<a id="readme-top"></a>
|
||||
|
||||
<!-- PROJECT SHIELDS -->
|
||||
[![Contributors][contributors-shield]][contributors-url]
|
||||
[![Forks][forks-shield]][forks-url]
|
||||
[![Stargazers][stars-shield]][stars-url]
|
||||
[![Issues][issues-shield]][issues-url]
|
||||
[![License][license-shield]][license-url]
|
||||
|
||||
<!-- PROJECT LOGO -->
|
||||
<br />
|
||||
<div align="center">
|
||||
<img src="assets/titlelogo.png" alt="Nuvio Logo" width="120" />
|
||||
<h1 align="center">🎬 Nuvio Media Hub</h1>
|
||||
<p align="center">
|
||||
A modern media hub built with React Native and Expo
|
||||
|
||||
<img src="https://github.com/tapframe/NuvioTV/blob/main/assets/brand/app_logo_wordmark.png" alt="Nuvio" width="300" />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
[![Contributors][contributors-shield]][contributors-url]
|
||||
[![Forks][forks-shield]][forks-url]
|
||||
[![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 • Offline metadata & sync
|
||||
<br />
|
||||
<br />
|
||||
<a href="#getting-started"><strong>Get Started »</strong></a>
|
||||
<br />
|
||||
<br />
|
||||
<a href="#demo">View Screenshots</a>
|
||||
·
|
||||
<a href="https://github.com/tapframe/NuvioStreaming/issues/new?labels=bug&template=bug_report.md">Report Bug</a>
|
||||
·
|
||||
<a href="https://github.com/tapframe/NuvioStreaming/issues/new?labels=enhancement&template=feature_request.md">Request Feature</a>
|
||||
Stremio Addon ecosystem • Cross-platform
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- TABLE OF CONTENTS -->
|
||||
<details>
|
||||
<summary>Table of Contents</summary>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#about-the-project">About The Project</a>
|
||||
</li>
|
||||
<li><a href="#demo">Screenshots</a></li>
|
||||
<li>
|
||||
<a href="#getting-started">Getting Started</a>
|
||||
<ul>
|
||||
<li><a href="#installation">Installation</a></li>
|
||||
<li><a href="#build">Build</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#contributing">Contributing</a></li>
|
||||
<li><a href="#support">Support</a></li>
|
||||
<li><a href="#license">License</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
<li><a href="#acknowledgments">Acknowledgments</a></li>
|
||||
<li><a href="#built-with">Built With</a></li>
|
||||
</ol>
|
||||
</details>
|
||||
## About
|
||||
|
||||
<!-- ABOUT THE PROJECT -->
|
||||
## About The Project
|
||||
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.
|
||||
|
||||
Nuvio Media Hub is a cross‑platform app for managing, discovering, and streaming your media via a flexible addon ecosystem. Built with React Native + Expo, it integrates providers and sync services while keeping a simple, fast UI.
|
||||
## Installation
|
||||
|
||||
|
||||
### Android
|
||||
|
||||
<!-- DEMO / SCREENSHOTS -->
|
||||
## Demo
|
||||
<a id="demo"></a>
|
||||
Download the latest APK from [GitHub Releases](https://github.com/tapframe/NuvioStreaming/releases/latest).
|
||||
|
||||
| Home | Details |
|
||||
|:----:|:-------:|
|
||||
|  |  |
|
||||
### iOS
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
- [TestFlight](https://testflight.apple.com/join/QkKMGRqp)
|
||||
- [AltStore](https://tinyurl.com/NuvioAltstore)
|
||||
- [SideStore](https://tinyurl.com/NuvioSidestore)
|
||||
|
||||
<!-- GETTING STARTED -->
|
||||
## Getting Started
|
||||
**Manual source:** `https://raw.githubusercontent.com/tapframe/NuvioStreaming/main/nuvio-source.json`
|
||||
|
||||
Follow the steps below to run the app locally.
|
||||
|
||||
### Installation
|
||||
## Development
|
||||
|
||||
```bash
|
||||
git clone https://github.com/tapframe/NuvioStreaming.git
|
||||
cd NuvioStreaming
|
||||
npm install
|
||||
# If you hit peer dependency conflicts:
|
||||
# npm install --legacy-peer-deps
|
||||
npx expo start
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
npm install --legacy-peer-deps
|
||||
npx expo prebuild
|
||||
npx expo run:android # Android
|
||||
npx expo run:ios # iOS
|
||||
npx expo run:android
|
||||
# or
|
||||
npx expo run:ios
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Alternative iOS Installation</summary>
|
||||
## Legal & DMCA
|
||||
|
||||
### AltStore
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/2/20/AltStore_logo.png" width="24" height="24" align="left"> [](https://tinyurl.com/NuvioAltstore)
|
||||
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.
|
||||
|
||||
### SideStore
|
||||
<img src="https://github.com/SideStore/assets/blob/main/icon.png?raw=true" width="24" height="24" align="left"> [](https://tinyurl.com/NuvioSidestore)
|
||||
Nuvio is not affiliated with any third-party extensions, catalogs, sources, or content providers. It does not host, store, or distribute any media content.
|
||||
|
||||
**Manual URL:** `https://raw.githubusercontent.com/tapframe/NuvioStreaming/main/nuvio-source.json`
|
||||
|
||||
</details>
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions make the open‑source community amazing! Any contributions are greatly appreciated.
|
||||
|
||||
1. Fork the project
|
||||
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
||||
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## Support
|
||||
|
||||
If you find Nuvio helpful, consider supporting development:
|
||||
|
||||
* **Ko‑Fi** – `https://ko-fi.com/tapframe`
|
||||
* **GitHub Star** – Star the repo to show support
|
||||
* **Share** – Tell others about the project
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the GNU GPLv3 License. See `LICENSE` for more information.
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## Contact
|
||||
|
||||
**Project Links:**
|
||||
* GitHub: `https://github.com/tapframe`
|
||||
* Issues: `https://github.com/tapframe/NuvioStreaming/issues`
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
* [React Native](https://reactnative.dev/)
|
||||
* [Expo](https://expo.dev/)
|
||||
* [TypeScript](https://www.typescriptlang.org/)
|
||||
* Community contributors and testers
|
||||
|
||||
**Disclaimer:** 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.
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
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)**.
|
||||
|
||||
## Built With
|
||||
|
||||
<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>
|
||||
<br/>
|
||||
React Native • Expo • TypeScript
|
||||
</p>
|
||||
- React Native
|
||||
- Expo
|
||||
- TypeScript
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
## 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>
|
||||
|
||||
<!-- MARKDOWN LINKS & IMAGES -->
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||
|
|
@ -181,4 +82,4 @@ Distributed under the GNU GPLv3 License. See `LICENSE` for more information.
|
|||
[issues-shield]: https://img.shields.io/github/issues/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||
[issues-url]: https://github.com/tapframe/NuvioStreaming/issues
|
||||
[license-shield]: https://img.shields.io/github/license/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||
[license-url]: http://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
[license-url]: http://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
apply plugin: "com.android.application"
|
||||
apply plugin: "org.jetbrains.kotlin.android"
|
||||
apply plugin: "com.facebook.react"
|
||||
apply plugin: "io.sentry.android.gradle"
|
||||
|
||||
def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
|
||||
|
||||
|
|
@ -81,7 +82,7 @@ def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBu
|
|||
*/
|
||||
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")
|
||||
|
||||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
|
@ -94,8 +95,8 @@ android {
|
|||
applicationId 'com.nuvio.app'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 20
|
||||
versionName "1.2.5"
|
||||
versionCode 37
|
||||
versionName "1.4.1"
|
||||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
}
|
||||
|
|
@ -117,7 +118,7 @@ android {
|
|||
def abiVersionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
def baseVersionCode = 20 // Current versionCode from defaultConfig
|
||||
def baseVersionCode = 37 // Current versionCode 37 from defaultConfig
|
||||
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
|
||||
|
||||
def versionCode = baseVersionCode * 100 // Base multiplier
|
||||
|
|
@ -184,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 {
|
||||
// @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
|
||||
implementation("com.facebook.react:react-android")
|
||||
|
||||
|
|
@ -214,4 +246,14 @@ dependencies {
|
|||
|
||||
// Include only FFmpeg decoder AAR to avoid duplicates with Maven Media3
|
||||
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
|
|
@ -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.READ_EXTERNAL_STORAGE"/>
|
||||
<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.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
|
@ -14,11 +16,13 @@
|
|||
</intent>
|
||||
</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" 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.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_LAUNCH_WAIT_MS" android:value="30000"/>
|
||||
<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_UPDATES_LAUNCH_WAIT_MS" android:value="5000"/>
|
||||
<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">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
|
@ -33,4 +37,4 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnable
|
|||
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
||||
|
||||
import expo.modules.ReactActivityDelegateWrapper
|
||||
import com.reactnative.googlecast.api.RNGCCastContext
|
||||
|
||||
class MainActivity : ReactActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
@ -17,6 +18,12 @@ class MainActivity : ReactActivity() {
|
|||
// This is required for expo-splash-screen.
|
||||
setTheme(R.style.AppTheme);
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.facebook.react.defaults.DefaultReactNativeHost
|
|||
|
||||
import expo.modules.ApplicationLifecycleDispatcher
|
||||
import expo.modules.ReactNativeHostWrapper
|
||||
import com.nuvio.app.mpv.MpvPackage
|
||||
|
||||
class MainApplication : Application(), ReactApplication {
|
||||
|
||||
|
|
@ -24,7 +25,7 @@ class MainApplication : Application(), ReactApplication {
|
|||
override fun getPackages(): List<ReactPackage> =
|
||||
PackageList(this).packages.apply {
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// add(MyReactNativePackage())
|
||||
add(com.nuvio.app.mpv.MpvPackage())
|
||||
}
|
||||
|
||||
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
|
||||
|
|
|
|||
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_status_bar_translucent" translatable="false">false</string>
|
||||
<string name="expo_system_ui_user_interface_style" translatable="false">dark</string>
|
||||
<string name="expo_runtime_version">1.2.5</string>
|
||||
<string name="expo_runtime_version">1.4.1</string>
|
||||
</resources>
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "35.0.0"
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 35
|
||||
targetSdkVersion = 35
|
||||
castFrameworkVersion = "22.1.0"
|
||||
ndkVersion = "29.0.14206865" // Required for libmpv AAR built with NDK r29
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
|
@ -9,6 +17,7 @@ buildscript {
|
|||
classpath('com.android.tools.build:gradle')
|
||||
classpath('com.facebook.react:react-native-gradle-plugin')
|
||||
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
|
||||
classpath("io.sentry:sentry-android-gradle-plugin:5.12.2")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
defaults.url=https://sentry.io/
|
||||
defaults.org=tapframe
|
||||
defaults.project=react-native
|
||||
# Using SENTRY_AUTH_TOKEN environment variable
|
||||
auth.token=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
|
||||
|
|
|
|||
44
app.json
|
|
@ -2,7 +2,7 @@
|
|||
"expo": {
|
||||
"name": "Nuvio",
|
||||
"slug": "nuvio",
|
||||
"version": "1.2.5",
|
||||
"version": "1.4.1",
|
||||
"orientation": "default",
|
||||
"backgroundColor": "#020404",
|
||||
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
|
||||
|
|
@ -10,22 +10,24 @@
|
|||
"scheme": "nuvio",
|
||||
"newArchEnabled": true,
|
||||
"splash": {
|
||||
"image": "./assets/splash-icon.png",
|
||||
"image": "./src/assets/splash-icon-new.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#020404"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
|
||||
"buildNumber": "20",
|
||||
"buildNumber": "37",
|
||||
"infoPlist": {
|
||||
"NSAppTransportSecurity": {
|
||||
"NSAllowsArbitraryLoads": true
|
||||
},
|
||||
"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.",
|
||||
"UIBackgroundModes": [
|
||||
"audio"
|
||||
|
|
@ -33,9 +35,10 @@
|
|||
"LSSupportsOpeningDocumentsInPlace": true,
|
||||
"UIFileSharingEnabled": true
|
||||
},
|
||||
"bundleIdentifier": "com.nuvio.app",
|
||||
"bundleIdentifier": "com.nuvio.hub",
|
||||
"associatedDomains": [],
|
||||
"jsEngine": "hermes"
|
||||
"jsEngine": "hermes",
|
||||
"appleTeamId": "8QBDZ766S3"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
|
|
@ -45,10 +48,11 @@
|
|||
"icon": "./assets/android/mipmap-xxxhdpi/ic_launcher.png",
|
||||
"permissions": [
|
||||
"INTERNET",
|
||||
"WAKE_LOCK"
|
||||
"WAKE_LOCK",
|
||||
"android.permission.WRITE_SETTINGS"
|
||||
],
|
||||
"package": "com.nuvio.app",
|
||||
"versionCode": 20,
|
||||
"versionCode": 37,
|
||||
"architectures": [
|
||||
"arm64-v8a",
|
||||
"armeabi-v7a",
|
||||
|
|
@ -57,7 +61,6 @@
|
|||
],
|
||||
"jsEngine": "hermes"
|
||||
},
|
||||
|
||||
"extra": {
|
||||
"eas": {
|
||||
"projectId": "909107b8-fe61-45ce-b02f-b02510d306a6"
|
||||
|
|
@ -65,6 +68,7 @@
|
|||
},
|
||||
"owner": "nayifleo",
|
||||
"plugins": [
|
||||
"expo-live-activity",
|
||||
[
|
||||
"@sentry/react-native/expo",
|
||||
{
|
||||
|
|
@ -73,6 +77,12 @@
|
|||
"organization": "tapframe"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@kesha-antonov/react-native-background-downloader",
|
||||
{
|
||||
"skipMmkvDependency": true
|
||||
}
|
||||
],
|
||||
"expo-localization",
|
||||
[
|
||||
"expo-updates",
|
||||
|
|
@ -80,21 +90,21 @@
|
|||
"username": "nayifleo"
|
||||
}
|
||||
],
|
||||
"react-native-bottom-tabs",
|
||||
[
|
||||
"expo-libvlc-player",
|
||||
"react-native-google-cast",
|
||||
{
|
||||
"localNetworkPermission": "Allow $(PRODUCT_NAME) to access your local network",
|
||||
"supportsBackgroundPlayback": true
|
||||
"receiverAppId": "CC1AD845",
|
||||
"iosStartDiscoveryAfterFirstTapOnCastButton": true
|
||||
}
|
||||
],
|
||||
"react-native-bottom-tabs"
|
||||
]
|
||||
],
|
||||
"updates": {
|
||||
"enabled": true,
|
||||
"checkAutomatically": "ON_ERROR_RECOVERY",
|
||||
"fallbackToCacheTimeout": 30000,
|
||||
"url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"
|
||||
"url": "https://ota.nuvioapp.space/api/manifest"
|
||||
},
|
||||
"runtimeVersion": "1.2.5"
|
||||
"runtimeVersion": "1.4.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">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</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>
|
||||
<color name="ic_launcher_background">#151515</color>
|
||||
<color name="ic_launcher_background">#000000</color>
|
||||
</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",
|
||||
"size":"20x20",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-20x20@2x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "20x20",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-20x20@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"20x20",
|
||||
"scale":"3x",
|
||||
"filename":"Icon-App-20x20@3x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "20x20",
|
||||
"scale": "3x",
|
||||
"filename": "Icon-App-20x20@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"29x29",
|
||||
"scale":"1x",
|
||||
"filename":"Icon-App-29x29@1x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "29x29",
|
||||
"scale": "1x",
|
||||
"filename": "Icon-App-29x29@1x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"29x29",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-29x29@2x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "29x29",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-29x29@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"29x29",
|
||||
"scale":"3x",
|
||||
"filename":"Icon-App-29x29@3x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "29x29",
|
||||
"scale": "3x",
|
||||
"filename": "Icon-App-29x29@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"40x40",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-40x40@2x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "40x40",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-40x40@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"40x40",
|
||||
"scale":"3x",
|
||||
"filename":"Icon-App-40x40@3x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "40x40",
|
||||
"scale": "3x",
|
||||
"filename": "Icon-App-40x40@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"60x60",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-60x60@2x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "60x60",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-60x60@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"60x60",
|
||||
"scale":"3x",
|
||||
"filename":"Icon-App-60x60@3x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "60x60",
|
||||
"scale": "3x",
|
||||
"filename": "Icon-App-60x60@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"76x76",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-76x76@2x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "76x76",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-76x76@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"20x20",
|
||||
"scale":"1x",
|
||||
"filename":"Icon-App-20x20@1x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "20x20",
|
||||
"scale": "1x",
|
||||
"filename": "Icon-App-20x20@1x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"20x20",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-20x20@2x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "20x20",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-20x20@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"29x29",
|
||||
"scale":"1x",
|
||||
"filename":"Icon-App-29x29@1x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "29x29",
|
||||
"scale": "1x",
|
||||
"filename": "Icon-App-29x29@1x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"29x29",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-29x29@2x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "29x29",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-29x29@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"40x40",
|
||||
"scale":"1x",
|
||||
"filename":"Icon-App-40x40@1x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "40x40",
|
||||
"scale": "1x",
|
||||
"filename": "Icon-App-40x40@1x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"40x40",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-40x40@2x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "40x40",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-40x40@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"76x76",
|
||||
"scale":"1x",
|
||||
"filename":"Icon-App-76x76@1x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "76x76",
|
||||
"scale": "1x",
|
||||
"filename": "Icon-App-76x76@1x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"76x76",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-76x76@2x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "76x76",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-76x76@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"83.5x83.5",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-83.5x83.5@2x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "83.5x83.5",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-83.5x83.5@2x.png"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"filename" : "ItunesArtwork@2x.png"
|
||||
"size": "1024x1024",
|
||||
"idiom": "ios-marketing",
|
||||
"scale": "1x",
|
||||
"filename": "ItunesArtwork@2x.png"
|
||||
}
|
||||
],
|
||||
"info":{
|
||||
"version":1,
|
||||
"author":"easyappicon"
|
||||
"info": {
|
||||
"version": 1,
|
||||
"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 |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 125 KiB |