mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-11-08 01:52:32 +01:00
Compare commits
1071 Commits
v0.25.1
...
player-cla
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31ade7cd30 | ||
|
|
4e1b0e0555 | ||
|
|
0aa71a58ed | ||
|
|
7585cc2e73 | ||
|
|
803fd52859 | ||
|
|
ab8a9ae11c | ||
|
|
83486402df | ||
|
|
a5813f256a | ||
|
|
0a885492b6 | ||
|
|
731efc2124 | ||
|
|
a8da9946d1 | ||
|
|
3d069cdf5b | ||
|
|
eccedc0ab0 | ||
|
|
7cecda5713 | ||
|
|
d9dccfa8af | ||
|
|
81b4e3f970 | ||
|
|
ef068e1eca | ||
|
|
8407b5aefd | ||
|
|
b6aa07545a | ||
|
|
1dcb1953ba | ||
|
|
862a8e8f26 | ||
|
|
88395fa852 | ||
|
|
8d679626f0 | ||
|
|
e7f3750f5e | ||
|
|
48e826e912 | ||
|
|
088cb8353e | ||
|
|
5ca544bc42 | ||
|
|
aa1b7f8584 | ||
|
|
ce16c6df5f | ||
|
|
1d94fd1582 | ||
|
|
c9542ad6fd | ||
|
|
7615f79aca | ||
|
|
276bf390b2 | ||
|
|
f39eda086f | ||
|
|
756327da39 | ||
|
|
5840d3a437 | ||
|
|
47299c9184 | ||
|
|
6486f2de56 | ||
|
|
e1dedd45ed | ||
|
|
912f07a1dd | ||
|
|
205466c56a | ||
|
|
7f10312d0a | ||
|
|
63be3220e7 | ||
|
|
536b78f2e6 | ||
|
|
6d6b73ef73 | ||
|
|
196c27792b | ||
|
|
b3789315ad | ||
|
|
c7bf498c04 | ||
|
|
35abb99dac | ||
|
|
70416e73f3 | ||
|
|
a0b76c3385 | ||
|
|
c232193a46 | ||
|
|
f289bea6b3 | ||
|
|
48b200868a | ||
|
|
54bf7f0ced | ||
|
|
980a35a708 | ||
|
|
da106e2361 | ||
|
|
3532ac96b4 | ||
|
|
87693a2ad1 | ||
|
|
d321e57620 | ||
|
|
fb4a65a14a | ||
|
|
3047704e1c | ||
|
|
3dcfdaf510 | ||
|
|
2ceb70236e | ||
|
|
be097f26c8 | ||
|
|
098f60d593 | ||
|
|
eb0568044a | ||
|
|
f3b3d5c3e7 | ||
|
|
b888dc72cf | ||
|
|
599d86151a | ||
|
|
587df093ea | ||
|
|
8830e87242 | ||
|
|
f96b8f7b2a | ||
|
|
c28478ae53 | ||
|
|
10110397fd | ||
|
|
d81244e77c | ||
|
|
ea20ca9e72 | ||
|
|
f0c89494dd | ||
|
|
0fd2d4fed6 | ||
|
|
c1bdffd917 | ||
|
|
3c7b026d7d | ||
|
|
998d84de6c | ||
|
|
76a02d5858 | ||
|
|
24bb71a23f | ||
|
|
49b71942ad | ||
|
|
c9ec257a5e | ||
|
|
b1f995a78c | ||
|
|
acac50a1d1 | ||
|
|
c6b87cd316 | ||
|
|
94d4c21cc7 | ||
|
|
a7a7dc5363 | ||
|
|
126f4b0e30 | ||
|
|
6558794d26 | ||
|
|
1d12874937 | ||
|
|
1d98518bfa | ||
|
|
e5458bcb14 | ||
|
|
dc62d211f5 | ||
|
|
ec6612dd71 | ||
|
|
064e1d39c7 | ||
|
|
4c88a193bd | ||
|
|
3fcac10e7f | ||
|
|
6cedd117fe | ||
|
|
5eabcb52b5 | ||
|
|
690b40d0c4 | ||
|
|
9bb2c0b484 | ||
|
|
1e08cc8c8f | ||
|
|
7d17468266 | ||
|
|
5819546ea9 | ||
|
|
cfb6e114d6 | ||
|
|
b764ad33c4 | ||
|
|
430b4eb916 | ||
|
|
2339f51ad4 | ||
|
|
99aae7eb28 | ||
|
|
c6e1721884 | ||
|
|
94684fe380 | ||
|
|
398a2f55ce | ||
|
|
1f7b3b5b06 | ||
|
|
909ed616c4 | ||
|
|
dd223af28d | ||
|
|
dbee8d8128 | ||
|
|
b62a09b5b3 | ||
|
|
87317c6faf | ||
|
|
53b599b042 | ||
|
|
21df24abfd | ||
|
|
ca4592a935 | ||
|
|
3fc487310b | ||
|
|
056809cb0d | ||
|
|
a60bb3e7af | ||
|
|
ecd3f6c2ee | ||
|
|
70ff47b810 | ||
|
|
b8e050f6c4 | ||
|
|
46d0bc1004 | ||
|
|
e7fe84f2c7 | ||
|
|
2b183a0576 | ||
|
|
f856bd9306 | ||
|
|
0066b322e1 | ||
|
|
3bdae81c0a | ||
|
|
6010c4ea7f | ||
|
|
690b3410e9 | ||
|
|
ba86ce137b | ||
|
|
410c01547c | ||
|
|
fd99c5e461 | ||
|
|
407d2d768d | ||
|
|
47263f5254 | ||
|
|
01bf855015 | ||
|
|
ebf3008729 | ||
|
|
33ecfb757e | ||
|
|
b109e4d3cc | ||
|
|
137ade24ff | ||
|
|
ffe26d882b | ||
|
|
83f8141fe7 | ||
|
|
b78e0b2da8 | ||
|
|
9253640fae | ||
|
|
3e6e980362 | ||
|
|
8b5aa5cd9b | ||
|
|
58393ad4ef | ||
|
|
977f7e28b5 | ||
|
|
99e77249de | ||
|
|
1890fbb19a | ||
|
|
a955408053 | ||
|
|
86203d6800 | ||
|
|
edd19641ac | ||
|
|
65749cbac0 | ||
|
|
658ddfc921 | ||
|
|
efb3aa530d | ||
|
|
f7d0fd545d | ||
|
|
27e6be792f | ||
|
|
ce919215fb | ||
|
|
6a4aaba431 | ||
|
|
83d93e16e7 | ||
|
|
8d15a141b1 | ||
|
|
a78bed700a | ||
|
|
ef3c76645f | ||
|
|
d4ed18bf08 | ||
|
|
3fc0147f47 | ||
|
|
fbafdeb2ca | ||
|
|
c6b05c6094 | ||
|
|
240a2fe36b | ||
|
|
de46e3abb3 | ||
|
|
70748fa0bc | ||
|
|
781040efaa | ||
|
|
3847b32c11 | ||
|
|
9054575f6c | ||
|
|
0dca92dd59 | ||
|
|
b19cd00dba | ||
|
|
88d8d90bbd | ||
|
|
c569f08a32 | ||
|
|
246fc034c1 | ||
|
|
1547b50b4e | ||
|
|
3f7ef49979 | ||
|
|
52942ffd30 | ||
|
|
e4b0245530 | ||
|
|
c6b8bcf0f4 | ||
|
|
dab0148a78 | ||
|
|
c0c08a4f63 | ||
|
|
e31a8ad7a2 | ||
|
|
b21981a9c7 | ||
|
|
aaf337421d | ||
|
|
79a0edacd7 | ||
|
|
d56eef6ece | ||
|
|
72f054a4fa | ||
|
|
172c3c92ac | ||
|
|
137ef3fee4 | ||
|
|
e49156fb11 | ||
|
|
de5d45849f | ||
|
|
a25034b898 | ||
|
|
ae9e82b2c1 | ||
|
|
a70b38a8e5 | ||
|
|
08f3dba42c | ||
|
|
f9711a3402 | ||
|
|
df941670a8 | ||
|
|
57e66b17c6 | ||
|
|
d298a12533 | ||
|
|
a79bc3db14 | ||
|
|
661e6155c1 | ||
|
|
12558172d1 | ||
|
|
dc3f55674f | ||
|
|
acf2e88cb3 | ||
|
|
726c12e934 | ||
|
|
0cff3a6ecd | ||
|
|
33b96d238a | ||
|
|
213f49f5c4 | ||
|
|
9b78e49e45 | ||
|
|
16c79c8219 | ||
|
|
e6eea8f851 | ||
|
|
4e55f1bee6 | ||
|
|
cff3834fde | ||
|
|
c8b01a06b0 | ||
|
|
414b1a8344 | ||
|
|
404d9f3fac | ||
|
|
55e4014036 | ||
|
|
1cd5563b27 | ||
|
|
1abced992b | ||
|
|
46b9243661 | ||
|
|
ad72b2cb31 | ||
|
|
8b79d0ee29 | ||
|
|
295f719b77 | ||
|
|
b584353f4d | ||
|
|
d73314b4dd | ||
|
|
9f4a33c7a8 | ||
|
|
3a9540b042 | ||
|
|
14081505cd | ||
|
|
ca855cbca0 | ||
|
|
6a98b1dac7 | ||
|
|
ebd4880188 | ||
|
|
ffcba175ff | ||
|
|
c7848e5e86 | ||
|
|
6d686b93cb | ||
|
|
2cc38f59d3 | ||
|
|
8bf24e6b14 | ||
|
|
10e7a5cf9c | ||
|
|
9f2f219613 | ||
|
|
841471bf85 | ||
|
|
06d25b0310 | ||
|
|
3c8d81a3c2 | ||
|
|
cf870add49 | ||
|
|
a962e6d633 | ||
|
|
970ef9357b | ||
|
|
4ba961fe7a | ||
|
|
e6c03bf4ac | ||
|
|
1f39523429 | ||
|
|
b43031fb99 | ||
|
|
986cd52da0 | ||
|
|
7d4a2836fc | ||
|
|
6ea715a18d | ||
|
|
a56debfce6 | ||
|
|
226b6de34f | ||
|
|
bcd4579187 | ||
|
|
6fe417abc6 | ||
|
|
a229ab68d5 | ||
|
|
544b30290d | ||
|
|
cb300724da | ||
|
|
0ac5a269ff | ||
|
|
13585ca0be | ||
|
|
62ab9bd740 | ||
|
|
fdf36cbad6 | ||
|
|
aea2b7c7f3 | ||
|
|
37d1c784fa | ||
|
|
cea149f852 | ||
|
|
a92a28517e | ||
|
|
800961c3d7 | ||
|
|
9d8a79b0bd | ||
|
|
0009613608 | ||
|
|
7c18d4dd01 | ||
|
|
fe1c538f9c | ||
|
|
ef56dea817 | ||
|
|
23b3835af0 | ||
|
|
412e1d602a | ||
|
|
802a094154 | ||
|
|
e6b1341246 | ||
|
|
36ede243e3 | ||
|
|
bac9f7eebf | ||
|
|
8ada566bf1 | ||
|
|
5bd4ed77df | ||
|
|
97652ac015 | ||
|
|
6dd24033a4 | ||
|
|
4de3ef20be | ||
|
|
702f74291d | ||
|
|
d8759993a9 | ||
|
|
7787eafd3a | ||
|
|
f08e07873a | ||
|
|
1193b02ca1 | ||
|
|
c0b36b86b9 | ||
|
|
66ec596f67 | ||
|
|
90404a23ce | ||
|
|
64ad05d813 | ||
|
|
734b6e2b67 | ||
|
|
94f992a2e2 | ||
|
|
c8550695aa | ||
|
|
cdac50bab3 | ||
|
|
23961548c0 | ||
|
|
ba1e9c8e1b | ||
|
|
f4baf4628e | ||
|
|
05a87da827 | ||
|
|
fef40014a0 | ||
|
|
1996c1176c | ||
|
|
0190bcee25 | ||
|
|
1ed4928f40 | ||
|
|
63bc982cb2 | ||
|
|
3a286515f2 | ||
|
|
2e96b65fda | ||
|
|
2482615460 | ||
|
|
9384365061 | ||
|
|
b1d4b66aa6 | ||
|
|
ea0da5fdbd | ||
|
|
d80b6a759c | ||
|
|
8106ba68b5 | ||
|
|
ee15a72e4f | ||
|
|
4f4136c6e9 | ||
|
|
b399030e19 | ||
|
|
2eb256799d | ||
|
|
0cf4732d8a | ||
|
|
53edd054aa | ||
|
|
678f0a786a | ||
|
|
b14f65804d | ||
|
|
781a69d60d | ||
|
|
eb9f300e60 | ||
|
|
063568b620 | ||
|
|
0991461d04 | ||
|
|
49bcf2c41b | ||
|
|
c00c6c460c | ||
|
|
4c4fe3f511 | ||
|
|
db485c3d77 | ||
|
|
c0388d948b | ||
|
|
43bbddcc26 | ||
|
|
6471b64ab6 | ||
|
|
b9fcf0dff8 | ||
|
|
3177ca6e8a | ||
|
|
5017f4f05a | ||
|
|
035c394cf6 | ||
|
|
823d4a041f | ||
|
|
62d4044d6c | ||
|
|
3785404618 | ||
|
|
c98ad62163 | ||
|
|
4cac111b66 | ||
|
|
941b8eb194 | ||
|
|
b1add13bfd | ||
|
|
5fffee2c7d | ||
|
|
f9340ae604 | ||
|
|
d3a6991fd4 | ||
|
|
b0bfd4a807 | ||
|
|
3641698379 | ||
|
|
2836191fb3 | ||
|
|
0df6c7fc2c | ||
|
|
b1ebd3ecd9 | ||
|
|
4758244cf5 | ||
|
|
294b9cf347 | ||
|
|
fad3120b00 | ||
|
|
6d05af484e | ||
|
|
38c823a042 | ||
|
|
e082bca5e0 | ||
|
|
f9dae9078e | ||
|
|
e955beeef1 | ||
|
|
eaac7f3f85 | ||
|
|
ea414f57d4 | ||
|
|
f984b26626 | ||
|
|
edab9a6a1f | ||
|
|
4740e3be86 | ||
|
|
e639b02fed | ||
|
|
ac1ca1412d | ||
|
|
d131d3399a | ||
|
|
1009dc4d4e | ||
|
|
42cb914616 | ||
|
|
e72da94eb1 | ||
|
|
c5d94a5b60 | ||
|
|
02c5f2607a | ||
|
|
369a46f8fe | ||
|
|
909d214002 | ||
|
|
5e7e14ee4d | ||
|
|
b092fe2c76 | ||
|
|
b9dd7078ad | ||
|
|
93310955f2 | ||
|
|
9c52e039ee | ||
|
|
be037e0756 | ||
|
|
5bfb0449cf | ||
|
|
0ec81c9e52 | ||
|
|
5841eaa6d7 | ||
|
|
e92ba8f5d1 | ||
|
|
1908e18dc4 | ||
|
|
e30d5e4305 | ||
|
|
11bb2495ba | ||
|
|
341cc37ce7 | ||
|
|
1620668966 | ||
|
|
56c80ce6dd | ||
|
|
8ce9a7e43c | ||
|
|
e05d97732e | ||
|
|
644a345b55 | ||
|
|
bda961a04c | ||
|
|
ba2efded76 | ||
|
|
b05b98ca61 | ||
|
|
7a7f81ac7f | ||
|
|
6e6c171dd7 | ||
|
|
8a41c8cf66 | ||
|
|
05271d95a9 | ||
|
|
9d04a73c85 | ||
|
|
d336f4cef2 | ||
|
|
51ee2f8d1e | ||
|
|
d442b45836 | ||
|
|
dbcb721dc2 | ||
|
|
64a8f6575b | ||
|
|
03a6b5c7b9 | ||
|
|
56b6241311 | ||
|
|
947ac2826a | ||
|
|
0e8303f13a | ||
|
|
4ec7532126 | ||
|
|
da83646303 | ||
|
|
72e9f7f9cf | ||
|
|
ad6b676c81 | ||
|
|
0f64158469 | ||
|
|
acc5be92ac | ||
|
|
0e0cee1bce | ||
|
|
6f71c000ad | ||
|
|
9f766ebf78 | ||
|
|
07c63f794e | ||
|
|
26dd86e967 | ||
|
|
5062d38b65 | ||
|
|
82b492c050 | ||
|
|
73e3a69aaf | ||
|
|
348a79f91d | ||
|
|
5e5e77f746 | ||
|
|
c4ada7ff6e | ||
|
|
39d0691c7e | ||
|
|
71361de8ee | ||
|
|
8aa2590fd3 | ||
|
|
e3b7bf467e | ||
|
|
f74402bc94 | ||
|
|
4d3b4a7b20 | ||
|
|
e6302cc868 | ||
|
|
844b4edf48 | ||
|
|
92a7f22d3c | ||
|
|
03167a1e9c | ||
|
|
1f309854bc | ||
|
|
2ac0d1f13a | ||
|
|
4eeea7b787 | ||
|
|
e64c01d2da | ||
|
|
0c7a91f852 | ||
|
|
a2d93b389c | ||
|
|
c795214abb | ||
|
|
71822a47a5 | ||
|
|
e1bf67c676 | ||
|
|
8583c48264 | ||
|
|
2a3d133bcf | ||
|
|
3e3d1fd265 | ||
|
|
8645618f1a | ||
|
|
e48ce5a103 | ||
|
|
c02ceda22f | ||
|
|
46139340fe | ||
|
|
d479f29e9b | ||
|
|
1af798b04b | ||
|
|
7204407690 | ||
|
|
e37336eef2 | ||
|
|
cf21b9feaf | ||
|
|
b74cab6642 | ||
|
|
8267d325ed | ||
|
|
879d7a24f0 | ||
|
|
9e4ac2eacb | ||
|
|
d9d6fff48f | ||
|
|
9828586762 | ||
|
|
8caaa6d297 | ||
|
|
83ca6b9468 | ||
|
|
24e65ef018 | ||
|
|
a69bbab732 | ||
|
|
a557ac3c7b | ||
|
|
d61b4b89ea | ||
|
|
b8daf16b92 | ||
|
|
caa3812e13 | ||
|
|
23a087c498 | ||
|
|
c3c39a7b24 | ||
|
|
00770fc634 | ||
|
|
5bf77160f7 | ||
|
|
d9da84c412 | ||
|
|
b3a6318672 | ||
|
|
67b41b970d | ||
|
|
3738e30949 | ||
|
|
0ba73b11c1 | ||
|
|
13baaa31cd | ||
|
|
f0db2aa43c | ||
|
|
f704721b59 | ||
|
|
7abf0f4886 | ||
|
|
c915b6e68b | ||
|
|
0b28c688c6 | ||
|
|
2756ef6d2f | ||
|
|
7da1d30010 | ||
|
|
8e192acb63 | ||
|
|
d8423499dc | ||
|
|
974167fcb8 | ||
|
|
6afdbd6fd3 | ||
|
|
d8668ed226 | ||
|
|
d75a6eaa41 | ||
|
|
235fb92638 | ||
|
|
ea18b4ea1f | ||
|
|
58f5ec0181 | ||
|
|
e42c9abdde | ||
|
|
5e7ad6ffd1 | ||
|
|
4c8238874e | ||
|
|
38d4887901 | ||
|
|
c9051d33c1 | ||
|
|
3cc0205def | ||
|
|
90979e2a81 | ||
|
|
e66e1b542c | ||
|
|
92e9c3e42e | ||
|
|
4591c09637 | ||
|
|
e1ce3fef1b | ||
|
|
3c0a200f7b | ||
|
|
bef5907ec3 | ||
|
|
f0beb662aa | ||
|
|
92402685f8 | ||
|
|
3703fed1a5 | ||
|
|
f4fb960c62 | ||
|
|
a3bbbf03b4 | ||
|
|
1d3a69a29f | ||
|
|
10c57b15da | ||
|
|
b85f7a6747 | ||
|
|
3f94e7b638 | ||
|
|
2af95cc1d4 | ||
|
|
cefdefdfd2 | ||
|
|
37f7fa7ef4 | ||
|
|
e687eb5631 | ||
|
|
88c3af7647 | ||
|
|
ddd6c8cbf1 | ||
|
|
81220f90d6 | ||
|
|
e0268a91ad | ||
|
|
29e4135aaa | ||
|
|
5d9adce40d | ||
|
|
d3afde8789 | ||
|
|
d8a5d5545d | ||
|
|
bed3516687 | ||
|
|
3a014d8d46 | ||
|
|
58ae7fbccb | ||
|
|
b06a9618d4 | ||
|
|
434c4a5cbc | ||
|
|
c34d30dc17 | ||
|
|
0d4c1bee3f | ||
|
|
34a25d0be3 | ||
|
|
3134f5e747 | ||
|
|
1732584e5e | ||
|
|
f50cafbac1 | ||
|
|
bc7c3f48ad | ||
|
|
b760419fd5 | ||
|
|
5cf3c58d0e | ||
|
|
206d1b6db4 | ||
|
|
2e318b8b03 | ||
|
|
5bdb6f18d6 | ||
|
|
2e53a99361 | ||
|
|
bec18e13d3 | ||
|
|
7edd471ec5 | ||
|
|
e6a4a3fa4f | ||
|
|
de2a139340 | ||
|
|
9d6ac67c46 | ||
|
|
6f7b905983 | ||
|
|
bcd4626008 | ||
|
|
27730a20d6 | ||
|
|
4aa0190175 | ||
|
|
6dd62335e9 | ||
|
|
32d2606a65 | ||
|
|
2051334bba | ||
|
|
575e809004 | ||
|
|
66e8e2a696 | ||
|
|
55373c95d9 | ||
|
|
04bdc1cc0b | ||
|
|
1d8850d1b2 | ||
|
|
f98548698a | ||
|
|
4b1824e8c1 | ||
|
|
17e88f1749 | ||
|
|
5edafca05a | ||
|
|
2c4c283099 | ||
|
|
9fb8125655 | ||
|
|
aab6580195 | ||
|
|
30f0db1d28 | ||
|
|
5a4dae2070 | ||
|
|
8345f348f6 | ||
|
|
9220e32463 | ||
|
|
845e72bf4a | ||
|
|
49429ff40a | ||
|
|
3df21ad25e | ||
|
|
d0f4600be4 | ||
|
|
0fa2e76c3e | ||
|
|
9ff1b5230f | ||
|
|
65eb631711 | ||
|
|
6c99557553 | ||
|
|
2b4357fa87 | ||
|
|
cda4b3faaa | ||
|
|
5d09a88335 | ||
|
|
edd4f6b9f3 | ||
|
|
1e7e2109d2 | ||
|
|
b31d3831e6 | ||
|
|
0f81a0504c | ||
|
|
4a7fda95ae | ||
|
|
ee3455e1e5 | ||
|
|
f9fc1cd817 | ||
|
|
76f1e588f7 | ||
|
|
f3b458c803 | ||
|
|
00566ed4d4 | ||
|
|
e4a07411b8 | ||
|
|
2c1bb2706f | ||
|
|
aa84d6fc8f | ||
|
|
d76e9b0bd8 | ||
|
|
b4016c91c1 | ||
|
|
5f32d001cc | ||
|
|
8c9287d0c8 | ||
|
|
3f37e27852 | ||
|
|
f41ab8b086 | ||
|
|
ad68f784ae | ||
|
|
4b6392df54 | ||
|
|
94ea329b50 | ||
|
|
591ed2e01f | ||
|
|
78cf9aaa7d | ||
|
|
f9494a294f | ||
|
|
0dd4553700 | ||
|
|
4f7b36cd70 | ||
|
|
5d350aec87 | ||
|
|
059db6fb31 | ||
|
|
4c709b2c4d | ||
|
|
8f4cd032b7 | ||
|
|
67629938d6 | ||
|
|
9aff49bd88 | ||
|
|
5b999a88f8 | ||
|
|
482531836f | ||
|
|
b3c82f54df | ||
|
|
77fa4bbe2f | ||
|
|
495c9850b4 | ||
|
|
c0f8d145f8 | ||
|
|
80f33daeeb | ||
|
|
a16dcb63b5 | ||
|
|
b871b5d2dd | ||
|
|
e876647af5 | ||
|
|
8d59812827 | ||
|
|
e39ac885de | ||
|
|
e6965622bd | ||
|
|
0d8d3479e1 | ||
|
|
35c1dfd145 | ||
|
|
096115def7 | ||
|
|
e784af3e2d | ||
|
|
ce30108efc | ||
|
|
edbd623e21 | ||
|
|
7cfd537755 | ||
|
|
ddd6d03e0b | ||
|
|
b4a0e08d9d | ||
|
|
545f9ae5f3 | ||
|
|
be4a5a5f3e | ||
|
|
3dc593fe88 | ||
|
|
e8ed18f1cf | ||
|
|
bf8890b0df | ||
|
|
e5fda35c51 | ||
|
|
84d50da009 | ||
|
|
2cf7764714 | ||
|
|
9fab0ec94f | ||
|
|
6d694518fe | ||
|
|
5265b767cb | ||
|
|
d10a93fe4f | ||
|
|
995986ecc7 | ||
|
|
6d0bb02544 | ||
|
|
6f51c47dc9 | ||
|
|
626daf89c1 | ||
|
|
b18ccffeb4 | ||
|
|
2ab2185e0a | ||
|
|
be47609405 | ||
|
|
5dee7a5262 | ||
|
|
bff7ada2d1 | ||
|
|
ed33d1d4f7 | ||
|
|
64e64f72f7 | ||
|
|
d3c783832a | ||
|
|
d963b69d5c | ||
|
|
49ce9ba387 | ||
|
|
d63a6d3f75 | ||
|
|
3d5a8af52b | ||
|
|
1cf670dad9 | ||
|
|
b50e3c07d2 | ||
|
|
fe7d1692c3 | ||
|
|
0758cd6980 | ||
|
|
e80b6b3057 | ||
|
|
9c86afe40d | ||
|
|
db4619f5a4 | ||
|
|
f90d74ca31 | ||
|
|
609f0a2eee | ||
|
|
77bbbc88f8 | ||
|
|
cdb79ef78a | ||
|
|
1630e309fb | ||
|
|
2d4f56f57c | ||
|
|
d622993483 | ||
|
|
c68a6ee0ed | ||
|
|
94c1438913 | ||
|
|
e206a26a85 | ||
|
|
242e20316b | ||
|
|
279fd2399d | ||
|
|
a5fcb41ab0 | ||
|
|
cb4f656673 | ||
|
|
b9e5ee6759 | ||
|
|
1084b7c3ad | ||
|
|
39c06c5461 | ||
|
|
b9c7f8769b | ||
|
|
dc45adf7f2 | ||
|
|
a69af42f7f | ||
|
|
1a5dfae7a0 | ||
|
|
d41b5d80ad | ||
|
|
f0bcb3ba28 | ||
|
|
7da35bf71d | ||
|
|
03c339dd4b | ||
|
|
11c74bd26b | ||
|
|
0a292cf893 | ||
|
|
ac6811867f | ||
|
|
0c9df501e8 | ||
|
|
4c4f9b45d9 | ||
|
|
5a921c9f10 | ||
|
|
bdc2aa2b39 | ||
|
|
b508dd69be | ||
|
|
f8b756c8bc | ||
|
|
027b829c38 | ||
|
|
0a2d6d1d62 | ||
|
|
1b485ddb5a | ||
|
|
0085ca6416 | ||
|
|
87dca0f7ec | ||
|
|
37af2c87e8 | ||
|
|
bf908f0b7d | ||
|
|
8d463b9577 | ||
|
|
4f7d206736 | ||
|
|
35073c780d | ||
|
|
0a8f28b1c6 | ||
|
|
af2375948d | ||
|
|
df2e0be08d | ||
|
|
ff1aca272e | ||
|
|
f2e352832a | ||
|
|
ad0855ac83 | ||
|
|
d7ef9b1f0c | ||
|
|
40a3e1b18a | ||
|
|
25a73090f5 | ||
|
|
a239a26b17 | ||
|
|
06d256294f | ||
|
|
81ad50e82a | ||
|
|
23de9bf93e | ||
|
|
5c46412faa | ||
|
|
076e9eee01 | ||
|
|
2103a04092 | ||
|
|
58517d1d27 | ||
|
|
aa1847189b | ||
|
|
5d101e7b88 | ||
|
|
e2de83188a | ||
|
|
2a1b506d98 | ||
|
|
b798ff5c92 | ||
|
|
673aa0a87b | ||
|
|
779ea19222 | ||
|
|
a1f2b7f8e8 | ||
|
|
fcb855cea9 | ||
|
|
50fb48f66d | ||
|
|
0acc3532c9 | ||
|
|
8bf2d996ea | ||
|
|
748c2babe9 | ||
|
|
6859f73c54 | ||
|
|
b1faed586d | ||
|
|
6c848b4766 | ||
|
|
725c18eada | ||
|
|
992bb5d7be | ||
|
|
9e353f1cdc | ||
|
|
8f83e39970 | ||
|
|
0eae9e7cdc | ||
|
|
031b893196 | ||
|
|
64da7a06c0 | ||
|
|
57eaa1bbe1 | ||
|
|
109d06b4bb | ||
|
|
0d9910cbbe | ||
|
|
8fbc8ffc7c | ||
|
|
f2ee3859ab | ||
|
|
89dc44be61 | ||
|
|
6ab8716e69 | ||
|
|
5c7c382323 | ||
|
|
78b4b9441e | ||
|
|
9e55014a13 | ||
|
|
6f23b56b06 | ||
|
|
1519527356 | ||
|
|
6b3a178f2a | ||
|
|
604419dd1f | ||
|
|
c48e702a50 | ||
|
|
1061bce4f3 | ||
|
|
013d513450 | ||
|
|
dca32efadf | ||
|
|
28d952a643 | ||
|
|
a2a717bd49 | ||
|
|
753a92055c | ||
|
|
371f986773 | ||
|
|
a1e8b9be4e | ||
|
|
c076a0f771 | ||
|
|
dfbd39e898 | ||
|
|
b5893f3fa3 | ||
|
|
e3614cb932 | ||
|
|
193c3e5b3d | ||
|
|
c03c344f49 | ||
|
|
25e3031830 | ||
|
|
b7911a8fd8 | ||
|
|
88384dc35e | ||
|
|
39b4ed082c | ||
|
|
d87aa23ae0 | ||
|
|
be548dcb52 | ||
|
|
4357a34339 | ||
|
|
2c03ba204e | ||
|
|
2c98d079de | ||
|
|
16cd47fa2e | ||
|
|
74a8bfba93 | ||
|
|
c929f00456 | ||
|
|
bb062f07f9 | ||
|
|
c3d1e75a8f | ||
|
|
506e3724a6 | ||
|
|
4859ab67d4 | ||
|
|
6d84d19520 | ||
|
|
8627efd0a1 | ||
|
|
6d13cf5e71 | ||
|
|
7e2ab0d384 | ||
|
|
19640d5e7c | ||
|
|
d1a82a85cd | ||
|
|
b1ab261890 | ||
|
|
038278283a | ||
|
|
c74bd11a6f | ||
|
|
f2c2f1735e | ||
|
|
4e41e12bd2 | ||
|
|
6df808f266 | ||
|
|
2cb973f150 | ||
|
|
b5463cf5e1 | ||
|
|
862546205a | ||
|
|
7c1790bbfd | ||
|
|
2d16a06bc4 | ||
|
|
25cf917969 | ||
|
|
d09c650afd | ||
|
|
2b833c5250 | ||
|
|
510db568eb | ||
|
|
e4003c842b | ||
|
|
68957d3880 | ||
|
|
e6747066ae | ||
|
|
62f0abee47 | ||
|
|
9118ecd68f | ||
|
|
15fd47c7f2 | ||
|
|
ef40ac7bb3 | ||
|
|
881d04ba1e | ||
|
|
4af5b5f6f2 | ||
|
|
90f0809029 | ||
|
|
db5ed48dbb | ||
|
|
ba84e7eead | ||
|
|
e51067177e | ||
|
|
f3859ed710 | ||
|
|
0db12e5561 | ||
|
|
ac5f991c0c | ||
|
|
4a0ff3f7ef | ||
|
|
601b1ef742 | ||
|
|
d957725805 | ||
|
|
4201723d10 | ||
|
|
bef79e77aa | ||
|
|
32f74273f0 | ||
|
|
c69bcaafbb | ||
|
|
50d7d1b7b3 | ||
|
|
c06d61a83c | ||
|
|
bc4f0c699f | ||
|
|
1e8efa7165 | ||
|
|
d4019f4b54 | ||
|
|
3f0f66f106 | ||
|
|
8f644e8aaf | ||
|
|
27f77518fe | ||
|
|
b56f3b3324 | ||
|
|
0195655479 | ||
|
|
3c91ec33ae | ||
|
|
6b3f51e5ea | ||
|
|
d6a1170ddb | ||
|
|
428a7d418b | ||
|
|
40d102fcb5 | ||
|
|
1db73370a7 | ||
|
|
8b63b437d8 | ||
|
|
78e577d260 | ||
|
|
96a7cc2971 | ||
|
|
9eedbae879 | ||
|
|
38d3b3c7ef | ||
|
|
e4d3b74f1b | ||
|
|
54f3003a6f | ||
|
|
cbc7b8ce18 | ||
|
|
ec7d01b794 | ||
|
|
3edd4c012d | ||
|
|
3243f97ff2 | ||
|
|
c658f28b02 | ||
|
|
5ab3a4a9e0 | ||
|
|
cb00c57009 | ||
|
|
cd2884d412 | ||
|
|
471137093a | ||
|
|
57064479c8 | ||
|
|
528bd502b4 | ||
|
|
90bc1905f5 | ||
|
|
a01e59e9db | ||
|
|
3f944c1bb2 | ||
|
|
43ef852117 | ||
|
|
bf22515bcd | ||
|
|
4c17c7b45b | ||
|
|
ec21200787 | ||
|
|
25fea73704 | ||
|
|
2377d85efb | ||
|
|
ddef550637 | ||
|
|
2f0ed7f3b7 | ||
|
|
b6bdd359d6 | ||
|
|
a4453bc699 | ||
|
|
3e87c40856 | ||
|
|
d80e531a2e | ||
|
|
d25e84a461 | ||
|
|
05cc520665 | ||
|
|
eeec6fd002 | ||
|
|
795bc82c7f | ||
|
|
7742c40ac0 | ||
|
|
d9e2ada369 | ||
|
|
5d6158ea76 | ||
|
|
00257e969e | ||
|
|
135f0f7249 | ||
|
|
fdd8b76add | ||
|
|
6b7ffbba4c | ||
|
|
8cfba4003d | ||
|
|
01b46edf1a | ||
|
|
f8599d17c2 | ||
|
|
5c7a9a52f5 | ||
|
|
c1f0a945c0 | ||
|
|
db7de05f2b | ||
|
|
e33bb676f9 | ||
|
|
30724dbc50 | ||
|
|
e765343162 | ||
|
|
62ce0b0408 | ||
|
|
3bbc606694 | ||
|
|
56eec9fed1 | ||
|
|
ea0d798ea0 | ||
|
|
5716d51112 | ||
|
|
d845a158f0 | ||
|
|
1a2fbd8122 | ||
|
|
8bdeed8f28 | ||
|
|
3c87462203 | ||
|
|
3622438a9d | ||
|
|
1848892ff8 | ||
|
|
72c6ed2804 | ||
|
|
42de2c7033 | ||
|
|
6bcc8691fa | ||
|
|
6cf13ed8fb | ||
|
|
ad75db40df | ||
|
|
4e3bf3c2f9 | ||
|
|
1925687f18 | ||
|
|
577301c4eb | ||
|
|
c87b42de1c | ||
|
|
c8e8915c2e | ||
|
|
17cdedfa85 | ||
|
|
677bb4070f | ||
|
|
fe82029dc7 | ||
|
|
0ab9961908 | ||
|
|
ecbf5d5ead | ||
|
|
df430badbc | ||
|
|
8639972a54 | ||
|
|
41038f452d | ||
|
|
2f31ea8864 | ||
|
|
e831059162 | ||
|
|
e109e8cf1c | ||
|
|
f1524b6aba | ||
|
|
51ee6f87e0 | ||
|
|
0bb3e7cb86 | ||
|
|
4bf063645a | ||
|
|
9866eab60f | ||
|
|
10c42de2f1 | ||
|
|
e1fd25fb71 | ||
|
|
2315b082ff | ||
|
|
023f6166ab | ||
|
|
d89a3c6c4d | ||
|
|
fb00ee8cf9 | ||
|
|
22671ca16c | ||
|
|
4e837e838d | ||
|
|
ed1781133c | ||
|
|
60fc662a26 | ||
|
|
43b0167a3a | ||
|
|
8519897089 | ||
|
|
60a5d02018 | ||
|
|
c377ffbce8 | ||
|
|
b567d428ad | ||
|
|
da30e539df | ||
|
|
f74d794b2a | ||
|
|
69ef4a987e | ||
|
|
78e1e0508e | ||
|
|
6d98ad7abc | ||
|
|
70b3ba310a | ||
|
|
2edc223e77 | ||
|
|
e18a6b09f8 | ||
|
|
f8c3ec4be7 | ||
|
|
ba3afd1e35 | ||
|
|
20f0011921 | ||
|
|
acebabd028 | ||
|
|
6243f34946 | ||
|
|
787758a436 | ||
|
|
a02b92fd59 | ||
|
|
a6ff85a208 | ||
|
|
41da8fc05f | ||
|
|
a4a9957a15 | ||
|
|
29318c64ed | ||
|
|
74bd28cbd9 | ||
|
|
365bb2d0e4 | ||
|
|
c08538d25d | ||
|
|
140ea8642c | ||
|
|
445d364193 | ||
|
|
4bb45c001d | ||
|
|
7350b1f32e | ||
|
|
4a33ee6045 | ||
|
|
704e9bd7b6 | ||
|
|
d2735607b8 | ||
|
|
3c72992c39 | ||
|
|
7689d1d15c | ||
|
|
65d8589e7a | ||
|
|
32cec6c9a7 | ||
|
|
72ca52a29b | ||
|
|
2ded8c7cc1 | ||
|
|
759a9080a8 | ||
|
|
2ba649949f | ||
|
|
c8d54ec6c7 | ||
|
|
96e9242431 | ||
|
|
3c74cb3439 | ||
|
|
7a8116b2cf | ||
|
|
d010384c88 | ||
|
|
39a5c8bdfb | ||
|
|
694418d30d | ||
|
|
ed06f559ae | ||
|
|
fdd3b03fe5 | ||
|
|
dbd6e4d11f | ||
|
|
61a14765f3 | ||
|
|
9b8ffdd2aa | ||
|
|
ef0a4cf8b2 | ||
|
|
7aed2eed8a | ||
|
|
87a88e4df7 | ||
|
|
366c39d4c6 | ||
|
|
77649d388c | ||
|
|
dba53d23aa | ||
|
|
208887d538 | ||
|
|
de7872d8f2 | ||
|
|
bb1f5d8f38 | ||
|
|
65680b2ccf | ||
|
|
c8ffe65acf | ||
|
|
26b29ca78d | ||
|
|
38db0cc713 | ||
|
|
ee217eb9b7 | ||
|
|
2b37721a6e | ||
|
|
0821f6463a | ||
|
|
16b0df69b1 | ||
|
|
8ad7bf60d7 | ||
|
|
898a936064 | ||
|
|
4e401bc059 | ||
|
|
9ecef6f011 | ||
|
|
ba394a7ab4 | ||
|
|
d32490a4be | ||
|
|
6526ff1612 | ||
|
|
bb5390d63a | ||
|
|
bd1aae8d66 | ||
|
|
c24aed054f | ||
|
|
0aa08a5e40 | ||
|
|
3c48825699 | ||
|
|
bfb56b4144 | ||
|
|
ba8370bcfd | ||
|
|
813f55152a | ||
|
|
270a541a7c | ||
|
|
c34549a47d | ||
|
|
96d6b309ec |
14
.github/CONTRIBUTING.md
vendored
14
.github/CONTRIBUTING.md
vendored
@@ -1,10 +1,12 @@
|
|||||||
|
### Please do **not** open pull requests for *new features* now, as we are planning to rewrite large chunks of the code. Only bugfix PRs will be accepted. More details will be announced soon!
|
||||||
|
|
||||||
NewPipe contribution guidelines
|
NewPipe contribution guidelines
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
## Crash reporting
|
## Crash reporting
|
||||||
|
|
||||||
Report crashes through the **automated crash report system** of NewPipe.
|
Report crashes through the **automated crash report system** of NewPipe.
|
||||||
This way all the data needed for debugging is included in your bugreport for GitHub.
|
This way all the data needed for debugging is included in your bug report for GitHub.
|
||||||
You'll see *exactly* what is sent, be able to add **your comments**, and then send it.
|
You'll see *exactly* what is sent, be able to add **your comments**, and then send it.
|
||||||
|
|
||||||
## Issue reporting/feature requests
|
## Issue reporting/feature requests
|
||||||
@@ -40,10 +42,6 @@ You'll see *exactly* what is sent, be able to add **your comments**, and then se
|
|||||||
* Create PRs that cover only **one specific issue/solution/bug**. Do not create PRs that are huge monoliths and could have been split into multiple independent contributions.
|
* Create PRs that cover only **one specific issue/solution/bug**. Do not create PRs that are huge monoliths and could have been split into multiple independent contributions.
|
||||||
* NewPipe uses [NewPipeExtractor](https://github.com/TeamNewPipe/NewPipeExtractor) to fetch data from services. If you need to change something there, you must test your changes in NewPipe. Telling NewPipe to use your extractor version can be accomplished by editing the `app/build.gradle` file: the comments under the "NewPipe libraries" section of `dependencies` will help you out.
|
* NewPipe uses [NewPipeExtractor](https://github.com/TeamNewPipe/NewPipeExtractor) to fetch data from services. If you need to change something there, you must test your changes in NewPipe. Telling NewPipe to use your extractor version can be accomplished by editing the `app/build.gradle` file: the comments under the "NewPipe libraries" section of `dependencies` will help you out.
|
||||||
|
|
||||||
### Kotlin in NewPipe
|
|
||||||
* NewPipe will remain mostly Java for time being
|
|
||||||
* Contributions containing a simple conversion from Java to Kotlin should be avoided. Conversions to Kotlin should only be done if Kotlin actually brings improvements like bug fixes or better performance which are not, or only with much more effort, implementable in Java. The core team sees Java as an easier to learn and generally well adopted programming language.
|
|
||||||
|
|
||||||
### Creating a Pull Request (PR)
|
### Creating a Pull Request (PR)
|
||||||
|
|
||||||
* Make changes on a **separate branch** with a meaningful name, not on the _master_ branch or the _dev_ branch. This is commonly known as *feature branch workflow*. You may then send your changes as a pull request (PR) on GitHub.
|
* Make changes on a **separate branch** with a meaningful name, not on the _master_ branch or the _dev_ branch. This is commonly known as *feature branch workflow*. You may then send your changes as a pull request (PR) on GitHub.
|
||||||
@@ -81,6 +79,6 @@ The [ktlint](https://github.com/pinterest/ktlint) plugin does the same job as ch
|
|||||||
|
|
||||||
## Communication
|
## Communication
|
||||||
|
|
||||||
* The #newpipe channel on Libera Chat (`ircs://irc.libera.chat:6697/newpipe`) has the core team and other developers in it. [Click here for webchat](https://web.libera.chat/#newpipe)!
|
* You can use a Matrix account to join the NewPipe channel at [#newpipe:matrix.newpipe-ev.de](https://matrix.to/#/#newpipe:matrix.newpipe-ev.de). Some convenient clients, available both for phone and desktop, are listed at that link.
|
||||||
* You can also use a Matrix account to join the NewPipe channel at [#newpipe:libera.chat](https://matrix.to/#/#newpipe:libera.chat). Some convenient clients, available both for phone and desktop, are listed at that link.
|
* Alternatively, the #newpipe channel on Libera Chat (`ircs://irc.libera.chat:6697/newpipe`) can also be joined, as it is bridged to the Matrix room. [Click here for webchat](https://web.libera.chat/#newpipe)!
|
||||||
* You can post your suggestions, changes, ideas etc. on either GitHub or IRC.
|
* You can post your suggestions, changes, ideas etc. on either GitHub or Matrix (including via IRC).
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
name: Question
|
|
||||||
description: Ask about anything NewPipe-related
|
|
||||||
labels: [question, needs triage]
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Thanks for taking the time to fill out this issue! :hugs:
|
Thanks for taking the time to fill out this form! :hugs:
|
||||||
|
|
||||||
Note that you can also ask questions on our [IRC channel](https://web.libera.chat/#newpipe).
|
Note that you can also ask questions on our [IRC channel](https://web.libera.chat/#newpipe).
|
||||||
|
|
||||||
@@ -14,7 +11,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: "Checklist"
|
label: "Checklist"
|
||||||
options:
|
options:
|
||||||
- label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
|
- label: "I made sure that there are *no existing issues or discussions* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
|
||||||
required: true
|
required: true
|
||||||
- label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my question isn't listed."
|
- label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my question isn't listed."
|
||||||
required: true
|
required: true
|
||||||
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: "Checklist"
|
label: "Checklist"
|
||||||
options:
|
options:
|
||||||
- label: "I am able to reproduce the bug with the [latest version](https://github.com/TeamNewPipe/NewPipe/releases/latest)."
|
- label: "I am able to reproduce the bug with the latest version given here: [CLICK THIS LINK](https://github.com/TeamNewPipe/NewPipe/releases/latest)."
|
||||||
required: true
|
required: true
|
||||||
- label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
|
- label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,11 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
|
- name: ❓ Question
|
||||||
|
url: https://github.com/TeamNewPipe/NewPipe/discussions/new?category=questions
|
||||||
|
about: Ask about anything NewPipe-related
|
||||||
|
- name: 💬 Matrix
|
||||||
|
url: https://matrix.to/#/#newpipe:matrix.newpipe-ev.de
|
||||||
|
about: Chat with us via Matrix for quick Q/A
|
||||||
- name: 💬 IRC
|
- name: 💬 IRC
|
||||||
url: https://web.libera.chat/#newpipe
|
url: https://web.libera.chat/#newpipe
|
||||||
about: Chat with us via IRC for quick Q/A
|
about: Chat with us via IRC for quick Q/A
|
||||||
- name: 💬 Matrix
|
|
||||||
url: https://matrix.to/#/#newpipe:libera.chat
|
|
||||||
about: Chat with us via Matrix for quick Q/A
|
|
||||||
|
|||||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -28,7 +28,7 @@
|
|||||||
#### APK testing
|
#### APK testing
|
||||||
<!-- Use a new, meaningfully named branch. The name is used as a suffix for the app ID to allow installing and testing multiple versions of NewPipe, e.g. "commentfix", if your PR implements a bugfix for comments. (No names like "patch-0" and "feature-1".) -->
|
<!-- Use a new, meaningfully named branch. The name is used as a suffix for the app ID to allow installing and testing multiple versions of NewPipe, e.g. "commentfix", if your PR implements a bugfix for comments. (No names like "patch-0" and "feature-1".) -->
|
||||||
<!-- Remove the following line if you directly link the APK created by the CI pipeline. Directly linking is preferred if you need to let users test.-->
|
<!-- Remove the following line if you directly link the APK created by the CI pipeline. Directly linking is preferred if you need to let users test.-->
|
||||||
The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR.
|
The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR. You can find more info and a video demonstration [on this wiki page](https://github.com/TeamNewPipe/NewPipe/wiki/Download-APK-for-PR).
|
||||||
|
|
||||||
#### Due diligence
|
#### Due diligence
|
||||||
- [ ] I read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md).
|
- [ ] I read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md).
|
||||||
|
|||||||
17
.github/changed-lines-count-labeler.yml
vendored
Normal file
17
.github/changed-lines-count-labeler.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Add 'size/small' label to any changes with less than 50 lines
|
||||||
|
size/small:
|
||||||
|
max: 49
|
||||||
|
|
||||||
|
# Add 'size/medium' label to any changes between 50 and 249 lines
|
||||||
|
size/medium:
|
||||||
|
min: 50
|
||||||
|
max: 249
|
||||||
|
|
||||||
|
# Add 'size/large' label to any changes between 250 and 749 lines
|
||||||
|
size/large:
|
||||||
|
min: 250
|
||||||
|
max: 749
|
||||||
|
|
||||||
|
# Add 'size/giant' label to any changes for more than 749 lines
|
||||||
|
size/giant:
|
||||||
|
min: 750
|
||||||
38
.github/workflows/build-release-apk.yml
vendored
Normal file
38
.github/workflows/build-release-apk.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: "Build unsigned release APK on master"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: 'master'
|
||||||
|
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '21'
|
||||||
|
cache: 'gradle'
|
||||||
|
|
||||||
|
- name: "Build release APK"
|
||||||
|
run: ./gradlew assembleRelease --stacktrace
|
||||||
|
|
||||||
|
- name: "Rename APK"
|
||||||
|
run: |
|
||||||
|
VERSION_NAME="$(jq -r ".elements[0].versionName" "app/build/outputs/apk/release/output-metadata.json")"
|
||||||
|
echo "Version name: $VERSION_NAME" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
echo '```json' >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
cat "app/build/outputs/apk/release/output-metadata.json" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
echo >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
# assume there is only one APK in that folder
|
||||||
|
mv app/build/outputs/apk/release/*.apk "app/build/outputs/apk/release/NewPipe_v$VERSION_NAME.apk"
|
||||||
|
|
||||||
|
- name: "Upload APK"
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: app
|
||||||
|
path: app/build/outputs/apk/release/*.apk
|
||||||
59
.github/workflows/ci.yml
vendored
59
.github/workflows/ci.yml
vendored
@@ -6,6 +6,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
- master
|
- master
|
||||||
|
- refactor
|
||||||
- release**
|
- release**
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'README.md'
|
- 'README.md'
|
||||||
@@ -36,18 +37,20 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: gradle/wrapper-validation-action@v1
|
- uses: gradle/wrapper-validation-action@v2
|
||||||
|
|
||||||
- name: create and checkout branch
|
- name: create and checkout branch
|
||||||
# push events already checked out the branch
|
# push events already checked out the branch
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
run: git checkout -B ${{ github.head_ref }}
|
env:
|
||||||
|
BRANCH: ${{ github.head_ref }}
|
||||||
|
run: git checkout -B "$BRANCH"
|
||||||
|
|
||||||
- name: set up JDK 11
|
- name: set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 21
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
cache: 'gradle'
|
cache: 'gradle'
|
||||||
|
|
||||||
@@ -55,30 +58,40 @@ jobs:
|
|||||||
run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint
|
run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint
|
||||||
|
|
||||||
- name: Upload APK
|
- name: Upload APK
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: app
|
name: app
|
||||||
path: app/build/outputs/apk/debug/*.apk
|
path: app/build/outputs/apk/debug/*.apk
|
||||||
|
|
||||||
test-android:
|
test-android:
|
||||||
# macos has hardware acceleration. See android-emulator-runner action
|
runs-on: ubuntu-latest
|
||||||
runs-on: macos-latest
|
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# api-level 19 is min sdk, but throws errors related to desugaring
|
include:
|
||||||
api-level: [ 21, 29 ]
|
- api-level: 21
|
||||||
|
target: default
|
||||||
|
arch: x86
|
||||||
|
- api-level: 33
|
||||||
|
target: google_apis # emulator API 33 only exists with Google APIs
|
||||||
|
arch: x86_64
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: set up JDK 11
|
- name: Enable KVM
|
||||||
uses: actions/setup-java@v3
|
run: |
|
||||||
|
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger --name-match=kvm
|
||||||
|
|
||||||
|
- name: set up JDK
|
||||||
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 21
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
cache: 'gradle'
|
cache: 'gradle'
|
||||||
|
|
||||||
@@ -86,12 +99,12 @@ jobs:
|
|||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
# workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
|
target: ${{ matrix.target }}
|
||||||
emulator-build: 7425822
|
arch: ${{ matrix.arch }}
|
||||||
script: ./gradlew connectedCheck --stacktrace
|
script: ./gradlew connectedCheck --stacktrace
|
||||||
|
|
||||||
- name: Upload test report when tests fail # because the printed out stacktrace (console) is too short, see also #7553
|
- name: Upload test report when tests fail # because the printed out stacktrace (console) is too short, see also #7553
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: android-test-report-api${{ matrix.api-level }}
|
name: android-test-report-api${{ matrix.api-level }}
|
||||||
@@ -104,19 +117,19 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||||
|
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 11 # Sonar requires JDK 11
|
java-version: 21
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
cache: 'gradle'
|
cache: 'gradle'
|
||||||
|
|
||||||
- name: Cache SonarCloud packages
|
- name: Cache SonarCloud packages
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.sonar/cache
|
path: ~/.sonar/cache
|
||||||
key: ${{ runner.os }}-sonar
|
key: ${{ runner.os }}-sonar
|
||||||
|
|||||||
115
.github/workflows/image-minimizer.js
vendored
115
.github/workflows/image-minimizer.js
vendored
@@ -17,6 +17,8 @@ module.exports = async ({github, context}) => {
|
|||||||
initialBody = context.payload.comment.body;
|
initialBody = context.payload.comment.body;
|
||||||
} else if (context.eventName == 'issues') {
|
} else if (context.eventName == 'issues') {
|
||||||
initialBody = context.payload.issue.body;
|
initialBody = context.payload.issue.body;
|
||||||
|
} else if (context.eventName == 'pull_request') {
|
||||||
|
initialBody = context.payload.pull_request.body;
|
||||||
} else {
|
} else {
|
||||||
console.log('Aborting: No body found');
|
console.log('Aborting: No body found');
|
||||||
return;
|
return;
|
||||||
@@ -30,10 +32,12 @@ module.exports = async ({github, context}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Regex for finding images (simple variant) 
|
// Regex for finding images (simple variant) 
|
||||||
const REGEX_IMAGE_LOOKUP = /\!\[(.*)\]\((https:\/\/[-a-z0-9]+\.githubusercontent\.com\/\d+\/[-0-9a-f]{32,512}\.(jpg|gif|png))\)/gm;
|
const REGEX_USER_CONTENT_IMAGE_LOOKUP = /\!\[([^\]]*)\]\((https:\/\/[-a-z0-9]+\.githubusercontent\.com\/\d+\/[-0-9a-f]{32,512}\.(jpg|gif|png))\)/gm;
|
||||||
|
const REGEX_ASSETS_IMAGE_LOOKUP = /\!\[([^\]]*)\]\((https:\/\/github\.com\/(?:user-attachments\/assets|[-\w\d]+\/[-\w\d]+\/assets\/\d+)\/[\-0-9a-f]{32,512})\)/gm;
|
||||||
|
|
||||||
// Check if we found something
|
// Check if we found something
|
||||||
let foundSimpleImages = REGEX_IMAGE_LOOKUP.test(initialBody);
|
let foundSimpleImages = REGEX_USER_CONTENT_IMAGE_LOOKUP.test(initialBody)
|
||||||
|
|| REGEX_ASSETS_IMAGE_LOOKUP.test(initialBody);
|
||||||
if (!foundSimpleImages) {
|
if (!foundSimpleImages) {
|
||||||
console.log('Found no simple images to process');
|
console.log('Found no simple images to process');
|
||||||
return;
|
return;
|
||||||
@@ -47,53 +51,8 @@ module.exports = async ({github, context}) => {
|
|||||||
var wasMatchModified = false;
|
var wasMatchModified = false;
|
||||||
|
|
||||||
// Try to find and replace the images with minimized ones
|
// Try to find and replace the images with minimized ones
|
||||||
let newBody = await replaceAsync(initialBody, REGEX_IMAGE_LOOKUP, async (match, g1, g2) => {
|
let newBody = await replaceAsync(initialBody, REGEX_USER_CONTENT_IMAGE_LOOKUP, minimizeAsync);
|
||||||
console.log(`Found match '${match}'`);
|
newBody = await replaceAsync(newBody, REGEX_ASSETS_IMAGE_LOOKUP, minimizeAsync);
|
||||||
|
|
||||||
if (g1.endsWith(IGNORE_ALT_NAME_END)) {
|
|
||||||
console.log(`Ignoring match '${match}': IGNORE_ALT_NAME_END`);
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
|
|
||||||
let probeAspectRatio = 0;
|
|
||||||
let shouldModify = false;
|
|
||||||
try {
|
|
||||||
console.log(`Probing ${g2}`);
|
|
||||||
let probeResult = await probe(g2);
|
|
||||||
if (probeResult == null) {
|
|
||||||
throw 'No probeResult';
|
|
||||||
}
|
|
||||||
if (probeResult.hUnits != 'px') {
|
|
||||||
throw `Unexpected probeResult.hUnits (expected px but got ${probeResult.hUnits})`;
|
|
||||||
}
|
|
||||||
if (probeResult.height <= 0) {
|
|
||||||
throw `Unexpected probeResult.height (height is invalid: ${probeResult.height})`;
|
|
||||||
}
|
|
||||||
if (probeResult.wUnits != 'px') {
|
|
||||||
throw `Unexpected probeResult.wUnits (expected px but got ${probeResult.wUnits})`;
|
|
||||||
}
|
|
||||||
if (probeResult.width <= 0) {
|
|
||||||
throw `Unexpected probeResult.width (width is invalid: ${probeResult.width})`;
|
|
||||||
}
|
|
||||||
console.log(`Probing resulted in ${probeResult.width}x${probeResult.height}px`);
|
|
||||||
|
|
||||||
probeAspectRatio = probeResult.width / probeResult.height;
|
|
||||||
shouldModify = probeResult.height > IMG_MAX_HEIGHT_PX && probeAspectRatio < MIN_ASPECT_RATIO;
|
|
||||||
} catch(e) {
|
|
||||||
console.log('Probing failed:', e);
|
|
||||||
// Immediately abort
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldModify) {
|
|
||||||
wasMatchModified = true;
|
|
||||||
console.log(`Modifying match '${match}'`);
|
|
||||||
return `<img alt="${g1}" src="${g2}" width=${Math.min(600, (IMG_MAX_HEIGHT_PX * probeAspectRatio).toFixed(0))} />`;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Match '${match}' is ok/will not be modified`);
|
|
||||||
return match;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!wasMatchModified) {
|
if (!wasMatchModified) {
|
||||||
console.log('Nothing was modified. Skipping update');
|
console.log('Nothing was modified. Skipping update');
|
||||||
@@ -117,9 +76,17 @@ module.exports = async ({github, context}) => {
|
|||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
body: newBody
|
body: newBody
|
||||||
});
|
});
|
||||||
|
} else if (context.eventName == 'pull_request') {
|
||||||
|
console.log('Updating pull request', context.payload.pull_request.number);
|
||||||
|
await github.rest.pulls.update({
|
||||||
|
pull_number: context.payload.pull_request.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: newBody
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asnyc replace function from https://stackoverflow.com/a/48032528
|
// Async replace function from https://stackoverflow.com/a/48032528
|
||||||
async function replaceAsync(str, regex, asyncFn) {
|
async function replaceAsync(str, regex, asyncFn) {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
str.replace(regex, (match, ...args) => {
|
str.replace(regex, (match, ...args) => {
|
||||||
@@ -129,4 +96,52 @@ module.exports = async ({github, context}) => {
|
|||||||
const data = await Promise.all(promises);
|
const data = await Promise.all(promises);
|
||||||
return str.replace(regex, () => data.shift());
|
return str.replace(regex, () => data.shift());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function minimizeAsync(match, g1, g2) {
|
||||||
|
console.log(`Found match '${match}'`);
|
||||||
|
|
||||||
|
if (g1.endsWith(IGNORE_ALT_NAME_END)) {
|
||||||
|
console.log(`Ignoring match '${match}': IGNORE_ALT_NAME_END`);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
let probeAspectRatio = 0;
|
||||||
|
let shouldModify = false;
|
||||||
|
try {
|
||||||
|
console.log(`Probing ${g2}`);
|
||||||
|
let probeResult = await probe(g2);
|
||||||
|
if (probeResult == null) {
|
||||||
|
throw 'No probeResult';
|
||||||
|
}
|
||||||
|
if (probeResult.hUnits != 'px') {
|
||||||
|
throw `Unexpected probeResult.hUnits (expected px but got ${probeResult.hUnits})`;
|
||||||
|
}
|
||||||
|
if (probeResult.height <= 0) {
|
||||||
|
throw `Unexpected probeResult.height (height is invalid: ${probeResult.height})`;
|
||||||
|
}
|
||||||
|
if (probeResult.wUnits != 'px') {
|
||||||
|
throw `Unexpected probeResult.wUnits (expected px but got ${probeResult.wUnits})`;
|
||||||
|
}
|
||||||
|
if (probeResult.width <= 0) {
|
||||||
|
throw `Unexpected probeResult.width (width is invalid: ${probeResult.width})`;
|
||||||
|
}
|
||||||
|
console.log(`Probing resulted in ${probeResult.width}x${probeResult.height}px`);
|
||||||
|
|
||||||
|
probeAspectRatio = probeResult.width / probeResult.height;
|
||||||
|
shouldModify = probeResult.height > IMG_MAX_HEIGHT_PX && probeAspectRatio < MIN_ASPECT_RATIO;
|
||||||
|
} catch(e) {
|
||||||
|
console.log('Probing failed:', e);
|
||||||
|
// Immediately abort
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldModify) {
|
||||||
|
wasMatchModified = true;
|
||||||
|
console.log(`Modifying match '${match}'`);
|
||||||
|
return `<img alt="${g1}" src="${g2}" width=${Math.min(600, Math.floor(IMG_MAX_HEIGHT_PX * probeAspectRatio))} />`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Match '${match}' is ok/will not be modified`);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
.github/workflows/image-minimizer.yml
vendored
8
.github/workflows/image-minimizer.yml
vendored
@@ -5,6 +5,8 @@ on:
|
|||||||
types: [created, edited]
|
types: [created, edited]
|
||||||
issues:
|
issues:
|
||||||
types: [opened, edited]
|
types: [opened, edited]
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
@@ -15,9 +17,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ jobs:
|
|||||||
run: npm i probe-image-size@7.2.3 --ignore-scripts
|
run: npm i probe-image-size@7.2.3 --ignore-scripts
|
||||||
|
|
||||||
- name: Minimize simple images
|
- name: Minimize simple images
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v7
|
||||||
timeout-minutes: 3
|
timeout-minutes: 3
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
18
.github/workflows/pr-labeler.yml
vendored
Normal file
18
.github/workflows/pr-labeler.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: "PR size labeler"
|
||||||
|
on: [pull_request_target]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
changed-lines-count-labeler:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Automatically labelling pull requests based on the changed lines count
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Set a label
|
||||||
|
uses: TeamNewPipe/changed-lines-count-labeler@main
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
configuration-path: .github/changed-lines-count-labeler.yml
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,6 +10,7 @@ captures/
|
|||||||
*.class
|
*.class
|
||||||
app/debug/
|
app/debug/
|
||||||
app/release/
|
app/release/
|
||||||
|
.kotlin/
|
||||||
|
|
||||||
# vscode / eclipse files
|
# vscode / eclipse files
|
||||||
*.classpath
|
*.classpath
|
||||||
|
|||||||
21
.idea/icon.svg
generated
Normal file
21
.idea/icon.svg
generated
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||||
|
viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#CD201F;}
|
||||||
|
.st1{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<g id="Alapkör">
|
||||||
|
<circle id="XMLID_23_" class="st0" cx="50" cy="50" r="50"/>
|
||||||
|
</g>
|
||||||
|
<g id="Elemek">
|
||||||
|
<path id="XMLID_19_" class="st1" d="M47,28.2c-9-5.3-15.3-9-15.3-9v61.7c0,0,30.4-18,52.3-30.9C72.1,43,57.7,34.5,47,28.2z"/>
|
||||||
|
</g>
|
||||||
|
<g id="Fedő">
|
||||||
|
<path id="XMLID_5_" class="st0" d="M48.4,40.1c-4.1-2.4-7-4.1-7-4.1V64c0,0,13.9-8.2,23.8-14C59.8,46.8,53.3,42.9,48.4,40.1z"/>
|
||||||
|
<rect id="XMLID_4_" x="41.4" y="55.6" class="st0" width="6.2" height="21"/>
|
||||||
|
</g>
|
||||||
|
<g id="Vonalak">
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 850 B |
263
app/build.gradle
263
app/build.gradle
File diff suppressed because it is too large
Load Diff
48
app/check-dependencies.gradle
Normal file
48
app/check-dependencies.gradle
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
tasks.register('checkDependenciesOrder') {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Checks that each section in libs.versions.toml is sorted alphabetically'
|
||||||
|
|
||||||
|
def tomlFile = file('../gradle/libs.versions.toml')
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
if (!tomlFile.exists()) {
|
||||||
|
throw new GradleException('TOML file not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
def lines = tomlFile.readLines()
|
||||||
|
def nonSortedBlocks = []
|
||||||
|
def currentBlock = []
|
||||||
|
def prevLine = ''
|
||||||
|
def prevIndex = 0
|
||||||
|
|
||||||
|
lines.eachWithIndex { line, lineIndex ->
|
||||||
|
if (line.trim() && !line.startsWith('#')) {
|
||||||
|
if (line.startsWith('[')) {
|
||||||
|
prevLine = ''
|
||||||
|
} else {
|
||||||
|
def currIndex = lineIndex + 1
|
||||||
|
if (prevLine > line) {
|
||||||
|
if (currentBlock && currentBlock[-1] == "${prevIndex}: ${prevLine}") {
|
||||||
|
currentBlock.add("${currIndex}: ${line}")
|
||||||
|
} else {
|
||||||
|
if (!currentBlock.isEmpty()) {
|
||||||
|
nonSortedBlocks.add(currentBlock)
|
||||||
|
currentBlock = []
|
||||||
|
}
|
||||||
|
currentBlock.add("${prevIndex}: ${prevLine}")
|
||||||
|
currentBlock.add("${currIndex}: ${line}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevLine = line
|
||||||
|
prevIndex = lineIndex + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentBlock.isEmpty()) {
|
||||||
|
nonSortedBlocks.add(currentBlock)
|
||||||
|
throw new GradleException("The following lines were not sorted:\n" +
|
||||||
|
nonSortedBlocks.collect { it.join("\n") }.join("\n\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
app/proguard-rules.pro
vendored
51
app/proguard-rules.pro
vendored
@@ -1,45 +1,30 @@
|
|||||||
# Add project specific ProGuard rules here.
|
# https://developer.android.com/build/shrink-code
|
||||||
# By default, the flags in this file are appended to flags specified
|
|
||||||
# in /home/the-scrabi/bin/Android/Sdk/tools/proguard/proguard-android.txt
|
|
||||||
# You can edit the include path and order by changing the proguardFiles
|
|
||||||
# directive in build.gradle.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# Add any project specific keep options here:
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
|
## Helps debug release versions
|
||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
|
## Rules for NewPipeExtractor
|
||||||
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
|
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
|
||||||
|
## Rules for Rhino and Rhino Engine
|
||||||
|
-keep class org.mozilla.javascript.* { *; }
|
||||||
-keep class org.mozilla.javascript.** { *; }
|
-keep class org.mozilla.javascript.** { *; }
|
||||||
|
-keep class org.mozilla.javascript.engine.** { *; }
|
||||||
-keep class org.mozilla.classfile.ClassFileWriter
|
-keep class org.mozilla.classfile.ClassFileWriter
|
||||||
|
-dontwarn org.mozilla.javascript.JavaToJSONConverters
|
||||||
|
-dontwarn org.mozilla.javascript.tools.**
|
||||||
|
-keep class javax.script.** { *; }
|
||||||
|
-dontwarn javax.script.**
|
||||||
|
-keep class jdk.dynalink.** { *; }
|
||||||
|
-dontwarn jdk.dynalink.**
|
||||||
|
|
||||||
|
## Rules for ExoPlayer
|
||||||
-keep class com.google.android.exoplayer2.** { *; }
|
-keep class com.google.android.exoplayer2.** { *; }
|
||||||
|
|
||||||
-dontwarn org.mozilla.javascript.tools.**
|
## Rules for OkHttp. Copy pasted from https://github.com/square/okhttp
|
||||||
|
|
||||||
# Rules for icepick. Copy paste from https://github.com/frankiesardo/icepick
|
|
||||||
-dontwarn icepick.**
|
|
||||||
-keep class icepick.** { *; }
|
|
||||||
-keep class **$$Icepick { *; }
|
|
||||||
-keepclasseswithmembernames class * {
|
|
||||||
@icepick.* <fields>;
|
|
||||||
}
|
|
||||||
-keepnames class * { @icepick.State *;}
|
|
||||||
|
|
||||||
## Rules for OkHttp. Copy paste from https://github.com/square/okhttp
|
|
||||||
-dontwarn okhttp3.**
|
-dontwarn okhttp3.**
|
||||||
-dontwarn okio.**
|
-dontwarn okio.**
|
||||||
##
|
|
||||||
|
|
||||||
|
## See https://github.com/TeamNewPipe/NewPipe/pull/1441
|
||||||
-keepclassmembers class * implements java.io.Serializable {
|
-keepclassmembers class * implements java.io.Serializable {
|
||||||
static final long serialVersionUID;
|
static final long serialVersionUID;
|
||||||
!static !transient <fields>;
|
!static !transient <fields>;
|
||||||
@@ -47,5 +32,5 @@
|
|||||||
private void readObject(java.io.ObjectInputStream);
|
private void readObject(java.io.ObjectInputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
# for some reason NotificationModeConfigFragment wasn't kept (only referenced in a preference xml)
|
## For some reason NotificationModeConfigFragment wasn't kept (only referenced in a preference xml)
|
||||||
-keep class org.schabi.newpipe.settings.notifications.** { *; }
|
-keep class org.schabi.newpipe.settings.notifications.** { *; }
|
||||||
|
|||||||
737
app/schemas/org.schabi.newpipe.database.AppDatabase/8.json
Normal file
737
app/schemas/org.schabi.newpipe.database.AppDatabase/8.json
Normal file
File diff suppressed because it is too large
Load Diff
730
app/schemas/org.schabi.newpipe.database.AppDatabase/9.json
Normal file
730
app/schemas/org.schabi.newpipe.database.AppDatabase/9.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,15 +4,18 @@ import android.content.ContentValues
|
|||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.room.testing.MigrationTestHelper
|
import androidx.room.testing.MigrationTestHelper
|
||||||
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
|
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotEquals
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistEntity
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity
|
||||||
|
import org.schabi.newpipe.extractor.ServiceList
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType
|
import org.schabi.newpipe.extractor.stream.StreamType
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@@ -21,20 +24,23 @@ class DatabaseMigrationTest {
|
|||||||
private const val DEFAULT_SERVICE_ID = 0
|
private const val DEFAULT_SERVICE_ID = 0
|
||||||
private const val DEFAULT_URL = "https://www.youtube.com/watch?v=cDphUib5iG4"
|
private const val DEFAULT_URL = "https://www.youtube.com/watch?v=cDphUib5iG4"
|
||||||
private const val DEFAULT_TITLE = "Test Title"
|
private const val DEFAULT_TITLE = "Test Title"
|
||||||
|
private const val DEFAULT_NAME = "Test Name"
|
||||||
private val DEFAULT_TYPE = StreamType.VIDEO_STREAM
|
private val DEFAULT_TYPE = StreamType.VIDEO_STREAM
|
||||||
private const val DEFAULT_DURATION = 480L
|
private const val DEFAULT_DURATION = 480L
|
||||||
private const val DEFAULT_UPLOADER_NAME = "Uploader Test"
|
private const val DEFAULT_UPLOADER_NAME = "Uploader Test"
|
||||||
private const val DEFAULT_THUMBNAIL = "https://example.com/example.jpg"
|
private const val DEFAULT_THUMBNAIL = "https://example.com/example.jpg"
|
||||||
|
|
||||||
private const val DEFAULT_SECOND_SERVICE_ID = 0
|
private const val DEFAULT_SECOND_SERVICE_ID = 1
|
||||||
private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
|
private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
|
||||||
|
|
||||||
|
private const val DEFAULT_THIRD_SERVICE_ID = 2
|
||||||
|
private const val DEFAULT_THIRD_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val testHelper = MigrationTestHelper(
|
val testHelper = MigrationTestHelper(
|
||||||
InstrumentationRegistry.getInstrumentation(),
|
InstrumentationRegistry.getInstrumentation(),
|
||||||
AppDatabase::class.java.canonicalName,
|
AppDatabase::class.java
|
||||||
FrameworkSQLiteOpenHelperFactory()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -108,6 +114,20 @@ class DatabaseMigrationTest {
|
|||||||
Migrations.MIGRATION_6_7
|
Migrations.MIGRATION_6_7
|
||||||
)
|
)
|
||||||
|
|
||||||
|
testHelper.runMigrationsAndValidate(
|
||||||
|
AppDatabase.DATABASE_NAME,
|
||||||
|
Migrations.DB_VER_8,
|
||||||
|
true,
|
||||||
|
Migrations.MIGRATION_7_8
|
||||||
|
)
|
||||||
|
|
||||||
|
testHelper.runMigrationsAndValidate(
|
||||||
|
AppDatabase.DATABASE_NAME,
|
||||||
|
Migrations.DB_VER_9,
|
||||||
|
true,
|
||||||
|
Migrations.MIGRATION_8_9
|
||||||
|
)
|
||||||
|
|
||||||
val migratedDatabaseV3 = getMigratedDatabase()
|
val migratedDatabaseV3 = getMigratedDatabase()
|
||||||
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
|
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
|
||||||
|
|
||||||
@@ -142,6 +162,157 @@ class DatabaseMigrationTest {
|
|||||||
assertNull(secondStreamFromMigratedDatabase.isUploadDateApproximation)
|
assertNull(secondStreamFromMigratedDatabase.isUploadDateApproximation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun migrateDatabaseFrom7to8() {
|
||||||
|
val databaseInV7 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_7)
|
||||||
|
|
||||||
|
val defaultSearch1 = " abc "
|
||||||
|
val defaultSearch2 = " abc"
|
||||||
|
|
||||||
|
val serviceId = DEFAULT_SERVICE_ID // YouTube
|
||||||
|
// Use id different to YouTube because two searches with the same query
|
||||||
|
// but different service are considered not equal.
|
||||||
|
val otherServiceId = ServiceList.SoundCloud.serviceId
|
||||||
|
|
||||||
|
databaseInV7.run {
|
||||||
|
insert(
|
||||||
|
"search_history", SQLiteDatabase.CONFLICT_FAIL,
|
||||||
|
ContentValues().apply {
|
||||||
|
put("service_id", serviceId)
|
||||||
|
put("search", defaultSearch1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
insert(
|
||||||
|
"search_history", SQLiteDatabase.CONFLICT_FAIL,
|
||||||
|
ContentValues().apply {
|
||||||
|
put("service_id", serviceId)
|
||||||
|
put("search", defaultSearch2)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
insert(
|
||||||
|
"search_history", SQLiteDatabase.CONFLICT_FAIL,
|
||||||
|
ContentValues().apply {
|
||||||
|
put("service_id", otherServiceId)
|
||||||
|
put("search", defaultSearch1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
insert(
|
||||||
|
"search_history", SQLiteDatabase.CONFLICT_FAIL,
|
||||||
|
ContentValues().apply {
|
||||||
|
put("service_id", otherServiceId)
|
||||||
|
put("search", defaultSearch2)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
testHelper.runMigrationsAndValidate(
|
||||||
|
AppDatabase.DATABASE_NAME, Migrations.DB_VER_8,
|
||||||
|
true, Migrations.MIGRATION_7_8
|
||||||
|
)
|
||||||
|
|
||||||
|
testHelper.runMigrationsAndValidate(
|
||||||
|
AppDatabase.DATABASE_NAME, Migrations.DB_VER_9,
|
||||||
|
true, Migrations.MIGRATION_8_9
|
||||||
|
)
|
||||||
|
|
||||||
|
val migratedDatabaseV8 = getMigratedDatabase()
|
||||||
|
val listFromDB = migratedDatabaseV8.searchHistoryDAO().all.blockingFirst()
|
||||||
|
|
||||||
|
assertEquals(2, listFromDB.size)
|
||||||
|
assertEquals("abc", listFromDB[0].search)
|
||||||
|
assertEquals("abc", listFromDB[1].search)
|
||||||
|
assertNotEquals(listFromDB[0].serviceId, listFromDB[1].serviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun migrateDatabaseFrom8to9() {
|
||||||
|
val databaseInV8 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_8)
|
||||||
|
|
||||||
|
val localUid1: Long
|
||||||
|
val localUid2: Long
|
||||||
|
val remoteUid1: Long
|
||||||
|
val remoteUid2: Long
|
||||||
|
databaseInV8.run {
|
||||||
|
localUid1 = insert(
|
||||||
|
"playlists", SQLiteDatabase.CONFLICT_FAIL,
|
||||||
|
ContentValues().apply {
|
||||||
|
put("name", DEFAULT_NAME + "1")
|
||||||
|
put("is_thumbnail_permanent", false)
|
||||||
|
put("thumbnail_stream_id", -1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
localUid2 = insert(
|
||||||
|
"playlists", SQLiteDatabase.CONFLICT_FAIL,
|
||||||
|
ContentValues().apply {
|
||||||
|
put("name", DEFAULT_NAME + "2")
|
||||||
|
put("is_thumbnail_permanent", false)
|
||||||
|
put("thumbnail_stream_id", -1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
delete(
|
||||||
|
"playlists", "uid = ?",
|
||||||
|
Array(1) { localUid1 }
|
||||||
|
)
|
||||||
|
remoteUid1 = insert(
|
||||||
|
"remote_playlists", SQLiteDatabase.CONFLICT_FAIL,
|
||||||
|
ContentValues().apply {
|
||||||
|
put("service_id", DEFAULT_SERVICE_ID)
|
||||||
|
put("url", DEFAULT_URL)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
remoteUid2 = insert(
|
||||||
|
"remote_playlists", SQLiteDatabase.CONFLICT_FAIL,
|
||||||
|
ContentValues().apply {
|
||||||
|
put("service_id", DEFAULT_SECOND_SERVICE_ID)
|
||||||
|
put("url", DEFAULT_SECOND_URL)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
delete(
|
||||||
|
"remote_playlists", "uid = ?",
|
||||||
|
Array(1) { remoteUid2 }
|
||||||
|
)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
testHelper.runMigrationsAndValidate(
|
||||||
|
AppDatabase.DATABASE_NAME,
|
||||||
|
Migrations.DB_VER_9,
|
||||||
|
true,
|
||||||
|
Migrations.MIGRATION_8_9
|
||||||
|
)
|
||||||
|
|
||||||
|
val migratedDatabaseV9 = getMigratedDatabase()
|
||||||
|
var localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
|
||||||
|
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
|
||||||
|
|
||||||
|
assertEquals(1, localListFromDB.size)
|
||||||
|
assertEquals(localUid2, localListFromDB[0].uid)
|
||||||
|
assertEquals(-1, localListFromDB[0].displayIndex)
|
||||||
|
assertEquals(1, remoteListFromDB.size)
|
||||||
|
assertEquals(remoteUid1, remoteListFromDB[0].uid)
|
||||||
|
assertEquals(-1, remoteListFromDB[0].displayIndex)
|
||||||
|
|
||||||
|
val localUid3 = migratedDatabaseV9.playlistDAO().insert(
|
||||||
|
PlaylistEntity(DEFAULT_NAME + "3", false, -1, -1)
|
||||||
|
)
|
||||||
|
val remoteUid3 = migratedDatabaseV9.playlistRemoteDAO().insert(
|
||||||
|
PlaylistRemoteEntity(
|
||||||
|
DEFAULT_THIRD_SERVICE_ID, DEFAULT_NAME, DEFAULT_THIRD_URL,
|
||||||
|
DEFAULT_THUMBNAIL, DEFAULT_UPLOADER_NAME, -1, 10
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
|
||||||
|
remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
|
||||||
|
assertEquals(2, localListFromDB.size)
|
||||||
|
assertEquals(localUid3, localListFromDB[1].uid)
|
||||||
|
assertEquals(-1, localListFromDB[1].displayIndex)
|
||||||
|
assertEquals(2, remoteListFromDB.size)
|
||||||
|
assertEquals(remoteUid3, remoteListFromDB[1].uid)
|
||||||
|
assertEquals(-1, remoteListFromDB[1].displayIndex)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getMigratedDatabase(): AppDatabase {
|
private fun getMigratedDatabase(): AppDatabase {
|
||||||
val database: AppDatabase = Room.databaseBuilder(
|
val database: AppDatabase = Room.databaseBuilder(
|
||||||
ApplicationProvider.getApplicationContext(),
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package org.schabi.newpipe.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.schabi.newpipe.database.feed.dao.FeedDAO
|
||||||
|
import org.schabi.newpipe.database.feed.model.FeedEntity
|
||||||
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
|
import org.schabi.newpipe.database.stream.StreamWithState
|
||||||
|
import org.schabi.newpipe.database.stream.dao.StreamDAO
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
|
import org.schabi.newpipe.database.subscription.SubscriptionDAO
|
||||||
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
|
import org.schabi.newpipe.extractor.ServiceList
|
||||||
|
import org.schabi.newpipe.extractor.channel.ChannelInfo
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamType
|
||||||
|
import java.io.IOException
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import kotlin.streams.toList
|
||||||
|
|
||||||
|
class FeedDAOTest {
|
||||||
|
private lateinit var db: AppDatabase
|
||||||
|
private lateinit var feedDAO: FeedDAO
|
||||||
|
private lateinit var streamDAO: StreamDAO
|
||||||
|
private lateinit var subscriptionDAO: SubscriptionDAO
|
||||||
|
|
||||||
|
private val serviceId = ServiceList.YouTube.serviceId
|
||||||
|
|
||||||
|
private val stream1 = StreamEntity(1, serviceId, "https://youtube.com/watch?v=1", "stream 1", StreamType.VIDEO_STREAM, 1000, "channel-1", "https://youtube.com/channel/1", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-01-01", OffsetDateTime.parse("2023-01-01T00:00:00Z"))
|
||||||
|
private val stream2 = StreamEntity(2, serviceId, "https://youtube.com/watch?v=2", "stream 2", StreamType.VIDEO_STREAM, 1000, "channel-1", "https://youtube.com/channel/1", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-01-02", OffsetDateTime.parse("2023-01-02T00:00:00Z"))
|
||||||
|
private val stream3 = StreamEntity(3, serviceId, "https://youtube.com/watch?v=3", "stream 3", StreamType.LIVE_STREAM, 1000, "channel-1", "https://youtube.com/channel/1", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-01-03", OffsetDateTime.parse("2023-01-03T00:00:00Z"))
|
||||||
|
private val stream4 = StreamEntity(4, serviceId, "https://youtube.com/watch?v=4", "stream 4", StreamType.VIDEO_STREAM, 1000, "channel-2", "https://youtube.com/channel/2", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-08-10", OffsetDateTime.parse("2023-08-10T00:00:00Z"))
|
||||||
|
private val stream5 = StreamEntity(5, serviceId, "https://youtube.com/watch?v=5", "stream 5", StreamType.VIDEO_STREAM, 1000, "channel-2", "https://youtube.com/channel/2", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-08-20", OffsetDateTime.parse("2023-08-20T00:00:00Z"))
|
||||||
|
private val stream6 = StreamEntity(6, serviceId, "https://youtube.com/watch?v=6", "stream 6", StreamType.VIDEO_STREAM, 1000, "channel-3", "https://youtube.com/channel/3", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-09-01", OffsetDateTime.parse("2023-09-01T00:00:00Z"))
|
||||||
|
private val stream7 = StreamEntity(7, serviceId, "https://youtube.com/watch?v=7", "stream 7", StreamType.VIDEO_STREAM, 1000, "channel-4", "https://youtube.com/channel/4", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-08-10", OffsetDateTime.parse("2023-08-10T00:00:00Z"))
|
||||||
|
|
||||||
|
private val allStreams = listOf(
|
||||||
|
stream1, stream2, stream3, stream4, stream5, stream6, stream7
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun createDb() {
|
||||||
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
|
db = Room.inMemoryDatabaseBuilder(
|
||||||
|
context, AppDatabase::class.java
|
||||||
|
).build()
|
||||||
|
feedDAO = db.feedDAO()
|
||||||
|
streamDAO = db.streamDAO()
|
||||||
|
subscriptionDAO = db.subscriptionDAO()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun closeDb() {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testUnlinkStreamsOlderThan_KeepOne() {
|
||||||
|
setupUnlinkDelete("2023-08-15T00:00:00Z")
|
||||||
|
val streams = feedDAO.getStreams(
|
||||||
|
FeedGroupEntity.GROUP_ALL_ID, includePlayed = true, includePartiallyPlayed = true, null
|
||||||
|
)
|
||||||
|
.blockingGet()
|
||||||
|
val allowedStreams = listOf(stream3, stream5, stream6, stream7)
|
||||||
|
assertEqual(streams, allowedStreams)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testUnlinkStreamsOlderThan_KeepMultiple() {
|
||||||
|
setupUnlinkDelete("2023-08-01T00:00:00Z")
|
||||||
|
val streams = feedDAO.getStreams(
|
||||||
|
FeedGroupEntity.GROUP_ALL_ID, includePlayed = true, includePartiallyPlayed = true, null
|
||||||
|
)
|
||||||
|
.blockingGet()
|
||||||
|
val allowedStreams = listOf(stream3, stream4, stream5, stream6, stream7)
|
||||||
|
assertEqual(streams, allowedStreams)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertEqual(streams: List<StreamWithState>?, allowedStreams: List<StreamEntity>) {
|
||||||
|
assertNotNull(streams)
|
||||||
|
assertEquals(
|
||||||
|
allowedStreams,
|
||||||
|
streams!!
|
||||||
|
.map { it.stream }
|
||||||
|
.sortedBy { it.uid }
|
||||||
|
.toList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupUnlinkDelete(time: String) {
|
||||||
|
clearAndFillTables()
|
||||||
|
Single.fromCallable {
|
||||||
|
feedDAO.unlinkStreamsOlderThan(OffsetDateTime.parse(time))
|
||||||
|
}.blockingSubscribe()
|
||||||
|
Single.fromCallable {
|
||||||
|
streamDAO.deleteOrphans()
|
||||||
|
}.blockingSubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearAndFillTables() {
|
||||||
|
db.clearAllTables()
|
||||||
|
streamDAO.insertAll(allStreams)
|
||||||
|
subscriptionDAO.insertAll(
|
||||||
|
listOf(
|
||||||
|
SubscriptionEntity.from(ChannelInfo(serviceId, "1", "https://youtube.com/channel/1", "https://youtube.com/channel/1", "channel-1")),
|
||||||
|
SubscriptionEntity.from(ChannelInfo(serviceId, "2", "https://youtube.com/channel/2", "https://youtube.com/channel/2", "channel-2")),
|
||||||
|
SubscriptionEntity.from(ChannelInfo(serviceId, "3", "https://youtube.com/channel/3", "https://youtube.com/channel/3", "channel-3")),
|
||||||
|
SubscriptionEntity.from(ChannelInfo(serviceId, "4", "https://youtube.com/channel/4", "https://youtube.com/channel/4", "channel-4")),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
feedDAO.insertAll(
|
||||||
|
listOf(
|
||||||
|
FeedEntity(1, 1),
|
||||||
|
FeedEntity(2, 1),
|
||||||
|
FeedEntity(3, 1),
|
||||||
|
FeedEntity(4, 2),
|
||||||
|
FeedEntity(5, 2),
|
||||||
|
FeedEntity(6, 3),
|
||||||
|
FeedEntity(7, 4),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package org.schabi.newpipe.local.subscription;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.schabi.newpipe.database.AppDatabase;
|
||||||
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity;
|
||||||
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||||
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
|
import org.schabi.newpipe.testUtil.TestDatabase;
|
||||||
|
import org.schabi.newpipe.testUtil.TrampolineSchedulerRule;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SubscriptionManagerTest {
|
||||||
|
private AppDatabase database;
|
||||||
|
private SubscriptionManager manager;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TrampolineSchedulerRule trampolineScheduler = new TrampolineSchedulerRule();
|
||||||
|
|
||||||
|
|
||||||
|
private SubscriptionEntity getAssertOneSubscriptionEntity() {
|
||||||
|
final List<SubscriptionEntity> entities = manager
|
||||||
|
.getSubscriptions(FeedGroupEntity.GROUP_ALL_ID, "", false)
|
||||||
|
.blockingFirst();
|
||||||
|
assertEquals(1, entities.size());
|
||||||
|
return entities.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
database = TestDatabase.Companion.createReplacingNewPipeDatabase();
|
||||||
|
manager = new SubscriptionManager(ApplicationProvider.getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanUp() {
|
||||||
|
database.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsert() throws ExtractionException, IOException {
|
||||||
|
final ChannelInfo info = ChannelInfo.getInfo("https://www.youtube.com/c/3blue1brown");
|
||||||
|
final SubscriptionEntity subscription = SubscriptionEntity.from(info);
|
||||||
|
|
||||||
|
manager.insertSubscription(subscription);
|
||||||
|
final SubscriptionEntity readSubscription = getAssertOneSubscriptionEntity();
|
||||||
|
|
||||||
|
// the uid has changed, since the uid is chosen upon inserting, but the rest should match
|
||||||
|
assertEquals(subscription.getServiceId(), readSubscription.getServiceId());
|
||||||
|
assertEquals(subscription.getUrl(), readSubscription.getUrl());
|
||||||
|
assertEquals(subscription.getName(), readSubscription.getName());
|
||||||
|
assertEquals(subscription.getAvatarUrl(), readSubscription.getAvatarUrl());
|
||||||
|
assertEquals(subscription.getSubscriberCount(), readSubscription.getSubscriberCount());
|
||||||
|
assertEquals(subscription.getDescription(), readSubscription.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateNotificationMode() throws ExtractionException, IOException {
|
||||||
|
final ChannelInfo info = ChannelInfo.getInfo("https://www.youtube.com/c/veritasium");
|
||||||
|
final SubscriptionEntity subscription = SubscriptionEntity.from(info);
|
||||||
|
subscription.setNotificationMode(0);
|
||||||
|
|
||||||
|
manager.insertSubscription(subscription);
|
||||||
|
manager.updateNotificationMode(subscription.getServiceId(), subscription.getUrl(), 1)
|
||||||
|
.blockingAwait();
|
||||||
|
final SubscriptionEntity anotherSubscription = getAssertOneSubscriptionEntity();
|
||||||
|
|
||||||
|
assertEquals(0, subscription.getNotificationMode());
|
||||||
|
assertEquals(subscription.getUrl(), anotherSubscription.getUrl());
|
||||||
|
assertEquals(1, anotherSubscription.getNotificationMode());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,15 +12,21 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
|||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
|
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.extractor.MediaFormat
|
import org.schabi.newpipe.extractor.MediaFormat
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Response
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream
|
import org.schabi.newpipe.extractor.stream.AudioStream
|
||||||
import org.schabi.newpipe.extractor.stream.Stream
|
import org.schabi.newpipe.extractor.stream.Stream
|
||||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream
|
import org.schabi.newpipe.extractor.stream.SubtitlesStream
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream
|
import org.schabi.newpipe.extractor.stream.VideoStream
|
||||||
|
import org.schabi.newpipe.util.StreamItemAdapter.StreamInfoWrapper
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@@ -84,7 +90,7 @@ class StreamItemAdapterTest {
|
|||||||
@Test
|
@Test
|
||||||
fun subtitleStreams_noIcon() {
|
fun subtitleStreams_noIcon() {
|
||||||
val adapter = StreamItemAdapter<SubtitlesStream, Stream>(
|
val adapter = StreamItemAdapter<SubtitlesStream, Stream>(
|
||||||
StreamItemAdapter.StreamSizeWrapper(
|
StreamItemAdapter.StreamInfoWrapper(
|
||||||
(0 until 5).map {
|
(0 until 5).map {
|
||||||
SubtitlesStream.Builder()
|
SubtitlesStream.Builder()
|
||||||
.setContent("https://example.com", true)
|
.setContent("https://example.com", true)
|
||||||
@@ -105,7 +111,7 @@ class StreamItemAdapterTest {
|
|||||||
@Test
|
@Test
|
||||||
fun audioStreams_noIcon() {
|
fun audioStreams_noIcon() {
|
||||||
val adapter = StreamItemAdapter<AudioStream, Stream>(
|
val adapter = StreamItemAdapter<AudioStream, Stream>(
|
||||||
StreamItemAdapter.StreamSizeWrapper(
|
StreamItemAdapter.StreamInfoWrapper(
|
||||||
(0 until 5).map {
|
(0 until 5).map {
|
||||||
AudioStream.Builder()
|
AudioStream.Builder()
|
||||||
.setId(Stream.ID_UNKNOWN)
|
.setId(Stream.ID_UNKNOWN)
|
||||||
@@ -123,12 +129,109 @@ class StreamItemAdapterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun retrieveMediaFormatFromFileTypeHeaders() {
|
||||||
|
val streams = getIncompleteAudioStreams(5)
|
||||||
|
val wrapper = StreamInfoWrapper(streams, context)
|
||||||
|
val retrieveMediaFormat = { stream: AudioStream, response: Response ->
|
||||||
|
StreamInfoWrapper.retrieveMediaFormatFromFileTypeHeaders(stream, wrapper, response)
|
||||||
|
}
|
||||||
|
val helper = AssertionHelper(streams, wrapper, retrieveMediaFormat)
|
||||||
|
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("content-length", "mp3"))), 0)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("file-type", "mp0"))), 1)
|
||||||
|
|
||||||
|
helper.assertValidResponse(getResponse(mapOf(Pair("x-amz-meta-file-type", "aiff"))), 2, MediaFormat.AIFF)
|
||||||
|
helper.assertValidResponse(getResponse(mapOf(Pair("file-type", "mp3"))), 3, MediaFormat.MP3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun retrieveMediaFormatFromContentDispositionHeader() {
|
||||||
|
val streams = getIncompleteAudioStreams(11)
|
||||||
|
val wrapper = StreamInfoWrapper(streams, context)
|
||||||
|
val retrieveMediaFormat = { stream: AudioStream, response: Response ->
|
||||||
|
StreamInfoWrapper.retrieveMediaFormatFromContentDispositionHeader(stream, wrapper, response)
|
||||||
|
}
|
||||||
|
val helper = AssertionHelper(streams, wrapper, retrieveMediaFormat)
|
||||||
|
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("content-length", "mp3"))), 0)
|
||||||
|
helper.assertInvalidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "filename=\"train.png\""))), 1
|
||||||
|
)
|
||||||
|
helper.assertInvalidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"data.csv\""))), 2
|
||||||
|
)
|
||||||
|
helper.assertInvalidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; filename=\"data.csv\""))), 3
|
||||||
|
)
|
||||||
|
helper.assertInvalidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"fieldName\"; filename*=\"filename.jpg\""))), 4
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "filename=\"train.ogg\""))),
|
||||||
|
5, MediaFormat.OGG
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "some-form-data; filename=\"audio.flac\""))),
|
||||||
|
6, MediaFormat.FLAC
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.aiff\"; filename=\"audio.aiff\""))),
|
||||||
|
7, MediaFormat.AIFF
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"alien?\"; filename*=UTF-8''%CE%B1%CE%BB%CE%B9%CF%B5%CE%BD.m4a"))),
|
||||||
|
8, MediaFormat.M4A
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.mp3\"; filename=\"audio.opus\"; filename*=UTF-8''alien.opus"))),
|
||||||
|
9, MediaFormat.OPUS
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.mp3\"; filename=\"audio.opus\"; filename*=\"UTF-8''alien.opus\""))),
|
||||||
|
10, MediaFormat.OPUS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun retrieveMediaFormatFromContentTypeHeader() {
|
||||||
|
val streams = getIncompleteAudioStreams(12)
|
||||||
|
val wrapper = StreamInfoWrapper(streams, context)
|
||||||
|
val retrieveMediaFormat = { stream: AudioStream, response: Response ->
|
||||||
|
StreamInfoWrapper.retrieveMediaFormatFromContentTypeHeader(stream, wrapper, response)
|
||||||
|
}
|
||||||
|
val helper = AssertionHelper(streams, wrapper, retrieveMediaFormat)
|
||||||
|
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("content-length", "984501"))), 0)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "audio/xyz"))), 1)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "mp3"))), 2)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "mp3"))), 3)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "audio/mpeg"))), 4)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "audio/aif"))), 5)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "whatever"))), 6)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf()), 7)
|
||||||
|
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Type", "audio/flac"))), 8, MediaFormat.FLAC
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Type", "audio/wav"))), 9, MediaFormat.WAV
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Type", "audio/opus"))), 10, MediaFormat.OPUS
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Type", "audio/aiff"))), 11, MediaFormat.AIFF
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a list of video streams, in which their video only property mirrors the provided
|
* @return a list of video streams, in which their video only property mirrors the provided
|
||||||
* [videoOnly] vararg.
|
* [videoOnly] vararg.
|
||||||
*/
|
*/
|
||||||
private fun getVideoStreams(vararg videoOnly: Boolean) =
|
private fun getVideoStreams(vararg videoOnly: Boolean) =
|
||||||
StreamItemAdapter.StreamSizeWrapper(
|
StreamItemAdapter.StreamInfoWrapper(
|
||||||
videoOnly.map {
|
videoOnly.map {
|
||||||
VideoStream.Builder()
|
VideoStream.Builder()
|
||||||
.setId(Stream.ID_UNKNOWN)
|
.setId(Stream.ID_UNKNOWN)
|
||||||
@@ -161,6 +264,19 @@ class StreamItemAdapterTest {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun getIncompleteAudioStreams(size: Int): List<AudioStream> {
|
||||||
|
val list = ArrayList<AudioStream>(size)
|
||||||
|
for (i in 1..size) {
|
||||||
|
list.add(
|
||||||
|
AudioStream.Builder()
|
||||||
|
.setId(Stream.ID_UNKNOWN)
|
||||||
|
.setContent("https://example.com/$i", true)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the item at [position] in the [spinner] has the correct icon visibility when
|
* Checks whether the item at [position] in the [spinner] has the correct icon visibility when
|
||||||
* it is shown in normal mode (selected) and in dropdown mode (user is choosing one of a list).
|
* it is shown in normal mode (selected) and in dropdown mode (user is choosing one of a list).
|
||||||
@@ -196,11 +312,56 @@ class StreamItemAdapterTest {
|
|||||||
streams.forEachIndexed { index, stream ->
|
streams.forEachIndexed { index, stream ->
|
||||||
val secondaryStreamHelper: SecondaryStreamHelper<T>? = stream?.let {
|
val secondaryStreamHelper: SecondaryStreamHelper<T>? = stream?.let {
|
||||||
SecondaryStreamHelper(
|
SecondaryStreamHelper(
|
||||||
StreamItemAdapter.StreamSizeWrapper(streams, context),
|
StreamItemAdapter.StreamInfoWrapper(streams, context),
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
put(index, secondaryStreamHelper)
|
put(index, secondaryStreamHelper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getResponse(headers: Map<String, String>): Response {
|
||||||
|
val listHeaders = HashMap<String, List<String>>()
|
||||||
|
headers.forEach { entry ->
|
||||||
|
listHeaders[entry.key] = listOf(entry.value)
|
||||||
|
}
|
||||||
|
return Response(200, null, listHeaders, "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for assertion related to extractions of [MediaFormat]s.
|
||||||
|
*/
|
||||||
|
class AssertionHelper<T : Stream>(
|
||||||
|
private val streams: List<T>,
|
||||||
|
private val wrapper: StreamInfoWrapper<T>,
|
||||||
|
private val retrieveMediaFormat: (stream: T, response: Response) -> Boolean
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that an invalid response does not result in wrongly extracted [MediaFormat].
|
||||||
|
*/
|
||||||
|
fun assertInvalidResponse(
|
||||||
|
response: Response,
|
||||||
|
index: Int
|
||||||
|
) {
|
||||||
|
assertFalse(
|
||||||
|
"invalid header returns valid value", retrieveMediaFormat(streams[index], response)
|
||||||
|
)
|
||||||
|
assertNull("Media format extracted although stated otherwise", wrapper.getFormat(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that a valid response results in correctly extracted and handled [MediaFormat].
|
||||||
|
*/
|
||||||
|
fun assertValidResponse(
|
||||||
|
response: Response,
|
||||||
|
index: Int,
|
||||||
|
format: MediaFormat
|
||||||
|
) {
|
||||||
|
assertTrue(
|
||||||
|
"header was not recognized", retrieveMediaFormat(streams[index], response)
|
||||||
|
)
|
||||||
|
assertEquals("Wrong media format extracted", format, wrapper.getFormat(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.schabi.newpipe
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.facebook.stetho.Stetho
|
import com.facebook.stetho.Stetho
|
||||||
import com.facebook.stetho.okhttp3.StethoInterceptor
|
import com.facebook.stetho.okhttp3.StethoInterceptor
|
||||||
import leakcanary.AppWatcher
|
|
||||||
import leakcanary.LeakCanary
|
import leakcanary.LeakCanary
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader
|
import org.schabi.newpipe.extractor.downloader.Downloader
|
||||||
@@ -13,8 +12,6 @@ class DebugApp : App() {
|
|||||||
super.onCreate()
|
super.onCreate()
|
||||||
initStetho()
|
initStetho()
|
||||||
|
|
||||||
// Give each object 10 seconds to be GC'ed, before LeakCanary gets nosy on it
|
|
||||||
AppWatcher.config = AppWatcher.config.copy(watchDurationMillis = 10000)
|
|
||||||
LeakCanary.config = LeakCanary.config.copy(
|
LeakCanary.config = LeakCanary.config.copy(
|
||||||
dumpHeap = PreferenceManager
|
dumpHeap = PreferenceManager
|
||||||
.getDefaultSharedPreferences(this).getBoolean(
|
.getDefaultSharedPreferences(this).getBoolean(
|
||||||
|
|||||||
@@ -64,6 +64,9 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.browse.MediaBrowserService"/>
|
||||||
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -77,6 +80,11 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/settings" />
|
android:label="@string/settings" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".settings.SettingsV2Activity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/settings" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".about.AboutActivity"
|
android:name=".about.AboutActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
@@ -357,15 +365,17 @@
|
|||||||
<data android:host="eduvid.org" />
|
<data android:host="eduvid.org" />
|
||||||
<data android:host="framatube.org" />
|
<data android:host="framatube.org" />
|
||||||
<data android:host="media.assassinate-you.net" />
|
<data android:host="media.assassinate-you.net" />
|
||||||
|
<data android:host="media.fsfe.org" />
|
||||||
<data android:host="peertube.co.uk" />
|
<data android:host="peertube.co.uk" />
|
||||||
<data android:host="peertube.cpy.re" />
|
<data android:host="peertube.cpy.re" />
|
||||||
<data android:host="peertube.mastodon.host" />
|
|
||||||
<data android:host="peertube.fr" />
|
<data android:host="peertube.fr" />
|
||||||
<data android:host="tilvids.com" />
|
<data android:host="peertube.mastodon.host" />
|
||||||
<data android:host="video.ploud.fr" />
|
<data android:host="peertube.stream" />
|
||||||
<data android:host="video.lqdn.fr" />
|
|
||||||
<data android:host="skeptikon.fr" />
|
<data android:host="skeptikon.fr" />
|
||||||
<data android:host="media.fsfe.org" />
|
<data android:host="tilvids.com" />
|
||||||
|
<data android:host="video.lqdn.fr" />
|
||||||
|
<data android:host="video.ploud.fr" />
|
||||||
|
<data android:host="subscribeto.me" />
|
||||||
|
|
||||||
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
|
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
|
||||||
<data android:pathPrefix="/w/" /> <!-- short video URLs -->
|
<data android:pathPrefix="/w/" /> <!-- short video URLs -->
|
||||||
@@ -422,5 +432,10 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.samsung.android.multidisplay.keep_process_alive"
|
android:name="com.samsung.android.multidisplay.keep_process_alive"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
<!-- Android Auto -->
|
||||||
|
<meta-data android:name="com.google.android.gms.car.application"
|
||||||
|
android:resource="@xml/automotive_app_desc" />
|
||||||
|
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||||
|
android:resource="@mipmap/ic_launcher" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
127
app/src/main/assets/po_token.html
Normal file
127
app/src/main/assets/po_token.html
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"><head><title></title><script>
|
||||||
|
/**
|
||||||
|
* Factory method to create and load a BotGuardClient instance.
|
||||||
|
* @param options - Configuration options for the BotGuardClient.
|
||||||
|
* @returns A promise that resolves to a loaded BotGuardClient instance.
|
||||||
|
*/
|
||||||
|
function loadBotGuard(challengeData) {
|
||||||
|
this.vm = this[challengeData.globalName];
|
||||||
|
this.program = challengeData.program;
|
||||||
|
this.vmFunctions = {};
|
||||||
|
this.syncSnapshotFunction = null;
|
||||||
|
|
||||||
|
if (!this.vm)
|
||||||
|
throw new Error('[BotGuardClient]: VM not found in the global object');
|
||||||
|
|
||||||
|
if (!this.vm.a)
|
||||||
|
throw new Error('[BotGuardClient]: Could not load program');
|
||||||
|
|
||||||
|
const vmFunctionsCallback = function (
|
||||||
|
asyncSnapshotFunction,
|
||||||
|
shutdownFunction,
|
||||||
|
passEventFunction,
|
||||||
|
checkCameraFunction
|
||||||
|
) {
|
||||||
|
this.vmFunctions = {
|
||||||
|
asyncSnapshotFunction: asyncSnapshotFunction,
|
||||||
|
shutdownFunction: shutdownFunction,
|
||||||
|
passEventFunction: passEventFunction,
|
||||||
|
checkCameraFunction: checkCameraFunction
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
this.syncSnapshotFunction = this.vm.a(this.program, vmFunctionsCallback, true, this.userInteractionElement, function () {/** no-op */ }, [ [], [] ])[0]
|
||||||
|
|
||||||
|
// an asynchronous function runs in the background and it will eventually call
|
||||||
|
// `vmFunctionsCallback`, however we need to manually tell JavaScript to pass
|
||||||
|
// control to the things running in the background by interrupting this async
|
||||||
|
// function in any way, e.g. with a delay of 1ms. The loop is most probably not
|
||||||
|
// needed but is there just because.
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
i = 0
|
||||||
|
refreshIntervalId = setInterval(function () {
|
||||||
|
if (!!this.vmFunctions.asyncSnapshotFunction) {
|
||||||
|
resolve(this)
|
||||||
|
clearInterval(refreshIntervalId);
|
||||||
|
}
|
||||||
|
if (i >= 10000) {
|
||||||
|
reject("asyncSnapshotFunction is null even after 10 seconds")
|
||||||
|
clearInterval(refreshIntervalId);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}, 1);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a snapshot asynchronously.
|
||||||
|
* @returns The snapshot result.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const result = await botguard.snapshot({
|
||||||
|
* contentBinding: {
|
||||||
|
* c: "a=6&a2=10&b=SZWDwKVIuixOp7Y4euGTgwckbJA&c=1729143849&d=1&t=7200&c1a=1&c6a=1&c6b=1&hh=HrMb5mRWTyxGJphDr0nW2Oxonh0_wl2BDqWuLHyeKLo",
|
||||||
|
* e: "ENGAGEMENT_TYPE_VIDEO_LIKE",
|
||||||
|
* encryptedVideoId: "P-vC09ZJcnM"
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* console.log(result);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function snapshot(args) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
if (!this.vmFunctions.asyncSnapshotFunction)
|
||||||
|
return reject(new Error('[BotGuardClient]: Async snapshot function not found'));
|
||||||
|
|
||||||
|
this.vmFunctions.asyncSnapshotFunction(function (response) { resolve(response) }, [
|
||||||
|
args.contentBinding,
|
||||||
|
args.signedTimestamp,
|
||||||
|
args.webPoSignalOutput,
|
||||||
|
args.skipPrivacyBuffer
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runBotGuard(challengeData) {
|
||||||
|
const interpreterJavascript = challengeData.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue;
|
||||||
|
|
||||||
|
if (interpreterJavascript) {
|
||||||
|
new Function(interpreterJavascript)();
|
||||||
|
} else throw new Error('Could not load VM');
|
||||||
|
|
||||||
|
const webPoSignalOutput = [];
|
||||||
|
return loadBotGuard({
|
||||||
|
globalName: challengeData.globalName,
|
||||||
|
globalObj: this,
|
||||||
|
program: challengeData.program
|
||||||
|
}).then(function (botguard) {
|
||||||
|
return botguard.snapshot({ webPoSignalOutput: webPoSignalOutput })
|
||||||
|
}).then(function (botguardResponse) {
|
||||||
|
return { webPoSignalOutput: webPoSignalOutput, botguardResponse: botguardResponse }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function obtainPoToken(webPoSignalOutput, integrityToken, identifier) {
|
||||||
|
const getMinter = webPoSignalOutput[0];
|
||||||
|
|
||||||
|
if (!getMinter)
|
||||||
|
throw new Error('PMD:Undefined');
|
||||||
|
|
||||||
|
const mintCallback = getMinter(integrityToken);
|
||||||
|
|
||||||
|
if (!(mintCallback instanceof Function))
|
||||||
|
throw new Error('APF:Failed');
|
||||||
|
|
||||||
|
const result = mintCallback(identifier);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
throw new Error('YNJ:Undefined');
|
||||||
|
|
||||||
|
if (!(result instanceof Uint8Array))
|
||||||
|
throw new Error('ODM:Invalid');
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
</script></head><body></body></html>
|
||||||
@@ -25,6 +25,7 @@ import android.view.ViewGroup;
|
|||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.os.BundleCompat;
|
||||||
import androidx.lifecycle.Lifecycle;
|
import androidx.lifecycle.Lifecycle;
|
||||||
import androidx.viewpager.widget.PagerAdapter;
|
import androidx.viewpager.widget.PagerAdapter;
|
||||||
|
|
||||||
@@ -284,7 +285,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
Bundle state = null;
|
Bundle state = null;
|
||||||
if (!mSavedState.isEmpty()) {
|
if (!mSavedState.isEmpty()) {
|
||||||
state = new Bundle();
|
state = new Bundle();
|
||||||
state.putParcelableArray("states", mSavedState.toArray(new Fragment.SavedState[0]));
|
state.putParcelableArrayList("states", mSavedState);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < mFragments.size(); i++) {
|
for (int i = 0; i < mFragments.size(); i++) {
|
||||||
final Fragment f = mFragments.get(i);
|
final Fragment f = mFragments.get(i);
|
||||||
@@ -311,13 +312,12 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
if (state != null) {
|
if (state != null) {
|
||||||
final Bundle bundle = (Bundle) state;
|
final Bundle bundle = (Bundle) state;
|
||||||
bundle.setClassLoader(loader);
|
bundle.setClassLoader(loader);
|
||||||
final Parcelable[] fss = bundle.getParcelableArray("states");
|
final var states = BundleCompat.getParcelableArrayList(bundle, "states",
|
||||||
|
Fragment.SavedState.class);
|
||||||
mSavedState.clear();
|
mSavedState.clear();
|
||||||
mFragments.clear();
|
mFragments.clear();
|
||||||
if (fss != null) {
|
if (states != null) {
|
||||||
for (final Parcelable parcelable : fss) {
|
mSavedState.addAll(states);
|
||||||
mSavedState.add((Fragment.SavedState) parcelable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
final Iterable<String> keys = bundle.keySet();
|
final Iterable<String> keys = bundle.keySet();
|
||||||
for (final String key : keys) {
|
for (final String key : keys) {
|
||||||
|
|||||||
@@ -1,255 +0,0 @@
|
|||||||
package org.schabi.newpipe;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.core.app.NotificationChannelCompat;
|
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
|
||||||
import androidx.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import com.jakewharton.processphoenix.ProcessPhoenix;
|
|
||||||
|
|
||||||
import org.acra.ACRA;
|
|
||||||
import org.acra.config.CoreConfigurationBuilder;
|
|
||||||
import org.schabi.newpipe.error.ReCaptchaActivity;
|
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
|
||||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
|
||||||
import org.schabi.newpipe.util.Localization;
|
|
||||||
import org.schabi.newpipe.util.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
|
||||||
import org.schabi.newpipe.util.StateSaver;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InterruptedIOException;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import io.reactivex.rxjava3.exceptions.CompositeException;
|
|
||||||
import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
|
|
||||||
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
|
|
||||||
import io.reactivex.rxjava3.exceptions.UndeliverableException;
|
|
||||||
import io.reactivex.rxjava3.functions.Consumer;
|
|
||||||
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org>
|
|
||||||
* App.java is part of NewPipe.
|
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class App extends Application {
|
|
||||||
public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
|
|
||||||
private static final String TAG = App.class.toString();
|
|
||||||
private static App app;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static App getApp() {
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void attachBaseContext(final Context base) {
|
|
||||||
super.attachBaseContext(base);
|
|
||||||
initACRA();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
|
|
||||||
app = this;
|
|
||||||
|
|
||||||
if (ProcessPhoenix.isPhoenixProcess(this)) {
|
|
||||||
Log.i(TAG, "This is a phoenix process! "
|
|
||||||
+ "Aborting initialization of App[onCreate]");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize settings first because others inits can use its values
|
|
||||||
NewPipeSettings.initSettings(this);
|
|
||||||
|
|
||||||
NewPipe.init(getDownloader(),
|
|
||||||
Localization.getPreferredLocalization(this),
|
|
||||||
Localization.getPreferredContentCountry(this));
|
|
||||||
Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext()));
|
|
||||||
|
|
||||||
StateSaver.init(this);
|
|
||||||
initNotificationChannels();
|
|
||||||
|
|
||||||
ServiceHelper.initServices(this);
|
|
||||||
|
|
||||||
// Initialize image loader
|
|
||||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
PicassoHelper.init(this);
|
|
||||||
PicassoHelper.setShouldLoadImages(
|
|
||||||
prefs.getBoolean(getString(R.string.download_thumbnail_key), true));
|
|
||||||
PicassoHelper.setIndicatorsEnabled(MainActivity.DEBUG
|
|
||||||
&& prefs.getBoolean(getString(R.string.show_image_indicators_key), false));
|
|
||||||
|
|
||||||
configureRxJavaErrorHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTerminate() {
|
|
||||||
super.onTerminate();
|
|
||||||
PicassoHelper.terminate();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Downloader getDownloader() {
|
|
||||||
final DownloaderImpl downloader = DownloaderImpl.init(null);
|
|
||||||
setCookiesToDownloader(downloader);
|
|
||||||
return downloader;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setCookiesToDownloader(final DownloaderImpl downloader) {
|
|
||||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
|
|
||||||
getApplicationContext());
|
|
||||||
final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
|
|
||||||
downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, null));
|
|
||||||
downloader.updateYoutubeRestrictedModeCookies(getApplicationContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void configureRxJavaErrorHandler() {
|
|
||||||
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
|
|
||||||
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void accept(@NonNull final Throwable throwable) {
|
|
||||||
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : "
|
|
||||||
+ "throwable = [" + throwable.getClass().getName() + "]");
|
|
||||||
|
|
||||||
final Throwable actualThrowable;
|
|
||||||
if (throwable instanceof UndeliverableException) {
|
|
||||||
// As UndeliverableException is a wrapper,
|
|
||||||
// get the cause of it to get the "real" exception
|
|
||||||
actualThrowable = Objects.requireNonNull(throwable.getCause());
|
|
||||||
} else {
|
|
||||||
actualThrowable = throwable;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Throwable> errors;
|
|
||||||
if (actualThrowable instanceof CompositeException) {
|
|
||||||
errors = ((CompositeException) actualThrowable).getExceptions();
|
|
||||||
} else {
|
|
||||||
errors = List.of(actualThrowable);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final Throwable error : errors) {
|
|
||||||
if (isThrowableIgnored(error)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isThrowableCritical(error)) {
|
|
||||||
reportException(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
|
|
||||||
// When exception is not reported, log it
|
|
||||||
if (isDisposedRxExceptionsReported()) {
|
|
||||||
reportException(actualThrowable);
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", actualThrowable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
|
|
||||||
// Don't crash the application over a simple network problem
|
|
||||||
return ExceptionUtils.hasAssignableCause(throwable,
|
|
||||||
// network api cancellation
|
|
||||||
IOException.class, SocketException.class,
|
|
||||||
// blocking code disposed
|
|
||||||
InterruptedException.class, InterruptedIOException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isThrowableCritical(@NonNull final Throwable throwable) {
|
|
||||||
// Though these exceptions cannot be ignored
|
|
||||||
return ExceptionUtils.hasAssignableCause(throwable,
|
|
||||||
NullPointerException.class, IllegalArgumentException.class, // bug in app
|
|
||||||
OnErrorNotImplementedException.class, MissingBackpressureException.class,
|
|
||||||
IllegalStateException.class); // bug in operator
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reportException(@NonNull final Throwable throwable) {
|
|
||||||
// Throw uncaught exception that will trigger the report system
|
|
||||||
Thread.currentThread().getUncaughtExceptionHandler()
|
|
||||||
.uncaughtException(Thread.currentThread(), throwable);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called in {@link #attachBaseContext(Context)} after calling the {@code super} method.
|
|
||||||
* Should be overridden if MultiDex is enabled, since it has to be initialized before ACRA.
|
|
||||||
*/
|
|
||||||
protected void initACRA() {
|
|
||||||
if (ACRA.isACRASenderServiceProcess()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final CoreConfigurationBuilder acraConfig = new CoreConfigurationBuilder()
|
|
||||||
.withBuildConfigClass(BuildConfig.class);
|
|
||||||
ACRA.init(this, acraConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initNotificationChannels() {
|
|
||||||
// Keep the importance below DEFAULT to avoid making noise on every notification update for
|
|
||||||
// the main and update channels
|
|
||||||
final List<NotificationChannelCompat> notificationChannelCompats = List.of(
|
|
||||||
new NotificationChannelCompat.Builder(getString(R.string.notification_channel_id),
|
|
||||||
NotificationManagerCompat.IMPORTANCE_LOW)
|
|
||||||
.setName(getString(R.string.notification_channel_name))
|
|
||||||
.setDescription(getString(R.string.notification_channel_description))
|
|
||||||
.build(),
|
|
||||||
new NotificationChannelCompat
|
|
||||||
.Builder(getString(R.string.app_update_notification_channel_id),
|
|
||||||
NotificationManagerCompat.IMPORTANCE_LOW)
|
|
||||||
.setName(getString(R.string.app_update_notification_channel_name))
|
|
||||||
.setDescription(
|
|
||||||
getString(R.string.app_update_notification_channel_description))
|
|
||||||
.build(),
|
|
||||||
new NotificationChannelCompat.Builder(getString(R.string.hash_channel_id),
|
|
||||||
NotificationManagerCompat.IMPORTANCE_HIGH)
|
|
||||||
.setName(getString(R.string.hash_channel_name))
|
|
||||||
.setDescription(getString(R.string.hash_channel_description))
|
|
||||||
.build(),
|
|
||||||
new NotificationChannelCompat.Builder(getString(R.string.error_report_channel_id),
|
|
||||||
NotificationManagerCompat.IMPORTANCE_LOW)
|
|
||||||
.setName(getString(R.string.error_report_channel_name))
|
|
||||||
.setDescription(getString(R.string.error_report_channel_description))
|
|
||||||
.build(),
|
|
||||||
new NotificationChannelCompat
|
|
||||||
.Builder(getString(R.string.streams_notification_channel_id),
|
|
||||||
NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
|
||||||
.setName(getString(R.string.streams_notification_channel_name))
|
|
||||||
.setDescription(
|
|
||||||
getString(R.string.streams_notification_channel_description))
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
|
|
||||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
|
||||||
notificationManager.createNotificationChannelsCompat(notificationChannelCompats);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isDisposedRxExceptionsReported() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
290
app/src/main/java/org/schabi/newpipe/App.kt
Normal file
290
app/src/main/java/org/schabi/newpipe/App.kt
Normal file
File diff suppressed because it is too large
Load Diff
22
app/src/main/java/org/schabi/newpipe/AppModule.kt
Normal file
22
app/src/main/java/org/schabi/newpipe/AppModule.kt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package org.schabi.newpipe
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
class AppModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providesSharedPreference(@ApplicationContext context: Context): SharedPreferences {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,9 +10,9 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
import icepick.Icepick;
|
import com.evernote.android.state.State;
|
||||||
import icepick.State;
|
import com.livefront.bridge.Bridge;
|
||||||
import leakcanary.AppWatcher;
|
|
||||||
|
|
||||||
public abstract class BaseFragment extends Fragment {
|
public abstract class BaseFragment extends Fragment {
|
||||||
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
||||||
@@ -49,7 +49,7 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
+ "savedInstanceState = [" + savedInstanceState + "]");
|
+ "savedInstanceState = [" + savedInstanceState + "]");
|
||||||
}
|
}
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Icepick.restoreInstanceState(this, savedInstanceState);
|
Bridge.restoreInstanceState(this, savedInstanceState);
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
onRestoreInstanceState(savedInstanceState);
|
onRestoreInstanceState(savedInstanceState);
|
||||||
}
|
}
|
||||||
@@ -71,26 +71,39 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(@NonNull final Bundle outState) {
|
public void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
Icepick.saveInstanceState(this, outState);
|
Bridge.saveInstanceState(this, outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
|
|
||||||
AppWatcher.INSTANCE.getObjectWatcher().watch(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called in {@link #onViewCreated(View, Bundle)} to initialize the views.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* {@link #initListeners()} is called after this method to initialize the corresponding
|
||||||
|
* listeners.
|
||||||
|
* </p>
|
||||||
|
* @param rootView The inflated view for this fragment
|
||||||
|
* (provided by {@link #onViewCreated(View, Bundle)})
|
||||||
|
* @param savedInstanceState The saved state of this fragment
|
||||||
|
* (provided by {@link #onViewCreated(View, Bundle)})
|
||||||
|
*/
|
||||||
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the listeners for this fragment.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This method is called after {@link #initViews(View, Bundle)}
|
||||||
|
* in {@link #onViewCreated(View, Bundle)}.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
protected void initListeners() {
|
protected void initListeners() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,9 +121,20 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the root fragment by looping through all of the parent fragments. The root fragment
|
||||||
|
* is supposed to be {@link org.schabi.newpipe.fragments.MainFragment}, and is the fragment that
|
||||||
|
* handles keeping the backstack of opened fragments in NewPipe, and also the player bottom
|
||||||
|
* sheet. This function therefore returns the fragment manager of said fragment.
|
||||||
|
*
|
||||||
|
* @return the fragment manager of the root fragment, i.e.
|
||||||
|
* {@link org.schabi.newpipe.fragments.MainFragment}
|
||||||
|
*/
|
||||||
protected FragmentManager getFM() {
|
protected FragmentManager getFM() {
|
||||||
return getParentFragment() == null
|
Fragment current = this;
|
||||||
? getFragmentManager()
|
while (current.getParentFragment() != null) {
|
||||||
: getParentFragment().getFragmentManager();
|
current = current.getParentFragment();
|
||||||
|
}
|
||||||
|
return current.getFragmentManager();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import okhttp3.ResponseBody;
|
|||||||
|
|
||||||
public final class DownloaderImpl extends Downloader {
|
public final class DownloaderImpl extends Downloader {
|
||||||
public static final String USER_AGENT =
|
public static final String USER_AGENT =
|
||||||
"Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0";
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0";
|
||||||
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY =
|
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY =
|
||||||
"youtube_restricted_mode_key";
|
"youtube_restricted_mode_key";
|
||||||
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
|
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
|
||||||
@@ -48,6 +48,11 @@ public final class DownloaderImpl extends Downloader {
|
|||||||
this.mCookies = new HashMap<>();
|
this.mCookies = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public OkHttpClient getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It's recommended to call exactly once in the entire lifetime of the application.
|
* It's recommended to call exactly once in the entire lifetime of the application.
|
||||||
*
|
*
|
||||||
@@ -137,7 +142,8 @@ public final class DownloaderImpl extends Downloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
|
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
|
||||||
.method(httpMethod, requestBody).url(url)
|
.method(httpMethod, requestBody)
|
||||||
|
.url(url)
|
||||||
.addHeader("User-Agent", USER_AGENT);
|
.addHeader("User-Agent", USER_AGENT);
|
||||||
|
|
||||||
final String cookies = getCookies(url);
|
final String cookies = getCookies(url);
|
||||||
@@ -145,38 +151,33 @@ public final class DownloaderImpl extends Downloader {
|
|||||||
requestBuilder.addHeader("Cookie", cookies);
|
requestBuilder.addHeader("Cookie", cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Map.Entry<String, List<String>> pair : headers.entrySet()) {
|
headers.forEach((headerName, headerValueList) -> {
|
||||||
final String headerName = pair.getKey();
|
requestBuilder.removeHeader(headerName);
|
||||||
final List<String> headerValueList = pair.getValue();
|
headerValueList.forEach(headerValue ->
|
||||||
|
requestBuilder.addHeader(headerName, headerValue));
|
||||||
|
});
|
||||||
|
|
||||||
if (headerValueList.size() > 1) {
|
try (
|
||||||
requestBuilder.removeHeader(headerName);
|
okhttp3.Response response = client.newCall(requestBuilder.build()).execute()
|
||||||
for (final String headerValue : headerValueList) {
|
) {
|
||||||
requestBuilder.addHeader(headerName, headerValue);
|
if (response.code() == 429) {
|
||||||
}
|
throw new ReCaptchaException("reCaptcha Challenge requested", url);
|
||||||
} else if (headerValueList.size() == 1) {
|
|
||||||
requestBuilder.header(headerName, headerValueList.get(0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String responseBodyToReturn = null;
|
||||||
|
try (ResponseBody body = response.body()) {
|
||||||
|
if (body != null) {
|
||||||
|
responseBodyToReturn = body.string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final String latestUrl = response.request().url().toString();
|
||||||
|
return new Response(
|
||||||
|
response.code(),
|
||||||
|
response.message(),
|
||||||
|
response.headers().toMultimap(),
|
||||||
|
responseBodyToReturn,
|
||||||
|
latestUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
final okhttp3.Response response = client.newCall(requestBuilder.build()).execute();
|
|
||||||
|
|
||||||
if (response.code() == 429) {
|
|
||||||
response.close();
|
|
||||||
|
|
||||||
throw new ReCaptchaException("reCaptcha Challenge requested", url);
|
|
||||||
}
|
|
||||||
|
|
||||||
final ResponseBody body = response.body();
|
|
||||||
String responseBodyToReturn = null;
|
|
||||||
|
|
||||||
if (body != null) {
|
|
||||||
responseBodyToReturn = body.string();
|
|
||||||
}
|
|
||||||
|
|
||||||
final String latestUrl = response.request().url().toString();
|
|
||||||
return new Response(response.code(), response.message(), response.headers().toMultimap(),
|
|
||||||
responseBodyToReturn, latestUrl);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user