mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-09-21 22:20:50 +02:00
Compare commits
407 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
48f966e7db | ||
![]() |
d8746dc592 | ||
![]() |
f24fab0fa2 | ||
![]() |
261620fd13 | ||
![]() |
9af6effad9 | ||
![]() |
92602916dd | ||
![]() |
6c7b90e1c3 | ||
![]() |
d8e57144f7 | ||
![]() |
e69c45a246 | ||
![]() |
3f87a6d714 | ||
![]() |
841124b1f3 | ||
![]() |
257a878ef4 | ||
![]() |
07d3f82912 | ||
![]() |
7d3eb4f5a6 | ||
![]() |
0efcc55373 | ||
![]() |
eafceb8a6c | ||
![]() |
5ac8318e71 | ||
![]() |
7981f9dc91 | ||
![]() |
a9bf04cbc6 | ||
![]() |
4b5591d884 | ||
![]() |
c08197f025 | ||
![]() |
5d22eabfa3 | ||
![]() |
c3a38e384a | ||
![]() |
0d3dd94559 | ||
![]() |
9cdaa37519 | ||
![]() |
202a319d69 | ||
![]() |
198384b2ed | ||
![]() |
9d8b070f1e | ||
![]() |
28ba2d5008 | ||
![]() |
37ddd63d27 | ||
![]() |
d7f8b8c1e0 | ||
![]() |
9e70c5bbea | ||
![]() |
4dd572063e | ||
![]() |
db9cf95648 | ||
![]() |
df6bae4712 | ||
![]() |
56cb8209b8 | ||
![]() |
aba9c2e113 | ||
![]() |
3d25008739 | ||
![]() |
9437f057d0 | ||
![]() |
917d6089a7 | ||
![]() |
f19e99e3ad | ||
![]() |
2d0fb05fa6 | ||
![]() |
c617f2dfad | ||
![]() |
747c5fc89a | ||
![]() |
4f662ef203 | ||
![]() |
eb274ad88f | ||
![]() |
bd7b41be9b | ||
![]() |
076720bfff | ||
![]() |
2aadae407e | ||
![]() |
b16bb07774 | ||
![]() |
81dd083388 | ||
![]() |
92d4cef1e2 | ||
![]() |
d2cd79dcf5 | ||
![]() |
0ef8b391e4 | ||
![]() |
0fbd105977 | ||
![]() |
5c23af541e | ||
![]() |
db021ff78b | ||
![]() |
31294c6f54 | ||
![]() |
03d3495759 | ||
![]() |
4644e105a8 | ||
![]() |
af5002508d | ||
![]() |
9416d2b9c7 | ||
![]() |
f60f5d5a6c | ||
![]() |
26d00f87a8 | ||
![]() |
6c33ea423c | ||
![]() |
ad354aca4e | ||
![]() |
ce6a5ebf0a | ||
![]() |
d66ef38e8d | ||
![]() |
1b6c49f621 | ||
![]() |
0262c83815 | ||
![]() |
c212934130 | ||
![]() |
ac30e47e59 | ||
![]() |
d640057453 | ||
![]() |
0cea136d4d | ||
![]() |
9b3d93e734 | ||
![]() |
ca3782ac62 | ||
![]() |
dc9ffb7fd5 | ||
![]() |
b21b3a55fe | ||
![]() |
e62bd85a7a | ||
![]() |
fed221b008 | ||
![]() |
935298d159 | ||
![]() |
909919250c | ||
![]() |
d24f278f7c | ||
![]() |
ca408a495c | ||
![]() |
ee99719137 | ||
![]() |
5fb0729eae | ||
![]() |
a1db3187cd | ||
![]() |
488a3cf1ff | ||
![]() |
1253a620a4 | ||
![]() |
64547fc4a7 | ||
![]() |
d643d140cf | ||
![]() |
222e2e3242 | ||
![]() |
ea7b6daf26 | ||
![]() |
14cfab495d | ||
![]() |
ed4b4a1a3c | ||
![]() |
511c552188 | ||
![]() |
8c650219eb | ||
![]() |
189b779abe | ||
![]() |
c26074b184 | ||
![]() |
6c70779ff1 | ||
![]() |
73897481ac | ||
![]() |
a62c523ed5 | ||
![]() |
0f46f57b52 | ||
![]() |
bc4d65a20c | ||
![]() |
f1e9a44ad9 | ||
![]() |
61972c4719 | ||
![]() |
9183c9c220 | ||
![]() |
1ce59f13ff | ||
![]() |
1bac5db6d5 | ||
![]() |
58f2c4d8e6 | ||
![]() |
2c2c61b2fc | ||
![]() |
98b04a4a83 | ||
![]() |
3ac64c44f4 | ||
![]() |
3375847302 | ||
![]() |
14043c86f5 | ||
![]() |
030117780a | ||
![]() |
971c9fe5a1 | ||
![]() |
77c6d3d576 | ||
![]() |
6edbfe2a6f | ||
![]() |
d0a3125df4 | ||
![]() |
d8c76d4c21 | ||
![]() |
a1cc0897df | ||
![]() |
e88a90f242 | ||
![]() |
1ae54f6f8c | ||
![]() |
8a4cb484fa | ||
![]() |
93defbf341 | ||
![]() |
ca1089da9a | ||
![]() |
dd07c7db0e | ||
![]() |
dfe932e37a | ||
![]() |
568cf9c259 | ||
![]() |
b4ef20e785 | ||
![]() |
3a56dabf42 | ||
![]() |
3ac4899b96 | ||
![]() |
b7b228d9ce | ||
![]() |
45339fd6d2 | ||
![]() |
1856a5a82f | ||
![]() |
b2aa703b62 | ||
![]() |
b31897d65d | ||
![]() |
a92f776ebe | ||
![]() |
b84ed675f5 | ||
![]() |
21eb98a52c | ||
![]() |
75dd8d492b | ||
![]() |
338893ded4 | ||
![]() |
b459900040 | ||
![]() |
d31300f4f9 | ||
![]() |
f2285c0b19 | ||
![]() |
684cb81974 | ||
![]() |
9db272f30e | ||
![]() |
c4a5e8dc86 | ||
![]() |
df4dd0122f | ||
![]() |
8fed18b2ac | ||
![]() |
ecabbb57e6 | ||
![]() |
8d1d4092aa | ||
![]() |
6185c4ddcf | ||
![]() |
3ccbbccd10 | ||
![]() |
30e0ccc77b | ||
![]() |
50f7b72b09 | ||
![]() |
a5828c7949 | ||
![]() |
22a5e72470 | ||
![]() |
5d14dca818 | ||
![]() |
f97cb821f8 | ||
![]() |
d02dce5562 | ||
![]() |
c6bd42843b | ||
![]() |
43e4fbfcd0 | ||
![]() |
ac8430cbba | ||
![]() |
86d619be30 | ||
![]() |
fec7672598 | ||
![]() |
44d2744a8c | ||
![]() |
22bfbe96c8 | ||
![]() |
c5d1271894 | ||
![]() |
050d3058f9 | ||
![]() |
2ae99afa21 | ||
![]() |
b094c9190f | ||
![]() |
1a9e1e6f7c | ||
![]() |
8c94926693 | ||
![]() |
880a176e65 | ||
![]() |
8de1b5f3d9 | ||
![]() |
178f4546fc | ||
![]() |
7564a49344 | ||
![]() |
6c5c42c2b5 | ||
![]() |
b1653b359e | ||
![]() |
11098afab5 | ||
![]() |
a64051e0f1 | ||
![]() |
db8ac4a9ae | ||
![]() |
890474a635 | ||
![]() |
c7941a85ed | ||
![]() |
fab5f26e09 | ||
![]() |
a39b10eee9 | ||
![]() |
97b933a990 | ||
![]() |
4926e90514 | ||
![]() |
6dca975844 | ||
![]() |
e31743770a | ||
![]() |
d8aab62d75 | ||
![]() |
f90603a18f | ||
![]() |
5fce9facbe | ||
![]() |
32b48d5cdb | ||
![]() |
3e4f0d682b | ||
![]() |
98449ddfe0 | ||
![]() |
26040d4e9f | ||
![]() |
83cea5e1ee | ||
![]() |
3e73a5dbd3 | ||
![]() |
dd424a4cb3 | ||
![]() |
0c79f5cce3 | ||
![]() |
794c3703e5 | ||
![]() |
b674006fcc | ||
![]() |
559c397b2f | ||
![]() |
646698f1ed | ||
![]() |
c9b938ae55 | ||
![]() |
990c220fa0 | ||
![]() |
38641d4edf | ||
![]() |
450072bd23 | ||
![]() |
25eb93fae0 | ||
![]() |
bcf6f60571 | ||
![]() |
2076b8f1d7 | ||
![]() |
f19cfb75e6 | ||
![]() |
ceaacc771d | ||
![]() |
48067e3285 | ||
![]() |
003855a94c | ||
![]() |
3599ab3caf | ||
![]() |
e7a26b436d | ||
![]() |
ab94bc18dc | ||
![]() |
36e91ea155 | ||
![]() |
6035be9ce6 | ||
![]() |
67499bdc65 | ||
![]() |
222c8fdb62 | ||
![]() |
9d648bad51 | ||
![]() |
1a62b9a161 | ||
![]() |
33021e8fe0 | ||
![]() |
5a2ae4c3e3 | ||
![]() |
9eaef84cc8 | ||
![]() |
fcc4d655f5 | ||
![]() |
8d0ac4f5f0 | ||
![]() |
d56cf003f8 | ||
![]() |
34d0c0b1ba | ||
![]() |
9bbbffbe7a | ||
![]() |
ccd0f7d9cc | ||
![]() |
ccb9bceecc | ||
![]() |
c1a67ff1f8 | ||
![]() |
bfda8dcc02 | ||
![]() |
8746e7c9ad | ||
![]() |
e2aa36d083 | ||
![]() |
ff90f257cc | ||
![]() |
9b84046865 | ||
![]() |
51434a39f8 | ||
![]() |
3e2031be7c | ||
![]() |
ea4e8805b7 | ||
![]() |
f62bfeac08 | ||
![]() |
b9042c37f9 | ||
![]() |
49a58dcab1 | ||
![]() |
e2acbeddc2 | ||
![]() |
449b17d830 | ||
![]() |
7ed460ce02 | ||
![]() |
9f4a7e664f | ||
![]() |
38eb75b13f | ||
![]() |
0ad56874b4 | ||
![]() |
c1168693fa | ||
![]() |
feb8c27f1f | ||
![]() |
c20ebd66e5 | ||
![]() |
4d78d530dc | ||
![]() |
22b20c15de | ||
![]() |
c69b224c65 | ||
![]() |
805d328d6c | ||
![]() |
edcb692f78 | ||
![]() |
f33586f062 | ||
![]() |
b8293f134d | ||
![]() |
7e9bcff0f3 | ||
![]() |
eba3b32708 | ||
![]() |
d61bf26e17 | ||
![]() |
2e662b5745 | ||
![]() |
9714fa369b | ||
![]() |
0e8acba08e | ||
![]() |
a18e588e55 | ||
![]() |
bf55e3c0cc | ||
![]() |
486d114e64 | ||
![]() |
ac5c060a98 | ||
![]() |
86a9d197cb | ||
![]() |
e911dbb9d4 | ||
![]() |
e26d123f67 | ||
![]() |
70aac81900 | ||
![]() |
28e78d98f6 | ||
![]() |
fabc5ae032 | ||
![]() |
e40882455d | ||
![]() |
f1483e8c8e | ||
![]() |
bd40e2c3ff | ||
![]() |
9479498713 | ||
![]() |
6bd8523ec2 | ||
![]() |
22e3cddd91 | ||
![]() |
c864b15c34 | ||
![]() |
069654c5f9 | ||
![]() |
f3d4d4747a | ||
![]() |
5bbb0cd666 | ||
![]() |
7b5ba3bdc2 | ||
![]() |
5ac17ee6b8 | ||
![]() |
d647555e3a | ||
![]() |
26e22f97ee | ||
![]() |
84976a65e0 | ||
![]() |
fef9d541ed | ||
![]() |
ad5535af81 | ||
![]() |
cdcfb4ffce | ||
![]() |
5403ac8893 | ||
![]() |
9b26457781 | ||
![]() |
939cc56951 | ||
![]() |
23309e6fdf | ||
![]() |
3f60c961d9 | ||
![]() |
9e7f07e196 | ||
![]() |
eda4439ee8 | ||
![]() |
ad02558ade | ||
![]() |
0d1901cfe5 | ||
![]() |
ce493d1ae2 | ||
![]() |
09312ecd1d | ||
![]() |
2f274d5f52 | ||
![]() |
6dbf226365 | ||
![]() |
6401b5f54d | ||
![]() |
ac2dd81d39 | ||
![]() |
6784522195 | ||
![]() |
f42d077f30 | ||
![]() |
eaca47ebc6 | ||
![]() |
4d57223847 | ||
![]() |
d9ab96236b | ||
![]() |
44b2e62eef | ||
![]() |
7d5d4df761 | ||
![]() |
da7716db60 | ||
![]() |
862c5d342b | ||
![]() |
6167d5dbfc | ||
![]() |
2856fb029f | ||
![]() |
8fb945312a | ||
![]() |
25d6806ebd | ||
![]() |
51070d9afd | ||
![]() |
1048caa496 | ||
![]() |
0cd7ac05aa | ||
![]() |
2793c42d91 | ||
![]() |
2eaa7288e4 | ||
![]() |
f9341bea79 | ||
![]() |
0df8d13020 | ||
![]() |
d27622de1e | ||
![]() |
47c3da131c | ||
![]() |
3a608bb582 | ||
![]() |
64f9228ee3 | ||
![]() |
c926482b3c | ||
![]() |
9562972c42 | ||
![]() |
b462b7fcc4 | ||
![]() |
b34f9d7fd3 | ||
![]() |
57732c3e5f | ||
![]() |
eb1f56488f | ||
![]() |
5825843f68 | ||
![]() |
dd56a5d869 | ||
![]() |
86f82c0e61 | ||
![]() |
8ae45240b2 | ||
![]() |
6d845af7f1 | ||
![]() |
733ebb8caf | ||
![]() |
695a194467 | ||
![]() |
8a56257ade | ||
![]() |
45fea983b9 | ||
![]() |
642b499e70 | ||
![]() |
5c5575bb72 | ||
![]() |
4d5dc0d39c | ||
![]() |
8589ee14ff | ||
![]() |
1727387c9c | ||
![]() |
e1146e4655 | ||
![]() |
c29b0645bd | ||
![]() |
96dac0f979 | ||
![]() |
fda9b59129 | ||
![]() |
fa5896ee5b | ||
![]() |
3c6d27b504 | ||
![]() |
6ef25eb861 | ||
![]() |
ec28e972bb | ||
![]() |
d1a9033525 | ||
![]() |
2d5bc3ada8 | ||
![]() |
506ffb9885 | ||
![]() |
8ef702fa07 | ||
![]() |
9fc38b5bb8 | ||
![]() |
907e5a10d6 | ||
![]() |
54ac5e8940 | ||
![]() |
e2341363d4 | ||
![]() |
f1aa3d8c90 | ||
![]() |
cf3e53eb71 | ||
![]() |
b8865e925d | ||
![]() |
2e9a860aaa | ||
![]() |
e4bef056e6 | ||
![]() |
803b8eab28 | ||
![]() |
08bbfc50ee | ||
![]() |
4e6722f201 | ||
![]() |
a29e2116a7 | ||
![]() |
515be677a9 | ||
![]() |
d694c5f511 | ||
![]() |
66c753f3a3 | ||
![]() |
7047b62442 | ||
![]() |
5cc73555cd | ||
![]() |
219922cd82 | ||
![]() |
7124d9bca5 | ||
![]() |
08127e5806 | ||
![]() |
6417bd91ef | ||
![]() |
cde5f7d12e | ||
![]() |
395c9587b6 | ||
![]() |
17197ad670 | ||
![]() |
910c10f554 | ||
![]() |
04e974b326 | ||
![]() |
e7abeb5ad9 | ||
![]() |
2a18eacf62 | ||
![]() |
af42e32ae6 | ||
![]() |
12b93d6637 | ||
![]() |
930c971035 | ||
![]() |
06f20c66f8 | ||
![]() |
7c875a8541 | ||
![]() |
f85e19c75d | ||
![]() |
5e2aa51627 | ||
![]() |
75a44fb30a |
46
.github/CONTRIBUTING.md
vendored
46
.github/CONTRIBUTING.md
vendored
@@ -5,44 +5,64 @@ PLEASE READ THESE GUIDELINES CAREFULLY BEFORE ANY CONTRIBUTION!
|
||||
|
||||
## Crash reporting
|
||||
|
||||
Do not report crashes in the GitHub issue tracker. NewPipe has an automated crash report system that will ask you to send a report via e-mail when a crash occurs. This contains all the data we need for debugging, and allows you to even add a comment to it. You'll see exactly what is sent, the system is 100% transparent.
|
||||
Do not report crashes in the GitHub issue tracker. NewPipe has an automated crash report system that will ask you to
|
||||
send a report via e-mail when a crash occurs. This contains all the data we need for debugging, and allows you to even
|
||||
add a comment to it. You'll see exactly what is sent, the system is 100% transparent.
|
||||
|
||||
## Issue reporting/feature requests
|
||||
|
||||
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature hasn't been reported/requested before
|
||||
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature
|
||||
hasn't been reported/requested before
|
||||
* Check whether your issue/feature is already fixed/implemented
|
||||
* Check if the issue still exists in the latest release/beta version
|
||||
* If you are an Android/Java developer, you are always welcome to fix/implement an issue/a feature yourself. PRs welcome!
|
||||
* We use English for development. Issues in other languages will be closed and ignored.
|
||||
* Please only add *one* issue at a time. Do not put multiple issues into one thread.
|
||||
* When reporting a bug please give us a context, and a description how to reproduce it.
|
||||
* Issues that only contain a generated bug report, but no describtion might be closed.
|
||||
* Issues that only contain a generated bug report, but no description might be closed.
|
||||
|
||||
## Bug Fixing
|
||||
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request, register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information.
|
||||
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to
|
||||
tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request,
|
||||
register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information.
|
||||
|
||||
## Translation
|
||||
|
||||
* NewPipe can be translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there with your GitHub account.
|
||||
* NewPipe can be translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there
|
||||
with your GitHub account.
|
||||
|
||||
## Code contribution
|
||||
|
||||
* Stick to NewPipe's style conventions (well, just look the other code and then do it the same way :))
|
||||
* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google libraries.
|
||||
* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google
|
||||
libraries.
|
||||
* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy)
|
||||
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might not be considered, GitHub is the primary platform. (This only affects you if you are a member of TeamNewPipe)
|
||||
* When submitting changes, you confirm that your code is licensed under the terms of the [GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR description. Untested code will **not** be merged!
|
||||
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You
|
||||
may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might
|
||||
not be considered, GitHub is the primary platform. (This only affects you if you are a member of TeamNewPipe)
|
||||
* When submitting changes, you confirm that your code is licensed under the terms of the
|
||||
[GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR
|
||||
description. Untested code will **not** be merged!
|
||||
* Try to figure out yourself why builds on our CI fail.
|
||||
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job, but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the maintainers' jobs way easier.
|
||||
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again about submission, or clearly state that in the description of your PR.
|
||||
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job,
|
||||
but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the
|
||||
maintainers' jobs way easier.
|
||||
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for
|
||||
the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again
|
||||
about submission, or clearly state that in the description of your PR.
|
||||
* Respond yourselves if someone requests changes or otherwise raises issues about your PRs.
|
||||
* Check if your contributions align with the [fdroid inclusion guidelines](https://f-droid.org/en/docs/Inclusion_Policy/).
|
||||
* Check if your submission can be build with the current fdroid build server setup.
|
||||
* Send PR that only cover one specific issue/solution/bug. Do not send PRs that are huge and consists of multiple
|
||||
independent solutions.
|
||||
|
||||
## Communication
|
||||
|
||||
* WE DO NOW HAVE A MAILING LIST: [newpipe@list.schabi.org](https://list.schabi.org/cgi-bin/mailman/listinfo/newpipe).
|
||||
* There is an IRC channel on Freenode which is regularly visited by the core team and other developers: [#newpipe](irc:irc.freenode.net/newpipe). [Click here for Webchat](https://webchat.freenode.net/?channels=newpipe)!
|
||||
* If you want to get in touch with the core team or one of our other contributors you can send an email to tnp(at)schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue tracker described above!
|
||||
* There is an IRC channel on Freenode which is regularly visited by the core team and other developers:
|
||||
[#newpipe](irc:irc.freenode.net/newpipe). [Click here for Webchat](https://webchat.freenode.net/?channels=newpipe)!
|
||||
* If you want to get in touch with the core team or one of our other contributors you can send an email to
|
||||
tnp(at)schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue
|
||||
tracker described above!
|
||||
* Feel free to post suggestions, changes, ideas etc. on GitHub, IRC or the mailing list!
|
||||
|
@@ -6,20 +6,22 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.schabi.newpipe"
|
||||
minSdkVersion 15
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 69
|
||||
versionName "0.14.2"
|
||||
versionCode 730
|
||||
versionName "0.16.1"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
debug {
|
||||
multiDexEnabled true
|
||||
debuggable true
|
||||
@@ -33,6 +35,7 @@ android {
|
||||
// but continue the build even when errors are found:
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
@@ -54,7 +57,7 @@ dependencies {
|
||||
exclude module: 'support-annotations'
|
||||
})
|
||||
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:32d316330c26'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:e072bf6461b295'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.mockito:mockito-core:2.23.0'
|
||||
|
@@ -1,13 +0,0 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
@@ -35,12 +35,6 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".player.old.PlayVideoActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:theme="@style/OldVideoPlayerTheme"
|
||||
tools:ignore="UnusedAttribute"/>
|
||||
|
||||
<service
|
||||
android:name=".player.BackgroundPlayer"
|
||||
android:exported="false">
|
||||
@@ -119,7 +113,6 @@
|
||||
<activity
|
||||
android:name=".ReCaptchaActivity"
|
||||
android:label="@string/reCaptchaActivity"/>
|
||||
<activity android:name=".download.ExtSDDownloadFailedActivity" />
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
@@ -184,6 +177,19 @@
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="www.youtube-nocookie.com"/>
|
||||
<data android:pathPrefix="/embed/"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:scheme="vnd.youtube"/>
|
||||
<data android:scheme="vnd.youtube.launch"/>
|
||||
</intent-filter>
|
||||
@@ -210,6 +216,29 @@
|
||||
<data android:pathPrefix="/user/"/>
|
||||
</intent-filter>
|
||||
|
||||
<!-- Invidious filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="invidio.us"/>
|
||||
<data android:host="www.invidio.us"/>
|
||||
<!-- video prefix -->
|
||||
<data android:pathPrefix="/embed/"/>
|
||||
<data android:pathPrefix="/watch"/>
|
||||
<!-- channel prefix -->
|
||||
<data android:pathPrefix="/channel/"/>
|
||||
<data android:pathPrefix="/user/"/>
|
||||
<!-- playlist prefix -->
|
||||
<data android:pathPrefix="/playlist"/>
|
||||
</intent-filter>
|
||||
|
||||
<!-- Soundcloud filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
@@ -0,0 +1,116 @@
|
||||
package android.support.design.widget;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.animation.AnimationUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
// check this https://github.com/ToDou/appbarlayout-spring-behavior/blob/master/appbarspring/src/main/java/android/support/design/widget/AppBarFlingFixBehavior.java
|
||||
public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||
|
||||
private ValueAnimator mOffsetAnimator;
|
||||
private static final int MAX_OFFSET_ANIMATION_DURATION = 600; // ms
|
||||
|
||||
public FlingBehavior() {
|
||||
}
|
||||
|
||||
public FlingBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) {
|
||||
if (dy != 0) {
|
||||
int val = child.getBottom();
|
||||
if (val != 0) {
|
||||
int min, max;
|
||||
if (dy < 0) {
|
||||
// We're scrolling down
|
||||
} else {
|
||||
// We're scrolling up
|
||||
if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) {
|
||||
mOffsetAnimator.cancel();
|
||||
}
|
||||
min = -child.getUpNestedPreScrollRange();
|
||||
max = 0;
|
||||
consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull AppBarLayout child, @NonNull View target, float velocityX, float velocityY) {
|
||||
|
||||
if (velocityY != 0) {
|
||||
if (velocityY < 0) {
|
||||
// We're flinging down
|
||||
int val = child.getBottom();
|
||||
if (val != 0) {
|
||||
final int targetScroll =
|
||||
+child.getDownNestedPreScrollRange();
|
||||
animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
|
||||
}
|
||||
|
||||
} else {
|
||||
// We're flinging up
|
||||
int val = child.getBottom();
|
||||
if (val != 0) {
|
||||
final int targetScroll = -child.getUpNestedPreScrollRange();
|
||||
if (getTopBottomOffsetForScrollingSibling() > targetScroll) {
|
||||
animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
|
||||
}
|
||||
|
||||
private void animateOffsetTo(final CoordinatorLayout coordinatorLayout,
|
||||
final AppBarLayout child, final int offset, float velocity) {
|
||||
final int distance = Math.abs(getTopBottomOffsetForScrollingSibling() - offset);
|
||||
|
||||
final int duration;
|
||||
velocity = Math.abs(velocity);
|
||||
if (velocity > 0) {
|
||||
duration = 3 * Math.round(1000 * (distance / velocity));
|
||||
} else {
|
||||
final float distanceRatio = (float) distance / child.getHeight();
|
||||
duration = (int) ((distanceRatio + 1) * 150);
|
||||
}
|
||||
|
||||
animateOffsetWithDuration(coordinatorLayout, child, offset, duration);
|
||||
}
|
||||
|
||||
private void animateOffsetWithDuration(final CoordinatorLayout coordinatorLayout,
|
||||
final AppBarLayout child, final int offset, final int duration) {
|
||||
final int currentOffset = getTopBottomOffsetForScrollingSibling();
|
||||
if (currentOffset == offset) {
|
||||
if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) {
|
||||
mOffsetAnimator.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mOffsetAnimator == null) {
|
||||
mOffsetAnimator = new ValueAnimator();
|
||||
mOffsetAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
|
||||
mOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animator) {
|
||||
setHeaderTopBottomOffset(coordinatorLayout, child,
|
||||
(Integer) animator.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mOffsetAnimator.cancel();
|
||||
}
|
||||
|
||||
mOffsetAnimator.setDuration(Math.min(duration, MAX_OFFSET_ANIMATION_DURATION));
|
||||
mOffsetAnimator.setIntValues(currentOffset, offset);
|
||||
mOffsetAnimator.start();
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
@@ -65,6 +66,7 @@ import io.reactivex.plugins.RxJavaPlugins;
|
||||
public class App extends Application {
|
||||
protected static final String TAG = App.class.toString();
|
||||
private RefWatcher refWatcher;
|
||||
private static App app;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Class<? extends ReportSenderFactory>[]
|
||||
@@ -88,6 +90,8 @@ public class App extends Application {
|
||||
}
|
||||
refWatcher = installLeakCanary();
|
||||
|
||||
app = this;
|
||||
|
||||
// Initialize settings first because others inits can use its values
|
||||
SettingsActivity.initSettings(this);
|
||||
|
||||
@@ -100,6 +104,9 @@ public class App extends Application {
|
||||
ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50));
|
||||
|
||||
configureRxJavaErrorHandler();
|
||||
|
||||
// Check for new version
|
||||
new CheckForNewAppVersionTask().execute();
|
||||
}
|
||||
|
||||
protected Downloader getDownloader() {
|
||||
@@ -211,6 +218,31 @@ public class App extends Application {
|
||||
NotificationManager mNotificationManager =
|
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mNotificationManager.createNotificationChannel(mChannel);
|
||||
|
||||
setUpUpdateNotificationChannel(importance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up notification channel for app update.
|
||||
* @param importance
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
private void setUpUpdateNotificationChannel(int importance) {
|
||||
|
||||
final String appUpdateId
|
||||
= getString(R.string.app_update_notification_channel_id);
|
||||
final CharSequence appUpdateName
|
||||
= getString(R.string.app_update_notification_channel_name);
|
||||
final String appUpdateDescription
|
||||
= getString(R.string.app_update_notification_channel_description);
|
||||
|
||||
NotificationChannel appUpdateChannel
|
||||
= new NotificationChannel(appUpdateId, appUpdateName, importance);
|
||||
appUpdateChannel.setDescription(appUpdateDescription);
|
||||
|
||||
NotificationManager appUpdateNotificationManager
|
||||
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -226,4 +258,8 @@ public class App extends Application {
|
||||
protected boolean isDisposedRxExceptionsReported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static App getApp() {
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,230 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* AsyncTask to check if there is a newer version of the NewPipe github apk available or not.
|
||||
* If there is a newer version we show a notification, informing the user. On tapping
|
||||
* the notification, the user will be directed to the download link.
|
||||
*/
|
||||
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||
|
||||
private static final Application app = App.getApp();
|
||||
private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
||||
private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json";
|
||||
private static final int timeoutPeriod = 30;
|
||||
|
||||
private SharedPreferences mPrefs;
|
||||
private OkHttpClient client;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||
|
||||
// Check if user has enabled/ disabled update checking
|
||||
// and if the current apk is a github one or not.
|
||||
if (!mPrefs.getBoolean(app.getString(R.string.update_app_key), true)
|
||||
|| !isGithubApk()) {
|
||||
this.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
|
||||
// Make a network request to get latest NewPipe data.
|
||||
if (client == null) {
|
||||
|
||||
client = new OkHttpClient
|
||||
.Builder()
|
||||
.readTimeout(timeoutPeriod, TimeUnit.SECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(newPipeApiUrl)
|
||||
.build();
|
||||
|
||||
try {
|
||||
Response response = client.newCall(request).execute();
|
||||
return response.body().string();
|
||||
} catch (IOException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"app update API fail", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String response) {
|
||||
|
||||
// Parse the json from the response.
|
||||
if (response != null) {
|
||||
|
||||
try {
|
||||
JSONObject mainObject = new JSONObject(response);
|
||||
JSONObject flavoursObject = mainObject.getJSONObject("flavors");
|
||||
JSONObject githubObject = flavoursObject.getJSONObject("github");
|
||||
JSONObject githubStableObject = githubObject.getJSONObject("stable");
|
||||
|
||||
String versionName = githubStableObject.getString("version");
|
||||
String versionCode = githubStableObject.getString("version_code");
|
||||
String apkLocationUrl = githubStableObject.getString("apk");
|
||||
|
||||
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
|
||||
|
||||
} catch (JSONException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"could not parse app update JSON data", R.string.app_ui_crash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to compare the current and latest available app version.
|
||||
* If a newer version is available, we show the update notification.
|
||||
* @param versionName
|
||||
* @param apkLocationUrl
|
||||
*/
|
||||
private void compareAppVersionAndShowNotification(String versionName,
|
||||
String apkLocationUrl,
|
||||
String versionCode) {
|
||||
|
||||
int NOTIFICATION_ID = 2000;
|
||||
|
||||
if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
|
||||
|
||||
// A pending intent to open the apk location url in the browser.
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
||||
PendingIntent pendingIntent
|
||||
= PendingIntent.getActivity(app, 0, intent, 0);
|
||||
|
||||
NotificationCompat.Builder notificationBuilder = new NotificationCompat
|
||||
.Builder(app, app.getString(R.string.app_update_notification_channel_id))
|
||||
.setSmallIcon(R.drawable.ic_newpipe_update)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
|
||||
.setContentText(app.getString(R.string.app_update_notification_content_text)
|
||||
+ " " + versionName);
|
||||
|
||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app);
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the apk's SHA1 key.
|
||||
* https://stackoverflow.com/questions/9293019/get-certificate-fingerprint-from-android-app#22506133
|
||||
*/
|
||||
private static String getCertificateSHA1Fingerprint() {
|
||||
|
||||
PackageManager pm = app.getPackageManager();
|
||||
String packageName = app.getPackageName();
|
||||
int flags = PackageManager.GET_SIGNATURES;
|
||||
PackageInfo packageInfo = null;
|
||||
|
||||
try {
|
||||
packageInfo = pm.getPackageInfo(packageName, flags);
|
||||
} catch (PackageManager.NameNotFoundException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not find package info", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
Signature[] signatures = packageInfo.signatures;
|
||||
byte[] cert = signatures[0].toByteArray();
|
||||
InputStream input = new ByteArrayInputStream(cert);
|
||||
|
||||
CertificateFactory cf = null;
|
||||
X509Certificate c = null;
|
||||
|
||||
try {
|
||||
cf = CertificateFactory.getInstance("X509");
|
||||
c = (X509Certificate) cf.generateCertificate(input);
|
||||
} catch (CertificateException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Certificate error", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
String hexString = null;
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
byte[] publicKey = md.digest(c.getEncoded());
|
||||
hexString = byte2HexFormatted(publicKey);
|
||||
} catch (NoSuchAlgorithmException ex1) {
|
||||
ErrorActivity.reportError(app, ex1, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
||||
} catch (CertificateEncodingException ex2) {
|
||||
ErrorActivity.reportError(app, ex2, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
return hexString;
|
||||
}
|
||||
|
||||
private static String byte2HexFormatted(byte[] arr) {
|
||||
|
||||
StringBuilder str = new StringBuilder(arr.length * 2);
|
||||
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
String h = Integer.toHexString(arr[i]);
|
||||
int l = h.length();
|
||||
if (l == 1) h = "0" + h;
|
||||
if (l > 2) h = h.substring(l - 2, l);
|
||||
str.append(h.toUpperCase());
|
||||
if (i < (arr.length - 1)) str.append(':');
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public static boolean isGithubApk() {
|
||||
|
||||
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
|
||||
}
|
||||
}
|
@@ -3,18 +3,24 @@ package org.schabi.newpipe;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.schabi.newpipe.extractor.DownloadRequest;
|
||||
import org.schabi.newpipe.extractor.DownloadResponse;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.utils.Localization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
@@ -89,7 +95,8 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
.build();
|
||||
response = client.newCall(request).execute();
|
||||
|
||||
return Long.parseLong(response.header("Content-Length"));
|
||||
String contentLength = response.header("Content-Length");
|
||||
return contentLength == null ? -1 : Long.parseLong(contentLength);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("Invalid content length", e);
|
||||
} finally {
|
||||
@@ -138,13 +145,16 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
|
||||
private ResponseBody getBody(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException {
|
||||
final Request.Builder requestBuilder = new Request.Builder()
|
||||
.method("GET", null).url(siteUrl)
|
||||
.addHeader("User-Agent", USER_AGENT);
|
||||
.method("GET", null).url(siteUrl);
|
||||
|
||||
for (Map.Entry<String, String> header : customProperties.entrySet()) {
|
||||
requestBuilder.addHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
|
||||
if (!customProperties.containsKey("User-Agent")) {
|
||||
requestBuilder.header("User-Agent", USER_AGENT);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
}
|
||||
@@ -176,4 +186,96 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
public String download(String siteUrl) throws IOException, ReCaptchaException {
|
||||
return download(siteUrl, Collections.emptyMap());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DownloadResponse get(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
|
||||
final Request.Builder requestBuilder = new Request.Builder()
|
||||
.method("GET", null).url(siteUrl);
|
||||
|
||||
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
|
||||
// set custom headers in request
|
||||
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
|
||||
for(String value : pair.getValue()){
|
||||
requestBuilder.addHeader(pair.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!requestHeaders.containsKey("User-Agent")) {
|
||||
requestBuilder.header("User-Agent", USER_AGENT);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
}
|
||||
|
||||
final Request okRequest = requestBuilder.build();
|
||||
final Response response = client.newCall(okRequest).execute();
|
||||
final ResponseBody body = response.body();
|
||||
|
||||
if (response.code() == 429) {
|
||||
throw new ReCaptchaException("reCaptcha Challenge requested");
|
||||
}
|
||||
|
||||
if (body == null) {
|
||||
response.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DownloadResponse(body.string(), response.headers().toMultimap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadResponse get(String siteUrl) throws IOException, ReCaptchaException {
|
||||
return get(siteUrl, DownloadRequest.emptyRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadResponse post(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
|
||||
|
||||
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
|
||||
if(null == requestHeaders.get("Content-Type") || requestHeaders.get("Content-Type").isEmpty()){
|
||||
// content type header is required. maybe throw an exception here
|
||||
return null;
|
||||
}
|
||||
|
||||
String contentType = requestHeaders.get("Content-Type").get(0);
|
||||
|
||||
RequestBody okRequestBody = null;
|
||||
if(null != request.getRequestBody()){
|
||||
okRequestBody = RequestBody.create(MediaType.parse(contentType), request.getRequestBody());
|
||||
}
|
||||
final Request.Builder requestBuilder = new Request.Builder()
|
||||
.method("POST", okRequestBody).url(siteUrl);
|
||||
|
||||
// set custom headers in request
|
||||
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
|
||||
for(String value : pair.getValue()){
|
||||
requestBuilder.addHeader(pair.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!requestHeaders.containsKey("User-Agent")) {
|
||||
requestBuilder.header("User-Agent", USER_AGENT);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
}
|
||||
|
||||
final Request okRequest = requestBuilder.build();
|
||||
final Response response = client.newCall(okRequest).execute();
|
||||
final ResponseBody body = response.body();
|
||||
|
||||
if (response.code() == 429) {
|
||||
throw new ReCaptchaException("reCaptcha Challenge requested");
|
||||
}
|
||||
|
||||
if (body == null) {
|
||||
response.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DownloadResponse(body.string(), response.headers().toMultimap());
|
||||
}
|
||||
}
|
@@ -542,8 +542,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
|
||||
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
|
||||
boolean useOldVideoPlayer = PlayerHelper.isUsingOldPlayer(this);
|
||||
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);;
|
||||
|
||||
PlayQueue playQueue;
|
||||
String playerChoice = choice.playerChoice;
|
||||
@@ -555,9 +554,6 @@ public class RouterActivity extends AppCompatActivity {
|
||||
} else if (playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) {
|
||||
NavigationHelper.playOnExternalVideoPlayer(this, (StreamInfo) info);
|
||||
|
||||
} else if (playerChoice.equals(videoPlayerKey) && useOldVideoPlayer) {
|
||||
NavigationHelper.playOnOldVideoPlayer(this, (StreamInfo) info);
|
||||
|
||||
} else {
|
||||
playQueue = new SinglePlayQueue((StreamInfo) info);
|
||||
|
||||
|
@@ -1,158 +0,0 @@
|
||||
package org.schabi.newpipe.download;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.BaseTransientBottomBar;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.view.View;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
import us.shandian.giga.get.DownloadManager;
|
||||
import us.shandian.giga.get.DownloadMission;
|
||||
|
||||
public class DeleteDownloadManager {
|
||||
|
||||
private static final String KEY_STATE = "delete_manager_state";
|
||||
|
||||
private final View mView;
|
||||
private final HashSet<String> mPendingMap;
|
||||
private final List<Disposable> mDisposableList;
|
||||
private DownloadManager mDownloadManager;
|
||||
private final PublishSubject<DownloadMission> publishSubject = PublishSubject.create();
|
||||
|
||||
DeleteDownloadManager(Activity activity) {
|
||||
mPendingMap = new HashSet<>();
|
||||
mDisposableList = new ArrayList<>();
|
||||
mView = activity.findViewById(android.R.id.content);
|
||||
}
|
||||
|
||||
public Observable<DownloadMission> getUndoObservable() {
|
||||
return publishSubject;
|
||||
}
|
||||
|
||||
public boolean contains(@NonNull DownloadMission mission) {
|
||||
return mPendingMap.contains(mission.url);
|
||||
}
|
||||
|
||||
public void add(@NonNull DownloadMission mission) {
|
||||
mPendingMap.add(mission.url);
|
||||
|
||||
if (mPendingMap.size() == 1) {
|
||||
showUndoDeleteSnackbar(mission);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDownloadManager(@NonNull DownloadManager downloadManager) {
|
||||
mDownloadManager = downloadManager;
|
||||
|
||||
if (mPendingMap.size() < 1) return;
|
||||
|
||||
showUndoDeleteSnackbar();
|
||||
}
|
||||
|
||||
public void restoreState(@Nullable Bundle savedInstanceState) {
|
||||
if (savedInstanceState == null) return;
|
||||
|
||||
List<String> list = savedInstanceState.getStringArrayList(KEY_STATE);
|
||||
if (list != null) {
|
||||
mPendingMap.addAll(list);
|
||||
}
|
||||
}
|
||||
|
||||
public void saveState(@Nullable Bundle outState) {
|
||||
if (outState == null) return;
|
||||
|
||||
for (Disposable disposable : mDisposableList) {
|
||||
disposable.dispose();
|
||||
}
|
||||
|
||||
outState.putStringArrayList(KEY_STATE, new ArrayList<>(mPendingMap));
|
||||
}
|
||||
|
||||
private void showUndoDeleteSnackbar() {
|
||||
if (mPendingMap.size() < 1) return;
|
||||
|
||||
String url = mPendingMap.iterator().next();
|
||||
|
||||
for (int i = 0; i < mDownloadManager.getCount(); i++) {
|
||||
DownloadMission mission = mDownloadManager.getMission(i);
|
||||
if (url.equals(mission.url)) {
|
||||
showUndoDeleteSnackbar(mission);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showUndoDeleteSnackbar(@NonNull DownloadMission mission) {
|
||||
final Snackbar snackbar = Snackbar.make(mView, mission.name, Snackbar.LENGTH_INDEFINITE);
|
||||
final Disposable disposable = Observable.timer(3, TimeUnit.SECONDS)
|
||||
.subscribeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(l -> snackbar.dismiss());
|
||||
|
||||
mDisposableList.add(disposable);
|
||||
|
||||
snackbar.setAction(R.string.undo, v -> {
|
||||
mPendingMap.remove(mission.url);
|
||||
publishSubject.onNext(mission);
|
||||
disposable.dispose();
|
||||
snackbar.dismiss();
|
||||
});
|
||||
|
||||
snackbar.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
@Override
|
||||
public void onDismissed(Snackbar transientBottomBar, int event) {
|
||||
if (!disposable.isDisposed()) {
|
||||
Completable.fromAction(() -> deletePending(mission))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe();
|
||||
}
|
||||
mPendingMap.remove(mission.url);
|
||||
snackbar.removeCallback(this);
|
||||
mDisposableList.remove(disposable);
|
||||
showUndoDeleteSnackbar();
|
||||
}
|
||||
});
|
||||
|
||||
snackbar.show();
|
||||
}
|
||||
|
||||
public void deletePending() {
|
||||
if (mPendingMap.size() < 1) return;
|
||||
|
||||
HashSet<Integer> idSet = new HashSet<>();
|
||||
for (int i = 0; i < mDownloadManager.getCount(); i++) {
|
||||
if (contains(mDownloadManager.getMission(i))) {
|
||||
idSet.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (Integer id : idSet) {
|
||||
mDownloadManager.deleteMission(id);
|
||||
}
|
||||
|
||||
mPendingMap.clear();
|
||||
}
|
||||
|
||||
private void deletePending(@NonNull DownloadMission mission) {
|
||||
for (int i = 0; i < mDownloadManager.getCount(); i++) {
|
||||
if (mission.url.equals(mDownloadManager.getMission(i).url)) {
|
||||
mDownloadManager.deleteMission(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,16 +15,12 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import us.shandian.giga.service.DownloadManagerService;
|
||||
import us.shandian.giga.ui.fragment.AllMissionsFragment;
|
||||
import us.shandian.giga.ui.fragment.MissionsFragment;
|
||||
|
||||
public class DownloadActivity extends AppCompatActivity {
|
||||
|
||||
private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag";
|
||||
private DeleteDownloadManager mDeleteDownloadManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -47,32 +43,17 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
}
|
||||
|
||||
mDeleteDownloadManager = new DeleteDownloadManager(this);
|
||||
mDeleteDownloadManager.restoreState(savedInstanceState);
|
||||
|
||||
MissionsFragment fragment = (MissionsFragment) getFragmentManager().findFragmentByTag(MISSIONS_FRAGMENT_TAG);
|
||||
if (fragment != null) {
|
||||
fragment.setDeleteManager(mDeleteDownloadManager);
|
||||
} else {
|
||||
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
updateFragments();
|
||||
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
mDeleteDownloadManager.saveState(outState);
|
||||
super.onSaveInstanceState(outState);
|
||||
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
updateFragments();
|
||||
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateFragments() {
|
||||
MissionsFragment fragment = new AllMissionsFragment();
|
||||
fragment.setDeleteManager(mDeleteDownloadManager);
|
||||
MissionsFragment fragment = new MissionsFragment();
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG)
|
||||
@@ -99,7 +80,6 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
case R.id.action_settings: {
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
deletePending();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
@@ -108,14 +88,7 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
deletePending();
|
||||
}
|
||||
|
||||
private void deletePending() {
|
||||
Completable.fromAction(mDeleteDownloadManager::deletePending)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe();
|
||||
public void onRestoreInstanceState(Bundle inState){
|
||||
super.onRestoreInstanceState(inState);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,38 +0,0 @@
|
||||
package org.schabi.newpipe.download;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
public class ExtSDDownloadFailedActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.download_to_sdcard_error_title)
|
||||
.setMessage(R.string.download_to_sdcard_error_message)
|
||||
.setPositiveButton(R.string.yes, (DialogInterface dialogInterface, int i) -> {
|
||||
NewPipeSettings.resetDownloadFolders(this);
|
||||
finish();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (DialogInterface dialogInterface, int i) -> {
|
||||
dialogInterface.dismiss();
|
||||
finish();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
package org.schabi.newpipe.fragments.detail;
|
||||
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TabAdaptor extends FragmentPagerAdapter {
|
||||
|
||||
private final List<Fragment> mFragmentList = new ArrayList<>();
|
||||
private final List<String> mFragmentTitleList = new ArrayList<>();
|
||||
private final FragmentManager fragmentManager;
|
||||
|
||||
public TabAdaptor(FragmentManager fm) {
|
||||
super(fm);
|
||||
this.fragmentManager = fm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return mFragmentList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mFragmentList.size();
|
||||
}
|
||||
|
||||
public void addFragment(Fragment fragment, String title) {
|
||||
mFragmentList.add(fragment);
|
||||
mFragmentTitleList.add(title);
|
||||
}
|
||||
|
||||
public void clearAllItems() {
|
||||
mFragmentList.clear();
|
||||
mFragmentTitleList.clear();
|
||||
}
|
||||
|
||||
public void removeItem(int position){
|
||||
mFragmentList.remove(position == 0 ? 0 : position - 1);
|
||||
mFragmentTitleList.remove(position == 0 ? 0 : position - 1);
|
||||
}
|
||||
|
||||
public void updateItem(int position, Fragment fragment){
|
||||
mFragmentList.set(position, fragment);
|
||||
}
|
||||
|
||||
public void updateItem(String title, Fragment fragment){
|
||||
int index = mFragmentTitleList.indexOf(title);
|
||||
if(index != -1){
|
||||
updateItem(index, fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemPosition(Object object) {
|
||||
if (mFragmentList.contains(object)) return mFragmentList.indexOf(object);
|
||||
else return POSITION_NONE;
|
||||
}
|
||||
|
||||
public void notifyDataSetUpdate(){
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@ import android.view.View;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
@@ -220,6 +221,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
}
|
||||
});
|
||||
|
||||
infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() {
|
||||
@Override
|
||||
public void selected(CommentsInfoItem selectedItem) {
|
||||
onItemSelected(selectedItem);
|
||||
}
|
||||
});
|
||||
|
||||
itemsList.clearOnScrollListeners();
|
||||
itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
|
||||
@Override
|
||||
|
@@ -233,10 +233,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
openRssFeed();
|
||||
break;
|
||||
case R.id.menu_item_openInBrowser:
|
||||
openUrlInBrowser(url);
|
||||
openUrlInBrowser(currentInfo.getOriginalUrl());
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
shareUrl(name, url);
|
||||
shareUrl(name, currentInfo.getOriginalUrl());
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
@@ -0,0 +1,149 @@
|
||||
package org.schabi.newpipe.fragments.list.comments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
|
||||
|
||||
private boolean mIsVisibleToUser = false;
|
||||
|
||||
public static CommentsFragment getInstance(int serviceId, String url, String name) {
|
||||
CommentsFragment instance = new CommentsFragment();
|
||||
instance.setInitialData(serviceId, url, name);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_comments, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Load and handle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<CommentsInfo> loadResult(boolean forceLoad) {
|
||||
return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void showLoading() {
|
||||
super.showLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull CommentsInfo result) {
|
||||
super.handleResult(result);
|
||||
|
||||
AnimationUtils.slideUp(getView(),120, 96, 0.06f);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||
}
|
||||
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
||||
super.handleNextItems(result);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(),
|
||||
UserAction.REQUESTED_COMMENTS,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
"Get next page of: " + url,
|
||||
R.string.general_error);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// OnError
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected boolean onError(Throwable exception) {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
hideLoading();
|
||||
showSnackBarError(exception, UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(serviceId), url, R.string.error_unable_to_load_comments);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setTitle(String title) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGridLayout() {
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -39,6 +39,7 @@ import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||
import org.schabi.newpipe.util.FireTvUtils;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
@@ -104,8 +105,13 @@ public class SearchFragment
|
||||
// this three represet the current search query
|
||||
@State
|
||||
protected String searchString;
|
||||
|
||||
/**
|
||||
* No content filter should add like contentfilter = all
|
||||
* be aware of this when implementing an extractor.
|
||||
*/
|
||||
@State
|
||||
protected String[] contentFilter;
|
||||
protected String[] contentFilter = new String[0];
|
||||
@State
|
||||
protected String sortFilter;
|
||||
|
||||
@@ -335,7 +341,7 @@ public class SearchFragment
|
||||
|| (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
|
||||
search(!TextUtils.isEmpty(searchString)
|
||||
? searchString
|
||||
: searchEditText.getText().toString(), new String[0], "");
|
||||
: searchEditText.getText().toString(), this.contentFilter, "");
|
||||
} else {
|
||||
if (searchEditText != null) {
|
||||
searchEditText.setText("");
|
||||
@@ -449,6 +455,9 @@ public class SearchFragment
|
||||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||
showSuggestionsPanel();
|
||||
}
|
||||
if(FireTvUtils.isFireTv()){
|
||||
showKeyboardSearch();
|
||||
}
|
||||
});
|
||||
|
||||
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
|
||||
@@ -499,7 +508,9 @@ public class SearchFragment
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
|
||||
}
|
||||
if (event != null
|
||||
if(actionId == EditorInfo.IME_ACTION_PREVIOUS){
|
||||
hideKeyboardSearch();
|
||||
} else if (event != null
|
||||
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|
||||
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
|
||||
search(searchEditText.getText().toString(), new String[0], "");
|
||||
@@ -541,7 +552,7 @@ public class SearchFragment
|
||||
if (searchEditText.requestFocus()) {
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,8 +562,7 @@ public class SearchFragment
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
|
||||
InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
||||
|
||||
searchEditText.clearFocus();
|
||||
}
|
||||
@@ -736,6 +746,7 @@ public class SearchFragment
|
||||
|
||||
@Override
|
||||
protected void loadMoreItems() {
|
||||
if(nextPageUrl == null || nextPageUrl.isEmpty()) return;
|
||||
isLoading.set(true);
|
||||
showListFooter(true);
|
||||
if (searchDisposable != null) searchDisposable.dispose();
|
||||
|
@@ -0,0 +1,208 @@
|
||||
package org.schabi.newpipe.fragments.list.videos;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.Switch;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.RelatedStreamInfo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo> implements SharedPreferences.OnSharedPreferenceChangeListener{
|
||||
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
private RelatedStreamInfo relatedStreamInfo;
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
private View headerRootLayout;
|
||||
private Switch aSwitch;
|
||||
|
||||
private boolean mIsVisibleToUser = false;
|
||||
|
||||
public static RelatedVideosFragment getInstance(StreamInfo info) {
|
||||
RelatedVideosFragment instance = new RelatedVideosFragment();
|
||||
instance.setInitialData(info);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_related_streams, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
protected View getListHeader(){
|
||||
if(relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null){
|
||||
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.related_streams_header, itemsList, false);
|
||||
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
|
||||
|
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
aSwitch.setChecked(autoplay);
|
||||
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
|
||||
SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
|
||||
prefEdit.putBoolean(getString(R.string.auto_queue_key), b);
|
||||
prefEdit.apply();
|
||||
}
|
||||
});
|
||||
return headerRootLayout;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<RelatedStreamInfo> loadResult(boolean forceLoad) {
|
||||
return Single.fromCallable(() -> relatedStreamInfo);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void showLoading() {
|
||||
super.showLoading();
|
||||
if(null != headerRootLayout) headerRootLayout.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull RelatedStreamInfo result) {
|
||||
|
||||
super.handleResult(result);
|
||||
|
||||
if(null != headerRootLayout) headerRootLayout.setVisibility(View.VISIBLE);
|
||||
AnimationUtils.slideUp(getView(),120, 96, 0.06f);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||
}
|
||||
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
||||
super.handleNextItems(result);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(),
|
||||
UserAction.REQUESTED_STREAM,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
"Get next page of: " + url,
|
||||
R.string.general_error);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// OnError
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected boolean onError(Throwable exception) {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
hideLoading();
|
||||
showSnackBarError(exception, UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(serviceId), url, R.string.general_error);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setTitle(String title) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void setInitialData(StreamInfo info) {
|
||||
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
|
||||
if(this.relatedStreamInfo == null) this.relatedStreamInfo = RelatedStreamInfo.getInfo(info);
|
||||
}
|
||||
|
||||
|
||||
private static final String INFO_KEY = "related_info_key";
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putSerializable(INFO_KEY, relatedStreamInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedState) {
|
||||
super.onRestoreInstanceState(savedState);
|
||||
if (savedState != null) {
|
||||
Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||
if(serializable instanceof RelatedStreamInfo){
|
||||
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
|
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
if(null != aSwitch) aSwitch.setChecked(autoplay);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGridLayout() {
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -10,10 +10,13 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
|
||||
@@ -50,6 +53,7 @@ public class InfoItemBuilder {
|
||||
private OnClickGesture<StreamInfoItem> onStreamSelectedListener;
|
||||
private OnClickGesture<ChannelInfoItem> onChannelSelectedListener;
|
||||
private OnClickGesture<PlaylistInfoItem> onPlaylistSelectedListener;
|
||||
private OnClickGesture<CommentsInfoItem> onCommentsSelectedListener;
|
||||
|
||||
public InfoItemBuilder(Context context) {
|
||||
this.context = context;
|
||||
@@ -73,6 +77,8 @@ public class InfoItemBuilder {
|
||||
return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent) : new ChannelInfoItemHolder(this, parent);
|
||||
case PLAYLIST:
|
||||
return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent) : new PlaylistInfoItemHolder(this, parent);
|
||||
case COMMENT:
|
||||
return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) : new CommentsInfoItemHolder(this, parent);
|
||||
default:
|
||||
Log.e(TAG, "Trollolo");
|
||||
throw new RuntimeException("InfoType not expected = " + infoType.name());
|
||||
@@ -111,4 +117,12 @@ public class InfoItemBuilder {
|
||||
this.onPlaylistSelectedListener = listener;
|
||||
}
|
||||
|
||||
public OnClickGesture<CommentsInfoItem> getOnCommentsSelectedListener() {
|
||||
return onCommentsSelectedListener;
|
||||
}
|
||||
|
||||
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> onCommentsSelectedListener) {
|
||||
this.onCommentsSelectedListener = onCommentsSelectedListener;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,10 +9,13 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder;
|
||||
@@ -63,6 +66,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300;
|
||||
private static final int PLAYLIST_HOLDER_TYPE = 0x301;
|
||||
private static final int GRID_PLAYLIST_HOLDER_TYPE = 0x302;
|
||||
private static final int MINI_COMMENT_HOLDER_TYPE = 0x400;
|
||||
private static final int COMMENT_HOLDER_TYPE = 0x401;
|
||||
|
||||
private final InfoItemBuilder infoItemBuilder;
|
||||
private final ArrayList<InfoItem> infoItemList;
|
||||
@@ -98,6 +103,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
infoItemBuilder.setOnPlaylistSelectedListener(listener);
|
||||
}
|
||||
|
||||
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> listener) {
|
||||
infoItemBuilder.setOnCommentsSelectedListener(listener);
|
||||
}
|
||||
|
||||
public void useMiniItemVariants(boolean useMiniVariant) {
|
||||
this.useMiniVariant = useMiniVariant;
|
||||
}
|
||||
@@ -223,6 +232,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
|
||||
case PLAYLIST:
|
||||
return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
|
||||
case COMMENT:
|
||||
return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE;
|
||||
default:
|
||||
Log.e(TAG, "Trollolo");
|
||||
return -1;
|
||||
@@ -231,7 +242,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
|
||||
if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
|
||||
switch (type) {
|
||||
case HEADER_TYPE:
|
||||
return new HFHolder(header);
|
||||
@@ -255,6 +267,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
return new PlaylistInfoItemHolder(infoItemBuilder, parent);
|
||||
case GRID_PLAYLIST_HOLDER_TYPE:
|
||||
return new PlaylistGridInfoItemHolder(infoItemBuilder, parent);
|
||||
case MINI_COMMENT_HOLDER_TYPE:
|
||||
return new CommentsMiniInfoItemHolder(infoItemBuilder, parent);
|
||||
case COMMENT_HOLDER_TYPE:
|
||||
return new CommentsInfoItemHolder(infoItemBuilder, parent);
|
||||
default:
|
||||
Log.e(TAG, "Trollolo");
|
||||
return new FallbackViewHolder(new View(parent.getContext()));
|
||||
|
@@ -0,0 +1,53 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItemHolder .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 CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
|
||||
|
||||
public final TextView itemTitleView;
|
||||
|
||||
public CommentsInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_comments_item, parent);
|
||||
|
||||
itemTitleView = itemView.findViewById(R.id.itemTitleView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem) {
|
||||
super.updateFromItem(infoItem);
|
||||
|
||||
if (!(infoItem instanceof CommentsInfoItem)) return;
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
itemTitleView.setText(item.getAuthorName());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
|
||||
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
public final CircleImageView itemThumbnailView;
|
||||
private final TextView itemContentView;
|
||||
private final TextView itemLikesCountView;
|
||||
private final TextView itemDislikesCountView;
|
||||
private final TextView itemPublishedTime;
|
||||
|
||||
private static final int commentDefaultLines = 2;
|
||||
private static final int commentExpandedLines = 1000;
|
||||
|
||||
CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
|
||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||
itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
|
||||
itemDislikesCountView = itemView.findViewById(R.id.detail_thumbs_down_count_view);
|
||||
itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime);
|
||||
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
|
||||
}
|
||||
|
||||
public CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
this(infoItemBuilder, R.layout.list_comments_mini_item, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem) {
|
||||
if (!(infoItem instanceof CommentsInfoItem)) return;
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
itemBuilder.getImageLoader()
|
||||
.displayImage(item.getAuthorThumbnail(),
|
||||
itemThumbnailView,
|
||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
||||
|
||||
itemThumbnailView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
try {
|
||||
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
||||
NavigationHelper.openChannelFragment(
|
||||
activity.getSupportFragmentManager(),
|
||||
item.getServiceId(),
|
||||
item.getAuthorEndpoint(),
|
||||
item.getAuthorName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ellipsize if not already ellipsized
|
||||
if (null == itemContentView.getEllipsize()) {
|
||||
itemContentView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
itemContentView.setMaxLines(commentDefaultLines);
|
||||
}
|
||||
|
||||
itemContentView.setText(item.getCommentText());
|
||||
if (null != item.getLikeCount()) {
|
||||
itemLikesCountView.setText(String.valueOf(item.getLikeCount()));
|
||||
}
|
||||
itemPublishedTime.setText(item.getPublishedTime());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
toggleEllipsize(item.getCommentText());
|
||||
if (itemBuilder.getOnCommentsSelectedListener() != null) {
|
||||
itemBuilder.getOnCommentsSelectedListener().selected(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleEllipsize(String text) {
|
||||
// toggle ellipsize
|
||||
if (null == itemContentView.getEllipsize()) {
|
||||
itemContentView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
itemContentView.setMaxLines(commentDefaultLines);
|
||||
} else {
|
||||
itemContentView.setEllipsize(null);
|
||||
itemContentView.setMaxLines(commentExpandedLines);
|
||||
}
|
||||
}
|
||||
}
|
@@ -147,11 +147,16 @@ public class SubscriptionService {
|
||||
}
|
||||
|
||||
private boolean isSubscriptionUpToDate(final ChannelInfo info, final SubscriptionEntity entity) {
|
||||
return info.getUrl().equals(entity.getUrl()) &&
|
||||
return equalsAndNotNull(info.getUrl(), entity.getUrl()) &&
|
||||
info.getServiceId() == entity.getServiceId() &&
|
||||
info.getName().equals(entity.getName()) &&
|
||||
info.getAvatarUrl().equals(entity.getAvatarUrl()) &&
|
||||
info.getDescription().equals(entity.getDescription()) &&
|
||||
equalsAndNotNull(info.getAvatarUrl(), entity.getAvatarUrl()) &&
|
||||
equalsAndNotNull(info.getDescription(), entity.getDescription()) &&
|
||||
info.getSubscriberCount() == entity.getSubscriberCount();
|
||||
}
|
||||
|
||||
private boolean equalsAndNotNull(final Object o1, final Object o2) {
|
||||
return (o1 != null && o2 != null)
|
||||
&& o1.equals(o2);
|
||||
}
|
||||
}
|
||||
|
@@ -98,35 +98,54 @@ public abstract class BasePlayer implements
|
||||
Player.EventListener, PlaybackListener, ImageLoadingListener {
|
||||
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
@NonNull public static final String TAG = "BasePlayer";
|
||||
@NonNull
|
||||
public static final String TAG = "BasePlayer";
|
||||
|
||||
@NonNull final protected Context context;
|
||||
@NonNull
|
||||
final protected Context context;
|
||||
|
||||
@NonNull final protected BroadcastReceiver broadcastReceiver;
|
||||
@NonNull final protected IntentFilter intentFilter;
|
||||
@NonNull
|
||||
final protected BroadcastReceiver broadcastReceiver;
|
||||
@NonNull
|
||||
final protected IntentFilter intentFilter;
|
||||
|
||||
@NonNull final protected HistoryRecordManager recordManager;
|
||||
@NonNull
|
||||
final protected HistoryRecordManager recordManager;
|
||||
|
||||
@NonNull final protected CustomTrackSelector trackSelector;
|
||||
@NonNull final protected PlayerDataSource dataSource;
|
||||
@NonNull
|
||||
final protected CustomTrackSelector trackSelector;
|
||||
@NonNull
|
||||
final protected PlayerDataSource dataSource;
|
||||
|
||||
@NonNull final private LoadControl loadControl;
|
||||
@NonNull final private RenderersFactory renderFactory;
|
||||
@NonNull
|
||||
final private LoadControl loadControl;
|
||||
@NonNull
|
||||
final private RenderersFactory renderFactory;
|
||||
|
||||
@NonNull final private SerialDisposable progressUpdateReactor;
|
||||
@NonNull final private CompositeDisposable databaseUpdateReactor;
|
||||
@NonNull
|
||||
final private SerialDisposable progressUpdateReactor;
|
||||
@NonNull
|
||||
final private CompositeDisposable databaseUpdateReactor;
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Intent
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@NonNull public static final String REPEAT_MODE = "repeat_mode";
|
||||
@NonNull public static final String PLAYBACK_PITCH = "playback_pitch";
|
||||
@NonNull public static final String PLAYBACK_SPEED = "playback_speed";
|
||||
@NonNull public static final String PLAYBACK_SKIP_SILENCE = "playback_skip_silence";
|
||||
@NonNull public static final String PLAYBACK_QUALITY = "playback_quality";
|
||||
@NonNull public static final String PLAY_QUEUE_KEY = "play_queue_key";
|
||||
@NonNull public static final String APPEND_ONLY = "append_only";
|
||||
@NonNull public static final String SELECT_ON_APPEND = "select_on_append";
|
||||
@NonNull
|
||||
public static final String REPEAT_MODE = "repeat_mode";
|
||||
@NonNull
|
||||
public static final String PLAYBACK_PITCH = "playback_pitch";
|
||||
@NonNull
|
||||
public static final String PLAYBACK_SPEED = "playback_speed";
|
||||
@NonNull
|
||||
public static final String PLAYBACK_SKIP_SILENCE = "playback_skip_silence";
|
||||
@NonNull
|
||||
public static final String PLAYBACK_QUALITY = "playback_quality";
|
||||
@NonNull
|
||||
public static final String PLAY_QUEUE_KEY = "play_queue_key";
|
||||
@NonNull
|
||||
public static final String APPEND_ONLY = "append_only";
|
||||
@NonNull
|
||||
public static final String SELECT_ON_APPEND = "select_on_append";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback
|
||||
@@ -137,13 +156,18 @@ public abstract class BasePlayer implements
|
||||
protected PlayQueue playQueue;
|
||||
protected PlayQueueAdapter playQueueAdapter;
|
||||
|
||||
@Nullable protected MediaSourceManager playbackManager;
|
||||
@Nullable
|
||||
protected MediaSourceManager playbackManager;
|
||||
|
||||
@Nullable private PlayQueueItem currentItem;
|
||||
@Nullable private MediaSourceTag currentMetadata;
|
||||
@Nullable private Bitmap currentThumbnail;
|
||||
@Nullable
|
||||
private PlayQueueItem currentItem;
|
||||
@Nullable
|
||||
private MediaSourceTag currentMetadata;
|
||||
@Nullable
|
||||
private Bitmap currentThumbnail;
|
||||
|
||||
@Nullable protected Toast errorToast;
|
||||
@Nullable
|
||||
protected Toast errorToast;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Player
|
||||
@@ -213,7 +237,8 @@ public abstract class BasePlayer implements
|
||||
registerBroadcastReceiver();
|
||||
}
|
||||
|
||||
public void initListeners() {}
|
||||
public void initListeners() {
|
||||
}
|
||||
|
||||
public void handleIntent(Intent intent) {
|
||||
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
||||
@@ -297,7 +322,6 @@ public abstract class BasePlayer implements
|
||||
databaseUpdateReactor.clear();
|
||||
progressUpdateReactor.set(null);
|
||||
|
||||
simpleExoPlayer = null;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -425,13 +449,15 @@ public abstract class BasePlayer implements
|
||||
if (!isProgressLoopRunning()) startProgressLoop();
|
||||
}
|
||||
|
||||
public void onBuffering() {}
|
||||
public void onBuffering() {
|
||||
}
|
||||
|
||||
public void onPaused() {
|
||||
if (isProgressLoopRunning()) stopProgressLoop();
|
||||
}
|
||||
|
||||
public void onPausedSeek() {}
|
||||
public void onPausedSeek() {
|
||||
}
|
||||
|
||||
public void onCompleted() {
|
||||
if (DEBUG) Log.d(TAG, "onCompleted() called");
|
||||
@@ -602,19 +628,19 @@ public abstract class BasePlayer implements
|
||||
/**
|
||||
* Processes the exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}.
|
||||
* There are multiple types of errors: <br><br>
|
||||
*
|
||||
* <p>
|
||||
* {@link ExoPlaybackException#TYPE_SOURCE TYPE_SOURCE}: <br><br>
|
||||
*
|
||||
* <p>
|
||||
* {@link ExoPlaybackException#TYPE_UNEXPECTED TYPE_UNEXPECTED}: <br><br>
|
||||
* If a runtime error occurred, then we can try to recover it by restarting the playback
|
||||
* after setting the timestamp recovery. <br><br>
|
||||
*
|
||||
* <p>
|
||||
* {@link ExoPlaybackException#TYPE_RENDERER TYPE_RENDERER}: <br><br>
|
||||
* If the renderer failed, treat the error as unrecoverable.
|
||||
*
|
||||
* @see #processSourceError(IOException)
|
||||
* @see Player.EventListener#onPlayerError(ExoPlaybackException)
|
||||
* */
|
||||
*/
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException error) {
|
||||
if (DEBUG) Log.d(TAG, "ExoPlayer - onPlayerError() called with: " +
|
||||
@@ -900,8 +926,8 @@ public abstract class BasePlayer implements
|
||||
if (DEBUG) Log.d(TAG, "onPlayPrevious() called");
|
||||
|
||||
/* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT_MILLIS milliseconds,
|
||||
* restart current track. Also restart the track if the current track
|
||||
* is the first in a queue.*/
|
||||
* restart current track. Also restart the track if the current track
|
||||
* is the first in a queue.*/
|
||||
if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT_MILLIS ||
|
||||
playQueue.getIndex() == 0) {
|
||||
seekToDefault();
|
||||
@@ -1010,8 +1036,8 @@ public abstract class BasePlayer implements
|
||||
try {
|
||||
metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag();
|
||||
} catch (IndexOutOfBoundsException | ClassCastException error) {
|
||||
if(DEBUG) Log.d(TAG, "Could not update metadata: " + error.getMessage());
|
||||
if(DEBUG) error.printStackTrace();
|
||||
if (DEBUG) Log.d(TAG, "Could not update metadata: " + error.getMessage());
|
||||
if (DEBUG) error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1075,7 +1101,9 @@ public abstract class BasePlayer implements
|
||||
currentThumbnail;
|
||||
}
|
||||
|
||||
/** Checks if the current playback is a livestream AND is playing at or beyond the live edge */
|
||||
/**
|
||||
* Checks if the current playback is a livestream AND is playing at or beyond the live edge
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean isLiveEdge() {
|
||||
if (simpleExoPlayer == null || !isLive()) return false;
|
||||
@@ -1099,13 +1127,14 @@ public abstract class BasePlayer implements
|
||||
} catch (@NonNull IndexOutOfBoundsException ignored) {
|
||||
// Why would this even happen =(
|
||||
// But lets log it anyway. Save is save
|
||||
if(DEBUG) Log.d(TAG, "Could not update metadata: " + ignored.getMessage());
|
||||
if(DEBUG) ignored.printStackTrace();
|
||||
if (DEBUG) Log.d(TAG, "Could not update metadata: " + ignored.getMessage());
|
||||
if (DEBUG) ignored.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
if (simpleExoPlayer == null) return false;
|
||||
final int state = simpleExoPlayer.getPlaybackState();
|
||||
return (state == Player.STATE_READY || state == Player.STATE_BUFFERING)
|
||||
&& simpleExoPlayer.getPlayWhenReady();
|
||||
|
@@ -900,6 +900,11 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
public void onMove(int sourceIndex, int targetIndex) {
|
||||
if (playQueue != null) playQueue.move(sourceIndex, targetIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(int index) {
|
||||
if(index != -1) playQueue.remove(index);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -68,7 +68,6 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
@@ -80,7 +79,6 @@ import java.util.List;
|
||||
import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING;
|
||||
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION;
|
||||
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.isUsingOldPlayer;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
/**
|
||||
@@ -554,27 +552,17 @@ public final class PopupVideoPlayer extends Service {
|
||||
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
|
||||
|
||||
setRecovery();
|
||||
Intent intent;
|
||||
if (!isUsingOldPlayer(getApplicationContext())) {
|
||||
intent = NavigationHelper.getPlayerIntent(
|
||||
context,
|
||||
MainVideoPlayer.class,
|
||||
this.getPlayQueue(),
|
||||
this.getRepeatMode(),
|
||||
this.getPlaybackSpeed(),
|
||||
this.getPlaybackPitch(),
|
||||
this.getPlaybackSkipSilence(),
|
||||
this.getPlaybackQuality()
|
||||
);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
} else {
|
||||
intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
|
||||
.putExtra(PlayVideoActivity.VIDEO_TITLE, getVideoTitle())
|
||||
.putExtra(PlayVideoActivity.STREAM_URL, getSelectedVideoStream().getUrl())
|
||||
.putExtra(PlayVideoActivity.VIDEO_URL, getVideoUrl())
|
||||
.putExtra(PlayVideoActivity.START_POSITION, Math.round(getPlayer().getCurrentPosition() / 1000f));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
final Intent intent = NavigationHelper.getPlayerIntent(
|
||||
context,
|
||||
MainVideoPlayer.class,
|
||||
this.getPlayQueue(),
|
||||
this.getRepeatMode(),
|
||||
this.getPlaybackSpeed(),
|
||||
this.getPlaybackPitch(),
|
||||
this.getPlaybackSkipSilence(),
|
||||
this.getPlaybackQuality()
|
||||
);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
closePopup();
|
||||
}
|
||||
@@ -638,6 +626,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
super.onLoadingComplete(imageUri, view, loadedImage);
|
||||
if (playerImpl == null) return;
|
||||
// rebuild notification here since remote view does not release bitmaps,
|
||||
// causing memory leaks
|
||||
resetNotification();
|
||||
|
@@ -375,6 +375,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
public void onMove(int sourceIndex, int targetIndex) {
|
||||
if (player != null) player.getPlayQueue().move(sourceIndex, targetIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(int index) {
|
||||
if (index != -1) player.getPlayQueue().remove(index);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -131,7 +131,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
private void onAudioFocusLossCanDuck() {
|
||||
Log.d(TAG, "onAudioFocusLossCanDuck() called");
|
||||
// Set the volume to 1/10 on ducking
|
||||
animateAudio(player.getVolume(), DUCK_AUDIO_TO);
|
||||
player.setVolume(DUCK_AUDIO_TO);
|
||||
}
|
||||
|
||||
private void animateAudio(final float from, final float to) {
|
||||
|
@@ -19,11 +19,11 @@ import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.Subtitles;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesFormat;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
@@ -45,7 +45,9 @@ import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MOD
|
||||
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT;
|
||||
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.*;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
|
||||
|
||||
public class PlayerHelper {
|
||||
private PlayerHelper() {}
|
||||
@@ -87,7 +89,7 @@ public class PlayerHelper {
|
||||
return pitchFormatter.format(pitch);
|
||||
}
|
||||
|
||||
public static String mimeTypesOf(final SubtitlesFormat format) {
|
||||
public static String subtitleMimeTypesOf(final MediaFormat format) {
|
||||
switch (format) {
|
||||
case VTT: return MimeTypes.TEXT_VTT;
|
||||
case TTML: return MimeTypes.APPLICATION_TTML;
|
||||
@@ -97,8 +99,8 @@ public class PlayerHelper {
|
||||
|
||||
@NonNull
|
||||
public static String captionLanguageOf(@NonNull final Context context,
|
||||
@NonNull final Subtitles subtitles) {
|
||||
final String displayName = subtitles.getLocale().getDisplayName(subtitles.getLocale());
|
||||
@NonNull final SubtitlesStream subtitles) {
|
||||
final String displayName = subtitles.getDisplayLanguageName();
|
||||
return displayName + (subtitles.isAutoGenerated() ? " (" + context.getString(R.string.caption_auto_generated)+ ")" : "");
|
||||
}
|
||||
|
||||
@@ -145,7 +147,7 @@ public class PlayerHelper {
|
||||
|
||||
final StreamInfoItem nextVideo = info.getNextVideo();
|
||||
if (nextVideo != null && !urls.contains(nextVideo.getUrl())) {
|
||||
return new SinglePlayQueue(nextVideo);
|
||||
return getAutoQueuedSinglePlayQueue(nextVideo);
|
||||
}
|
||||
|
||||
final List<InfoItem> relatedItems = info.getRelatedStreams();
|
||||
@@ -158,7 +160,7 @@ public class PlayerHelper {
|
||||
}
|
||||
}
|
||||
Collections.shuffle(autoQueueItems);
|
||||
return autoQueueItems.isEmpty() ? null : new SinglePlayQueue(autoQueueItems.get(0));
|
||||
return autoQueueItems.isEmpty() ? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@@ -177,10 +179,6 @@ public class PlayerHelper {
|
||||
return isBrightnessGestureEnabled(context, true);
|
||||
}
|
||||
|
||||
public static boolean isUsingOldPlayer(@NonNull final Context context) {
|
||||
return isUsingOldPlayer(context, false);
|
||||
}
|
||||
|
||||
public static boolean isRememberingPopupDimensions(@NonNull final Context context) {
|
||||
return isRememberingPopupDimensions(context, true);
|
||||
}
|
||||
@@ -318,10 +316,6 @@ public class PlayerHelper {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.brightness_gesture_control_key), b);
|
||||
}
|
||||
|
||||
private static boolean isUsingOldPlayer(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.use_old_player_key), b);
|
||||
}
|
||||
|
||||
private static boolean isRememberingPopupDimensions(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
|
||||
}
|
||||
@@ -358,4 +352,10 @@ public class PlayerHelper {
|
||||
return getPreferences(context).getString(context.getString(R.string.minimize_on_exit_key),
|
||||
key);
|
||||
}
|
||||
|
||||
private static SinglePlayQueue getAutoQueuedSinglePlayQueue(StreamInfoItem streamInfoItem) {
|
||||
SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);
|
||||
singlePlayQueue.getItem().setAutoQueued(true);
|
||||
return singlePlayQueue;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user