mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-09-21 10:40:51 +02:00
Compare commits
451 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
43a1c4ce11 | ||
![]() |
9ca048a881 | ||
![]() |
bab3dd417e | ||
![]() |
11541310d6 | ||
![]() |
76740303c5 | ||
![]() |
0a7ecb89ce | ||
![]() |
c16a7d5da2 | ||
![]() |
b03723c3fb | ||
![]() |
40213b2d6a | ||
![]() |
e8b71e867c | ||
![]() |
8ab1b7fd8f | ||
![]() |
8009aa975e | ||
![]() |
cea706d14a | ||
![]() |
3cb4952281 | ||
![]() |
ec1ae647b0 | ||
![]() |
a3be9f36b3 | ||
![]() |
451910763d | ||
![]() |
884dc38701 | ||
![]() |
3cca7aead9 | ||
![]() |
4c4852129e | ||
![]() |
ae2b0cc76b | ||
![]() |
71e963c853 | ||
![]() |
89b680f6d9 | ||
![]() |
f3eacac4ce | ||
![]() |
26ec32cbe1 | ||
![]() |
6d74038866 | ||
![]() |
7e45e88914 | ||
![]() |
62a4869eb7 | ||
![]() |
4f8b51701b | ||
![]() |
169bcc2550 | ||
![]() |
88b29cbbf9 | ||
![]() |
51d4d0d3dc | ||
![]() |
a482aa1e21 | ||
![]() |
040b38689d | ||
![]() |
8e8e53c4d5 | ||
![]() |
d717c6d2f6 | ||
![]() |
9587ce97a8 | ||
![]() |
aaaf573475 | ||
![]() |
6c6f322d90 | ||
![]() |
c03f0ed1fb | ||
![]() |
55287393be | ||
![]() |
9575f92165 | ||
![]() |
a5cbaad804 | ||
![]() |
f492414b5e | ||
![]() |
3e6ddf7176 | ||
![]() |
29ffb05653 | ||
![]() |
50c3ee2e9c | ||
![]() |
6292470677 | ||
![]() |
8d912f2673 | ||
![]() |
227d129c3a | ||
![]() |
d9eeb6afa0 | ||
![]() |
478aecb0f4 | ||
![]() |
abb82154c9 | ||
![]() |
03c8b6b5f5 | ||
![]() |
2281300165 | ||
![]() |
84068d101b | ||
![]() |
62020fa85b | ||
![]() |
3c8bf5ccc9 | ||
![]() |
71d5762be8 | ||
![]() |
65d5358366 | ||
![]() |
6ecdfaf19e | ||
![]() |
32bd6ae1ac | ||
![]() |
92231a1e26 | ||
![]() |
38ad4dc440 | ||
![]() |
6645a47b0e | ||
![]() |
75a2d20b0b | ||
![]() |
8c15d708e6 | ||
![]() |
539b7ad87b | ||
![]() |
fc7c2c9f5a | ||
![]() |
3cd760f654 | ||
![]() |
2ec0a5d003 | ||
![]() |
1a605e814b | ||
![]() |
fe0053a15d | ||
![]() |
014f3e5aff | ||
![]() |
4a25e3b644 | ||
![]() |
e7f59bc436 | ||
![]() |
488d1ccd5a | ||
![]() |
409b36c254 | ||
![]() |
b0e567dbfa | ||
![]() |
c6086ba281 | ||
![]() |
aede925351 | ||
![]() |
9d38c66510 | ||
![]() |
0c516189c3 | ||
![]() |
c2936ea289 | ||
![]() |
dc33460a34 | ||
![]() |
ffacc93b55 | ||
![]() |
74f0ee2718 | ||
![]() |
7189791d9f | ||
![]() |
dc18d53c8f | ||
![]() |
f71ef8e130 | ||
![]() |
6eec9d8993 | ||
![]() |
9560f98359 | ||
![]() |
217433bf69 | ||
![]() |
ff5db1b939 | ||
![]() |
455a46d3ad | ||
![]() |
039a879104 | ||
![]() |
dfba9ea53b | ||
![]() |
177cce5e8f | ||
![]() |
309d36260e | ||
![]() |
bdc73eb755 | ||
![]() |
0786750eb9 | ||
![]() |
9f5d921275 | ||
![]() |
8991b2d8e3 | ||
![]() |
79dffce59b | ||
![]() |
e0301a621b | ||
![]() |
1e4361abdc | ||
![]() |
80c26fd278 | ||
![]() |
3339531086 | ||
![]() |
07ad9fbb11 | ||
![]() |
6ddc581d76 | ||
![]() |
dfe94172aa | ||
![]() |
edb632f9c7 | ||
![]() |
7f73612b9f | ||
![]() |
7f130f18b6 | ||
![]() |
d05a4c53aa | ||
![]() |
d99a9b90a0 | ||
![]() |
f311225312 | ||
![]() |
dee17fc313 | ||
![]() |
0662538d60 | ||
![]() |
5b52ad91ac | ||
![]() |
8552c41bff | ||
![]() |
7ecd298285 | ||
![]() |
2546f515d1 | ||
![]() |
8804924d27 | ||
![]() |
e410cae141 | ||
![]() |
140cfaec90 | ||
![]() |
a0f20aac23 | ||
![]() |
93fafb362f | ||
![]() |
9152df5512 | ||
![]() |
415bcd9f7e | ||
![]() |
2224033a85 | ||
![]() |
ade0498684 | ||
![]() |
211b00fff4 | ||
![]() |
77c72d03a8 | ||
![]() |
f429d93351 | ||
![]() |
e428f8b116 | ||
![]() |
9482b9d638 | ||
![]() |
e019c9f720 | ||
![]() |
e2778366e9 | ||
![]() |
36d2f8339c | ||
![]() |
a4a0c3b9fd | ||
![]() |
1b1b6c8af8 | ||
![]() |
064fd2bc68 | ||
![]() |
13152ab6ea | ||
![]() |
1743d821eb | ||
![]() |
ff16e577ef | ||
![]() |
86062c6e94 | ||
![]() |
8ce2350563 | ||
![]() |
cb8989af7f | ||
![]() |
877fa45eb4 | ||
![]() |
e33942486d | ||
![]() |
9606e080ef | ||
![]() |
d5cd9c55be | ||
![]() |
9c197ced80 | ||
![]() |
f8424599e1 | ||
![]() |
ccee18057a | ||
![]() |
1ad0f342ad | ||
![]() |
7a16ac574b | ||
![]() |
f46738f750 | ||
![]() |
fb8ff3fece | ||
![]() |
7498dd3800 | ||
![]() |
957c31b9c5 | ||
![]() |
b260570e8c | ||
![]() |
46542747b7 | ||
![]() |
d64480fc9b | ||
![]() |
c00e694d40 | ||
![]() |
f0761cc95e | ||
![]() |
068554955c | ||
![]() |
a2f915f556 | ||
![]() |
cffd35c974 | ||
![]() |
2ef2aa98a4 | ||
![]() |
ba98b30aa4 | ||
![]() |
87c0f9c803 | ||
![]() |
ce620b9e28 | ||
![]() |
fcbb5536eb | ||
![]() |
72df6dbd80 | ||
![]() |
3a6b023dbf | ||
![]() |
8b67f1358d | ||
![]() |
2aebb3b8db | ||
![]() |
f2b38be2b0 | ||
![]() |
a43001f30d | ||
![]() |
17abe1ea5c | ||
![]() |
6aaefab618 | ||
![]() |
cd867fb4d1 | ||
![]() |
25988f61a6 | ||
![]() |
be421e580d | ||
![]() |
641ab25470 | ||
![]() |
a508539c2e | ||
![]() |
cac79d9a0d | ||
![]() |
9ede8118da | ||
![]() |
e47761750a | ||
![]() |
2f181ce7c9 | ||
![]() |
3a13d4a1de | ||
![]() |
719de00e0f | ||
![]() |
ddffe99f53 | ||
![]() |
9ade596f06 | ||
![]() |
46f413b7f2 | ||
![]() |
5fe2e7b8ad | ||
![]() |
3008cbb5f4 | ||
![]() |
f7983960e5 | ||
![]() |
bb8007bb7c | ||
![]() |
01751ba97a | ||
![]() |
f41475d11c | ||
![]() |
d8914d9b6d | ||
![]() |
94cc2ad365 | ||
![]() |
e07464b81c | ||
![]() |
9d231b55b5 | ||
![]() |
77d8dac3c1 | ||
![]() |
e160015283 | ||
![]() |
aeb0cac3ee | ||
![]() |
a539f94837 | ||
![]() |
df70751071 | ||
![]() |
e55f1dff78 | ||
![]() |
47646e1c62 | ||
![]() |
80e673f20c | ||
![]() |
9ca8c5480c | ||
![]() |
4d0d3c7ead | ||
![]() |
58137aadc9 | ||
![]() |
82a59ae479 | ||
![]() |
affd23b14e | ||
![]() |
9c7f249756 | ||
![]() |
e2a0502171 | ||
![]() |
3832a4b355 | ||
![]() |
84f059415c | ||
![]() |
bb292e3199 | ||
![]() |
70541bf561 | ||
![]() |
48dd8e88e3 | ||
![]() |
52c2e0899d | ||
![]() |
9955d5b62f | ||
![]() |
8e26247fa1 | ||
![]() |
72924e2692 | ||
![]() |
8c2c8d630f | ||
![]() |
5ccf0baa6b | ||
![]() |
8fff0ccdf2 | ||
![]() |
35264bfb97 | ||
![]() |
4c9b0dc8e9 | ||
![]() |
4deb7caa83 | ||
![]() |
45f1a512b4 | ||
![]() |
ab95fdc087 | ||
![]() |
df608b9ded | ||
![]() |
6a4c81d160 | ||
![]() |
040d658540 | ||
![]() |
cbc9913e9c | ||
![]() |
f007cbd5ee | ||
![]() |
7351591df7 | ||
![]() |
5222d4cbba | ||
![]() |
ef9966815e | ||
![]() |
ad73440ce9 | ||
![]() |
c81cfdc455 | ||
![]() |
134c3804db | ||
![]() |
e355df5eda | ||
![]() |
a68e0a95f4 | ||
![]() |
6fd6facf72 | ||
![]() |
00102d4048 | ||
![]() |
adbeff11d4 | ||
![]() |
a8fe329678 | ||
![]() |
a4b61bf730 | ||
![]() |
ee592def0c | ||
![]() |
c14e117239 | ||
![]() |
b6e94dde0c | ||
![]() |
63a4a44d7e | ||
![]() |
eb8ab5d527 | ||
![]() |
b778d40d65 | ||
![]() |
2f570672dc | ||
![]() |
9290ed490f | ||
![]() |
1bda812c75 | ||
![]() |
ab82406c98 | ||
![]() |
be763349df | ||
![]() |
91764ad601 | ||
![]() |
1c4031aa38 | ||
![]() |
c3bc648dd4 | ||
![]() |
c97d794272 | ||
![]() |
8e7d2e91e9 | ||
![]() |
cf0fbbbd3d | ||
![]() |
7d3ede7946 | ||
![]() |
1aa308eb5d | ||
![]() |
4bfd30e34a | ||
![]() |
0f46c90688 | ||
![]() |
aa2173fc4a | ||
![]() |
932cb1d19c | ||
![]() |
18b038d8e4 | ||
![]() |
2ac71c75c0 | ||
![]() |
4067ba5232 | ||
![]() |
ab47dd5a5b | ||
![]() |
3130910307 | ||
![]() |
22113439a4 | ||
![]() |
2d25a9ad7a | ||
![]() |
8f0a2cc2f0 | ||
![]() |
4ca39258c9 | ||
![]() |
d0c0238b8b | ||
![]() |
f9f08e5169 | ||
![]() |
cfc51b2401 | ||
![]() |
54c2704cb5 | ||
![]() |
9b51c946fe | ||
![]() |
06f38cbcb4 | ||
![]() |
f368d6b257 | ||
![]() |
f4301da14d | ||
![]() |
f34c09f165 | ||
![]() |
6d1db56512 | ||
![]() |
b70c07d004 | ||
![]() |
f9f48a5eb6 | ||
![]() |
14e4e73444 | ||
![]() |
f2ce4d2daf | ||
![]() |
468ebdda87 | ||
![]() |
a1f0fb3b14 | ||
![]() |
3c9f4de234 | ||
![]() |
8ae411619f | ||
![]() |
61e5c9121a | ||
![]() |
8a3cf0d5dc | ||
![]() |
ecb5df65ac | ||
![]() |
5f1e98a0d3 | ||
![]() |
3b9a477499 | ||
![]() |
e5bf98a741 | ||
![]() |
06fafc247e | ||
![]() |
e8bb17b631 | ||
![]() |
363cd07883 | ||
![]() |
2b4a9286c4 | ||
![]() |
34c985026f | ||
![]() |
4a0aa42914 | ||
![]() |
746c2a15bf | ||
![]() |
9318bb5306 | ||
![]() |
2a10ceb74f | ||
![]() |
83f4db59e2 | ||
![]() |
9f66f759ad | ||
![]() |
6f015349e8 | ||
![]() |
a37802c2b9 | ||
![]() |
4fa3baf5e1 | ||
![]() |
ef8c2c81d5 | ||
![]() |
8c80d8c457 | ||
![]() |
1596872c54 | ||
![]() |
da8873fa78 | ||
![]() |
ecd8439b3f | ||
![]() |
5d178532ac | ||
![]() |
08e40a013d | ||
![]() |
c136f7363c | ||
![]() |
a60f10d739 | ||
![]() |
ae46afcb42 | ||
![]() |
bffb9f6800 | ||
![]() |
79aa9ad04b | ||
![]() |
ff5714f04a | ||
![]() |
ce499a9766 | ||
![]() |
d3bb8b7651 | ||
![]() |
6d9c23c4cb | ||
![]() |
b95a9332a9 | ||
![]() |
609715eb5c | ||
![]() |
a1266c919c | ||
![]() |
a1925a0302 | ||
![]() |
a7a4c03372 | ||
![]() |
37201600e0 | ||
![]() |
a94f40ed62 | ||
![]() |
0b08cf8c76 | ||
![]() |
33e29be7db | ||
![]() |
bd1c7851c7 | ||
![]() |
82faea5965 | ||
![]() |
0bbbfd3217 | ||
![]() |
fb578ecda8 | ||
![]() |
e804647a65 | ||
![]() |
c3c3a94593 | ||
![]() |
9d55569f80 | ||
![]() |
5dd8271c15 | ||
![]() |
7a4a54c3ea | ||
![]() |
7c9078a625 | ||
![]() |
71ae342f52 | ||
![]() |
b43c56085d | ||
![]() |
83d2ab95e0 | ||
![]() |
20a8d7372c | ||
![]() |
c36ba88db7 | ||
![]() |
4aa23023ee | ||
![]() |
8735cf931a | ||
![]() |
2473069326 | ||
![]() |
03bab57a97 | ||
![]() |
e3baf69533 | ||
![]() |
461f747af1 | ||
![]() |
028872a7d8 | ||
![]() |
a1483b6c55 | ||
![]() |
e406ba094c | ||
![]() |
c100d15ba8 | ||
![]() |
b4ea592638 | ||
![]() |
16b757d9a3 | ||
![]() |
2aa801a392 | ||
![]() |
b838344526 | ||
![]() |
233a3df222 | ||
![]() |
da6661b1ea | ||
![]() |
c6e120fc51 | ||
![]() |
c416a1254d | ||
![]() |
2aa4f6ddda | ||
![]() |
129597023d | ||
![]() |
02aed86b7e | ||
![]() |
e44f4b5823 | ||
![]() |
2f0c0f0fc2 | ||
![]() |
e5ce3f3007 | ||
![]() |
095a2be748 | ||
![]() |
c75fe88757 | ||
![]() |
a8ff4b0744 | ||
![]() |
c02c511e31 | ||
![]() |
b9550fb528 | ||
![]() |
a37d8f083a | ||
![]() |
abff1f537b | ||
![]() |
483fde5e42 | ||
![]() |
761b7ea57b | ||
![]() |
af92631a0c | ||
![]() |
ffe832d061 | ||
![]() |
a08cbfcef1 | ||
![]() |
d2d6ac1bf8 | ||
![]() |
615ffca64b | ||
![]() |
2fcf6197c5 | ||
![]() |
22d31ae14f | ||
![]() |
8c786e121b | ||
![]() |
99d2a33c8c | ||
![]() |
010a24db3f | ||
![]() |
9408da453b | ||
![]() |
dbda4202fd | ||
![]() |
5776e91459 | ||
![]() |
664d7b69b1 | ||
![]() |
6b9140c60d | ||
![]() |
383857d110 | ||
![]() |
cffd049c8a | ||
![]() |
ecabc65e94 | ||
![]() |
780f1d5da3 | ||
![]() |
e2d0fc34a3 | ||
![]() |
3404231457 | ||
![]() |
b17928570d | ||
![]() |
8ed86261ef | ||
![]() |
a313b91a0a | ||
![]() |
7b6dae20be | ||
![]() |
552c70bb0d | ||
![]() |
8f734737f0 | ||
![]() |
800e7bcb7a | ||
![]() |
2002234d86 | ||
![]() |
5923663e08 | ||
![]() |
4cdf20ab8c | ||
![]() |
af65a1cfef | ||
![]() |
927057ab83 | ||
![]() |
ffbc001ad5 | ||
![]() |
f5625a1151 | ||
![]() |
ce2ceb8a1b | ||
![]() |
c14771534f | ||
![]() |
553cec16d5 | ||
![]() |
d17496f720 | ||
![]() |
89e70626eb | ||
![]() |
2ccae841d6 | ||
![]() |
07f6d0f149 | ||
![]() |
319d769233 | ||
![]() |
6ec393699e | ||
![]() |
f8d9e0fa60 | ||
![]() |
50ed962a82 | ||
![]() |
8654705e9b | ||
![]() |
b79ed8185f | ||
![]() |
0d6c67f64f | ||
![]() |
e97a6569a6 | ||
![]() |
c2fcae7c43 |
3
.github/CONTRIBUTING.md
vendored
3
.github/CONTRIBUTING.md
vendored
@@ -5,13 +5,14 @@ READ THIS GUIDELINES CAREFULLY BEFORE CONTRIBUTING.
|
||||
|
||||
## 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 if a crash occures.
|
||||
Do not report crashes in the GitHub issue tracker. NewPipe has an automated crash report system that will ask you to send a report if a crash occurs.
|
||||
|
||||
## Issue reporting/feature request
|
||||
|
||||
* Search the [existing issues](https://github.com/theScrabi/NewPipe/issues) first to make sure your issue/feature hasn't been reported/requested before
|
||||
* Check if this issue/feature is already fixed/implemented in the repository
|
||||
* If you are an android/java developer you are always welcome to fix/implement an issue/a feature yourself
|
||||
* Use english
|
||||
|
||||
## Bugfixing
|
||||
* If you want to help NewPipe getting bug free, you can send me a mail to tnp@newpipe.schabi.org to let me know that you intent to help, and than register at our [sentry](https://support.schabi.org) setup.
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@
|
||||
/*.iml
|
||||
gradle.properties
|
||||
*~
|
||||
.weblate
|
||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "app/src/main/java/org/schabi/newpipe/extractor"]
|
||||
path = app/src/main/java/org/schabi/newpipe/extractor
|
||||
url = https://github.com/TeamNewPipe/NewPipeExtractor.git
|
24
.travis.yml
24
.travis.yml
@@ -5,35 +5,15 @@ android:
|
||||
components:
|
||||
# The BuildTools version used by NewPipe
|
||||
- tools
|
||||
- build-tools-23.0.3
|
||||
- build-tools-25.0.2
|
||||
|
||||
# The SDK version used to compile NewPipe
|
||||
- android-25
|
||||
|
||||
# Additional components
|
||||
- extra-android-support
|
||||
- extra-android-m2repository
|
||||
- extra-google-m2repository
|
||||
|
||||
# Emulators
|
||||
- sys-img-armeabi-v7a-android-21
|
||||
- sys-img-armeabi-v7a-android-19
|
||||
- sys-img-armeabi-v7a-android-15
|
||||
|
||||
env:
|
||||
global:
|
||||
- ADB_INSTALL_TIMEOUT=8 # minutes (2 by default)
|
||||
- GRADLE_OPTS=-Xmx512m # give gradle more memory since it seem to fail otherwise
|
||||
matrix:
|
||||
- ANDROID_TARGET=android-21 ANDROID_ABI=armeabi-v7a
|
||||
|
||||
before_script:
|
||||
- echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI
|
||||
- emulator -avd test -no-skin -no-audio -no-window &
|
||||
- android-wait-for-emulator
|
||||
- adb shell input keyevent 82 &
|
||||
|
||||
script: ./gradlew --info build connectedCheck
|
||||
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
|
||||
|
||||
licenses:
|
||||
- '.+'
|
||||
|
BIN
CCC_NewPipe_presentation.pdf
Normal file
BIN
CCC_NewPipe_presentation.pdf
Normal file
Binary file not shown.
26
README.md
26
README.md
@@ -19,11 +19,16 @@ Project status:
|
||||
|
||||
## Screenshots
|
||||
|
||||
[<img src="screenshots/screenshot_1.png" width=150>](screenshots/screenshot_1.png)
|
||||
[<img src="screenshots/screenshot_2.png" width=150>](screenshots/screenshot_2.png)
|
||||
[<img src="screenshots/screenshot_3.png" width=150>](screenshots/screenshot_3.png)
|
||||
[<img src="screenshots/screenshot_4.png" width=150>](screenshots/screenshot_4.png)
|
||||
[<img src="screenshots/screenshot_5.png" width=150>](screenshots/screenshot_5.png)
|
||||
[<img src="screenshots/screenshot_1.png" width=160>](screenshots/screenshot_1.png)
|
||||
[<img src="screenshots/screenshot_2.png" width=160>](screenshots/screenshot_2.png)
|
||||
[<img src="screenshots/screenshot_3.png" width=160>](screenshots/screenshot_3.png)
|
||||
[<img src="screenshots/screenshot_4.png" width=160>](screenshots/screenshot_4.png)
|
||||
[<img src="screenshots/screenshot_5.png" width=160>](screenshots/screenshot_5.png)
|
||||
[<img src="screenshots/screenshot_6.png" width=160>](screenshots/screenshot_6.png)
|
||||
[<img src="screenshots/screenshot_7.png" width=160>](screenshots/screenshot_7.png)
|
||||
[<img src="screenshots/screenshot_8.png" width=160>](screenshots/screenshot_8.png)
|
||||
[<img src="screenshots/screenshot_9.png" width=160>](screenshots/screenshot_9.png)
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
@@ -35,25 +40,30 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only
|
||||
* Display general information about a video
|
||||
* Watch YouTube videos
|
||||
* Listen to YouTube videos (experimental)
|
||||
* Popup mode (floating player)
|
||||
* Select the streaming player to watch the video with
|
||||
* Download videos
|
||||
* Download audio only * Open a video in Kodi
|
||||
* Download audio only
|
||||
* Open a video in Kodi
|
||||
* Show Next/Related videos
|
||||
* Search YouTube in a specific language
|
||||
* Watch age restricted material
|
||||
* Display general information about channels
|
||||
* Search channels
|
||||
* Watch videos from a channel
|
||||
* Orbot/Tor support (not yet directly)
|
||||
* 1080p/2k/4k support
|
||||
|
||||
### Coming Features
|
||||
|
||||
* Orbot/Tor support
|
||||
* Bookmarks
|
||||
* View history
|
||||
* Search history
|
||||
* Subscribe to channels
|
||||
* Watch videos from a channel
|
||||
* Search/Watch Playlists
|
||||
* Queeing videos
|
||||
* Subtitles support
|
||||
* livestream support
|
||||
* ... and many more
|
||||
|
||||
### Multiservice support
|
||||
|
@@ -2,14 +2,16 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion '23.0.3'
|
||||
buildToolsVersion '25.0.2'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.schabi.newpipe"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 25
|
||||
versionCode 24
|
||||
versionName "0.8.10"
|
||||
versionCode 36
|
||||
versionName "0.9.9"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
@@ -31,22 +33,29 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile 'junit:junit:4.12'
|
||||
compile 'com.android.support:appcompat-v7:25.1.1'
|
||||
compile 'com.android.support:support-v4:25.1.1'
|
||||
compile 'com.android.support:design:25.1.1'
|
||||
compile 'com.android.support:recyclerview-v7:25.1.1'
|
||||
compile 'org.jsoup:jsoup:1.8.3'
|
||||
compile 'org.mozilla:rhino:1.7.7'
|
||||
compile 'info.guardianproject.netcipher:netcipher:1.2'
|
||||
compile 'de.hdodenhof:circleimageview:2.0.0'
|
||||
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||
compile 'com.github.nirhart:parallaxscroll:1.0'
|
||||
compile 'com.google.android.exoplayer:exoplayer:r1.5.5'
|
||||
compile 'com.google.code.gson:gson:2.4'
|
||||
compile 'com.nononsenseapps:filepicker:3.0.0'
|
||||
compile 'ch.acra:acra:4.9.0'
|
||||
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
testCompile 'org.json:json:20160810'
|
||||
|
||||
compile 'com.android.support:appcompat-v7:25.3.1'
|
||||
compile 'com.android.support:support-v4:25.3.1'
|
||||
compile 'com.android.support:design:25.3.1'
|
||||
compile 'com.android.support:recyclerview-v7:25.3.1'
|
||||
|
||||
compile 'com.google.code.gson:gson:2.7'
|
||||
compile 'org.jsoup:jsoup:1.8.3'
|
||||
compile 'org.mozilla:rhino:1.7.7'
|
||||
compile 'ch.acra:acra:4.9.0'
|
||||
compile 'info.guardianproject.netcipher:netcipher:1.2'
|
||||
|
||||
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||
compile 'de.hdodenhof:circleimageview:2.0.0'
|
||||
compile 'com.github.nirhart:parallaxscroll:1.0'
|
||||
compile 'com.nononsenseapps:filepicker:3.0.0'
|
||||
compile 'com.google.android.exoplayer:exoplayer:r2.3.1'
|
||||
}
|
||||
|
@@ -0,0 +1,37 @@
|
||||
package org.schabi.newpipe.report;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.support.test.filters.LargeTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.report.ErrorActivity.ErrorInfo;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Instrumented tests for {@link ErrorInfo}
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class ErrorInfoTest {
|
||||
|
||||
@Test
|
||||
public void errorInfo_testParcelable() {
|
||||
ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request", R.string.general_error);
|
||||
// Obtain a Parcel object and write the parcelable object to it:
|
||||
Parcel parcel = Parcel.obtain();
|
||||
info.writeToParcel(parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
|
||||
|
||||
assertEquals(UserAction.USER_REPORT, infoFromParcel.userAction);
|
||||
assertEquals("youtube", infoFromParcel.serviceName);
|
||||
assertEquals("request", infoFromParcel.request);
|
||||
assertEquals(R.string.general_error, infoFromParcel.message);
|
||||
|
||||
parcel.recycle();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -20,10 +20,6 @@ package org.schabi.newpipe;
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Singleton:
|
||||
* Used to send data between certain Activity/Services within the same process.
|
||||
@@ -39,8 +35,5 @@ public class ActivityCommunicator {
|
||||
return activityCommunicator;
|
||||
}
|
||||
|
||||
// Thumbnail send from VideoItemDetailFragment to BackgroundPlayer
|
||||
public volatile Bitmap backgroundPlayerThumbnail;
|
||||
|
||||
public volatile Class returnActivity;
|
||||
}
|
||||
|
@@ -14,7 +14,9 @@ import org.acra.sender.ReportSenderFactory;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.report.AcraReportSenderFactory;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import info.guardianproject.netcipher.NetCipher;
|
||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
||||
@@ -58,7 +60,7 @@ public class App extends Application {
|
||||
} catch(ACRAConfigurationException ace) {
|
||||
ace.printStackTrace();
|
||||
ErrorActivity.reportError(this, ace, null, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,"none",
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SEARCHED,"none",
|
||||
"Could not initialize ACRA crash report", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
@@ -82,6 +84,8 @@ public class App extends Application {
|
||||
// DO NOT REMOVE THIS FUNCTION!!!
|
||||
// Otherwise downloadPathPreference has invalid value.
|
||||
SettingsActivity.initSettings(this);
|
||||
|
||||
ThemeHelper.setTheme(getApplicationContext());
|
||||
}
|
||||
|
||||
/**
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,15 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.View;
|
||||
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 01.08.16.
|
||||
@@ -33,11 +34,11 @@ import org.schabi.newpipe.extractor.NewPipe;
|
||||
public class ImageErrorLoadingListener implements ImageLoadingListener {
|
||||
|
||||
private int serviceId = -1;
|
||||
private Activity activity = null;
|
||||
private Context context = null;
|
||||
private View rootView = null;
|
||||
|
||||
public ImageErrorLoadingListener(Activity activity, View rootView, int serviceId) {
|
||||
this.activity = activity;
|
||||
public ImageErrorLoadingListener(Context context, View rootView, int serviceId) {
|
||||
this.context = context;
|
||||
this.serviceId= serviceId;
|
||||
this.rootView = rootView;
|
||||
}
|
||||
@@ -47,9 +48,9 @@ public class ImageErrorLoadingListener implements ImageLoadingListener {
|
||||
|
||||
@Override
|
||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||
ErrorActivity.reportError(activity,
|
||||
ErrorActivity.reportError(context,
|
||||
failReason.getCause(), null, rootView,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.LOAD_IMAGE,
|
||||
NewPipe.getNameOfService(serviceId), imageUri,
|
||||
R.string.could_not_load_image));
|
||||
}
|
||||
|
@@ -1,72 +1,158 @@
|
||||
/*
|
||||
* Created by Christian Schabesberger on 02.08.16.
|
||||
* <p>
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* DownloadActivity.java is part of NewPipe.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import org.schabi.newpipe.download.DownloadActivity;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.fragments.MainFragment;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
import org.schabi.newpipe.fragments.search.SearchFragment;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 02.08.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* DownloadActivity.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 MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
public static final boolean DEBUG = false;
|
||||
|
||||
public class MainActivity extends ThemableActivity {
|
||||
private Fragment mainFragment = null;
|
||||
private static final String TAG = MainActivity.class.toString();
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Activity's LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||
ThemeHelper.setTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
mainFragment = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.search_fragment);
|
||||
|
||||
if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
initFragments();
|
||||
}
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
inflater.inflate(R.menu.main_menu, menu);
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
|
||||
if (DEBUG) Log.d(TAG, "Theme has changed, recreating activity...");
|
||||
sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
|
||||
this.recreate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
|
||||
if (intent != null) {
|
||||
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
|
||||
// to not destroy the already created backstack
|
||||
String action = intent.getAction();
|
||||
if ((action != null && action.equals(Intent.ACTION_MAIN)) && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) return;
|
||||
}
|
||||
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (DEBUG) Log.d(TAG, "onBackPressed() called");
|
||||
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||
if (fragment instanceof VideoDetailFragment) if (((VideoDetailFragment) fragment).onActivityBackPressed()) return;
|
||||
|
||||
super.onBackPressed();
|
||||
|
||||
fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0 && !(fragment instanceof MainFragment)) {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "]");
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||
if (!(fragment instanceof VideoDetailFragment)) {
|
||||
findViewById(R.id.toolbar).findViewById(R.id.toolbar_spinner).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (!(fragment instanceof SearchFragment)) {
|
||||
findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container).setVisibility(View.GONE);
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.main_menu, menu);
|
||||
}
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayShowTitleEnabled(false);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
|
||||
int id = item.getItemId();
|
||||
|
||||
switch (id) {
|
||||
case android.R.id.home: {
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||
if (fragment instanceof VideoDetailFragment) ((VideoDetailFragment) fragment).clearHistory();
|
||||
|
||||
getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
NavigationHelper.openMainFragment(getSupportFragmentManager());
|
||||
return true;
|
||||
}
|
||||
case R.id.action_settings: {
|
||||
@@ -87,8 +173,44 @@ public class MainActivity extends ThemableActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
//ignore back
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Init
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void initFragments() {
|
||||
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_URL)) {
|
||||
handleIntent(getIntent());
|
||||
} else NavigationHelper.openMainFragment(getSupportFragmentManager());
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
||||
|
||||
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
|
||||
String url = intent.getStringExtra(Constants.KEY_URL);
|
||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
String title = intent.getStringExtra(Constants.KEY_TITLE);
|
||||
switch (((StreamingService.LinkType) intent.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
|
||||
case STREAM:
|
||||
boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
|
||||
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(), serviceId, url, title, autoPlay);
|
||||
break;
|
||||
case CHANNEL:
|
||||
NavigationHelper.openChannelFragment(getSupportFragmentManager(), serviceId, url, title);
|
||||
break;
|
||||
}
|
||||
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
|
||||
String searchQuery = intent.getStringExtra(Constants.KEY_QUERY);
|
||||
if (searchQuery == null) searchQuery = "";
|
||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
NavigationHelper.openSearchFragment(getSupportFragmentManager(), serviceId, searchQuery);
|
||||
} else {
|
||||
getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
NavigationHelper.openMainFragment(getSupportFragmentManager());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.ValueCallback;
|
||||
@@ -48,10 +49,15 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
// Set return to Cancel by default
|
||||
setResult(RESULT_CANCELED);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setTitle(R.string.reCaptcha_title);
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setTitle(R.string.reCaptcha_title);
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
}
|
||||
|
||||
WebView myWebView = (WebView) findViewById(R.id.reCaptchaWebView);
|
||||
|
||||
|
133
app/src/main/java/org/schabi/newpipe/RouterActivity.java
Normal file
133
app/src/main/java/org/schabi/newpipe/RouterActivity.java
Normal file
@@ -0,0 +1,133 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
/*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* RouterActivity .java is part of NewPipe.
|
||||
*
|
||||
* OpenHitboxStreams 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.
|
||||
*
|
||||
* OpenHitboxStreams 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 OpenHitboxStreams. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This Acitivty is designed to route share/open intents to the specified service, and
|
||||
* to the part of the service which can handle the url.
|
||||
*/
|
||||
public class RouterActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String videoUrl = getUrl(getIntent());
|
||||
handleUrl(videoUrl);
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
protected void handleUrl(String url) {
|
||||
try {
|
||||
NavigationHelper.openByLink(this, url);
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Removes invisible separators (\p{Z}) and punctuation characters including
|
||||
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
|
||||
* more details.
|
||||
*/
|
||||
protected final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
|
||||
|
||||
protected String getUrl(Intent intent) {
|
||||
// first gather data and find service
|
||||
String videoUrl = null;
|
||||
if (intent.getData() != null) {
|
||||
// this means the video was called though another app
|
||||
videoUrl = intent.getData().toString();
|
||||
} else if (intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
|
||||
//this means that vidoe was called through share menu
|
||||
String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
videoUrl = getUris(extraText)[0];
|
||||
}
|
||||
|
||||
return videoUrl;
|
||||
}
|
||||
|
||||
protected String removeHeadingGibberish(final String input) {
|
||||
int start = 0;
|
||||
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
|
||||
if (!input.substring(i, i + 1).matches("\\p{L}")) {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return input.substring(start, input.length());
|
||||
}
|
||||
|
||||
protected String trim(final String input) {
|
||||
if (input == null || input.length() < 1) {
|
||||
return input;
|
||||
} else {
|
||||
String output = input;
|
||||
while (output.length() > 0 && output.substring(0, 1).matches(REGEX_REMOVE_FROM_URL)) {
|
||||
output = output.substring(1);
|
||||
}
|
||||
while (output.length() > 0
|
||||
&& output.substring(output.length() - 1, output.length()).matches(REGEX_REMOVE_FROM_URL)) {
|
||||
output = output.substring(0, output.length() - 1);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all Strings which look remotely like URLs from a text.
|
||||
* Used if NewPipe was called through share menu.
|
||||
*
|
||||
* @param sharedText text to scan for URLs.
|
||||
* @return potential URLs
|
||||
*/
|
||||
protected String[] getUris(final String sharedText) {
|
||||
final Collection<String> result = new HashSet<>();
|
||||
if (sharedText != null) {
|
||||
final String[] array = sharedText.split("\\p{Space}");
|
||||
for (String s : array) {
|
||||
s = trim(s);
|
||||
if (s.length() != 0) {
|
||||
if (s.matches(".+://.+")) {
|
||||
result.add(removeHeadingGibberish(s));
|
||||
} else if (s.matches(".+\\..+")) {
|
||||
result.add("http://" + s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
|
||||
/**
|
||||
* Get the url from the intent and open a popup player
|
||||
*/
|
||||
public class RouterPopupActivity extends RouterActivity {
|
||||
|
||||
@Override
|
||||
protected void handleUrl(String url) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& !PermissionHelper.checkSystemAlertWindowPermission(this)) {
|
||||
Toast.makeText(this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
StreamingService service = NewPipe.getServiceByUrl(url);
|
||||
if (service == null) {
|
||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent callIntent = new Intent(this, PopupVideoPlayer.class);
|
||||
switch (service.getLinkTypeByUrl(url)) {
|
||||
case STREAM:
|
||||
break;
|
||||
default:
|
||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
callIntent.putExtra(Constants.KEY_URL, url);
|
||||
callIntent.putExtra(Constants.KEY_SERVICE_ID, service.getServiceId());
|
||||
startService(callIntent);
|
||||
}
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
public class ThemableActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getString("theme", getResources().getString(R.string.light_theme_title)).
|
||||
equals(getResources().getString(R.string.dark_theme_title))) {
|
||||
setTheme(R.style.DarkTheme);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getString("theme", getResources().getString(R.string.light_theme_title)).
|
||||
equals(getResources().getString(R.string.dark_theme_title))) {
|
||||
setTheme(R.style.DarkTheme);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,234 +0,0 @@
|
||||
package org.schabi.newpipe.detail;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 18.08.15.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* DetailsMenuHandler.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/>.
|
||||
*/
|
||||
|
||||
|
||||
class ActionBarHandler {
|
||||
private static final String TAG = ActionBarHandler.class.toString();
|
||||
|
||||
private AppCompatActivity activity;
|
||||
private int selectedVideoStream = -1;
|
||||
|
||||
private SharedPreferences defaultPreferences;
|
||||
|
||||
private Menu menu;
|
||||
|
||||
// Only callbacks are listed here, there are more actions which don't need a callback.
|
||||
// those are edited directly. Typically VideoItemDetailFragment will implement those callbacks.
|
||||
private OnActionListener onShareListener;
|
||||
private OnActionListener onOpenInBrowserListener;
|
||||
private OnActionListener onDownloadListener;
|
||||
private OnActionListener onPlayWithKodiListener;
|
||||
private OnActionListener onPlayAudioListener;
|
||||
|
||||
|
||||
// Triggered when a stream related action is triggered.
|
||||
public interface OnActionListener {
|
||||
void onActionSelected(int selectedStreamId);
|
||||
}
|
||||
|
||||
public ActionBarHandler(AppCompatActivity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "ConstantConditions"})
|
||||
public void setupNavMenu(AppCompatActivity activity) {
|
||||
this.activity = activity;
|
||||
try {
|
||||
activity.getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
|
||||
} catch (NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void setupStreamList(final List<VideoStream> videoStreams) {
|
||||
if (activity != null) {
|
||||
selectedVideoStream = 0;
|
||||
|
||||
|
||||
// this array will be shown in the dropdown menu for selecting the stream/resolution.
|
||||
String[] itemArray = new String[videoStreams.size()];
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
VideoStream item = videoStreams.get(i);
|
||||
itemArray[i] = MediaFormat.getNameById(item.format) + " " + item.resolution;
|
||||
}
|
||||
int defaultResolution = getDefaultResolution(videoStreams);
|
||||
|
||||
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(activity.getBaseContext(),
|
||||
android.R.layout.simple_spinner_dropdown_item, itemArray);
|
||||
|
||||
ActionBar ab = activity.getSupportActionBar();
|
||||
//todo: make this throwsable
|
||||
assert ab != null : "Could not get actionbar";
|
||||
ab.setListNavigationCallbacks(itemAdapter
|
||||
, new ActionBar.OnNavigationListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
|
||||
selectedVideoStream = (int) itemId;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
ab.setSelectedNavigationItem(defaultResolution);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int getDefaultResolution(final List<VideoStream> videoStreams) {
|
||||
if (defaultPreferences == null)
|
||||
return 0;
|
||||
|
||||
String defaultResolution = defaultPreferences
|
||||
.getString(activity.getString(R.string.default_resolution_key),
|
||||
activity.getString(R.string.default_resolution_value));
|
||||
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
VideoStream item = videoStreams.get(i);
|
||||
if (defaultResolution.equals(item.resolution)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// this is actually an error,
|
||||
// but maybe there is really no stream fitting to the default value.
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void setupMenu(Menu menu, MenuInflater inflater) {
|
||||
this.menu = menu;
|
||||
|
||||
// CAUTION set item properties programmatically otherwise it would not be accepted by
|
||||
// appcompat itemsinflater.inflate(R.menu.videoitem_detail, menu);
|
||||
|
||||
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
inflater.inflate(R.menu.videoitem_detail, menu);
|
||||
|
||||
showPlayWithKodiAction(defaultPreferences
|
||||
.getBoolean(activity.getString(R.string.show_play_with_kodi_key), false));
|
||||
}
|
||||
|
||||
public boolean onItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
switch (id) {
|
||||
case R.id.menu_item_share: {
|
||||
/*
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, websiteUrl);
|
||||
intent.setType("text/plain");
|
||||
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
|
||||
*/
|
||||
if(onShareListener != null) {
|
||||
onShareListener.onActionSelected(selectedVideoStream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_item_openInBrowser: {
|
||||
if(onOpenInBrowserListener != null) {
|
||||
onOpenInBrowserListener.onActionSelected(selectedVideoStream);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.menu_item_download:
|
||||
if(onDownloadListener != null) {
|
||||
onDownloadListener.onActionSelected(selectedVideoStream);
|
||||
}
|
||||
return true;
|
||||
case R.id.action_settings: {
|
||||
Intent intent = new Intent(activity, SettingsActivity.class);
|
||||
activity.startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
case R.id.action_play_with_kodi:
|
||||
if(onPlayWithKodiListener != null) {
|
||||
onPlayWithKodiListener.onActionSelected(selectedVideoStream);
|
||||
}
|
||||
return true;
|
||||
case R.id.menu_item_play_audio:
|
||||
if(onPlayAudioListener != null) {
|
||||
onPlayAudioListener.onActionSelected(selectedVideoStream);
|
||||
}
|
||||
return true;
|
||||
case R.id.menu_item_downloads: {
|
||||
Intent intent =
|
||||
new Intent(activity, org.schabi.newpipe.download.DownloadActivity.class);
|
||||
activity.startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
Log.e(TAG, "Menu Item not known");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getSelectedVideoStream() {
|
||||
return selectedVideoStream;
|
||||
}
|
||||
|
||||
public void setOnShareListener(OnActionListener listener) {
|
||||
onShareListener = listener;
|
||||
}
|
||||
|
||||
public void setOnOpenInBrowserListener(OnActionListener listener) {
|
||||
onOpenInBrowserListener = listener;
|
||||
}
|
||||
|
||||
public void setOnDownloadListener(OnActionListener listener) {
|
||||
onDownloadListener = listener;
|
||||
}
|
||||
|
||||
public void setOnPlayWithKodiListener(OnActionListener listener) {
|
||||
onPlayWithKodiListener = listener;
|
||||
}
|
||||
|
||||
public void setOnPlayAudioListener(OnActionListener listener) {
|
||||
onPlayAudioListener = listener;
|
||||
}
|
||||
|
||||
public void showAudioAction(boolean visible) {
|
||||
menu.findItem(R.id.menu_item_play_audio).setVisible(visible);
|
||||
}
|
||||
|
||||
public void showDownloadAction(boolean visible) {
|
||||
menu.findItem(R.id.menu_item_download).setVisible(visible);
|
||||
}
|
||||
|
||||
public void showPlayWithKodiAction(boolean visible) {
|
||||
menu.findItem(R.id.action_play_with_kodi).setVisible(visible);
|
||||
}
|
||||
}
|
@@ -1,230 +0,0 @@
|
||||
package org.schabi.newpipe.detail;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 02.08.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* StreamInfoWorker.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 StreamInfoWorker {
|
||||
|
||||
private static final String TAG = StreamInfoWorker.class.toString();
|
||||
|
||||
public interface OnStreamInfoReceivedListener {
|
||||
void onReceive(StreamInfo info);
|
||||
void onError(int messageId);
|
||||
void onReCaptchaException();
|
||||
void onBlockedByGemaError();
|
||||
void onContentErrorWithMessage(int messageId);
|
||||
void onContentError();
|
||||
}
|
||||
|
||||
private class StreamExtractorRunnable implements Runnable {
|
||||
private final Handler h = new Handler();
|
||||
private StreamExtractor streamExtractor;
|
||||
private final int serviceId;
|
||||
private final String videoUrl;
|
||||
private Activity a;
|
||||
|
||||
public StreamExtractorRunnable(Activity a, String videoUrl, int serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
this.videoUrl = videoUrl;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
StreamInfo streamInfo = null;
|
||||
StreamingService service = null;
|
||||
try {
|
||||
service = NewPipe.getService(serviceId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
"", videoUrl, R.string.could_not_get_stream));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
streamExtractor = service.getExtractorInstance(videoUrl);
|
||||
streamInfo = StreamInfo.getVideoInfo(streamExtractor);
|
||||
|
||||
final StreamInfo info = streamInfo;
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener.onReceive(info);
|
||||
}
|
||||
});
|
||||
|
||||
// look for errors during extraction
|
||||
// this if statement only covers extra information.
|
||||
// if these are not available or caused an error, they are just not available
|
||||
// but don't render the stream information unusalbe.
|
||||
if(streamInfo != null &&
|
||||
!streamInfo.errors.isEmpty()) {
|
||||
Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:");
|
||||
for (Throwable e : streamInfo.errors) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "------");
|
||||
}
|
||||
|
||||
View rootView = a != null ? a.findViewById(R.id.video_item_detail) : null;
|
||||
ErrorActivity.reportError(h, a,
|
||||
streamInfo.errors, null, rootView,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, 0 /* no message for the user */));
|
||||
}
|
||||
|
||||
// These errors render the stream information unusable.
|
||||
} catch (ReCaptchaException e) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener.onReCaptchaException();
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener.onError(R.string.network_error);
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} catch (YoutubeStreamExtractor.DecryptException de) {
|
||||
// custom service related exceptions
|
||||
ErrorActivity.reportError(h, a, de, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.youtube_signature_decryption_error));
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
a.finish();
|
||||
}
|
||||
});
|
||||
de.printStackTrace();
|
||||
} catch (YoutubeStreamExtractor.GemaException ge) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener.onBlockedByGemaError();
|
||||
}
|
||||
});
|
||||
} catch(YoutubeStreamExtractor.LiveStreamException e) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener
|
||||
.onContentErrorWithMessage(R.string.live_streams_not_supported);
|
||||
}
|
||||
});
|
||||
}
|
||||
// ----------------------------------------
|
||||
catch(StreamExtractor.ContentNotAvailableException e) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener
|
||||
.onContentError();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} catch(StreamInfo.StreamExctractException e) {
|
||||
if(!streamInfo.errors.isEmpty()) {
|
||||
// !!! if this case ever kicks in someone gets kicked out !!!
|
||||
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
|
||||
} else {
|
||||
ErrorActivity.reportError(h, a, streamInfo.errors, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
|
||||
}
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
a.finish();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} catch (ParsingException e) {
|
||||
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.parsing_error));
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
a.finish();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} catch(Exception e) {
|
||||
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.general_error));
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
a.finish();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static StreamInfoWorker streamInfoWorker = null;
|
||||
private StreamExtractorRunnable runnable = null;
|
||||
private OnStreamInfoReceivedListener onStreamInfoReceivedListener = null;
|
||||
|
||||
private StreamInfoWorker() {
|
||||
|
||||
}
|
||||
|
||||
public static StreamInfoWorker getInstance() {
|
||||
return streamInfoWorker == null ? (streamInfoWorker = new StreamInfoWorker()) : streamInfoWorker;
|
||||
}
|
||||
|
||||
public void search(int serviceId, String url, Activity a) {
|
||||
runnable = new StreamExtractorRunnable(a, url, serviceId);
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void setOnStreamInfoReceivedListener(
|
||||
OnStreamInfoReceivedListener onStreamInfoReceivedListener) {
|
||||
this.onStreamInfoReceivedListener = onStreamInfoReceivedListener;
|
||||
}
|
||||
}
|
@@ -1,253 +0,0 @@
|
||||
package org.schabi.newpipe.detail;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.ThemableActivity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.NavStack;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
|
||||
/**
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* VideoItemDetailActivity.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 VideoItemDetailActivity extends ThemableActivity {
|
||||
|
||||
/**
|
||||
* Removes invisible separators (\p{Z}) and punctuation characters including
|
||||
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
|
||||
* more details.
|
||||
*/
|
||||
private final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
|
||||
|
||||
private static final String TAG = VideoItemDetailActivity.class.toString();
|
||||
|
||||
private VideoItemDetailFragment fragment;
|
||||
|
||||
private String videoUrl;
|
||||
private int currentStreamingService = -1;
|
||||
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_videoitem_detail);
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
// Show the Up button in the action bar.
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
} catch(Exception e) {
|
||||
Log.d(TAG, "Could not get SupportActionBar");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// savedInstanceState is non-null when there is fragment state
|
||||
// saved from previous configurations of this activity
|
||||
// (e.g. when rotating the screen from portrait to landscape).
|
||||
// In this case, the fragment will automatically be re-added
|
||||
// to its container so we don't need to manually add it.
|
||||
// For more information, see the Fragments API guide at:
|
||||
//
|
||||
// http://developer.android.com/guide/components/fragments.html
|
||||
//
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
handleIntent(getIntent());
|
||||
} else {
|
||||
videoUrl = savedInstanceState.getString(NavStack.URL);
|
||||
currentStreamingService = savedInstanceState.getInt(NavStack.SERVICE_ID);
|
||||
NavStack.getInstance()
|
||||
.restoreSavedInstanceState(savedInstanceState);
|
||||
addFragment(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
Bundle arguments = new Bundle();
|
||||
boolean autoplay = false;
|
||||
if (intent.getData() != null) {
|
||||
// this means the video was called though another app
|
||||
videoUrl = intent.getData().toString();
|
||||
currentStreamingService = getServiceIdByUrl(videoUrl);
|
||||
if(currentStreamingService == -1) {
|
||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
autoplay = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getBoolean(getString(R.string.autoplay_through_intent_key), false);
|
||||
} else if(intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
|
||||
//this means that vidoe was called through share menu
|
||||
String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
videoUrl = getUris(extraText)[0];
|
||||
currentStreamingService = getServiceIdByUrl(videoUrl);
|
||||
} else {
|
||||
//this is if the video was called through another NewPipe activity
|
||||
videoUrl = intent.getStringExtra(NavStack.URL);
|
||||
currentStreamingService = intent.getIntExtra(NavStack.SERVICE_ID, -1);
|
||||
}
|
||||
arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY, autoplay);
|
||||
arguments.putString(NavStack.URL, videoUrl);
|
||||
arguments.putInt(NavStack.SERVICE_ID, currentStreamingService);
|
||||
addFragment(arguments);
|
||||
}
|
||||
|
||||
private void addFragment(final Bundle arguments) {
|
||||
// Create the detail fragment and add it to the activity
|
||||
// using a fragment transaction.
|
||||
fragment = new VideoItemDetailFragment();
|
||||
fragment.setArguments(arguments);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.videoitem_detail_container, fragment)
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
App.checkStartTor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(NavStack.URL, videoUrl);
|
||||
outState.putInt(NavStack.SERVICE_ID, currentStreamingService);
|
||||
outState.putBoolean(VideoItemDetailFragment.AUTO_PLAY, false);
|
||||
NavStack.getInstance()
|
||||
.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
// This ID represents the Home or Up button. In the case of this
|
||||
// activity, the Up button is shown. Use NavUtils to allow users
|
||||
// to navigate up one level in the application structure. For
|
||||
// more details, see the Navigation pattern on Android Design:
|
||||
|
||||
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
|
||||
|
||||
NavStack.getInstance()
|
||||
.openMainActivity(this);
|
||||
return true;
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
try {
|
||||
NavStack.getInstance()
|
||||
.navBack(this);
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all Strings which look remotely like URLs from a text.
|
||||
* Used if NewPipe was called through share menu.
|
||||
*
|
||||
* @param sharedText text to scan for URLs.
|
||||
* @return potential URLs
|
||||
*/
|
||||
private String[] getUris(final String sharedText) {
|
||||
final Collection<String> result = new HashSet<>();
|
||||
if (sharedText != null) {
|
||||
final String[] array = sharedText.split("\\p{Space}");
|
||||
for (String s : array) {
|
||||
s = trim(s);
|
||||
if (s.length() != 0) {
|
||||
if (s.matches(".+://.+")) {
|
||||
result.add(removeHeadingGibberish(s));
|
||||
} else if (s.matches(".+\\..+")) {
|
||||
result.add("http://" + s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
|
||||
private static String removeHeadingGibberish(final String input) {
|
||||
int start = 0;
|
||||
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
|
||||
if (!input.substring(i, i + 1).matches("\\p{L}")) {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return input.substring(start, input.length());
|
||||
}
|
||||
|
||||
private static String trim(final String input) {
|
||||
if (input == null || input.length() < 1) {
|
||||
return input;
|
||||
} else {
|
||||
String output = input;
|
||||
while (output.length() > 0 && output.substring(0, 1).matches(REGEX_REMOVE_FROM_URL)) {
|
||||
output = output.substring(1);
|
||||
}
|
||||
while (output.length() > 0
|
||||
&& output.substring(output.length() - 1, output.length()).matches(REGEX_REMOVE_FROM_URL)) {
|
||||
output = output.substring(0, output.length() - 1);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
private int getServiceIdByUrl(String url) {
|
||||
StreamingService[] serviceList = NewPipe.getServices();
|
||||
int service = -1;
|
||||
for (int i = 0; i < serviceList.length; i++) {
|
||||
if (serviceList[i].getStreamUrlIdHandlerInstance().acceptUrl(videoUrl)) {
|
||||
service = i;
|
||||
//videoExtractor = ServiceList.getService(i).getExtractorInstance();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return service;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,89 +1,46 @@
|
||||
package org.schabi.newpipe.download;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.ThemableActivity;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Vector;
|
||||
|
||||
import us.shandian.giga.get.DownloadManager;
|
||||
import us.shandian.giga.service.DownloadManagerService;
|
||||
import us.shandian.giga.ui.fragment.AllMissionsFragment;
|
||||
import us.shandian.giga.ui.fragment.MissionsFragment;
|
||||
import us.shandian.giga.util.CrashHandler;
|
||||
import us.shandian.giga.util.Utility;
|
||||
|
||||
public class DownloadActivity extends ThemableActivity implements AdapterView.OnItemClickListener{
|
||||
|
||||
public static final String INTENT_DOWNLOAD = "us.shandian.giga.intent.DOWNLOAD";
|
||||
|
||||
public static final String INTENT_LIST = "us.shandian.giga.intent.LIST";
|
||||
|
||||
private static final String TAG = DownloadActivity.class.toString();
|
||||
public static final String THREADS = "threads";
|
||||
|
||||
|
||||
private MissionsFragment mFragment;
|
||||
|
||||
|
||||
private String mPendingUrl;
|
||||
private SharedPreferences mPrefs;
|
||||
public class DownloadActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
@TargetApi(21)
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
CrashHandler.init(this);
|
||||
CrashHandler.register();
|
||||
|
||||
// Service
|
||||
Intent i = new Intent();
|
||||
i.setClass(this, DownloadManagerService.class);
|
||||
startService(i);
|
||||
|
||||
ThemeHelper.setTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_downloader);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
|
||||
// its ok if this fails, we will catch that error later, and send it as report
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setTitle(R.string.downloads_title);
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setTitle(R.string.downloads_title);
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
}
|
||||
|
||||
// Fragment
|
||||
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@@ -93,140 +50,17 @@ public class DownloadActivity extends ThemableActivity implements AdapterView.On
|
||||
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
});
|
||||
|
||||
// Intent
|
||||
if (getIntent().getAction() != null && getIntent().getAction().equals(INTENT_DOWNLOAD)) {
|
||||
mPendingUrl = getIntent().getData().toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
if (intent.getAction().equals(INTENT_DOWNLOAD)) {
|
||||
mPendingUrl = intent.getData().toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mPendingUrl != null) {
|
||||
showUrlDialog();
|
||||
mPendingUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFragments() {
|
||||
|
||||
mFragment = new AllMissionsFragment();
|
||||
|
||||
MissionsFragment fragment = new AllMissionsFragment();
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(R.id.frame, mFragment)
|
||||
.replace(R.id.frame, fragment)
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
.commit();
|
||||
}
|
||||
|
||||
private void showUrlDialog() {
|
||||
// Create the view
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View v = inflater.inflate(R.layout.dialog_url, null);
|
||||
final EditText name = Utility.findViewById(v, R.id.file_name);
|
||||
final TextView tCount = Utility.findViewById(v, R.id.threads_count);
|
||||
final SeekBar threads = Utility.findViewById(v, R.id.threads);
|
||||
final Toolbar toolbar = Utility.findViewById(v, R.id.toolbar);
|
||||
final RadioButton audioButton = (RadioButton) Utility.findViewById(v, R.id.audio_button);
|
||||
|
||||
|
||||
threads.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
|
||||
tCount.setText(String.valueOf(progress + 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar p1) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar p1) {
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
int def = mPrefs.getInt(THREADS, 4);
|
||||
threads.setProgress(def - 1);
|
||||
tCount.setText(String.valueOf(def));
|
||||
|
||||
name.setText(getIntent().getStringExtra("fileName"));
|
||||
|
||||
toolbar.setTitle(R.string.add);
|
||||
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp);
|
||||
toolbar.inflateMenu(R.menu.dialog_url);
|
||||
|
||||
// Show the dialog
|
||||
final AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setCancelable(true)
|
||||
.setView(v)
|
||||
.create();
|
||||
|
||||
dialog.show();
|
||||
|
||||
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (item.getItemId() == R.id.okay) {
|
||||
|
||||
String location;
|
||||
if(audioButton.isChecked()) {
|
||||
location = NewPipeSettings.getAudioDownloadPath(DownloadActivity.this);
|
||||
} else {
|
||||
location = NewPipeSettings.getVideoDownloadPath(DownloadActivity.this);
|
||||
}
|
||||
|
||||
String fName = name.getText().toString().trim();
|
||||
|
||||
File f = new File(location, fName);
|
||||
if (f.exists()) {
|
||||
Toast.makeText(DownloadActivity.this, R.string.msg_exists, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
DownloadManagerService.startMission(
|
||||
DownloadActivity.this,
|
||||
getIntent().getData().toString(), location, fName,
|
||||
audioButton.isChecked(), threads.getProgress() + 1);
|
||||
mFragment.notifyChange();
|
||||
|
||||
mPrefs.edit().putInt(THREADS, threads.getProgress() + 1).commit();
|
||||
mPendingUrl = null;
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
|
||||
}
|
||||
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
@@ -238,9 +72,7 @@ public class DownloadActivity extends ThemableActivity implements AdapterView.On
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
|
||||
switch (id) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: {
|
||||
onBackPressed();
|
||||
return true;
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,165 +0,0 @@
|
||||
package org.schabi.newpipe.download;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import info.guardianproject.netcipher.NetCipher;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 14.08.15.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* FileDownloader.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/>.
|
||||
*/
|
||||
|
||||
|
||||
// TODO: FOR HEAVEN SAKE !!! DO NOT SIMPLY USE ASYNCTASK. MAKE THIS A PROPER SERVICE !!!
|
||||
public class FileDownloader extends AsyncTask<Void, Integer, Void> {
|
||||
public static final String TAG = "FileDownloader";
|
||||
|
||||
|
||||
private NotificationManager nm;
|
||||
private NotificationCompat.Builder builder;
|
||||
private int notifyId = 0x1234;
|
||||
private int fileSize = 0xffffffff;
|
||||
|
||||
private final Context context;
|
||||
private final String fileURL;
|
||||
private final File saveFilePath;
|
||||
private final String title;
|
||||
|
||||
private final String debugContext;
|
||||
|
||||
public FileDownloader(Context context, String fileURL, File saveFilePath, String title) {
|
||||
this.context = context;
|
||||
this.fileURL = fileURL;
|
||||
this.saveFilePath = saveFilePath;
|
||||
this.title = title;
|
||||
|
||||
this.debugContext = "'" + fileURL +
|
||||
"' => '" + saveFilePath + "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a file from a URL in the background using an {@link AsyncTask}.
|
||||
*
|
||||
* @param fileURL HTTP URL of the file to be downloaded
|
||||
* @param saveFilePath path of the directory to save the file
|
||||
* @param title
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void downloadFile(final Context context, final String fileURL, final File saveFilePath, String title) {
|
||||
new FileDownloader(context, fileURL, saveFilePath, title).execute();
|
||||
}
|
||||
|
||||
/** AsyncTask impl: executed in gui thread */
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
Drawable icon = context.getResources().getDrawable(R.mipmap.ic_launcher);
|
||||
builder = new NotificationCompat.Builder(context)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
|
||||
.setContentTitle(saveFilePath.getName())
|
||||
.setContentText(saveFilePath.getAbsolutePath())
|
||||
.setProgress(fileSize, 0, false);
|
||||
nm.notify(notifyId, builder.build());
|
||||
}
|
||||
|
||||
/** AsyncTask impl: executed in background thread does the download */
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
HttpsURLConnection con = null;
|
||||
InputStream inputStream = null;
|
||||
FileOutputStream outputStream = null;
|
||||
try {
|
||||
con = NetCipher.getHttpsURLConnection(fileURL);
|
||||
int responseCode = con.getResponseCode();
|
||||
|
||||
// always check HTTP response code first
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
fileSize = con.getContentLength();
|
||||
inputStream = new BufferedInputStream(con.getInputStream());
|
||||
outputStream = new FileOutputStream(saveFilePath);
|
||||
|
||||
int bufferSize = 8192;
|
||||
int downloaded = 0;
|
||||
|
||||
int bytesRead = -1;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
downloaded += bytesRead;
|
||||
if (downloaded % 50000 < bufferSize) {
|
||||
publishProgress(downloaded);
|
||||
}
|
||||
}
|
||||
|
||||
publishProgress(bufferSize);
|
||||
|
||||
} else {
|
||||
Log.i(TAG, "No file to download. Server replied HTTP code: " + responseCode);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "No file to download. Server replied HTTP code: ", e);
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (outputStream != null) {
|
||||
outputStream.close();
|
||||
}
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (con != null) {
|
||||
con.disconnect();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
builder.setProgress(fileSize, progress[0], false);
|
||||
nm.notify(notifyId, builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
super.onPostExecute(aVoid);
|
||||
nm.cancel(notifyId);
|
||||
}
|
||||
}
|
1
app/src/main/java/org/schabi/newpipe/extractor
Submodule
1
app/src/main/java/org/schabi/newpipe/extractor
Submodule
Submodule app/src/main/java/org/schabi/newpipe/extractor added at ab530381cf
@@ -1,41 +0,0 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
/**
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* AbstractStreamInfo.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/>.
|
||||
*/
|
||||
|
||||
/**Common properties between StreamInfo and StreamInfoItem.*/
|
||||
public abstract class AbstractStreamInfo {
|
||||
public enum StreamType {
|
||||
NONE, // placeholder to check if stream type was checked or not
|
||||
VIDEO_STREAM,
|
||||
AUDIO_STREAM,
|
||||
LIVE_STREAM,
|
||||
AUDIO_LIVE_STREAM,
|
||||
FILE
|
||||
}
|
||||
|
||||
public StreamType stream_type;
|
||||
public int service_id = -1;
|
||||
public String id = "";
|
||||
public String title = "";
|
||||
public String uploader = "";
|
||||
public String thumbnail_url = "";
|
||||
public String webpage_url = "";
|
||||
public String upload_date = "";
|
||||
public long view_count = -1;
|
||||
}
|
@@ -1,113 +0,0 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import android.util.Xml;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.stream_info.AudioStream;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 02.02.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* DashMpdParser.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 DashMpdParser {
|
||||
|
||||
private DashMpdParser() {
|
||||
}
|
||||
|
||||
static class DashMpdParsingException extends ParsingException {
|
||||
DashMpdParsingException(String message, Exception e) {
|
||||
super(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<AudioStream> getAudioStreams(String dashManifestUrl)
|
||||
throws DashMpdParsingException, ReCaptchaException {
|
||||
String dashDoc;
|
||||
Downloader downloader = NewPipe.getDownloader();
|
||||
try {
|
||||
dashDoc = downloader.download(dashManifestUrl);
|
||||
} catch(IOException ioe) {
|
||||
throw new DashMpdParsingException("Could not get dash mpd: " + dashManifestUrl, ioe);
|
||||
} catch (ReCaptchaException e) {
|
||||
throw new ReCaptchaException("reCaptcha Challenge needed");
|
||||
}
|
||||
Vector<AudioStream> audioStreams = new Vector<>();
|
||||
try {
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setInput(new StringReader(dashDoc));
|
||||
String tagName = "";
|
||||
String currentMimeType = "";
|
||||
int currentBandwidth = -1;
|
||||
int currentSamplingRate = -1;
|
||||
boolean currentTagIsBaseUrl = false;
|
||||
for(int eventType = parser.getEventType();
|
||||
eventType != XmlPullParser.END_DOCUMENT;
|
||||
eventType = parser.next() ) {
|
||||
switch(eventType) {
|
||||
case XmlPullParser.START_TAG:
|
||||
tagName = parser.getName();
|
||||
if(tagName.equals("AdaptationSet")) {
|
||||
currentMimeType = parser.getAttributeValue(XmlPullParser.NO_NAMESPACE, "mimeType");
|
||||
} else if(tagName.equals("Representation") && currentMimeType.contains("audio")) {
|
||||
currentBandwidth = Integer.parseInt(
|
||||
parser.getAttributeValue(XmlPullParser.NO_NAMESPACE, "bandwidth"));
|
||||
currentSamplingRate = Integer.parseInt(
|
||||
parser.getAttributeValue(XmlPullParser.NO_NAMESPACE, "audioSamplingRate"));
|
||||
} else if(tagName.equals("BaseURL")) {
|
||||
currentTagIsBaseUrl = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case XmlPullParser.TEXT:
|
||||
// actual stream tag
|
||||
if(currentTagIsBaseUrl &&
|
||||
(currentMimeType.contains("audio"))) {
|
||||
int format = -1;
|
||||
if(currentMimeType.equals(MediaFormat.WEBMA.mimeType)) {
|
||||
format = MediaFormat.WEBMA.id;
|
||||
} else if(currentMimeType.equals(MediaFormat.M4A.mimeType)) {
|
||||
format = MediaFormat.M4A.id;
|
||||
}
|
||||
audioStreams.add(new AudioStream(parser.getText(),
|
||||
format, currentBandwidth, currentSamplingRate));
|
||||
}
|
||||
break;
|
||||
|
||||
case XmlPullParser.END_TAG:
|
||||
if(tagName.equals("AdaptationSet")) {
|
||||
currentMimeType = "";
|
||||
} else if(tagName.equals("BaseURL")) {
|
||||
currentTagIsBaseUrl = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
throw new DashMpdParsingException("Could not parse Dash mpd", e);
|
||||
}
|
||||
return audioStreams;
|
||||
}
|
||||
}
|
@@ -1,52 +0,0 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 28.01.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* Downloader.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 interface Downloader {
|
||||
|
||||
/**Download the text file at the supplied URL as in download(String),
|
||||
* but set the HTTP header field "Accept-Language" to the supplied string.
|
||||
* @param siteUrl the URL of the text file to return the contents of
|
||||
* @param language the language (usually a 2-character code) to set as the preferred language
|
||||
* @return the contents of the specified text file
|
||||
* @throws IOException*/
|
||||
String download(String siteUrl, String language) throws IOException, ReCaptchaException;
|
||||
|
||||
/**Download the text file at the supplied URL as in download(String),
|
||||
* but set the HTTP header field "Accept-Language" to the supplied string.
|
||||
* @param siteUrl the URL of the text file to return the contents of
|
||||
* @param customProperties set request header properties
|
||||
* @return the contents of the specified text file
|
||||
* @throws IOException*/
|
||||
String download(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException;
|
||||
|
||||
/**Download (via HTTP) the text file located at the supplied URL, and return its contents.
|
||||
* Primarily intended for downloading web pages.
|
||||
* @param siteUrl the URL of the text file to download
|
||||
* @return the contents of the specified text file
|
||||
* @throws IOException*/
|
||||
String download(String siteUrl) throws IOException, ReCaptchaException;
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
/**
|
||||
* Created by the-scrabi on 11.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* InfoItem.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 interface InfoItem {
|
||||
public enum InfoType {
|
||||
STREAM,
|
||||
PLAYLIST,
|
||||
CHANNEL
|
||||
}
|
||||
|
||||
InfoType infoType();
|
||||
String getTitle();
|
||||
String getLink();
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* InfoItemCollector.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 InfoItemCollector {
|
||||
private List<InfoItem> itemList = new Vector<>();
|
||||
private List<Throwable> errors = new Vector<>();
|
||||
private int serviceId = -1;
|
||||
|
||||
public InfoItemCollector(int serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
public List<InfoItem> getItemList() {
|
||||
return itemList;
|
||||
}
|
||||
public List<Throwable> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
protected void addFromCollector(InfoItemCollector otherC) throws ExtractionException {
|
||||
if(serviceId != otherC.serviceId) {
|
||||
throw new ExtractionException("Service Id does not equal: "
|
||||
+ NewPipe.getNameOfService(serviceId)
|
||||
+ " and " + NewPipe.getNameOfService(otherC.serviceId));
|
||||
}
|
||||
errors.addAll(otherC.errors);
|
||||
itemList.addAll(otherC.itemList);
|
||||
}
|
||||
protected void addError(Exception e) {
|
||||
errors.add(e);
|
||||
}
|
||||
protected void addItem(InfoItem item) {
|
||||
itemList.add(item);
|
||||
}
|
||||
protected int getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
/**
|
||||
* Created by Adam Howard on 08/11/15.
|
||||
*
|
||||
* Copyright (c) Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* and Adam Howard <achdisposable1@gmail.com> 2015
|
||||
*
|
||||
* MediaFormat.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/>.
|
||||
*/
|
||||
|
||||
/**Static data about various media formats support by Newpipe, eg mime type, extension*/
|
||||
|
||||
public enum MediaFormat {
|
||||
//video and audio combined formats
|
||||
// id name suffix mime type
|
||||
MPEG_4 (0x0, "MPEG-4", "mp4", "video/mp4"),
|
||||
v3GPP (0x1, "3GPP", "3gp", "video/3gpp"),
|
||||
WEBM (0x2, "WebM", "webm", "video/webm"),
|
||||
// audio formats
|
||||
M4A (0x3, "m4a", "m4a", "audio/mp4"),
|
||||
WEBMA (0x4, "WebM", "webm", "audio/webm");
|
||||
|
||||
public final int id;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public final String name;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public final String suffix;
|
||||
public final String mimeType;
|
||||
|
||||
MediaFormat(int id, String name, String suffix, String mimeType) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.suffix = suffix;
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
/**Return the friendly name of the media format with the supplied id
|
||||
* @param ident the id of the media format. Currently an arbitrary, NewPipe-specific number.
|
||||
* @return the friendly name of the MediaFormat associated with this ids,
|
||||
* or an empty String if none match it.*/
|
||||
public static String getNameById(int ident) {
|
||||
for (MediaFormat vf : MediaFormat.values()) {
|
||||
if(vf.id == ident) return vf.name;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**Return the file extension of the media format with the supplied id
|
||||
* @param ident the id of the media format. Currently an arbitrary, NewPipe-specific number.
|
||||
* @return the file extension of the MediaFormat associated with this ids,
|
||||
* or an empty String if none match it.*/
|
||||
public static String getSuffixById(int ident) {
|
||||
for (MediaFormat vf : MediaFormat.values()) {
|
||||
if(vf.id == ident) return vf.suffix;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**Return the MIME type of the media format with the supplied id
|
||||
* @param ident the id of the media format. Currently an arbitrary, NewPipe-specific number.
|
||||
* @return the MIME type of the MediaFormat associated with this ids,
|
||||
* or an empty String if none match it.*/
|
||||
public static String getMimeById(int ident) {
|
||||
for (MediaFormat vf : MediaFormat.values()) {
|
||||
if(vf.id == ident) return vf.mimeType;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user