1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2026-01-14 02:32:40 +00:00

Compare commits

...

165 Commits

Author SHA1 Message Date
Christian Schabesberger
9e76f94cf6 update support lib and move on to 0.8.8 2017-01-22 14:47:05 +01:00
Christian Schabesberger
7d6b92e064 Merge branch 'feature-improve-search-fragment' of git://github.com/coffeemakr/NewPipe into cofe 2017-01-22 14:42:36 +01:00
Christian Schabesberger
849a45a3ca resolve conflict 2017-01-22 14:26:01 +01:00
Christian Schabesberger
7ddea5a71b Merge branch 'feature-accept-embed-links' of git://github.com/coffeemakr/NewPipe into url 2017-01-22 13:55:53 +01:00
Christian Schabesberger
5d81358c15 rename Themer to ThemableActivity 2017-01-22 13:48:50 +01:00
Christian Schabesberger
492aad9d70 Merge branch 'master' of git://github.com/ksyko/NewPipe into kyko 2017-01-22 13:32:39 +01:00
Weblate
647cfcd401 Merge remote-tracking branch 'origin/master' 2017-01-21 15:44:14 +01:00
Yann Hodiesne
c60d98e52d Translated using Weblate (French)
Currently translated at 97.1% (137 of 141 strings)
2017-01-21 15:44:14 +01:00
Osoitz
d3cfac6b15 Translated using Weblate (Basque)
Currently translated at 37.5% (53 of 141 strings)
2017-01-21 15:44:11 +01:00
Coffeemakr
7fb4e5a143 Rename download db sqlite file
Rename the sqlite database to "downloads.db" instead of "newpipe.db"
to make sure the file will create a conflicts later.
2017-01-20 18:57:30 +01:00
Coffeemakr
8e451b2a83 Use fragments setHasOptionsMenu
Remove complications by allowing android to handle fragment's
options menu.

See https://developer.android.com/guide/components/fragments.html#ActionBar
2017-01-19 19:39:33 +01:00
ksyko
8083f06fe7 Update SettingsFragment.java 2017-01-18 00:12:36 +05:30
59436419
44521a2e56 Added dark theme 2017-01-17 22:35:23 +05:30
59436419
dfeed3d0eb Added dark theme 2017-01-17 22:31:00 +05:30
59436419
081a45b70d Added dark theme 2017-01-17 22:13:33 +05:30
59436419
616a721bba Added dark theme 2017-01-17 21:43:30 +05:30
59436419
c9aa553b32 Added dark theme 2017-01-17 20:54:00 +05:30
59436419
e3d59c3cff Added dark theme 2017-01-17 20:47:12 +05:30
59436419
df51635674 Added dark theme 2017-01-17 20:33:54 +05:30
59436419
1e81f61760 Added dark theme 2017-01-17 20:31:14 +05:30
59436419
a4043eab83 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/Themer.java
#	app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
#	app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
#	app/src/main/res/layout-v18/fragment_videoitem_detail.xml
#	app/src/main/res/layout/fragment_videoitem_detail.xml
#	app/src/main/res/xml/settings.xml
2017-01-17 16:18:00 +05:30
59436419
60dc19d2bc Added Dark Theme 2017-01-17 16:13:14 +05:30
ksyko
a2e4585fe8 Update InfoItemHolder.java 2017-01-17 16:13:02 +05:30
ksyko
b5df281447 Update fragment_videoitem_detail.xml 2017-01-17 16:13:02 +05:30
ksyko
650917f9f9 Update fragment_videoitem_detail.xml 2017-01-17 16:13:02 +05:30
ksyko
3a9c95a9ae Update fragment_videoitem_detail.xml 2017-01-17 16:13:01 +05:30
59436419
5458acfcad Added dark theme 2017-01-17 16:13:01 +05:30
59436419
68e80e6054 Added dark theme 2017-01-17 16:13:01 +05:30
Christian Schabesberger
e4aa69b8d3 add some super.function() thingies 2017-01-16 16:06:54 +01:00
Coffeemakr
dfd40e43da Fix for #407 2017-01-16 13:01:52 +01:00
Coffeemakr
7efd111d9c Improve Search fragment
* Keep search query when fragment is restored
 * Simplify SuggestionListAdapter
   * Use ResourceCursorAdapter for view creation
 * Fix deprecation warning
 * Some clean code
2017-01-16 07:33:58 +01:00
Matej U
6809172203 Translated using Weblate (Slovenian)
Currently translated at 99.2% (140 of 141 strings)
2017-01-15 12:46:28 +01:00
Coffeemaker
40a4343f06 Translated using Weblate (German)
Currently translated at 98.5% (139 of 141 strings)
2017-01-13 18:45:02 +01:00
Matej U
cd9333b39e Translated using Weblate (Slovenian)
Currently translated at 98.5% (139 of 141 strings)
2017-01-12 21:46:19 +01:00
Mladen Pejaković
82d426c781 Translated using Weblate (Serbian)
Currently translated at 100.0% (141 of 141 strings)
2017-01-12 21:46:11 +01:00
YFdyh000
c7e773de25 Translated using Weblate (Chinese (Simplified))
Currently translated at 98.5% (139 of 141 strings)
2017-01-12 18:44:21 +01:00
Coffeemakr
2ded33110f Improve YoutubeStreamUrlIdHandler
* Make it a singelton
 * Accept embed links
 * Accept share links (youtube.com/shared?ci=...)
 * Add tests
 * Accept host case insensititve
2017-01-11 17:25:53 +01:00
zmni
b26c0aa9da Translated using Weblate (Indonesian)
Currently translated at 100.0% (141 of 141 strings)
2017-01-11 16:51:32 +01:00
Mladen Pejaković
2fc8fb6f17 Translated using Weblate (Serbian)
Currently translated at 100.0% (141 of 141 strings)
2017-01-10 21:08:24 +01:00
Coffeemakr
ea76f1d6e2 Improve DownloadManager and -Service
* Fix permission at some places
 * Fix access problem for downloaded files with external player
 * Store finished Downloads
 * Remove binding to DownloadService just to download a file
 * Javadoc
 * Code improvements
2017-01-10 17:48:55 +01:00
naofum
7cda1d116b Translated using Weblate (Japanese)
Currently translated at 100.0% (141 of 141 strings)
2017-01-10 17:03:28 +01:00
Weblate
8bf7a1a9db Merge remote-tracking branch 'origin/master' 2017-01-10 17:02:24 +01:00
naofum
0aa0ad65c0 Translated using Weblate (Japanese)
Currently translated at 100.0% (140 of 140 strings)
2017-01-10 17:02:21 +01:00
Coffeemakr
53ff58daa3 DownloadManagerService: Don't bind if no permissions 2017-01-10 13:35:16 +01:00
Christian Schabesberger
b3225bebe6 fix recaptch weblate conflict 2017-01-09 22:46:07 +01:00
Allan Nordhøy
4beafad71f Translated using Weblate (Norwegian Bokmål)
Currently translated at 92.8% (130 of 140 strings)
2017-01-09 21:45:13 +01:00
Allan Nordhøy
c96f933626 Translated using Weblate (English)
Currently translated at 100.0% (140 of 140 strings)
2017-01-09 21:45:12 +01:00
LNJ
bea6359d5f Translated using Weblate (German)
Currently translated at 99.2% (139 of 140 strings)
2017-01-09 21:45:04 +01:00
Christian Schabesberger
92db9cb59b update support lib and move on to 0.8.7 2017-01-04 17:20:01 +01:00
Benoît Mauduit
410c4ca736 Remove old comment 2017-01-03 14:38:22 +01:00
Benoît Mauduit
80c9dbf180 Cosmetic 2017-01-03 14:38:22 +01:00
Benoît Mauduit
c9edac2820 Change the way to start reCaptchaActivity
* Use startActivityForResult() and onActivityResult() to refresh the
  search request when returning from reCaptcha activity.
2017-01-03 14:38:22 +01:00
Benoît Mauduit
143df9a529 Start reCaptcha activity when starting video directly 2017-01-03 14:38:22 +01:00
Benoît Mauduit
c87da9903f Set result code and finish() Activity instead of starting MainActivity
* Activities will start reCaptchaActivity with startActivityForResult and
look for RECAPTCHA_REQUEST and RESULT_OK | RESULT_CANCELED in : 'onActivityResult()'
2017-01-03 14:38:22 +01:00
Andi Saputro
0f69e6c64d Translated using Weblate (Indonesian)
Currently translated at 100.0% (140 of 140 strings)
2016-12-31 18:45:16 +01:00
ksyko
1b9a6e53ce Update InfoItemHolder.java 2016-12-31 22:32:16 +05:30
ksyko
c7abf377eb Update fragment_videoitem_detail.xml 2016-12-31 21:51:41 +05:30
ksyko
175d8ce572 Update fragment_videoitem_detail.xml 2016-12-31 21:43:01 +05:30
ksyko
1708a401cf Update fragment_videoitem_detail.xml 2016-12-31 21:42:04 +05:30
59436419
141278e668 Added dark theme 2016-12-31 13:46:52 +05:30
59436419
754bd82699 Added dark theme 2016-12-30 01:07:27 +05:30
andideveloper
999efb6660 Translated using Weblate (Indonesian)
Currently translated at 100.0% (140 of 140 strings)
2016-12-29 17:25:23 +01:00
Weblate
0b391a9ef3 Merge remote-tracking branch 'origin/master' 2016-12-28 12:46:32 +01:00
naofum
2a99ac4430 Translated using Weblate (Japanese)
Currently translated at 100.0% (140 of 140 strings)
2016-12-28 12:46:22 +01:00
Christian Schabesberger
a5589d0865 Merge branch 'feature-background-progress' of git://github.com/coffeemakr/NewPipe into coffeemakr-feature-background-progress 2016-12-28 10:46:38 +01:00
Coffeemakr
83541a0d5d Add 8dp margin on the right side of the progress bar 2016-12-28 08:15:12 +01:00
Coffeemakr
ac0dff7aa1 Make shure thread quits before service 2016-12-27 16:17:59 +01:00
Coffeemakr
f22b5157f5 Notify thread on play/pause 2016-12-27 15:58:17 +01:00
Coffeemakr
659d0d6115 Add PlaybackState broadcast messages
They can be used to retrieve the current playback
 * Duration
 * Played time
 * If the media player is playing
2016-12-27 15:52:02 +01:00
Marian Hanzel
fd8c99fd8d Translated using Weblate (Slovak)
Currently translated at 100.0% (140 of 140 strings)
2016-12-27 15:47:58 +01:00
Coffeemakr
9494f3a299 Add progress bar to expanded notification 2016-12-27 14:49:59 +01:00
Coffeemakr
8021848b03 Remove note field completly
Always use noteBuilder directly to generate a notification
2016-12-27 14:41:30 +01:00
Coffeemakr
5a127c26e6 Improve notification building/updateing
* Use custom notification builder which has methods to set the artist
  and title and also a method to set the playing state
* Update builder instead of view -> resovles deprecated warnings
2016-12-27 14:37:18 +01:00
Coffeemakr
a7d734c20c Ignore vim's temporary files 2016-12-27 13:32:03 +01:00
Coffeemakr
7c7129f9a1 Notifications: Set customs views in builder 2016-12-27 13:31:34 +01:00
Coffeemakr
05cbc7891d Typos corrected in StreamInfo 2016-12-27 13:26:48 +01:00
Coffeemakr
14623456ff Correct typos 2016-12-27 13:19:12 +01:00
Coffeemakr
5064ec3ac4 Fix spelling 2016-12-27 13:16:51 +01:00
Gian Maria Viglianti
892888796d Translated using Weblate (Italian)
Currently translated at 100.0% (140 of 140 strings)
2016-12-26 14:57:31 +01:00
Weblate
4f2826d2c2 Merge remote-tracking branch 'origin/master' 2016-12-26 10:23:57 +01:00
YFdyh000
7f824d725b Translated using Weblate (Chinese (Simplified))
Currently translated at 98.5% (138 of 140 strings)
2016-12-26 10:23:56 +01:00
naofum
da4096c4ef Translated using Weblate (Japanese)
Currently translated at 100.0% (140 of 140 strings)
2016-12-26 10:23:55 +01:00
Christian Schabesberger
1aed11c156 change ip address provider
removed null bomb
2016-12-25 19:32:38 +01:00
Christian Schabesberger
e99e944ac3 make share with newpipe part of newpipe 2016-12-25 19:28:40 +01:00
Christian Schabesberger
6e523d37ba add copyright notice to DownloadListener 2016-12-25 15:24:37 +01:00
Christian Schabesberger
2aebf6b522 Merge branch 'master' of git://github.com/fr3ts0n/NewPipe into fr3ts0n-master 2016-12-25 15:20:31 +01:00
Mladen Pejaković
3f740980a3 Translated using Weblate (Serbian)
Currently translated at 100.0% (140 of 140 strings)
2016-12-25 14:16:58 +01:00
Weblate
66b73d1592 Merge remote-tracking branch 'origin/master' 2016-12-25 12:44:13 +01:00
RACER
be4b03b84b Translated using Weblate (Japanese)
Currently translated at 100.0% (138 of 138 strings)
2016-12-25 12:44:13 +01:00
Osoitz
3594037efe Translated using Weblate (Basque)
Currently translated at 34.0% (47 of 138 strings)
2016-12-25 12:44:11 +01:00
Christian Schabesberger
38c5cb50fb fixed Downloader.getInstance() in unit tests 2016-12-24 17:11:29 +01:00
Christian Schabesberger
3767a96e0f moved on to version 0.8.6 2016-12-24 16:06:24 +01:00
Christian Schabesberger
937a387f4e rename reCaptchaException to ReCaptchaException 2016-12-24 15:19:40 +01:00
Christian Schabesberger
cd65f1dffc Merge branch 'reCaptcha' of git://github.com/be-neth/NewPipe into be-neth-reCaptcha 2016-12-24 14:13:12 +01:00
Christian Schabesberger
d4e6856cbe Merge pull request #394 from ngoisaosang/master
Try to fix accept SDK Licenses
2016-12-24 14:12:36 +01:00
ngoisaosang
f61d779108 Try to fix travis 2016-12-24 07:25:27 +07:00
ngoisaosang
b8b22d4d91 Fix travis 2016-12-23 13:47:20 +07:00
ngoisaosang
25d0e39736 Update gradle 2016-12-22 22:45:42 +07:00
ngoisaosang
79a9497e65 Fix travis build 2016-12-22 17:40:01 +07:00
Christian Schabesberger
c55dcccb1e Merge branch 'fix-channel-bannel-url' of git://github.com/coffeemakr/NewPipe into coffeemakr-fix-channel-bannel-url 2016-12-21 21:01:47 +01:00
Christian Schabesberger
820e606719 fix duration_background name 2016-12-21 21:00:05 +01:00
Christian Schabesberger
96291a8522 Merge branch 'coffeemakr-fix-yt-dublicated-result-page' 2016-12-21 20:56:40 +01:00
Christian Schabesberger
e7b52bd3b0 Merge pull request #392 from ksyko/patch-1
Fixes spelling
2016-12-21 20:42:03 +01:00
ksyko
dd3251c08d Fixes spelling 2016-12-21 20:38:51 +05:30
Coffeemakr
f4aabdd9b8 Accept SDK Licenses 2016-12-19 19:40:28 +01:00
Coffeemakr
cb1fe5f017 Try to fix travis 2016-12-19 19:36:24 +01:00
Coffeemakr
83837bde11 Google begins with page 1 so we add 1 to our page starting with 0 2016-12-19 19:28:16 +01:00
Coffeemakr
05189dadbf Fix banner extaction 2016-12-19 13:32:03 +01:00
Selim
dbb1f371b3 Translated using Weblate (Turkish)
Currently translated at 94.2% (130 of 138 strings)
2016-12-13 21:46:13 +01:00
Benoît Mauduit
5cc1bbc4bb Add missing Override decorator 2016-12-07 22:26:54 +01:00
Benoît Mauduit
53796043c3 Start reCaptchaActivity when reCaptcha is requested and fix Typo
* Fix typo setSearchWorkerResultListner to setSearchWorkerResultListener
2016-12-07 22:26:54 +01:00
Benoît Mauduit
a5ac528c02 Add reCaptchaException 2016-12-07 22:26:54 +01:00
Benoît Mauduit
54eb353d0d Add ReCaptcha Activity
* ReCaptchas are implemented using a simple WebView with Javascript enable.

 * All HTTP responses inside the WebView are catched (using onPageFinished())
 ** When Google reCatcha cookies are detected, register cookies to the
    Downloader class and Return to MainActivity
2016-12-07 22:26:54 +01:00
Benoît Mauduit
3391067cab Adding cookies member to Downloader 2016-12-07 22:26:54 +01:00
Benoît Mauduit
b4f595eb75 Make Downloader class a Singleton 2016-12-07 22:26:54 +01:00
Weblate
58bc0c17d0 Merge remote-tracking branch 'origin/master' 2016-12-02 10:24:55 +01:00
Cyxae Dexyc
4385404ad6 Translated using Weblate (French)
Currently translated at 98.5% (136 of 138 strings)
2016-12-02 10:24:54 +01:00
Marian Hanzel
61aadcffd2 Translated using Weblate (Slovak)
Currently translated at 100.0% (138 of 138 strings)
2016-12-02 10:24:52 +01:00
Christian Schabesberger
f575826394 fix unit tests 2016-11-20 19:01:06 +01:00
Christian Schabesberger
389959dd18 Merge pull request #366 from coffeemakr/delete-swiss-high-german
Delete Swiss High German translation
2016-11-20 15:43:37 +01:00
Christian Schabesberger
af4734eee3 update to sdk version 25 2016-11-18 23:56:08 +01:00
Christian Schabesberger
f76b37ec39 Merge branch 'master' of github.com:TeamNewPipe/NewPipe 2016-11-18 23:52:34 +01:00
Christian Schabesberger
379149fe2f update unit tests so it works with current updates 2016-11-18 23:52:17 +01:00
Christian Schabesberger
3bd477631c fixed spelling error 2016-11-18 22:08:53 +01:00
Freddy Morán Jr
72c0987bad Translated using Weblate (Spanish)
Currently translated at 100.0% (138 of 138 strings)
2016-11-10 03:46:02 +01:00
fr3ts0n
5bba8e02a6 add media scanner notification after successful download 2016-11-06 21:08:57 +01:00
Freddy Morán Jr
b270de3335 Translated using Weblate (Spanish)
Currently translated at 100.0% (138 of 138 strings)
2016-11-03 21:46:25 +01:00
Freddy Morán Jr
e22bcf0ac5 Translated using Weblate (Serbian)
Currently translated at 100.0% (138 of 138 strings)
2016-11-03 21:46:13 +01:00
Freddy Morán Jr
c1d55d828f Translated using Weblate (Portuguese)
Currently translated at 100.0% (138 of 138 strings)
2016-11-03 21:45:51 +01:00
Freddy Morán Jr
da77970328 Translated using Weblate (Indonesian)
Currently translated at 100.0% (138 of 138 strings)
2016-11-03 21:45:12 +01:00
Freddy Morán Jr
32f3caaee0 Translated using Weblate (German)
Currently translated at 100.0% (138 of 138 strings)
2016-11-03 21:44:57 +01:00
Дима Гайнуллин
8bbacb1d78 Translated using Weblate (Russian)
Currently translated at 88.4% (122 of 138 strings)
2016-11-03 18:45:52 +01:00
Nathan Follens
5904510410 Translated using Weblate (Dutch)
Currently translated at 100.0% (138 of 138 strings)
2016-10-30 00:44:27 +02:00
Enol P
e16624251b Translated using Weblate (Asturian)
Currently translated at 97.1% (134 of 138 strings)
2016-10-21 03:44:09 +02:00
zmni
389f22a0e5 Translated using Weblate (Indonesian)
Currently translated at 99.2% (137 of 138 strings)
2016-10-16 12:45:00 +02:00
zmni
7b91aa16b6 Translated using Weblate (Indonesian)
Currently translated at 95.6% (132 of 138 strings)
2016-10-13 21:45:17 +02:00
Jona Abdinghoff
89ec688632 Translated using Weblate (German)
Currently translated at 99.2% (137 of 138 strings)
2016-10-12 00:44:52 +02:00
Nathan Follens
fdd0d586c9 Translated using Weblate (Dutch)
Currently translated at 99.2% (137 of 138 strings)
2016-10-12 00:44:25 +02:00
Coffeemakr
6f5604791f Delete swiss high german 2016-10-11 07:30:18 +02:00
Gian Maria Viglianti
eb9fba4147 Translated using Weblate (Italian)
Currently translated at 100.0% (138 of 138 strings)
2016-10-10 18:45:06 +02:00
Weblate
afb62f729f Merge remote-tracking branch 'origin/master' 2016-10-04 15:45:09 +02:00
Nathan Follens
1ccc23dc9c Translated using Weblate (Dutch)
Currently translated at 68.8% (95 of 138 strings)
2016-10-04 15:45:07 +02:00
intrnl
7ea5cb9c5c Translated using Weblate (Indonesian)
Currently translated at 37.6% (52 of 138 strings)
2016-10-04 15:44:59 +02:00
Christian Schabesberger
c301d6e5d5 Merge branch 'master' of github.com:TeamNewPipe/NewPipe 2016-10-01 23:22:18 +02:00
naofum
44ad69b94d Translated using Weblate (Japanese)
Currently translated at 100.0% (138 of 138 strings)
2016-10-01 18:45:10 +02:00
Christian Schabesberger
d14515ab88 Merge remote-tracking branch 'origin/master' 2016-09-28 17:13:23 +02:00
Christian Schabesberger
01875b389d clean up extractor 2016-09-28 17:13:15 +02:00
Christian Schabesberger
5eef116aaa put Suggestion extraction into its own class 2016-09-28 12:53:23 +02:00
Weblate
442220debc Merge remote-tracking branch 'origin/master' 2016-09-28 12:45:55 +02:00
kamadi
4914caad51 Translated using Weblate (Russian)
Currently translated at 48.5% (67 of 138 strings)
2016-09-28 12:45:54 +02:00
Gian Maria Viglianti
08cab863f1 Translated using Weblate (Italian)
Currently translated at 98.5% (136 of 138 strings)
2016-09-28 12:45:54 +02:00
Marian Hanzel
02458d4fc1 Translated using Weblate (Slovak)
Currently translated at 98.5% (136 of 138 strings)
2016-09-28 12:45:51 +02:00
Christian Schabesberger
6ed4130b66 fix collisions 2016-09-28 11:54:35 +02:00
Felix Ableitner
5f7ee15d1e Launch video player in single task mode (fixes #154) 2016-09-28 13:09:42 +09:00
Christian Schabesberger
43afd5a2b8 clean up downloader thing 2016-09-27 22:59:04 +02:00
Christian Schabesberger
f9ac199c1f fixed .webm download locatiion issue 2016-09-27 21:33:26 +02:00
Christian Schabesberger
920c169d55 update CI batch in README 2016-09-27 20:22:01 +02:00
Christian Schabesberger
76ba2824a2 stability improvements 2016-09-27 20:18:41 +02:00
Christian Schabesberger
ca0d594547 cleaning away android stuff from extractor 2016-09-27 20:06:33 +02:00
Christian Schabesberger
44b6d900f0 Merge branch 'master' of github.com:TeamNewPipe/NewPipe 2016-09-27 14:58:54 +02:00
Christian Schabesberger
3b2c0186aa update issue teamplate 2016-09-27 14:58:46 +02:00
Christian Schabesberger
efa605700d update contributions link in the readme 2016-09-27 14:33:30 +02:00
Christian Schabesberger
cac360d37b add .github folder 2016-09-27 14:30:03 +02:00
Christian Schabesberger
75e28893fb update contribution guidelines once again 2016-09-27 14:14:59 +02:00
Christian Schabesberger
360a44b5a0 update contribution guidelines 2016-09-27 13:43:43 +02:00
191 changed files with 5185 additions and 1495 deletions

36
.github/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,36 @@
NewPipe contribution guidelines
===============================
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.
## 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
## Translation
* NewPipe can be translated on [weblate](https://hosted.weblate.org/projects/newpipe/strings/)
## Code contribution
* Stick to NewPipe style guidelines (just look the other code and than do it the same way :) )
* Do not bring nonfree software/binary blobs into the project (keep it google free)
* Stick to [f-droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy)
* Make changes on a separate branch, not on the master branch (Feature-branching)
* When submitting changes, you agree that your code will be licensed under GPLv3
* Please test (compile and run) your code before you submit changes!!!
* Try to figure out you selves why CI fails, or why a merge request collides
* Please maintain your code after you contributed it.
* Respond yourselves if someone request changes or notifies issues
## Communication
* I hereby declare our Slack channel as dead!!! There are no plans on building a new chat, but if there is interest on creating one and keeping it alive, I'd be pleased to create one again.
* If you want to get in contact with me or one of our other contributors you can send me an email at tnp(at)schabi.org
* Feel free to post suggestions, changes, ideas etc!

2
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,2 @@
- [ ] I carefully read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them.
- [ ] I checked if the issue/feature exists in the latest version.

1
.github/PULL_REQUEST_TEAMPLATE.md vendored Normal file
View File

@@ -0,0 +1 @@
[ ] I carefully reed the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them.

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@
/.idea
/*.iml
gradle.properties
*~

View File

@@ -8,11 +8,12 @@ android:
- build-tools-23.0.3
# The SDK version used to compile NewPipe
- android-24
- android-25
# Additional components
- extra-android-support
- extra-android-m2repository
- extra-google-m2repository
# Emulators
- sys-img-armeabi-v7a-android-21
@@ -33,3 +34,7 @@ before_script:
- adb shell input keyevent 82 &
script: ./gradlew --info build connectedCheck
licenses:
- '.+'

View File

@@ -1,33 +0,0 @@
#Contribution
This document contains guidelines on making contributions to NewPipe.
## Programming
* Follow the [Google Style Guidelines](https://google.github.io/styleguide/javaguide.html)
* Make a new feature on a separate branch, not on the master branch
* Make a [pull request](https://github.com/theScrabi/NewPipe/pulls) if you're done with your changes
* When submitting changes, you agree that your code will be GPLv3 licensed
## Commit messages
* The subject line of your commit message shouldn't be longer than 72 characters
* Try to keep each line of your commit message 72 characters to ensure proper
compatibility with all git tools
* [This guide](http://chris.beams.io/posts/git-commit/) goes more in depth on what makes a good commit message
## Translation
* NewPipe can be translated on [weblate](https://hosted.weblate.org/projects/newpipe/strings/)
## Issue reporting
* Search the [existing issues](https://github.com/theScrabi/NewPipe/issues) first to make sure your issue hasn't been reported before
* Check if this issue is already fixed in the repository
* When making bug reports, be sure to tell which version of NewPipe you are using and the steps to reproduce the problem
* Please include a log if you can
## Communication
* For the time being, [Slack](https://newpipe.slack.com/) is being used for project communication. Ask [me](https://github.com/theScrabi) to sign up.
* Feel free to post suggestions, changes, ideas etc!

View File

@@ -9,7 +9,7 @@ NewPipe: A free lightweight Youtube frontend for Android.
Project status:
[![Translation Status](https://hosted.weblate.org/widgets/NewPipe/-/svg-badge.svg)](https://hosted.weblate.org/engage/NewPipe/)
[![Build Status](https://travis-ci.org/theScrabi/NewPipe.svg)](https://travis-ci.org/theScrabi/NewPipe)
[![Build Status](https://travis-ci.org/TeamNewPipe/NewPipe.svg)](https://travis-ci.org/TeamNewPipe/NewPipe)
## Donate
![Bitcoin](https://bitcoin.org/img/icons/logotop.svg)
@@ -64,7 +64,7 @@ Although NewPipe only supports YouTube at the moment, it's designed to support m
Whether you have ideas, translation, design changes, code cleaning, or real heavy code changes, help is always welcome.
The more is done the better it gets!
If you'd like to get involved, check our [contribution notes](CONTRIBUTING.md).
If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md).
## License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)

View File

@@ -1,15 +1,15 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
compileSdkVersion 25
buildToolsVersion '23.0.3'
defaultConfig {
applicationId "org.schabi.newpipe"
minSdkVersion 15
targetSdkVersion 24
versionCode 19
versionName "0.8.5"
targetSdkVersion 25
versionCode 22
versionName "0.8.8"
}
buildTypes {
release {
@@ -32,10 +32,10 @@ android {
dependencies {
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:support-v4:24.2.1'
compile 'com.android.support:design:24.2.1'
compile 'com.android.support:recyclerview-v7:24.2.1'
compile 'com.android.support:appcompat-v7:25.1.0'
compile 'com.android.support:support-v4:25.1.0'
compile 'com.android.support:design:25.1.0'
compile 'com.android.support:recyclerview-v7:25.1.0'
compile 'org.jsoup:jsoup:1.8.3'
compile 'org.mozilla:rhino:1.7.7'
compile 'info.guardianproject.netcipher:netcipher:1.2'
@@ -46,5 +46,6 @@ dependencies {
compile 'com.google.code.gson:gson:2.4'
compile 'com.nononsenseapps:filepicker:3.0.0'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
compile 'ch.acra:acra:4.9.0'
}

View File

@@ -3,9 +3,8 @@ package org.schabi.newpipe.extractor.youtube;
import android.test.AndroidTestCase;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.ChannelExtractor;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
/**
* Created by Christian Schabesberger on 12.09.16.
@@ -33,8 +32,13 @@ public class YoutubeChannelExtractorTest extends AndroidTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
extractor = ServiceList.getService("Youtube")
.getChannelExtractorInstance("https://www.youtube.com/channel/UCYJ61XIK64sp6ZFFS8sctxw", 0, new Downloader());
NewPipe.init(Downloader.getInstance());
extractor = NewPipe.getService("Youtube")
.getChannelExtractorInstance("https://www.youtube.com/channel/UCYJ61XIK64sp6ZFFS8sctxw", 0);
}
public void testGetDownloader() throws Exception {
assertNotNull(NewPipe.getDownloader());
}
public void testGetChannelName() throws Exception {
@@ -67,14 +71,14 @@ public class YoutubeChannelExtractorTest extends AndroidTestCase {
}
public void testGetNextPage() throws Exception {
extractor = ServiceList.getService("Youtube")
.getChannelExtractorInstance("https://www.youtube.com/channel/UCYJ61XIK64sp6ZFFS8sctxw", 1, new Downloader());
extractor = NewPipe.getService("Youtube")
.getChannelExtractorInstance("https://www.youtube.com/channel/UCYJ61XIK64sp6ZFFS8sctxw", 1);
assertTrue("next page didn't have content", !extractor.getStreams().getItemList().isEmpty());
}
public void testGetNextNextPageUrl() throws Exception {
extractor = ServiceList.getService("Youtube")
.getChannelExtractorInstance("https://www.youtube.com/channel/UCYJ61XIK64sp6ZFFS8sctxw", 2, new Downloader());
extractor = NewPipe.getService("Youtube")
.getChannelExtractorInstance("https://www.youtube.com/channel/UCYJ61XIK64sp6ZFFS8sctxw", 2);
assertTrue("next page didn't have content", extractor.hasNextPage());
}
}

View File

@@ -2,15 +2,11 @@ package org.schabi.newpipe.extractor.youtube;
import android.test.AndroidTestCase;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.SearchResult;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
/**
@@ -35,16 +31,26 @@ import java.util.List;
public class YoutubeSearchEngineTest extends AndroidTestCase {
private SearchResult result;
private List<String> suggestionReply;
@Override
public void setUp() throws Exception {
super.setUp();
SearchEngine engine = ServiceList.getService("Youtube")
.getSearchEngineInstance(new Downloader());
NewPipe.init(Downloader.getInstance());
SearchEngine engine = NewPipe.getService("Youtube").getSearchEngineInstance();
result = engine.search("this is something boring",
0, "de", new Downloader()).getSearchResult();
suggestionReply = engine.suggestionList("hello", "de", new Downloader());
result = engine.search("this is something boring", 0, "de").getSearchResult();
}
public void testResultList() {
assertFalse(result.resultList.isEmpty());
}
public void testResultErrors() {
assertTrue(result.errors == null || result.errors.isEmpty());
}
public void testSuggestion() {
//todo write a real test
assertTrue(result.suggestion != null);
}
}

View File

@@ -0,0 +1,49 @@
package org.schabi.newpipe.extractor.youtube;
import android.test.AndroidTestCase;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.search.SuggestionExtractor;
import org.schabi.newpipe.extractor.services.youtube.YoutubeSuggestionExtractor;
import java.io.IOException;
import java.util.List;
/**
* Created by Christian Schabesberger on 18.11.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* YoutubeSearchResultTest.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 YoutubeSearchResultTest extends AndroidTestCase {
List<String> suggestionReply;
@Override
public void setUp() throws Exception {
super.setUp();
NewPipe.init(Downloader.getInstance());
SuggestionExtractor engine = new YoutubeSuggestionExtractor(0);
suggestionReply = engine.suggestionList("hello", "de");
}
public void testIfSuggestions() {
assertFalse(suggestionReply.isEmpty());
}
}

View File

@@ -3,12 +3,12 @@ package org.schabi.newpipe.extractor.youtube;
import android.test.AndroidTestCase;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.VideoStream;
import org.schabi.newpipe.extractor.AbstractStreamInfo;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import java.io.IOException;
@@ -36,9 +36,11 @@ public class YoutubeStreamExtractorDefaultTest extends AndroidTestCase {
public static final String HTTPS = "https://";
private StreamExtractor extractor;
public void setUp() throws IOException, ExtractionException {
extractor = ServiceList.getService("Youtube")
.getExtractorInstance("https://www.youtube.com/watch?v=YQHsXMglC9A", new Downloader());
public void setUp() throws Exception {
super.setUp();
NewPipe.init(Downloader.getInstance());
extractor = NewPipe.getService("Youtube")
.getExtractorInstance("https://www.youtube.com/watch?v=YQHsXMglC9A");
}
public void testGetInvalidTimeStamp() throws ParsingException {
@@ -48,8 +50,8 @@ public class YoutubeStreamExtractorDefaultTest extends AndroidTestCase {
public void testGetValidTimeStamp() throws ExtractionException, IOException {
StreamExtractor extractor =
ServiceList.getService("Youtube")
.getExtractorInstance("https://youtu.be/FmG385_uUys?t=174", new Downloader());
NewPipe.getService("Youtube")
.getExtractorInstance("https://youtu.be/FmG385_uUys?t=174");
assertTrue(Integer.toString(extractor.getTimeStamp()),
extractor.getTimeStamp() == 174);
}
@@ -108,7 +110,7 @@ public class YoutubeStreamExtractorDefaultTest extends AndroidTestCase {
}
public void testStreamType() throws ParsingException {
assertTrue(extractor.getStreamType() == AbstractVideoInfo.StreamType.VIDEO_STREAM);
assertTrue(extractor.getStreamType() == AbstractStreamInfo.StreamType.VIDEO_STREAM);
}
public void testGetDashMpd() throws ParsingException {

View File

@@ -3,8 +3,8 @@ package org.schabi.newpipe.extractor.youtube;
import android.test.AndroidTestCase;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
import java.io.IOException;
@@ -39,9 +39,9 @@ public class YoutubeStreamExtractorGemaTest extends AndroidTestCase {
public void testGemaError() throws IOException, ExtractionException {
if(testActive) {
try {
ServiceList.getService("Youtube")
.getExtractorInstance("https://www.youtube.com/watch?v=3O1_3zBUKM8",
new Downloader());
NewPipe.init(Downloader.getInstance());
NewPipe.getService("Youtube")
.getExtractorInstance("https://www.youtube.com/watch?v=3O1_3zBUKM8");
} catch(YoutubeStreamExtractor.GemaException ge) {
assertTrue(true);
}

View File

@@ -2,12 +2,10 @@ package org.schabi.newpipe.extractor.youtube;
import android.test.AndroidTestCase;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
import java.io.IOException;
@@ -39,8 +37,10 @@ public class YoutubeStreamExtractorLiveStreamTest extends AndroidTestCase {
public void setUp() throws IOException, ExtractionException {
//todo: make the extractor not throw over a livestream
/*
extractor = ServiceList.getService("Youtube")
.getExtractorInstance("https://www.youtube.com/watch?v=J0s6NjqdjLE", new Downloader());
NewPipe.init(Downloader.getInstance());
extractor = NewPipe.getService("Youtube")
.getExtractorInstance("https://www.youtube.com/watch?v=J0s6NjqdjLE", Downloader.getInstance());
*/
}

View File

@@ -3,11 +3,11 @@ package org.schabi.newpipe.extractor.youtube;
import android.test.AndroidTestCase;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.VideoStream;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import java.io.IOException;
@@ -15,10 +15,11 @@ public class YoutubeStreamExtractorRestrictedTest extends AndroidTestCase {
public static final String HTTPS = "https://";
private StreamExtractor extractor;
public void setUp() throws IOException, ExtractionException {
extractor = ServiceList.getService("Youtube")
.getExtractorInstance("https://www.youtube.com/watch?v=i6JTvzrpBy0",
new Downloader());
public void setUp() throws Exception {
super.setUp();
NewPipe.init(Downloader.getInstance());
extractor = NewPipe.getService("Youtube")
.getExtractorInstance("https://www.youtube.com/watch?v=i6JTvzrpBy0");
}
public void testGetInvalidTimeStamp() throws ParsingException {
@@ -27,9 +28,8 @@ public class YoutubeStreamExtractorRestrictedTest extends AndroidTestCase {
}
public void testGetValidTimeStamp() throws ExtractionException, IOException {
StreamExtractor extractor=ServiceList.getService("Youtube")
.getExtractorInstance("https://youtu.be/FmG385_uUys?t=174",
new Downloader());
StreamExtractor extractor= NewPipe.getService("Youtube")
.getExtractorInstance("https://youtu.be/FmG385_uUys?t=174");
assertTrue(Integer.toString(extractor.getTimeStamp()),
extractor.getTimeStamp() == 174);
}
@@ -73,7 +73,8 @@ public class YoutubeStreamExtractorRestrictedTest extends AndroidTestCase {
}
public void testGetAudioStreams() throws ParsingException {
assertTrue(!extractor.getAudioStreams().isEmpty());
// audiostream not always necessary
//assertTrue(!extractor.getAudioStreams().isEmpty());
}
public void testGetVideoStreams() throws ParsingException {

View File

@@ -29,6 +29,7 @@
<activity
android:name=".detail.VideoItemDetailActivity"
android:label="@string/title_videoitem_detail"
android:launchMode="singleTask"
android:theme="@style/AppTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@@ -75,6 +76,11 @@
<data android:scheme="vnd.youtube" />
<data android:scheme="vnd.youtube.launch" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name=".player.PlayVideoActivity"
@@ -133,7 +139,7 @@
<!-- giga get related -->
<activity
android:name=".download.MainActivity"
android:name=".download.DownloadActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/AppTheme" />
@@ -152,6 +158,19 @@
android:label="@string/title_activity_channel"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".ReCaptchaActivity"
android:label="@string/reCaptchaActivity" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>

View File

@@ -11,6 +11,7 @@ import org.acra.config.ACRAConfiguration;
import org.acra.config.ACRAConfigurationException;
import org.acra.config.ConfigurationBuilder;
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.settings.SettingsActivity;
@@ -47,6 +48,7 @@ public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
// init crashreport
try {
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
@@ -60,6 +62,9 @@ public class App extends Application {
"Could not initialize ACRA crash report", R.string.app_ui_crash));
}
//init NewPipe
NewPipe.init(Downloader.getInstance());
// Initialize image loader
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this).build();
ImageLoader.getInstance().init(config);

View File

@@ -4,6 +4,7 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
@@ -20,17 +21,18 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.detail.VideoItemDetailFragment;
import org.schabi.newpipe.extractor.ChannelExtractor;
import org.schabi.newpipe.extractor.ChannelInfo;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.ErrorActivity;
import java.io.IOException;
import java.util.Objects;
/**
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
@@ -71,6 +73,10 @@ public class ChannelActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
if (Objects.equals(PreferenceManager.getDefaultSharedPreferences(this)
.getString("theme", getResources().getString(R.string.light_theme_title)), getResources().getString(R.string.dark_theme_title))) {
setTheme(R.style.DarkTheme_NoActionBar);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_channel);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
@@ -128,7 +134,7 @@ public class ChannelActivity extends AppCompatActivity {
CollapsingToolbarLayout ctl = (CollapsingToolbarLayout) findViewById(R.id.channel_toolbar_layout);
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
ImageView channelBanner = (ImageView) findViewById(R.id.channel_banner_image);
FloatingActionButton feedButton = (FloatingActionButton) findViewById(R.id.channel_rss_fab);
final FloatingActionButton feedButton = (FloatingActionButton) findViewById(R.id.channel_rss_fab);
ImageView avatarView = (ImageView) findViewById(R.id.channel_avatar_view);
ImageView haloView = (ImageView) findViewById(R.id.channel_avatar_halo);
@@ -188,11 +194,11 @@ public class ChannelActivity extends AppCompatActivity {
public void run() {
StreamingService service = null;
try {
service = ServiceList.getService(serviceId);
service = NewPipe.getService(serviceId);
ChannelExtractor extractor = service.getChannelExtractorInstance(
channelUrl, pageNumber, new Downloader());
channelUrl, pageNumber);
final ChannelInfo info = ChannelInfo.getInfo(extractor, new Downloader());
final ChannelInfo info = ChannelInfo.getInfo(extractor);
h.post(new Runnable() {

View File

@@ -1,5 +1,7 @@
package org.schabi.newpipe;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -35,13 +37,37 @@ import javax.net.ssl.HttpsURLConnection;
public class Downloader implements org.schabi.newpipe.extractor.Downloader {
private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0";
private static String mCookies = "";
private static Downloader instance = null;
private Downloader() {}
public static Downloader getInstance() {
if(instance == null) {
synchronized (Downloader.class) {
if (instance == null) {
instance = new Downloader();
}
}
}
return instance;
}
public static synchronized void setCookies(String cookies) {
Downloader.mCookies = cookies;
}
public static synchronized String getCookies() {
return Downloader.mCookies;
}
/**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*/
public String download(String siteUrl, String language) throws IOException {
public String download(String siteUrl, String language) throws IOException, ReCaptchaException {
Map<String, String> requestProperties = new HashMap<>();
requestProperties.put("Accept-Language", language);
return download(siteUrl, requestProperties);
@@ -54,7 +80,7 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
* @param customProperties set request header properties
* @return the contents of the specified text file
* @throws IOException*/
public String download(String siteUrl, Map<String, String> customProperties) throws IOException {
public String download(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException {
URL url = new URL(siteUrl);
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
Iterator it = customProperties.entrySet().iterator();
@@ -66,7 +92,7 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
}
/**Common functionality between download(String url) and download(String url, String language)*/
private static String dl(HttpsURLConnection con) throws IOException {
private static String dl(HttpsURLConnection con) throws IOException, ReCaptchaException {
StringBuilder response = new StringBuilder();
BufferedReader in = null;
@@ -74,6 +100,10 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", USER_AGENT);
if (getCookies().length() > 0) {
con.setRequestProperty("Cookie", getCookies());
}
in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
@@ -85,6 +115,14 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
throw new IOException("unknown host or no network", uhe);
//Toast.makeText(getActivity(), uhe.getMessage(), Toast.LENGTH_LONG).show();
} catch(Exception e) {
/*
* HTTP 429 == Too Many Request
* Receive from Youtube.com = ReCaptcha challenge request
* See : https://github.com/rg3/youtube-dl/issues/5138
*/
if (con.getResponseCode() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested");
}
throw new IOException(e);
} finally {
if(in != null) {
@@ -99,7 +137,7 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
* Primarily intended for downloading web pages.
* @param siteUrl the URL of the text file to download
* @return the contents of the specified text file*/
public String download(String siteUrl) throws IOException {
public String download(String siteUrl) throws IOException, ReCaptchaException {
URL url = new URL(siteUrl);
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
//HttpsURLConnection con = NetCipher.getHttpsURLConnection(url);

View File

@@ -8,7 +8,7 @@ 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.ServiceList;
import org.schabi.newpipe.extractor.NewPipe;
/**
* Created by Christian Schabesberger on 01.08.16.
@@ -50,7 +50,7 @@ public class ImageErrorLoadingListener implements ImageLoadingListener {
ErrorActivity.reportError(activity,
failReason.getCause(), null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
ServiceList.getNameOfService(serviceId), imageUri,
NewPipe.getNameOfService(serviceId), imageUri,
R.string.could_not_load_image));
}

View File

@@ -4,19 +4,19 @@ import android.content.Intent;
import android.media.AudioManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.PermissionHelper;
/**
* Created by Christian Schabesberger on 02.08.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* MainActivity.java is part of NewPipe.
* 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
@@ -32,7 +32,7 @@ import org.schabi.newpipe.settings.SettingsActivity;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class MainActivity extends AppCompatActivity {
public class MainActivity extends ThemableActivity {
private Fragment mainFragment = null;
@@ -40,19 +40,17 @@ public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
mainFragment = getSupportFragmentManager()
.findFragmentById(R.id.search_fragment);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
mainFragment.onCreateOptionsMenu(menu, inflater);
return true;
}
@@ -73,13 +71,15 @@ public class MainActivity extends AppCompatActivity {
return true;
}
case R.id.action_show_downloads: {
Intent intent = new Intent(this, org.schabi.newpipe.download.MainActivity.class);
if(!PermissionHelper.checkStoragePermissions(this)) {
return false;
}
Intent intent = new Intent(this, org.schabi.newpipe.download.DownloadActivity.class);
startActivity(intent);
return true;
}
default:
return mainFragment.onOptionsItemSelected(item) ||
super.onOptionsItemSelected(item);
return super.onOptionsItemSelected(item);
}
}
}

View File

@@ -0,0 +1,151 @@
package org.schabi.newpipe;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.webkit.CookieManager;
import android.webkit.ValueCallback;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* Created by beneth <bmauduit@beneth.fr> on 06.12.16.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* ReCaptchaActivity.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 ReCaptchaActivity extends AppCompatActivity {
public static final int RECAPTCHA_REQUEST = 10;
public static final String TAG = ReCaptchaActivity.class.toString();
public static final String YT_URL = "https://www.youtube.com";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recaptcha);
// Set return to Cancel by default
setResult(RESULT_CANCELED);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.reCaptcha_title);
actionBar.setDisplayShowTitleEnabled(true);
WebView myWebView = (WebView) findViewById(R.id.reCaptchaWebView);
// Enable Javascript
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
ReCaptchaWebViewClient webClient = new ReCaptchaWebViewClient(this);
myWebView.setWebViewClient(webClient);
// Cleaning cache, history and cookies from webView
myWebView.clearCache(true);
myWebView.clearHistory();
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.removeAllCookies(new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean aBoolean) {}
});
} else {
cookieManager.removeAllCookie();
}
myWebView.loadUrl(YT_URL);
}
private class ReCaptchaWebViewClient extends WebViewClient {
private Activity context;
private String mCookies;
ReCaptchaWebViewClient(Activity ctx) {
context = ctx;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// TODO: Start Loader
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
String cookies = CookieManager.getInstance().getCookie(url);
// TODO: Stop Loader
// find cookies : s_gl & goojf and Add cookies to Downloader
if (find_access_cookies(cookies)) {
// Give cookies to Downloader class
Downloader.setCookies(mCookies);
// Closing activity and return to parent
setResult(RESULT_OK);
finish();
}
}
private boolean find_access_cookies(String cookies) {
boolean ret = false;
String c_s_gl = "";
String c_goojf = "";
String[] parts = cookies.split("; ");
for (String part : parts) {
if (part.trim().startsWith("s_gl")) {
c_s_gl = part.trim();
}
if (part.trim().startsWith("goojf")) {
c_goojf = part.trim();
}
}
if (c_s_gl.length() > 0 && c_goojf.length() > 0) {
ret = true;
//mCookies = c_s_gl + "; " + c_goojf;
// Youtube seems to also need the other cookies:
mCookies = cookies;
}
return ret;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case android.R.id.home: {
Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
return true;
}
default:
return false;
}
}
}

View File

@@ -0,0 +1,21 @@
package org.schabi.newpipe;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import java.util.Objects;
import static org.schabi.newpipe.R.attr.theme;
public class ThemableActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Objects.equals(PreferenceManager.getDefaultSharedPreferences(this)
.getString("theme", getResources().getString(R.string.light_theme_title)), getResources().getString(R.string.dark_theme_title))) {
setTheme(R.style.DarkTheme);
}
}
}

View File

@@ -14,7 +14,7 @@ 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.VideoStream;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import java.util.List;
@@ -111,6 +111,9 @@ class ActionBarHandler {
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));
@@ -183,7 +186,7 @@ class ActionBarHandler {
return true;
case R.id.menu_item_downloads: {
Intent intent =
new Intent(activity, org.schabi.newpipe.download.MainActivity.class);
new Intent(activity, org.schabi.newpipe.download.DownloadActivity.class);
activity.startActivity(intent);
return true;
}

View File

@@ -5,20 +5,36 @@ import android.os.Handler;
import android.util.Log;
import android.view.View;
import org.schabi.newpipe.Downloader;
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.ParsingException;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.StreamInfo;
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 the-scrabi on 02.08.16.
* 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 {
@@ -28,6 +44,7 @@ public class StreamInfoWorker {
public interface OnStreamInfoReceivedListener {
void onReceive(StreamInfo info);
void onError(int messageId);
void onReCaptchaException();
void onBlockedByGemaError();
void onContentErrorWithMessage(int messageId);
void onContentError();
@@ -51,7 +68,7 @@ public class StreamInfoWorker {
StreamInfo streamInfo = null;
StreamingService service = null;
try {
service = ServiceList.getService(serviceId);
service = NewPipe.getService(serviceId);
} catch (Exception e) {
e.printStackTrace();
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
@@ -60,8 +77,8 @@ public class StreamInfoWorker {
return;
}
try {
streamExtractor = service.getExtractorInstance(videoUrl, new Downloader());
streamInfo = StreamInfo.getVideoInfo(streamExtractor, new Downloader());
streamExtractor = service.getExtractorInstance(videoUrl);
streamInfo = StreamInfo.getVideoInfo(streamExtractor);
final StreamInfo info = streamInfo;
h.post(new Runnable() {
@@ -91,6 +108,13 @@ public class StreamInfoWorker {
}
// 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
@@ -99,9 +123,8 @@ public class StreamInfoWorker {
}
});
e.printStackTrace();
}
// custom service related exceptions
catch (YoutubeStreamExtractor.DecryptException de) {
} catch (YoutubeStreamExtractor.DecryptException de) {
// custom service related exceptions
h.post(new Runnable() {
@Override
public void run() {

View File

@@ -5,7 +5,6 @@ import android.media.AudioManager;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -14,9 +13,13 @@ import android.widget.Toast;
import org.schabi.newpipe.App;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.ThemableActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import java.util.Collection;
import java.util.HashSet;
/**
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
@@ -36,7 +39,14 @@ import org.schabi.newpipe.extractor.StreamingService;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class VideoItemDetailActivity extends AppCompatActivity {
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();
@@ -68,52 +78,58 @@ public class VideoItemDetailActivity extends AppCompatActivity {
// http://developer.android.com/guide/components/fragments.html
//
Bundle arguments = new Bundle();
if (savedInstanceState == null) {
// this means the video was called though another app
if (getIntent().getData() != null) {
videoUrl = getIntent().getData().toString();
StreamingService[] serviceList = ServiceList.getServices();
//StreamExtractor videoExtractor = null;
for (int i = 0; i < serviceList.length; i++) {
if (serviceList[i].getUrlIdHandlerInstance().acceptUrl(videoUrl)) {
arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, i);
currentStreamingService = i;
//videoExtractor = ServiceList.getService(i).getExtractorInstance();
break;
}
}
if(currentStreamingService == -1) {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG)
.show();
}
//arguments.putString(VideoItemDetailFragment.VIDEO_URL,
// videoExtractor.getUrl(videoExtractor.getId(videoUrl)));//cleans URL
arguments.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY,
PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(getString(R.string.autoplay_through_intent_key), false));
} else {
videoUrl = getIntent().getStringExtra(VideoItemDetailFragment.VIDEO_URL);
currentStreamingService = getIntent().getIntExtra(VideoItemDetailFragment.STREAMING_SERVICE, -1);
arguments.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingService);
arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY, false);
}
handleIntent(getIntent());
} else {
videoUrl = savedInstanceState.getString(VideoItemDetailFragment.VIDEO_URL);
currentStreamingService = savedInstanceState.getInt(VideoItemDetailFragment.STREAMING_SERVICE);
arguments = 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(VideoItemDetailFragment.VIDEO_URL);
currentStreamingService = intent.getIntExtra(VideoItemDetailFragment.STREAMING_SERVICE, -1);
}
arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY, autoplay);
arguments.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, 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()
.add(R.id.videoitem_detail_container, fragment)
.replace(R.id.videoitem_detail_container, fragment)
.commit();
}
@@ -125,6 +141,7 @@ public class VideoItemDetailActivity extends AppCompatActivity {
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
outState.putInt(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingService);
outState.putBoolean(VideoItemDetailFragment.AUTO_PLAY, false);
@@ -132,6 +149,7 @@ public class VideoItemDetailActivity extends AppCompatActivity {
@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
@@ -146,15 +164,72 @@ public class VideoItemDetailActivity extends AppCompatActivity {
NavUtils.navigateUpTo(this, intent);
return true;
} else {
return fragment.onOptionsItemSelected(item) ||
super.onOptionsItemSelected(item);
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
fragment.onCreateOptionsMenu(menu, getMenuInflater());
return true;
/**
* 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].getUrlIdHandlerInstance().acceptUrl(videoUrl)) {
service = i;
//videoExtractor = ServiceList.getService(i).getExtractorInstance();
break;
}
}
return service;
}
}

View File

@@ -21,6 +21,7 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -30,7 +31,6 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.view.MenuItem;
import android.widget.Toast;
import com.google.android.exoplayer.util.Util;
@@ -39,25 +39,29 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import java.util.Vector;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.ChannelActivity;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.ImageErrorLoadingListener;
import org.schabi.newpipe.Localization;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.extractor.AudioStream;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamInfo;
import org.schabi.newpipe.extractor.VideoStream;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.PlayVideoActivity;
import org.schabi.newpipe.player.ExoPlayerActivity;
import org.schabi.newpipe.player.PlayVideoActivity;
import org.schabi.newpipe.report.ErrorActivity;
import java.util.Vector;
import org.schabi.newpipe.util.PermissionHelper;
import static android.app.Activity.RESULT_OK;
import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST;
/**
@@ -143,146 +147,150 @@ public class VideoItemDetailFragment extends Fragment {
View topView = activity.findViewById(R.id.detailTopView);
Button channelButton = (Button) activity.findViewById(R.id.channel_button);
progressBar.setVisibility(View.GONE);
if(info.next_video != null) {
// todo: activate this function or remove it
nextStreamView.setVisibility(View.GONE);
} else {
nextStreamView.setVisibility(View.GONE);
activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE);
}
// prevents a crash if the activity/fragment was already left when the response came
if(channelButton != null) {
textContentLayout.setVisibility(View.VISIBLE);
if (android.os.Build.VERSION.SDK_INT < 18) {
playVideoButton.setVisibility(View.VISIBLE);
} else {
ImageView playArrowView = (ImageView) activity.findViewById(R.id.play_arrow_view);
playArrowView.setVisibility(View.VISIBLE);
}
progressBar.setVisibility(View.GONE);
if (info.next_video != null) {
// todo: activate this function or remove it
nextStreamView.setVisibility(View.GONE);
} else {
nextStreamView.setVisibility(View.GONE);
activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE);
}
if (!showNextStreamItem) {
nextVideoRootFrame.setVisibility(View.GONE);
similarTitle.setVisibility(View.GONE);
}
textContentLayout.setVisibility(View.VISIBLE);
if (android.os.Build.VERSION.SDK_INT < 18) {
playVideoButton.setVisibility(View.VISIBLE);
} else {
ImageView playArrowView = (ImageView) activity.findViewById(R.id.play_arrow_view);
playArrowView.setVisibility(View.VISIBLE);
}
videoTitleView.setText(info.title);
if (!showNextStreamItem) {
nextVideoRootFrame.setVisibility(View.GONE);
similarTitle.setVisibility(View.GONE);
}
topView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == android.view.MotionEvent.ACTION_UP) {
ImageView arrow = (ImageView) activity.findViewById(R.id.toggle_description_view);
View extra = activity.findViewById(R.id.detailExtraView);
if (extra.getVisibility() == View.VISIBLE) {
extra.setVisibility(View.GONE);
arrow.setImageResource(R.drawable.arrow_down);
} else {
extra.setVisibility(View.VISIBLE);
arrow.setImageResource(R.drawable.arrow_up);
videoTitleView.setText(info.title);
topView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == android.view.MotionEvent.ACTION_UP) {
ImageView arrow = (ImageView) activity.findViewById(R.id.toggle_description_view);
View extra = activity.findViewById(R.id.detailExtraView);
if (extra.getVisibility() == View.VISIBLE) {
extra.setVisibility(View.GONE);
arrow.setImageResource(R.drawable.arrow_down);
} else {
extra.setVisibility(View.VISIBLE);
arrow.setImageResource(R.drawable.arrow_up);
}
}
return true;
}
return true;
});
// Since newpipe is designed to work even if certain information is not available,
// the UI has to react on missing information.
videoTitleView.setText(info.title);
if (!info.uploader.isEmpty()) {
uploaderView.setText(info.uploader);
} else {
activity.findViewById(R.id.detail_uploader_view).setVisibility(View.GONE);
}
});
// Since newpipe is designed to work even if certain information is not available,
// the UI has to react on missing information.
videoTitleView.setText(info.title);
if(!info.uploader.isEmpty()) {
uploaderView.setText(info.uploader);
} else {
activity.findViewById(R.id.detail_uploader_view).setVisibility(View.GONE);
}
if(info.view_count >= 0) {
viewCountView.setText(Localization.localizeViewCount(info.view_count, a));
} else {
viewCountView.setVisibility(View.GONE);
}
if(info.dislike_count >= 0) {
thumbsDownView.setText(Localization.localizeNumber(info.dislike_count, a));
} else {
thumbsDownView.setVisibility(View.INVISIBLE);
activity.findViewById(R.id.detail_thumbs_down_count_view).setVisibility(View.GONE);
}
if(info.like_count >= 0) {
thumbsUpView.setText(Localization.localizeNumber(info.like_count, a));
} else {
thumbsUpView.setVisibility(View.GONE);
activity.findViewById(R.id.detail_thumbs_up_img_view).setVisibility(View.GONE);
thumbsDownView.setVisibility(View.GONE);
activity.findViewById(R.id.detail_thumbs_down_img_view).setVisibility(View.GONE);
}
if(!info.upload_date.isEmpty()) {
uploadDateView.setText(Localization.localizeDate(info.upload_date, a));
} else {
uploadDateView.setVisibility(View.GONE);
}
if(!info.description.isEmpty()) {
descriptionView.setText(Html.fromHtml(info.description));
} else {
descriptionView.setVisibility(View.GONE);
}
descriptionView.setMovementMethod(LinkMovementMethod.getInstance());
// parse streams
Vector<VideoStream> streamsToUse = new Vector<>();
for (VideoStream i : info.video_streams) {
if (useStream(i, streamsToUse)) {
streamsToUse.add(i);
if (info.view_count >= 0) {
viewCountView.setText(Localization.localizeViewCount(info.view_count, a));
} else {
viewCountView.setVisibility(View.GONE);
}
if (info.dislike_count >= 0) {
thumbsDownView.setText(Localization.localizeNumber(info.dislike_count, a));
} else {
thumbsDownView.setVisibility(View.INVISIBLE);
activity.findViewById(R.id.detail_thumbs_down_count_view).setVisibility(View.GONE);
}
if (info.like_count >= 0) {
thumbsUpView.setText(Localization.localizeNumber(info.like_count, a));
} else {
thumbsUpView.setVisibility(View.GONE);
activity.findViewById(R.id.detail_thumbs_up_img_view).setVisibility(View.GONE);
thumbsDownView.setVisibility(View.GONE);
activity.findViewById(R.id.detail_thumbs_down_img_view).setVisibility(View.GONE);
}
if (!info.upload_date.isEmpty()) {
uploadDateView.setText(Localization.localizeDate(info.upload_date, a));
} else {
uploadDateView.setVisibility(View.GONE);
}
if (!info.description.isEmpty()) {
descriptionView.setText(Html.fromHtml(info.description));
} else {
descriptionView.setVisibility(View.GONE);
}
}
textContentLayout.setVisibility(View.VISIBLE);
descriptionView.setMovementMethod(LinkMovementMethod.getInstance());
if(info.next_video == null) {
activity.findViewById(R.id.detail_next_stream_title).setVisibility(View.GONE);
}
// parse streams
Vector<VideoStream> streamsToUse = new Vector<>();
for (VideoStream i : info.video_streams) {
if (useStream(i, streamsToUse)) {
streamsToUse.add(i);
}
}
if(info.related_streams != null && !info.related_streams.isEmpty()) {
initSimilarVideos(info);
} else {
activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE);
activity.findViewById(R.id.similar_streams_view).setVisibility(View.GONE);
}
textContentLayout.setVisibility(View.VISIBLE);
setupActionBarHandler(info);
if (info.next_video == null) {
activity.findViewById(R.id.detail_next_stream_title).setVisibility(View.GONE);
}
if(autoPlayEnabled) {
playVideo(info);
}
if (info.related_streams != null && !info.related_streams.isEmpty()) {
initSimilarVideos(info);
} else {
activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE);
activity.findViewById(R.id.similar_streams_view).setVisibility(View.GONE);
}
if (android.os.Build.VERSION.SDK_INT < 18) {
playVideoButton.setOnClickListener(new View.OnClickListener() {
setupActionBarHandler(info);
if (autoPlayEnabled) {
playVideo(info);
}
if (android.os.Build.VERSION.SDK_INT < 18) {
playVideoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
playVideo(info);
}
});
}
backgroundButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
playVideo(info);
}
});
}
backgroundButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
playVideo(info);
if (info.channel_url != null && info.channel_url != "") {
channelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(activity, ChannelActivity.class);
i.putExtra(ChannelActivity.CHANNEL_URL, info.channel_url);
i.putExtra(ChannelActivity.SERVICE_ID, info.service_id);
startActivity(i);
}
});
} else {
channelButton.setVisibility(Button.GONE);
}
});
if(info.channel_url != null && info.channel_url != "") {
channelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(activity, ChannelActivity.class);
i.putExtra(ChannelActivity.CHANNEL_URL, info.channel_url);
i.putExtra(ChannelActivity.SERVICE_ID, info.service_id);
startActivity(i);
}
});
} else {
channelButton.setVisibility(Button.GONE);
initThumbnailViews(info);
}
initThumbnailViews(info);
}
private void initThumbnailViews(final StreamInfo info) {
@@ -290,7 +298,7 @@ public class VideoItemDetailFragment extends Fragment {
ImageView uploaderThumb
= (ImageView) activity.findViewById(R.id.detail_uploader_thumbnail_view);
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
if (info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.thumbnail_url, videoThumbnailView,
displayImageOptions, new ImageLoadingListener() {
@Override
@@ -302,7 +310,7 @@ public class VideoItemDetailFragment extends Fragment {
ErrorActivity.reportError(getActivity(),
failReason.getCause(), null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
ServiceList.getNameOfService(info.service_id), imageUri,
NewPipe.getNameOfService(info.service_id), imageUri,
R.string.could_not_load_thumbnails));
}
@@ -318,7 +326,7 @@ public class VideoItemDetailFragment extends Fragment {
} else {
videoThumbnailView.setImageResource(R.drawable.dummy_thumbnail_dark);
}
if(info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) {
if (info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.uploader_thumbnail_url,
uploaderThumb, displayImageOptions,
new ImageErrorLoadingListener(activity, rootView, info.service_id));
@@ -385,6 +393,10 @@ public class VideoItemDetailFragment extends Fragment {
actionBarHandler.setOnDownloadListener(new ActionBarHandler.OnActionListener() {
@Override
public void onActionSelected(int selectedStreamId) {
if(!PermissionHelper.checkStoragePermissions(getActivity())) {
return;
}
try {
Bundle args = new Bundle();
@@ -418,7 +430,7 @@ public class VideoItemDetailFragment extends Fragment {
}
});
if(info.audio_streams == null) {
if (info.audio_streams == null) {
actionBarHandler.showAudioAction(false);
} else {
actionBarHandler.setOnPlayAudioListener(new ActionBarHandler.OnActionListener() {
@@ -575,11 +587,6 @@ public class VideoItemDetailFragment extends Fragment {
return true;
}
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -600,6 +607,17 @@ public class VideoItemDetailFragment extends Fragment {
postNewErrorToast(messageId);
}
@Override
public void onReCaptchaException() {
Toast.makeText(getActivity(), R.string.recaptcha_request_toast,
Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
startActivityForResult(
new Intent(getActivity(), ReCaptchaActivity.class),
RECAPTCHA_REQUEST);
}
@Override
public void onBlockedByGemaError() {
onErrorBlockedByGema();
@@ -615,6 +633,7 @@ public class VideoItemDetailFragment extends Fragment {
onNotSpecifiedContentError();
}
});
setHasOptionsMenu(true);
}
@Override
@@ -633,10 +652,9 @@ public class VideoItemDetailFragment extends Fragment {
}
@Override
public void onActivityCreated(Bundle savedInstanceBundle) {
super.onActivityCreated(savedInstanceBundle);
public void onStart() {
super.onStart();
Activity a = getActivity();
infoItemBuilder = new InfoItemBuilder(a, a.findViewById(android.R.id.content));
if (android.os.Build.VERSION.SDK_INT < 18) {
@@ -765,11 +783,13 @@ public class VideoItemDetailFragment extends Fragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
actionBarHandler.setupMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
return actionBarHandler.onItemSelected(item);
}
@@ -789,4 +809,24 @@ public class VideoItemDetailFragment extends Fragment {
VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
activity.startActivity(detailIntent);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case RECAPTCHA_REQUEST:
if (resultCode == RESULT_OK) {
String videoUrl = getArguments().getString(VIDEO_URL);
StreamInfoWorker siw = StreamInfoWorker.getInstance();
siw.search(streamingServiceId, videoUrl, getActivity());
} else {
Log.d(TAG, "ReCaptcha failed");
}
break;
default:
Log.e(TAG, "Request code from activity not supported [" + requestCode + "]");
break;
}
}
}

View File

@@ -13,8 +13,8 @@ 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;
@@ -23,12 +23,15 @@ 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 java.io.File;
@@ -41,37 +44,22 @@ import us.shandian.giga.ui.fragment.MissionsFragment;
import us.shandian.giga.util.CrashHandler;
import us.shandian.giga.util.Utility;
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener{
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 = MainActivity.class.toString();
private static final String TAG = DownloadActivity.class.toString();
public static final String THREADS = "threads";
private MissionsFragment mFragment;
private DownloadManager mManager;
private DownloadManagerService.DMBinder mBinder;
private String mPendingUrl;
private SharedPreferences mPrefs;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName p1, IBinder binder) {
mBinder = (DownloadManagerService.DMBinder) binder;
mManager = mBinder.getDownloadManager();
}
@Override
public void onServiceDisconnected(ComponentName p1) {
}
};
@Override
@TargetApi(21)
protected void onCreate(Bundle savedInstanceState) {
@@ -82,7 +70,6 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
Intent i = new Intent();
i.setClass(this, DownloadManagerService.class);
startService(i);
bindService(i, mConnection, Context.BIND_AUTO_CREATE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_downloader);
@@ -90,7 +77,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
//noinspection ConstantConditions
// its ok if this failes, we will catch that error later, and send it as report
// 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);
@@ -150,6 +137,8 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
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() {
@@ -199,18 +188,24 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
@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(mManager.getLocation() + "/" + fName);
File f = new File(location, fName);
if (f.exists()) {
Toast.makeText(MainActivity.this, R.string.msg_exists, Toast.LENGTH_SHORT).show();
Toast.makeText(DownloadActivity.this, R.string.msg_exists, Toast.LENGTH_SHORT).show();
} else {
while (mBinder == null);
int res = mManager.startMission(getIntent().getData().toString(), fName, threads.getProgress() + 1);
mBinder.onMissionAdded(mManager.getMission(res));
DownloadManagerService.startMission(
DownloadActivity.this,
getIntent().getData().toString(), location, fName,
audioButton.isChecked(), threads.getProgress() + 1);
mFragment.notifyChange();
mPrefs.edit().putInt(THREADS, threads.getProgress() + 1).commit();
@@ -258,7 +253,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
return true;
}
case R.id.action_report_error: {
ErrorActivity.reportError(MainActivity.this, new Vector<Throwable>(),
ErrorActivity.reportError(DownloadActivity.this, new Vector<Throwable>(),
null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
null,
@@ -266,8 +261,8 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
return true;
}
default:
return mFragment.onOptionsItemSelected(item) ||
super.onOptionsItemSelected(item);
return super.onOptionsItemSelected(item);
}
}
}

View File

@@ -26,12 +26,15 @@ import android.widget.TextView;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.settings.NewPipeSettings;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.service.DownloadManagerService;
@@ -64,24 +67,6 @@ public class DownloadDialog extends DialogFragment {
public static final String AUDIO_URL = "audio_url";
public static final String VIDEO_URL = "video_url";
private DownloadManager mManager;
private DownloadManagerService.DMBinder mBinder;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName p1, IBinder binder) {
mBinder = (DownloadManagerService.DMBinder) binder;
mManager = mBinder.getDownloadManager();
}
@Override
public void onServiceDisconnected(ComponentName p1) {
}
};
public DownloadDialog() {
}
@@ -101,12 +86,6 @@ public class DownloadDialog extends DialogFragment {
if(ContextCompat.checkSelfPermission(this.getContext(),Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},0);
Intent i = new Intent();
i.setClass(getContext(), DownloadManagerService.class);
getContext().startService(i);
getContext().bindService(i, mConnection, Context.BIND_AUTO_CREATE);
return inflater.inflate(R.layout.dialog_url, container);
}
@@ -218,26 +197,22 @@ public class DownloadDialog extends DialogFragment {
String fName = name.getText().toString().trim();
// todo: add timeout? would be bad if the thread gets locked dueto this.
while (mBinder == null);
if(audioButton.isChecked()){
int res = mManager.startMission(
arguments.getString(AUDIO_URL),
fName + arguments.getString(FILE_SUFFIX_AUDIO),
threads.getProgress() + 1);
mBinder.onMissionAdded(mManager.getMission(res));
boolean isAudio = audioButton.isChecked();
String url, location, filename;
if(isAudio) {
url = arguments.getString(AUDIO_URL);
location = NewPipeSettings.getAudioDownloadPath(getContext());
filename = fName + arguments.getString(FILE_SUFFIX_AUDIO);
} else {
url = arguments.getString(VIDEO_URL);
location = NewPipeSettings.getVideoDownloadPath(getContext());
filename = fName + arguments.getString(FILE_SUFFIX_VIDEO);
}
if(videoButton.isChecked()){
int res = mManager.startMission(
arguments.getString(VIDEO_URL),
fName + arguments.getString(FILE_SUFFIX_VIDEO),
threads.getProgress() + 1);
mBinder.onMissionAdded(mManager.getMission(res));
}
DownloadManagerService.startMission(getContext(), url, location, filename, isAudio,
threads.getProgress() + 1);
getDialog().dismiss();
}
private void download(String url, String title,
@@ -255,8 +230,8 @@ public class DownloadDialog extends DialogFragment {
//we'll see later
FileDownloader.downloadFile(getContext(), url, saveFilePath, title);
} else {
Intent intent = new Intent(getContext(), MainActivity.class);
intent.setAction(MainActivity.INTENT_DOWNLOAD);
Intent intent = new Intent(getContext(), DownloadActivity.class);
intent.setAction(DownloadActivity.INTENT_DOWNLOAD);
intent.setData(Uri.parse(url));
intent.putExtra("fileName", createFileName(title) + fileSuffix);
startActivity(intent);

View File

@@ -42,7 +42,7 @@ import info.guardianproject.netcipher.NetCipher;
*/
// TODO: FOR HEVEN SAKE !!! DO NOT SIMPLY USE ASYNCTASK. MAKE THIS A PROPER SERVICE !!!
// 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";

View File

@@ -1,10 +1,8 @@
package org.schabi.newpipe.extractor;
import android.graphics.Bitmap;
/**
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* AbstractVideoInfo.java is part of NewPipe.
* 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
@@ -21,7 +19,7 @@ import android.graphics.Bitmap;
*/
/**Common properties between StreamInfo and StreamPreviewInfo.*/
public abstract class AbstractVideoInfo {
public abstract class AbstractStreamInfo {
public static enum StreamType {
NONE, // placeholder to check if stream type was checked or not
VIDEO_STREAM,
@@ -37,7 +35,6 @@ public abstract class AbstractVideoInfo {
public String title = "";
public String uploader = "";
public String thumbnail_url = "";
public Bitmap thumbnail;
public String webpage_url = "";
public String upload_date = "";
public long view_count = -1;

View File

@@ -2,6 +2,9 @@ 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;
@@ -40,14 +43,16 @@ public class DashMpdParser {
}
}
public static List<AudioStream> getAudioStreams(String dashManifestUrl,
Downloader downloader)
throws DashMpdParsingException {
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 {

View File

@@ -1,5 +1,7 @@
package org.schabi.newpipe.extractor;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import java.io.IOException;
import java.util.Map;
@@ -31,7 +33,7 @@ public interface Downloader {
* @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;
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.
@@ -39,12 +41,12 @@ public interface Downloader {
* @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;
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;
String download(String siteUrl) throws IOException, ReCaptchaException;
}

View File

@@ -1,14 +1,13 @@
package org.schabi.newpipe.extractor;
import android.util.Log;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.services.youtube.YoutubeService;
/**
* Created by Christian Schabesberger on 23.08.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* ServiceList.java is part of NewPipe.
* NewPipe.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
@@ -28,20 +27,24 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeService;
* Currently only Youtube until the API becomes more stable.*/
@SuppressWarnings("ALL")
public class ServiceList {
public class NewPipe {
private ServiceList() {
private NewPipe() {
}
private static final String TAG = ServiceList.class.toString();
private static final StreamingService[] services = {
private static final String TAG = NewPipe.class.toString();
private static final StreamingService[] serviceList = {
new YoutubeService(0)
};
private static Downloader downloader = null;
public static StreamingService[] getServices() {
return services;
return serviceList;
}
public static StreamingService getService(int serviceId)throws ExtractionException {
for(StreamingService s : services) {
for(StreamingService s : serviceList) {
if(s.getServiceId() == serviceId) {
return s;
}
@@ -49,7 +52,7 @@ public class ServiceList {
throw new ExtractionException("Service not known: " + Integer.toString(serviceId));
}
public static StreamingService getService(String serviceName) throws ExtractionException {
return services[getIdOfService(serviceName)];
return serviceList[getIdOfService(serviceName)];
}
public static String getNameOfService(int id) {
try {
@@ -61,11 +64,19 @@ public class ServiceList {
}
}
public static int getIdOfService(String serviceName) throws ExtractionException {
for(int i = 0; i < services.length; i++) {
if(services[i].getServiceInfo().name.equals(serviceName)) {
for(int i = 0; i < serviceList.length; i++) {
if(serviceList[i].getServiceInfo().name.equals(serviceName)) {
return i;
}
}
throw new ExtractionException("Error: Service " + serviceName + " not known.");
}
public static void init(Downloader d) {
downloader = d;
}
public static Downloader getDownloader() {
return downloader;
}
}

View File

@@ -1,6 +1,6 @@
package org.schabi.newpipe.extractor;
import android.util.Log;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

View File

@@ -1,5 +1,11 @@
package org.schabi.newpipe.extractor;
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SuggestionExtractor;
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
import java.io.IOException;
/**
@@ -35,13 +41,14 @@ public abstract class StreamingService {
public abstract ServiceInfo getServiceInfo();
public abstract StreamExtractor getExtractorInstance(String url, Downloader downloader)
public abstract StreamExtractor getExtractorInstance(String url)
throws IOException, ExtractionException;
public abstract SearchEngine getSearchEngineInstance(Downloader downloader);
public abstract SearchEngine getSearchEngineInstance();
public abstract UrlIdHandler getUrlIdHandlerInstance();
public abstract UrlIdHandler getChannelUrlIdHandlerInstance();
public abstract ChannelExtractor getChannelExtractorInstance(String url, int page, Downloader downloader)
public abstract ChannelExtractor getChannelExtractorInstance(String url, int page)
throws ExtractionException, IOException;
public abstract SuggestionExtractor getSuggestionExtractorInstance();
public final int getServiceId() {
return serviceId;

View File

@@ -1,5 +1,7 @@
package org.schabi.newpipe.extractor;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
/**
* Created by Christian Schabesberger on 26.07.16.
*

View File

@@ -1,4 +1,9 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.channel;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoCollector;
import java.io.IOException;
@@ -26,15 +31,13 @@ public abstract class ChannelExtractor {
private int serviceId;
private String url;
private UrlIdHandler urlIdHandler;
private Downloader downloader;
private StreamPreviewInfoCollector previewInfoCollector;
private int page = -1;
public ChannelExtractor(UrlIdHandler urlIdHandler, String url, int page, Downloader dl, int serviceId)
public ChannelExtractor(UrlIdHandler urlIdHandler, String url, int page, int serviceId)
throws ExtractionException, IOException {
this.url = url;
this.page = page;
this.downloader = dl;
this.serviceId = serviceId;
this.urlIdHandler = urlIdHandler;
previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId);
@@ -42,7 +45,6 @@ public abstract class ChannelExtractor {
public String getUrl() { return url; }
public UrlIdHandler getUrlIdHandler() { return urlIdHandler; }
public Downloader getDownloader() { return downloader; }
public StreamPreviewInfoCollector getStreamPreviewInfoCollector() {
return previewInfoCollector;
}

View File

@@ -1,6 +1,8 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.channel;
import android.util.Log;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoCollector;
import java.util.List;
import java.util.Vector;
@@ -32,7 +34,7 @@ public class ChannelInfo {
errors.add(e);
}
public static ChannelInfo getInfo(ChannelExtractor extractor, Downloader dl)
public static ChannelInfo getInfo(ChannelExtractor extractor)
throws ParsingException {
ChannelInfo info = new ChannelInfo();

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.exceptions;
/**
* Created by Christian Schabesberger on 30.01.16.
@@ -24,11 +24,9 @@ public class ExtractionException extends Exception {
public ExtractionException(String message) {
super(message);
}
public ExtractionException(Throwable cause) {
super(cause);
}
public ExtractionException(String message, Throwable cause) {
super(message, cause);
}

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.exceptions;
/**
* Created by Christian Schabesberger on 12.09.16.

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.exceptions;
/**
* Created by Christian Schabesberger on 31.01.16.

View File

@@ -0,0 +1,27 @@
package org.schabi.newpipe.extractor.exceptions;
/**
* Created by beneth <bmauduit@beneth.fr> on 07.12.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* ReCaptchaException.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 ReCaptchaException extends ExtractionException {
public ReCaptchaException(String message) {
super(message);
}
}

View File

@@ -1,7 +1,9 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.search;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import java.io.IOException;
import java.util.List;
/**
* Created by Christian Schabesberger on 10.08.15.
@@ -23,7 +25,6 @@ import java.util.List;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
@SuppressWarnings("ALL")
public abstract class SearchEngine {
public static class NothingFoundException extends ExtractionException {
public NothingFoundException(String message) {
@@ -41,12 +42,8 @@ public abstract class SearchEngine {
return collector;
}
public abstract List<String> suggestionList(
String query,String contentCountry, Downloader dl)
throws ExtractionException, IOException;
//Result search(String query, int page);
public abstract StreamPreviewInfoSearchCollector search(
String query, int page, String contentCountry, Downloader dl)
String query, int page, String contentCountry)
throws ExtractionException, IOException;
}

View File

@@ -1,4 +1,7 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.search;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo;
import java.io.IOException;
import java.util.List;
@@ -26,10 +29,10 @@ import java.util.Vector;
public class SearchResult {
public static SearchResult getSearchResult(SearchEngine engine, String query,
int page, String languageCode, Downloader dl)
int page, String languageCode)
throws ExtractionException, IOException {
SearchResult result = engine.search(query, page, languageCode, dl).getSearchResult();
SearchResult result = engine.search(query, page, languageCode).getSearchResult();
if(result.resultList.isEmpty()) {
if(result.suggestion.isEmpty()) {
throw new ExtractionException("Empty result despite no error");

View File

@@ -1,4 +1,7 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.search;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoCollector;
/**
* Created by Christian Schabesberger on 11.05.16.

View File

@@ -0,0 +1,43 @@
package org.schabi.newpipe.extractor.search;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import java.io.IOException;
import java.util.List;
/**
* Created by Christian Schabesberger on 28.09.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SuggestionExtractor.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 abstract class SuggestionExtractor {
private int serviceId;
public SuggestionExtractor(int serviceId) {
this.serviceId = serviceId;
}
public abstract List<String> suggestionList(
String query,String contentCountry)
throws ExtractionException, IOException;
public int getServiceId() {
return serviceId;
}
}

View File

@@ -7,20 +7,19 @@ import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.ChannelExtractor;
import org.schabi.newpipe.extractor.AbstractStreamInfo;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoExtractor;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Christian Schabesberger on 25.07.16.
@@ -48,7 +47,6 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
// private CSSOMParser cssParser = new CSSOMParser(new SACParserCSS3());
private Downloader downloader;
private Document doc = null;
private boolean isAjaxPage = false;
@@ -61,12 +59,13 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
// this request url.
private static String nextPageUrl = "";
public YoutubeChannelExtractor(UrlIdHandler urlIdHandler, String url, int page, Downloader dl, int serviceId)
public YoutubeChannelExtractor(UrlIdHandler urlIdHandler, String url, int page, int serviceId)
throws ExtractionException, IOException {
super(urlIdHandler, url, page, dl, serviceId);
super(urlIdHandler, url, page, serviceId);
Downloader downloader = NewPipe.getDownloader();
url = urlIdHandler.cleanUrl(url) ; //+ "/video?veiw=0&flow=list&sort=dd";
downloader = dl;
if(page == 0) {
if (isUserUrl(url)) {
@@ -137,7 +136,7 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
if(!isAjaxPage) {
Element el = doc.select("div[id=\"gh-banner\"]").first().select("style").first();
String cssContent = el.html();
String url = "https:" + Parser.matchGroup1("url\\((.*)\\)", cssContent);
String url = "https:" + Parser.matchGroup1("url\\(([^)]+)\\)", cssContent);
if (url.contains("s.ytimg.com")) {
bannerUrl = null;
} else {
@@ -164,8 +163,8 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
if (li.select("div[class=\"feed-item-dismissable\"]").first() != null) {
collector.commit(new StreamPreviewInfoExtractor() {
@Override
public AbstractVideoInfo.StreamType getStreamType() throws ParsingException {
return AbstractVideoInfo.StreamType.VIDEO_STREAM;
public AbstractStreamInfo.StreamType getStreamType() throws ParsingException {
return AbstractStreamInfo.StreamType.VIDEO_STREAM;
}
@Override

View File

@@ -1,8 +1,8 @@
package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
/**
* Created by Christian Schabesberger on 25.07.16.

View File

@@ -1,6 +1,7 @@
package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
/**
* Created by Christian Schabesberger on 02.03.16.

View File

@@ -1,34 +1,19 @@
package org.schabi.newpipe.extractor.services.youtube;
import android.util.Log;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
import org.schabi.newpipe.extractor.StreamPreviewInfoSearchCollector;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.StreamPreviewInfoSearchCollector;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoExtractor;
import java.net.URLEncoder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* Created by Christian Schabesberger on 09.08.15.
@@ -60,24 +45,15 @@ public class YoutubeSearchEngine extends SearchEngine {
}
@Override
public StreamPreviewInfoSearchCollector search(String query, int page, String languageCode, Downloader downloader)
public StreamPreviewInfoSearchCollector search(String query, int page, String languageCode)
throws IOException, ExtractionException {
StreamPreviewInfoSearchCollector collector = getStreamPreviewInfoSearchCollector();
/* Cant use Uri.Bilder since it's android code.
// Android code is baned from the extractor side.
Uri.Builder builder = new Uri.Builder();
builder.scheme("https")
.authority("www.youtube.com")
.appendPath("results")
.appendQueryParameter("search_query", query)
.appendQueryParameter("page", Integer.toString(page))
.appendQueryParameter("filters", "video");
*/
Downloader downloader = NewPipe.getDownloader();
String url = "https://www.youtube.com/results"
+ "?search_query=" + URLEncoder.encode(query, CHARSET_UTF_8)
+ "&page=" + Integer.toString(page)
+ "&page=" + Integer.toString(page + 1)
+ "&filters=" + "video";
String site;
@@ -109,18 +85,18 @@ public class YoutubeSearchEngine extends SearchEngine {
Element el;
// both types of spell correction item
if (!((el = item.select("div[class*=\"spell-correction\"]").first()) == null)) {
if ((el = item.select("div[class*=\"spell-correction\"]").first()) != null) {
collector.setSuggestion(el.select("a").first().text());
if(list.children().size() == 1) {
throw new NothingFoundException("Did you mean: " + el.select("a").first().text());
}
// search message item
} else if (!((el = item.select("div[class*=\"search-message\"]").first()) == null)) {
} else if ((el = item.select("div[class*=\"search-message\"]").first()) != null) {
//result.errorMessage = el.text();
throw new NothingFoundException(el.text());
// video item type
} else if (!((el = item.select("div[class*=\"yt-lockup-video\"").first()) == null)) {
} else if ((el = item.select("div[class*=\"yt-lockup-video\"").first()) != null) {
collector.commit(extractPreviewInfo(el));
} else {
//noinspection ConstantConditions
@@ -131,66 +107,6 @@ public class YoutubeSearchEngine extends SearchEngine {
return collector;
}
@Override
public List<String> suggestionList(String query, String contentCountry, Downloader dl)
throws IOException, ParsingException {
List<String> suggestions = new ArrayList<>();
/* Cant use Uri.Bilder since it's android code.
// Android code is baned from the extractor side.
Uri.Builder builder = new Uri.Builder();
builder.scheme("https")
.authority("suggestqueries.google.com")
.appendPath("complete")
.appendPath("search")
.appendQueryParameter("client", "")
.appendQueryParameter("output", "toolbar")
.appendQueryParameter("ds", "yt")
.appendQueryParameter("hl",contentCountry)
.appendQueryParameter("q", query);
*/
String url = "https://suggestqueries.google.com/complete/search"
+ "?client=" + ""
+ "&output=" + "toolbar"
+ "&ds=" + "yt"
+ "&hl=" + URLEncoder.encode(contentCountry, CHARSET_UTF_8)
+ "&q=" + URLEncoder.encode(query, CHARSET_UTF_8);
String response = dl.download(url);
//TODO: Parse xml data using Jsoup not done
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder;
org.w3c.dom.Document doc = null;
try {
dBuilder = dbFactory.newDocumentBuilder();
doc = dBuilder.parse(new InputSource(
new ByteArrayInputStream(response.getBytes(CHARSET_UTF_8))));
doc.getDocumentElement().normalize();
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new ParsingException("Could not parse document.");
}
try {
NodeList nList = doc.getElementsByTagName("CompleteSuggestion");
for (int temp = 0; temp < nList.getLength(); temp++) {
NodeList nList1 = doc.getElementsByTagName("suggestion");
Node nNode1 = nList1.item(temp);
if (nNode1.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element eElement = (org.w3c.dom.Element) nNode1;
suggestions.add(eElement.getAttribute("data"));
}
}
return suggestions;
} catch(Exception e) {
throw new ParsingException("Could not get suggestions form document.", e);
}
}
private StreamPreviewInfoExtractor extractPreviewInfo(final Element item) {
return new YoutubeStreamPreviewInfoExtractor(item);
}

View File

@@ -1,12 +1,12 @@
package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.ChannelExtractor;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SuggestionExtractor;
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
import java.io.IOException;
@@ -44,24 +44,24 @@ public class YoutubeService extends StreamingService {
return serviceInfo;
}
@Override
public StreamExtractor getExtractorInstance(String url, Downloader downloader)
public StreamExtractor getExtractorInstance(String url)
throws ExtractionException, IOException {
UrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler();
UrlIdHandler urlIdHandler = YoutubeStreamUrlIdHandler.getInstance();
if(urlIdHandler.acceptUrl(url)) {
return new YoutubeStreamExtractor(urlIdHandler, url, downloader, getServiceId());
return new YoutubeStreamExtractor(urlIdHandler, url, getServiceId());
}
else {
throw new IllegalArgumentException("supplied String is not a valid Youtube URL");
}
}
@Override
public SearchEngine getSearchEngineInstance(Downloader downloader) {
public SearchEngine getSearchEngineInstance() {
return new YoutubeSearchEngine(getUrlIdHandlerInstance(), getServiceId());
}
@Override
public UrlIdHandler getUrlIdHandlerInstance() {
return new YoutubeStreamUrlIdHandler();
return YoutubeStreamUrlIdHandler.getInstance();
}
@Override
@@ -70,8 +70,13 @@ public class YoutubeService extends StreamingService {
}
@Override
public ChannelExtractor getChannelExtractorInstance(String url, int page, Downloader downloader)
public ChannelExtractor getChannelExtractorInstance(String url, int page)
throws ExtractionException, IOException {
return new YoutubeChannelExtractor(getChannelUrlIdHandlerInstance(), url, page, downloader, getServiceId());
return new YoutubeChannelExtractor(getChannelUrlIdHandlerInstance(), url, page, getServiceId());
}
@Override
public SuggestionExtractor getSuggestionExtractorInstance() {
return new YoutubeSuggestionExtractor(getServiceId());
}
}

View File

@@ -8,19 +8,21 @@ import org.jsoup.nodes.Element;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.ScriptableObject;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.AudioStream;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.AbstractStreamInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.StreamInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.VideoStream;
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoExtractor;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import java.io.IOException;
import java.util.List;
@@ -86,7 +88,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
// $$el_type$$ will be replaced by the actual el_type (se the declarations below)
private static final String GET_VIDEO_INFO_URL =
"https://www.youtube.com/get_video_info?video_id=%%video_id%%$$el_type$$&ps=default&eurl=&gl=US&hl=en";
// eltype is nececeary for the url aboth
// eltype is necessary for the url above
private static final String EL_INFO = "el=info";
public enum ItagType {
@@ -182,18 +184,15 @@ public class YoutubeStreamExtractor extends StreamExtractor {
// cached values
private static volatile String decryptionCode = "";
UrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler();
UrlIdHandler urlidhandler = YoutubeStreamUrlIdHandler.getInstance();
String pageUrl = "";
private Downloader downloader;
public YoutubeStreamExtractor(UrlIdHandler urlIdHandler, String pageUrl,
Downloader dl, int serviceId)
public YoutubeStreamExtractor(UrlIdHandler urlIdHandler, String pageUrl, int serviceId)
throws ExtractionException, IOException {
super(urlIdHandler ,pageUrl, dl, serviceId);
super(urlIdHandler, pageUrl, serviceId);
//most common videoInfo fields are now set in our superclass, for all services
downloader = dl;
this.pageUrl = pageUrl;
Downloader downloader = NewPipe.getDownloader();
String pageContent = downloader.download(urlidhandler.cleanUrl(pageUrl));
doc = Jsoup.parse(pageContent, pageUrl);
JSONObject ytPlayerConfig;
@@ -282,8 +281,9 @@ public class YoutubeStreamExtractor extends StreamExtractor {
}
}
private String getPlayerUrlFromRestrictedVideo(String pageUrl) throws ParsingException {
private String getPlayerUrlFromRestrictedVideo(String pageUrl) throws ParsingException, ReCaptchaException {
try {
Downloader downloader = NewPipe.getDownloader();
String playerUrl = "";
String videoId = urlidhandler.getId(pageUrl);
String embedUrl = "https://www.youtube.com/embed/" + videoId;
@@ -303,6 +303,8 @@ public class YoutubeStreamExtractor extends StreamExtractor {
} catch (IOException e) {
throw new ParsingException(
"Could load decryption code form restricted video for the Youtube service.", e);
} catch (ReCaptchaException e) {
throw new ReCaptchaException("reCaptcha Challenge requested");
}
}
@@ -710,7 +712,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
private StreamPreviewInfoExtractor extractVideoPreviewInfo(final Element li) {
return new StreamPreviewInfoExtractor() {
@Override
public AbstractVideoInfo.StreamType getStreamType() throws ParsingException {
public AbstractStreamInfo.StreamType getStreamType() throws ParsingException {
return null;
}
@@ -788,6 +790,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
String decryptionCode;
try {
Downloader downloader = NewPipe.getDownloader();
String playerCode = downloader.download(playerUrl);
decryptionFuncName =

View File

@@ -1,10 +1,10 @@
package org.schabi.newpipe.extractor.services.youtube;
import org.jsoup.nodes.Element;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.AbstractStreamInfo;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoExtractor;
/**
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
@@ -146,11 +146,11 @@ public class YoutubeStreamPreviewInfoExtractor implements StreamPreviewInfoExtra
}
@Override
public AbstractVideoInfo.StreamType getStreamType() {
public AbstractStreamInfo.StreamType getStreamType() {
if(isLiveStream(item)) {
return AbstractVideoInfo.StreamType.LIVE_STREAM;
return AbstractStreamInfo.StreamType.LIVE_STREAM;
} else {
return AbstractVideoInfo.StreamType.VIDEO_STREAM;
return AbstractStreamInfo.StreamType.VIDEO_STREAM;
}
}

View File

@@ -1,12 +1,21 @@
package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.FoundAdException;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.UrlIdHandler;
import android.support.annotation.NonNull;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.FoundAdException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.regex.Matcher;
/**
* Created by Christian Schabesberger on 02.02.16.
@@ -29,45 +38,55 @@ import java.net.URLDecoder;
*/
public class YoutubeStreamUrlIdHandler implements UrlIdHandler {
@SuppressWarnings("WeakerAccess")
private static final YoutubeStreamUrlIdHandler instance = new YoutubeStreamUrlIdHandler();
private static final String ID_PATTERN = "([\\-a-zA-Z0-9_]{11})";
private YoutubeStreamUrlIdHandler() {}
public static YoutubeStreamUrlIdHandler getInstance() {
return instance;
}
@Override
public String getUrl(String videoId) {
return "https://www.youtube.com/watch?v=" + videoId;
}
@SuppressWarnings("WeakerAccess")
@Override
public String getId(String url) throws ParsingException, IllegalArgumentException {
if(url.isEmpty())
{
if(url.isEmpty()) {
throw new IllegalArgumentException("The url parameter should not be empty");
}
String id;
if(url.contains("youtube")) {
if(url.contains("attribution_link")) {
String id;
String lowercaseUrl = url.toLowerCase();
if(lowercaseUrl.contains("youtube")) {
if (url.contains("attribution_link")) {
try {
String escapedQuery = Parser.matchGroup1("u=(.[^&|$]*)", url);
String query = URLDecoder.decode(escapedQuery, "UTF-8");
id = Parser.matchGroup1("v=([\\-a-zA-Z0-9_]{11})", query);
} catch(UnsupportedEncodingException uee) {
id = Parser.matchGroup1("v=" + ID_PATTERN, query);
} catch (UnsupportedEncodingException uee) {
throw new ParsingException("Could not parse attribution_link", uee);
}
}
else if(url.contains("vnd.youtube"))
{
id = Parser.matchGroup1("([\\-a-zA-Z0-9_]{11}).*", url);
} else if(lowercaseUrl.contains("youtube.com/shared?ci=")) {
return getRealIdFromSharedLink(url);
} else if (url.contains("vnd.youtube")) {
id = Parser.matchGroup1(ID_PATTERN, url);
} else if (url.contains("embed")) {
id = Parser.matchGroup1("embed/" + ID_PATTERN, url);
} else if(url.contains("googleads")) {
throw new FoundAdException("Error found add: " + url);
} else {
id = Parser.matchGroup1("[?&]v=([\\-a-zA-Z0-9_]{11})", url);
id = Parser.matchGroup1("[?&]v=" + ID_PATTERN, url);
}
}
else if(url.contains("youtu.be")) {
else if(lowercaseUrl.contains("youtu.be")) {
if(url.contains("v=")) {
id = Parser.matchGroup1("v=([\\-a-zA-Z0-9_]{11})", url);
id = Parser.matchGroup1("v=" + ID_PATTERN, url);
} else {
id = Parser.matchGroup1("youtu\\.be/([a-zA-Z0-9_-]{11})", url);
id = Parser.matchGroup1("[Yy][Oo][Uu][Tt][Uu]\\.[Bb][Ee]/" + ID_PATTERN, url);
}
}
else {
@@ -82,12 +101,55 @@ public class YoutubeStreamUrlIdHandler implements UrlIdHandler {
}
}
/**
* Get the real url from a shared uri.
*
* Shared URI's look like this:
* <pre>
* * https://www.youtube.com/shared?ci=PJICrTByb3E
* * vnd.youtube://www.youtube.com/shared?ci=PJICrTByb3E&feature=twitter-deep-link
* </pre>
* @param url The shared url
* @return the id of the stream
* @throws ParsingException
*/
private @NonNull String getRealIdFromSharedLink(String url) throws ParsingException {
URI uri;
try {
uri = new URI(url);
} catch (URISyntaxException e) {
throw new ParsingException("Invalid shared link", e);
}
String sharedId = getSharedId(uri);
Downloader downloader = NewPipe.getDownloader();
String content;
try {
content = downloader.download("https://www.youtube.com/shared?ci=" + sharedId);
} catch (IOException | ReCaptchaException e) {
throw new ParsingException("Unable to resolve shared link", e);
}
// is this bad? is this fragile?:
String realId = Parser.matchGroup1("rel=\"shortlink\" href=\"https://youtu.be/" + ID_PATTERN, content);
if(sharedId.equals(realId)) {
throw new ParsingException("Got same id for as shared id: " + sharedId);
}
return realId;
}
private @NonNull String getSharedId(URI uri) throws ParsingException {
if (!"/shared".equals(uri.getPath())) {
throw new ParsingException("Not a shared link: " + uri.toString() + " (path != " + uri.getPath() + ")");
}
return Parser.matchGroup1("ci=" + ID_PATTERN, uri.getQuery());
}
public String cleanUrl(String complexUrl) throws ParsingException {
return getUrl(getId(complexUrl));
}
@Override
public boolean acceptUrl(String videoUrl) {
videoUrl = videoUrl.toLowerCase();
return videoUrl.contains("youtube") ||
videoUrl.contains("youtu.be");
}

View File

@@ -0,0 +1,100 @@
package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.search.SuggestionExtractor;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* Created by Christian Schabesberger on 28.09.16.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* YoutubeSuggestionExtractor.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 YoutubeSuggestionExtractor extends SuggestionExtractor {
public static final String CHARSET_UTF_8 = "UTF-8";
public YoutubeSuggestionExtractor(int serviceId) {
super(serviceId);
}
@Override
public List<String> suggestionList(
String query, String contentCountry)
throws ExtractionException, IOException {
List<String> suggestions = new ArrayList<>();
Downloader dl = NewPipe.getDownloader();
String url = "https://suggestqueries.google.com/complete/search"
+ "?client=" + ""
+ "&output=" + "toolbar"
+ "&ds=" + "yt"
+ "&hl=" + URLEncoder.encode(contentCountry, CHARSET_UTF_8)
+ "&q=" + URLEncoder.encode(query, CHARSET_UTF_8);
String response = dl.download(url);
//TODO: Parse xml data using Jsoup not done
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder;
org.w3c.dom.Document doc = null;
try {
dBuilder = dbFactory.newDocumentBuilder();
doc = dBuilder.parse(new InputSource(
new ByteArrayInputStream(response.getBytes(CHARSET_UTF_8))));
doc.getDocumentElement().normalize();
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new ParsingException("Could not parse document.");
}
try {
NodeList nList = doc.getElementsByTagName("CompleteSuggestion");
for (int temp = 0; temp < nList.getLength(); temp++) {
NodeList nList1 = doc.getElementsByTagName("suggestion");
Node nNode1 = nList1.item(temp);
if (nNode1.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element eElement = (org.w3c.dom.Element) nNode1;
suggestions.add(eElement.getAttribute("data"));
}
}
return suggestions;
} catch(Exception e) {
throw new ParsingException("Could not get suggestions form document.", e);
}
}
}

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.stream_info;
/**
* Created by Christian Schabesberger on 04.03.16.
@@ -31,14 +31,14 @@ public class AudioStream {
this.bandwidth = bandwidth; this.sampling_rate = samplingRate;
}
// reveals wether two streams are the same, but have diferent urls
// reveals whether two streams are the same, but have different urls
public boolean equalStats(AudioStream cmp) {
return format == cmp.format
&& bandwidth == cmp.bandwidth
&& sampling_rate == cmp.sampling_rate;
}
// revelas wether two streams are equal
// reveals whether two streams are equal
public boolean equals(AudioStream cmp) {
return cmp != null && equalStats(cmp)
&& url == cmp.url;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.stream_info;
/**
* Created by Christian Schabesberger on 10.08.15.
@@ -20,6 +20,10 @@ package org.schabi.newpipe.extractor;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import java.util.List;
/**Scrapes information from a video streaming service (eg, YouTube).*/
@@ -31,17 +35,16 @@ public abstract class StreamExtractor {
private int serviceId;
private String url;
private UrlIdHandler urlIdHandler;
private Downloader downloader;
private StreamPreviewInfoCollector previewInfoCollector;
public class ExctractorInitException extends ExtractionException {
public ExctractorInitException(String message) {
public class ExtractorInitException extends ExtractionException {
public ExtractorInitException(String message) {
super(message);
}
public ExctractorInitException(Throwable cause) {
public ExtractorInitException(Throwable cause) {
super(cause);
}
public ExctractorInitException(String message, Throwable cause) {
public ExtractorInitException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -55,7 +58,7 @@ public abstract class StreamExtractor {
}
}
public StreamExtractor(UrlIdHandler urlIdHandler, String url, Downloader dl, int serviceId) {
public StreamExtractor(UrlIdHandler urlIdHandler, String url, int serviceId) {
this.serviceId = serviceId;
this.urlIdHandler = urlIdHandler;
previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId);
@@ -73,10 +76,6 @@ public abstract class StreamExtractor {
return urlIdHandler;
}
public Downloader getDownloader() {
return downloader;
}
public abstract int getTimeStamp() throws ParsingException;
public abstract String getTitle() throws ParsingException;
public abstract String getDescription() throws ParsingException;

View File

@@ -1,4 +1,9 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.stream_info;
import org.schabi.newpipe.extractor.AbstractStreamInfo;
import org.schabi.newpipe.extractor.DashMpdParser;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import java.io.IOException;
import java.util.List;
@@ -26,7 +31,7 @@ import java.util.Vector;
/**Info object for opened videos, ie the video ready to play.*/
@SuppressWarnings("ALL")
public class StreamInfo extends AbstractVideoInfo {
public class StreamInfo extends AbstractStreamInfo {
public static class StreamExctractException extends ExtractionException {
StreamExctractException(String message) {
@@ -39,12 +44,11 @@ public class StreamInfo extends AbstractVideoInfo {
/**Creates a new StreamInfo object from an existing AbstractVideoInfo.
* All the shared properties are copied to the new StreamInfo.*/
@SuppressWarnings("WeakerAccess")
public StreamInfo(AbstractVideoInfo avi) {
public StreamInfo(AbstractStreamInfo avi) {
this.id = avi.id;
this.title = avi.title;
this.uploader = avi.uploader;
this.thumbnail_url = avi.thumbnail_url;
this.thumbnail = avi.thumbnail;
this.webpage_url = avi.webpage_url;
this.upload_date = avi.upload_date;
this.upload_date = avi.upload_date;
@@ -68,22 +72,22 @@ public class StreamInfo extends AbstractVideoInfo {
/**Fills out the video info fields which are common to all services.
* Probably needs to be overridden by subclasses*/
public static StreamInfo getVideoInfo(StreamExtractor extractor, Downloader downloader)
public static StreamInfo getVideoInfo(StreamExtractor extractor)
throws ExtractionException, IOException {
StreamInfo streamInfo = new StreamInfo();
streamInfo = extractImportantData(streamInfo, extractor, downloader);
streamInfo = extractStreams(streamInfo, extractor, downloader);
streamInfo = extractOptionalData(streamInfo, extractor, downloader);
streamInfo = extractImportantData(streamInfo, extractor);
streamInfo = extractStreams(streamInfo, extractor);
streamInfo = extractOptionalData(streamInfo, extractor);
return streamInfo;
}
private static StreamInfo extractImportantData(
StreamInfo streamInfo, StreamExtractor extractor, Downloader downloader)
StreamInfo streamInfo, StreamExtractor extractor)
throws ExtractionException, IOException {
/* ---- importand data, withoug the video can't be displayed goes here: ---- */
// if one of these is not available an exception is ment to be thrown directly into the frontend.
/* ---- important data, withoug the video can't be displayed goes here: ---- */
// if one of these is not available an exception is meant to be thrown directly into the frontend.
UrlIdHandler uiconv = extractor.getUrlIdHandler();
@@ -106,7 +110,7 @@ public class StreamInfo extends AbstractVideoInfo {
}
private static StreamInfo extractStreams(
StreamInfo streamInfo, StreamExtractor extractor, Downloader downloader)
StreamInfo streamInfo, StreamExtractor extractor)
throws ExtractionException, IOException {
/* ---- stream extraction goes here ---- */
// At least one type of stream has to be available,
@@ -130,10 +134,10 @@ public class StreamInfo extends AbstractVideoInfo {
streamInfo.audio_streams = new Vector<>();
}
//todo: make this quick and dirty solution a real fallback
// same as the quick and dirty aboth
// same as the quick and dirty above
try {
streamInfo.audio_streams.addAll(
DashMpdParser.getAudioStreams(streamInfo.dashMpdUrl, downloader));
DashMpdParser.getAudioStreams(streamInfo.dashMpdUrl));
} catch(Exception e) {
streamInfo.addException(
new ExtractionException("Couldn't get audio streams from dash mpd", e));
@@ -167,11 +171,11 @@ public class StreamInfo extends AbstractVideoInfo {
}
private static StreamInfo extractOptionalData(
StreamInfo streamInfo, StreamExtractor extractor, Downloader downloader) {
StreamInfo streamInfo, StreamExtractor extractor) {
/* ---- optional data goes here: ---- */
// If one of these failes, the frontend neets to handle that they are not available.
// Exceptions are therfore not thrown into the frontend, but stored into the error List,
// so the frontend can afterwads check where errors happend.
// If one of these fails, the frontend needs to handle that they are not available.
// Exceptions are therefore not thrown into the frontend, but stored into the error List,
// so the frontend can afterwards check where errors happened.
try {
streamInfo.thumbnail_url = extractor.getThumbnailUrl();
@@ -286,4 +290,4 @@ public class StreamInfo extends AbstractVideoInfo {
public int start_position = 0;
public List<Throwable> errors = new Vector<>();
}
}

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.stream_info;
/**
* Created by Christian Schabesberger on 26.08.15.
@@ -20,7 +20,9 @@ package org.schabi.newpipe.extractor;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
import org.schabi.newpipe.extractor.AbstractStreamInfo;
/**Info object for previews of unopened videos, eg search results, related videos*/
public class StreamPreviewInfo extends AbstractVideoInfo {
public class StreamPreviewInfo extends AbstractStreamInfo {
public int duration;
}

View File

@@ -1,5 +1,9 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.stream_info;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.FoundAdException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamUrlIdHandler;
import java.util.List;
@@ -57,7 +61,7 @@ public class StreamPreviewInfoCollector {
if (urlIdHandler == null) {
throw new ParsingException("Error: UrlIdHandler not set");
} else if(!resultItem.webpage_url.isEmpty()) {
resultItem.id = (new YoutubeStreamUrlIdHandler()).getId(resultItem.webpage_url);
resultItem.id = NewPipe.getService(serviceId).getUrlIdHandlerInstance().getId(resultItem.webpage_url);
}
resultItem.title = extractor.getTitle();
resultItem.stream_type = extractor.getStreamType();

View File

@@ -1,4 +1,7 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.stream_info;
import org.schabi.newpipe.extractor.AbstractStreamInfo;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
/**
* Created by Christian Schabesberger on 28.02.16.
@@ -21,7 +24,7 @@ package org.schabi.newpipe.extractor;
*/
public interface StreamPreviewInfoExtractor {
AbstractVideoInfo.StreamType getStreamType() throws ParsingException;
AbstractStreamInfo.StreamType getStreamType() throws ParsingException;
String getWebPageUrl() throws ParsingException;
String getTitle() throws ParsingException;
int getDuration() throws ParsingException;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.extractor;
package org.schabi.newpipe.extractor.stream_info;
/**
* Created by Christian Schabesberger on 04.03.16.

View File

@@ -10,8 +10,8 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.ImageErrorLoadingListener;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.AbstractStreamInfo;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo;
/**
* Created by Christian Schabesberger on 26.09.16.
@@ -66,7 +66,7 @@ public class InfoItemBuilder {
if(info.duration > 0) {
holder.itemDurationView.setText(getDurationString(info.duration));
} else {
if(info.stream_type == AbstractVideoInfo.StreamType.LIVE_STREAM) {
if(info.stream_type == AbstractStreamInfo.StreamType.LIVE_STREAM) {
holder.itemDurationView.setText(R.string.duration_live);
} else {
holder.itemDurationView.setVisibility(View.GONE);

View File

@@ -30,13 +30,13 @@ import org.schabi.newpipe.R;
public class InfoItemHolder extends RecyclerView.ViewHolder {
public ImageView itemThumbnailView;
public TextView itemVideoTitleView,
public final ImageView itemThumbnailView;
public final TextView itemVideoTitleView,
itemUploaderView,
itemDurationView,
itemUploadDateView,
itemViewCountView;
public Button itemButton;
public final Button itemButton;
public InfoItemHolder(View v) {
super(v);
@@ -48,4 +48,5 @@ public class InfoItemHolder extends RecyclerView.ViewHolder {
itemViewCountView = (TextView) v.findViewById(R.id.itemViewCountView);
itemButton = (Button) v.findViewById(R.id.item_button);
}
}

View File

@@ -6,13 +6,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.ImageErrorLoadingListener;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo;
import java.util.List;
import java.util.Vector;
@@ -39,11 +34,12 @@ import java.util.Vector;
public class InfoListAdapter extends RecyclerView.Adapter<InfoItemHolder> {
InfoItemBuilder infoItemBuilder = null;
List<StreamPreviewInfo> streamList = new Vector<>();
private final InfoItemBuilder infoItemBuilder;
private final List<StreamPreviewInfo> streamList;
public InfoListAdapter(Activity a, View rootView) {
infoItemBuilder = new InfoItemBuilder(a, rootView);
streamList = new Vector<>();
}
public void setOnItemSelectedListener
@@ -59,7 +55,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<InfoItemHolder> {
}
public void clearSteamItemList() {
streamList = new Vector<>();
streamList.clear();
notifyDataSetChanged();
}

View File

@@ -14,6 +14,8 @@ import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.wifi.WifiManager;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PowerManager;
import android.support.v7.app.NotificationCompat;
import android.util.Log;
@@ -27,6 +29,7 @@ import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.detail.VideoItemDetailFragment;
import java.io.IOException;
import java.util.Arrays;
/**
* Created by Adam Howard on 08/11/15.
@@ -51,10 +54,13 @@ import java.io.IOException;
/**Plays the audio stream of videos in the background.*/
public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPreparedListener*/ {
private static final String TAG = BackgroundPlayer.class.toString();
private static final String ACTION_STOP = TAG + ".STOP";
private static final String ACTION_PLAYPAUSE = TAG + ".PLAYPAUSE";
private static final String ACTION_REWIND = TAG + ".REWIND";
private static final String TAG = "BackgroundPlayer";
private static final String CLASSNAME = "org.schabi.newpipe.player.BackgroundPlayer";
private static final String ACTION_STOP = CLASSNAME + ".STOP";
private static final String ACTION_PLAYPAUSE = CLASSNAME + ".PLAYPAUSE";
private static final String ACTION_REWIND = CLASSNAME + ".REWIND";
private static final String ACTION_PLAYBACK_STATE = CLASSNAME + ".PLAYBACK_STATE";
private static final String EXTRA_PLAYBACK_STATE = CLASSNAME + ".extras.EXTRA_PLAYBACK_STATE";
// Extra intent arguments
public static final String TITLE = "title";
@@ -114,6 +120,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
isRunning = false;
}
private class PlayerThread extends Thread {
MediaPlayer mediaPlayer;
private String source;
@@ -123,8 +130,8 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
private NotificationManager noteMgr;
private WifiManager.WifiLock wifiLock;
private Bitmap videoThumbnail;
private NotificationCompat.Builder noteBuilder;
private Notification note;
private NoteBuilder noteBuilder;
private volatile boolean donePlaying = false;
public PlayerThread(String src, String title, BackgroundPlayer owner) {
this.source = src;
@@ -134,6 +141,45 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
public boolean isDonePlaying() {
return donePlaying;
}
private boolean isPlaying() {
try {
return mediaPlayer.isPlaying();
} catch (IllegalStateException e) {
Log.w(TAG, "Unable to retrieve playing state", e);
return false;
}
}
private void setDonePlaying() {
donePlaying = true;
synchronized (PlayerThread.this) {
PlayerThread.this.notifyAll();
}
}
private synchronized PlaybackState getPlaybackState() {
try {
return new PlaybackState(mediaPlayer.getDuration(), mediaPlayer.getCurrentPosition(), isPlaying());
} catch (IllegalStateException e) {
// This isn't that nice way to handle this.
// maybe there is a better way
Log.w(TAG, this + ": Got illegal state exception while creating playback state", e);
return PlaybackState.UNPREPARED;
}
}
private void broadcastState() {
PlaybackState state = getPlaybackState();
if(state == null) return;
Intent intent = new Intent(ACTION_PLAYBACK_STATE);
intent.putExtra(EXTRA_PLAYBACK_STATE, state);
sendBroadcast(intent);
}
@Override
public void run() {
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);//cpu lock
@@ -181,27 +227,29 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
filter.addAction(ACTION_PLAYPAUSE);
filter.addAction(ACTION_STOP);
filter.addAction(ACTION_REWIND);
filter.addAction(ACTION_PLAYBACK_STATE);
registerReceiver(broadcastReceiver, filter);
note = buildNotification();
startForeground(noteID, note);
initNotificationBuilder();
startForeground(noteID, noteBuilder.build());
//currently decommissioned progressbar looping update code - works, but doesn't fit inside
//Notification.MediaStyle Notification layout.
noteMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
/*
//update every 2s or 4 times in the video, whichever is shorter
int sleepTime = Math.min(2000, (int)((double)vidLength/4));
while(mediaPlayer.isPlaying()) {
noteBuilder.setProgress(vidLength, mediaPlayer.getCurrentPosition(), false);
noteMgr.notify(noteID, noteBuilder.build());
int vidLength = mediaPlayer.getDuration();
int sleepTime = Math.min(2000, (int)(vidLength / 4));
while(!isDonePlaying()) {
broadcastState();
try {
Thread.sleep(sleepTime);
synchronized (this) {
wait(sleepTime);
}
} catch (InterruptedException e) {
Log.d(TAG, "sleep failure");
Log.e(TAG, "sleep failure", e);
}
}*/
}
}
/**Handles button presses from the notification. */
@@ -210,39 +258,50 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//Log.i(TAG, "received broadcast action:"+action);
if(action.equals(ACTION_PLAYPAUSE)) {
if(mediaPlayer.isPlaying()) {
mediaPlayer.pause();
note.contentView.setImageViewResource(R.id.notificationPlayPause, R.drawable.ic_play_circle_filled_white_24dp);
if(android.os.Build.VERSION.SDK_INT >=16){
note.bigContentView.setImageViewResource(R.id.notificationPlayPause, R.drawable.ic_play_circle_filled_white_24dp);
switch (action) {
case ACTION_PLAYPAUSE: {
boolean isPlaying = mediaPlayer.isPlaying();
if(isPlaying) {
mediaPlayer.pause();
} else {
//reacquire CPU lock after auto-releasing it on pause
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mediaPlayer.start();
}
noteMgr.notify(noteID, note);
}
else {
//reacquire CPU lock after auto-releasing it on pause
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mediaPlayer.start();
note.contentView.setImageViewResource(R.id.notificationPlayPause, R.drawable.ic_pause_white_24dp);
if(android.os.Build.VERSION.SDK_INT >=16){
note.bigContentView.setImageViewResource(R.id.notificationPlayPause, R.drawable.ic_pause_white_24dp);
synchronized (PlayerThread.this) {
PlayerThread.this.notifyAll();
}
noteMgr.notify(noteID, note);
break;
}
case ACTION_REWIND:
mediaPlayer.seekTo(0);
synchronized (PlayerThread.this) {
PlayerThread.this.notifyAll();
}
break;
case ACTION_STOP:
//this auto-releases CPU lock
mediaPlayer.stop();
afterPlayCleanup();
break;
case ACTION_PLAYBACK_STATE: {
PlaybackState playbackState = intent.getParcelableExtra(EXTRA_PLAYBACK_STATE);
if(!playbackState.equals(PlaybackState.UNPREPARED)) {
noteBuilder.setProgress(playbackState.getDuration(), playbackState.getPlayedTime(), false);
noteBuilder.setIsPlaying(playbackState.isPlaying());
} else {
noteBuilder.setProgress(0, 0, true);
}
noteMgr.notify(noteID, noteBuilder.build());
break;
}
}
else if(action.equals(ACTION_REWIND)) {
mediaPlayer.seekTo(0);
// noteMgr.notify(noteID, note);
}
else if(action.equals(ACTION_STOP)) {
//this auto-releases CPU lock
mediaPlayer.stop();
afterPlayCleanup();
}
}
};
private void afterPlayCleanup() {
// Notify thread to stop
setDonePlaying();
//remove progress bar
//noteBuilder.setProgress(0, 0, false);
@@ -256,7 +315,12 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
wifiLock.release();
//remove foreground status of service; make BackgroundPlayer killable
stopForeground(true);
try {
// Wait for thread to stop
PlayerThread.this.join();
} catch (InterruptedException e) {
Log.e(TAG, "unable to join player thread", e);
}
stopSelf();
}
@@ -272,10 +336,14 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
}
}
private Notification buildNotification() {
private void initNotificationBuilder() {
Notification note;
Resources res = getApplicationContext().getResources();
noteBuilder = new NotificationCompat.Builder(owner);
/*
NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder
(R.drawable.ic_pause_white_24dp, "Pause", playPI).build();
*/
PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID,
new Intent(ACTION_PLAYPAUSE), PendingIntent.FLAG_UPDATE_CURRENT);
@@ -283,10 +351,6 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
new Intent(ACTION_STOP), PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent rewindPI = PendingIntent.getBroadcast(owner, noteID,
new Intent(ACTION_REWIND), PendingIntent.FLAG_UPDATE_CURRENT);
/*
NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder
(R.drawable.ic_pause_white_24dp, "Pause", playPI).build();
*/
//build intent to return to video, on tapping notification
Intent openDetailViewIntent = new Intent(getApplicationContext(),
@@ -296,58 +360,226 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
openDetailViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent openDetailView = PendingIntent.getActivity(owner, noteID,
openDetailViewIntent, PendingIntent.FLAG_UPDATE_CURRENT);
noteBuilder = new NoteBuilder(owner, playPI, stopPI, rewindPI, openDetailView);
noteBuilder
.setTitle(title)
.setArtist(channelName)
.setOngoing(true)
.setDeleteIntent(stopPI)
//doesn't fit with Notification.MediaStyle
//.setProgress(vidLength, 0, false)
.setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp)
.setTicker(
String.format(res.getString(
R.string.background_player_time_text), title))
.setContentIntent(PendingIntent.getActivity(getApplicationContext(),
noteID, openDetailViewIntent,
PendingIntent.FLAG_UPDATE_CURRENT))
.setContentIntent(openDetailView);
.setContentIntent(openDetailView)
.setCategory(Notification.CATEGORY_TRANSPORT)
//Make notification appear on lockscreen
.setVisibility(Notification.VISIBILITY_PUBLIC);
}
RemoteViews view =
new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
view.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
view.setTextViewText(R.id.notificationSongName, title);
view.setTextViewText(R.id.notificationArtist, channelName);
view.setOnClickPendingIntent(R.id.notificationStop, stopPI);
view.setOnClickPendingIntent(R.id.notificationPlayPause, playPI);
view.setOnClickPendingIntent(R.id.notificationRewind, rewindPI);
view.setOnClickPendingIntent(R.id.notificationContent, openDetailView);
/**
* Notification builder which works like the real builder but uses a custom view.
*/
class NoteBuilder extends NotificationCompat.Builder {
//possibly found the expandedView problem,
//but can't test it as I don't have a 5.0 device. -medavox
RemoteViews expandedView =
new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded);
expandedView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
expandedView.setTextViewText(R.id.notificationSongName, title);
expandedView.setTextViewText(R.id.notificationArtist, channelName);
expandedView.setOnClickPendingIntent(R.id.notificationStop, stopPI);
expandedView.setOnClickPendingIntent(R.id.notificationPlayPause, playPI);
expandedView.setOnClickPendingIntent(R.id.notificationRewind, rewindPI);
expandedView.setOnClickPendingIntent(R.id.notificationContent, openDetailView);
noteBuilder.setCategory(Notification.CATEGORY_TRANSPORT);
//Make notification appear on lockscreen
noteBuilder.setVisibility(Notification.VISIBILITY_PUBLIC);
note = noteBuilder.build();
note.contentView = view;
if (android.os.Build.VERSION.SDK_INT > 16) {
note.bigContentView = expandedView;
/**
* @param context
* @inheritDoc
*/
public NoteBuilder(Context context, PendingIntent playPI, PendingIntent stopPI,
PendingIntent rewindPI, PendingIntent openDetailView) {
super(context);
setCustomContentView(createCustomContentView(playPI, stopPI, rewindPI, openDetailView));
setCustomBigContentView(createCustomBigContentView(playPI, stopPI, rewindPI, openDetailView));
}
return note;
private RemoteViews createCustomBigContentView(PendingIntent playPI,
PendingIntent stopPI,
PendingIntent rewindPI,
PendingIntent openDetailView) {
//possibly found the expandedView problem,
//but can't test it as I don't have a 5.0 device. -medavox
RemoteViews expandedView =
new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded);
expandedView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
expandedView.setOnClickPendingIntent(R.id.notificationStop, stopPI);
expandedView.setOnClickPendingIntent(R.id.notificationPlayPause, playPI);
expandedView.setOnClickPendingIntent(R.id.notificationRewind, rewindPI);
expandedView.setOnClickPendingIntent(R.id.notificationContent, openDetailView);
return expandedView;
}
private RemoteViews createCustomContentView(PendingIntent playPI, PendingIntent stopPI,
PendingIntent rewindPI,
PendingIntent openDetailView) {
RemoteViews view = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
view.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
view.setOnClickPendingIntent(R.id.notificationStop, stopPI);
view.setOnClickPendingIntent(R.id.notificationPlayPause, playPI);
view.setOnClickPendingIntent(R.id.notificationRewind, rewindPI);
view.setOnClickPendingIntent(R.id.notificationContent, openDetailView);
return view;
}
/**
* Set the title of the stream
* @param title the title of the stream
* @return this builder for chaining
*/
NoteBuilder setTitle(String title) {
setContentTitle(title);
getContentView().setTextViewText(R.id.notificationSongName, title);
getBigContentView().setTextViewText(R.id.notificationSongName, title);
setTicker(String.format(getBaseContext().getString(
R.string.background_player_time_text), title));
return this;
}
/**
* Set the artist of the stream
* @param artist the artist of the stream
* @return this builder for chaining
*/
NoteBuilder setArtist(String artist) {
setSubText(artist);
getContentView().setTextViewText(R.id.notificationArtist, artist);
getBigContentView().setTextViewText(R.id.notificationArtist, artist);
return this;
}
@Override
public android.support.v4.app.NotificationCompat.Builder setProgress(int max, int progress, boolean indeterminate) {
super.setProgress(max, progress, indeterminate);
getBigContentView().setProgressBar(R.id.playbackProgress, max, progress, indeterminate);
return this;
}
/**
* Set the isPlaying state
* @param isPlaying the is playing state
*/
public void setIsPlaying(boolean isPlaying) {
RemoteViews views = getContentView(), bigViews = getBigContentView();
int imageSrc;
if(isPlaying) {
imageSrc = R.drawable.ic_pause_white_24dp;
} else {
imageSrc = R.drawable.ic_play_circle_filled_white_24dp;
}
views.setImageViewResource(R.id.notificationPlayPause, imageSrc);
bigViews.setImageViewResource(R.id.notificationPlayPause, imageSrc);
}
}
}
/**
* Represents the state of the player.
*/
public static class PlaybackState implements Parcelable {
private static final int INDEX_IS_PLAYING = 0;
private static final int INDEX_IS_PREPARED= 1;
private static final int INDEX_HAS_ERROR = 2;
private final int duration;
private final int played;
private final boolean[] booleanValues = new boolean[3];
static final PlaybackState UNPREPARED = new PlaybackState(false, false, false);
static final PlaybackState FAILED = new PlaybackState(false, false, true);
PlaybackState(Parcel in) {
duration = in.readInt();
played = in.readInt();
in.readBooleanArray(booleanValues);
}
PlaybackState(int duration, int played, boolean isPlaying) {
this.played = played;
this.duration = duration;
this.booleanValues[INDEX_IS_PLAYING] = isPlaying;
this.booleanValues[INDEX_IS_PREPARED] = true;
this.booleanValues[INDEX_HAS_ERROR] = false;
}
private PlaybackState(boolean isPlaying, boolean isPrepared, boolean hasErrors) {
this.played = 0;
this.duration = 0;
this.booleanValues[INDEX_IS_PLAYING] = isPlaying;
this.booleanValues[INDEX_IS_PREPARED] = isPrepared;
this.booleanValues[INDEX_HAS_ERROR] = hasErrors;
}
int getDuration() {
return duration;
}
int getPlayedTime() {
return played;
}
boolean isPlaying() {
return booleanValues[INDEX_IS_PLAYING];
}
boolean isPrepared() {
return booleanValues[INDEX_IS_PREPARED];
}
boolean hasErrors() {
return booleanValues[INDEX_HAS_ERROR];
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(duration);
dest.writeInt(played);
dest.writeBooleanArray(booleanValues);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<PlaybackState> CREATOR = new Creator<PlaybackState>() {
@Override
public PlaybackState createFromParcel(Parcel in) {
return new PlaybackState(in);
}
@Override
public PlaybackState[] newArray(int size) {
return new PlaybackState[size];
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PlaybackState that = (PlaybackState) o;
if (duration != that.duration) return false;
if (played != that.played) return false;
return Arrays.equals(booleanValues, that.booleanValues);
}
@Override
public int hashCode() {
if(this == UNPREPARED) return 1;
if(this == FAILED) return 2;
int result = duration;
result = 31 * result + played;
result = 31 * result + Arrays.hashCode(booleanValues);
return result + 2;
}
}
}

View File

@@ -14,7 +14,6 @@ import android.preference.PreferenceManager;
import android.support.design.widget.Snackbar;
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
@@ -35,6 +34,7 @@ import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ThemableActivity;
import org.schabi.newpipe.extractor.Parser;
import java.io.PrintWriter;
@@ -65,7 +65,7 @@ import java.util.Vector;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class ErrorActivity extends AppCompatActivity {
public class ErrorActivity extends ThemableActivity {
public static class ErrorInfo implements Parcelable {
public int userAction;
public String request;
@@ -473,8 +473,8 @@ public class ErrorActivity extends AppCompatActivity {
public void run() {
String ipRange = "none";
try {
Downloader dl = new Downloader();
String ip = dl.download("https://ifcfg.me/ip");
Downloader dl = Downloader.getInstance();
String ip = dl.download("https://ipv4.icanhazip.com");
ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip)
+ "0.0";

View File

@@ -8,6 +8,7 @@ import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -18,15 +19,19 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.detail.VideoItemDetailFragment;
import org.schabi.newpipe.extractor.SearchResult;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.info_list.InfoListAdapter;
import static android.app.Activity.RESULT_OK;
import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST;
/**
* Created by Christian Schabesberger on 02.08.16.
*
@@ -51,13 +56,15 @@ public class SearchInfoItemFragment extends Fragment {
private static final String TAG = SearchInfoItemFragment.class.toString();
/**
* Listener for search queries
*/
public class SearchQueryListener implements SearchView.OnQueryTextListener {
@Override
public boolean onQueryTextSubmit(String query) {
Activity a = getActivity();
try {
searchQuery = query;
search(query);
// hide virtual keyboard
@@ -67,12 +74,12 @@ public class SearchInfoItemFragment extends Fragment {
//noinspection ConstantConditions
inputManager.hideSoftInputFromWindow(
a.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
} catch(NullPointerException e) {
} catch (NullPointerException e) {
e.printStackTrace();
ErrorActivity.reportError(a, e, null,
a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
ServiceList.getNameOfService(streamingServiceId),
NewPipe.getNameOfService(streamingServiceId),
"Could not get widget with focus", R.string.general_error));
}
// clear focus
@@ -81,17 +88,15 @@ public class SearchInfoItemFragment extends Fragment {
// onQueryTextSubmit to trigger twice when focus is not cleared.
// See: http://stackoverflow.com/questions/17874951/searchview-onquerytextsubmit-runs-twice-while-i-pressed-once
a.getCurrentFocus().clearFocus();
} catch(Exception e) {
} catch (Exception e) {
e.printStackTrace();
}
View bg = a.findViewById(R.id.mainBG);
bg.setVisibility(View.GONE);
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
if(!newText.isEmpty()) {
if (!newText.isEmpty()) {
searchSuggestions(newText);
}
return true;
@@ -103,12 +108,10 @@ public class SearchInfoItemFragment extends Fragment {
private boolean isLoading = false;
private ProgressBar loadingIndicator = null;
private SearchView searchView = null;
private int pageNumber = 0;
private SuggestionListAdapter suggestionListAdapter = null;
private InfoListAdapter infoListAdapter = null;
private LinearLayoutManager streamInfoListLayoutManager = null;
private RecyclerView recyclerView = null;
// savedInstanceBundle arguments
private static final String QUERY = "query";
@@ -121,40 +124,50 @@ public class SearchInfoItemFragment extends Fragment {
public SearchInfoItemFragment() {
}
// TODO: Customize parameter initialization
@SuppressWarnings("unused")
public static SearchInfoItemFragment newInstance(int columnCount) {
public static SearchInfoItemFragment newInstance(int streamingServiceId, String searchQuery) {
Bundle args = new Bundle();
args.putInt(STREAMING_SERVICE, streamingServiceId);
args.putString(QUERY, searchQuery);
SearchInfoItemFragment fragment = new SearchInfoItemFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
searchQuery = "";
if (savedInstanceState != null) {
searchQuery = savedInstanceState.getString(QUERY);
streamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
} else {
try {
streamingServiceId = ServiceList.getIdOfService("Youtube");
} catch(Exception e) {
Bundle args = getArguments();
if(args != null) {
searchQuery = args.getString(QUERY);
streamingServiceId = args.getInt(STREAMING_SERVICE);
} else {
streamingServiceId = NewPipe.getIdOfService("Youtube");
}
} catch (Exception e) {
e.printStackTrace();
ErrorActivity.reportError(getActivity(), e, null,
getActivity().findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
ServiceList.getNameOfService(streamingServiceId),
NewPipe.getNameOfService(streamingServiceId),
"", R.string.general_error));
}
}
setHasOptionsMenu(true);
SearchWorker sw = SearchWorker.getInstance();
sw.setSearchWorkerResultListner(new SearchWorker.SearchWorkerResultListner() {
sw.setSearchWorkerResultListener(new SearchWorker.SearchWorkerResultListener() {
@Override
public void onResult(SearchResult result) {
infoListAdapter.addStreamItemList(result.resultList);
isLoading = false;
loadingIndicator.setVisibility(View.GONE);
setDoneLoading();
}
@Override
@@ -162,8 +175,7 @@ public class SearchInfoItemFragment extends Fragment {
//setListShown(true);
Toast.makeText(getActivity(), getString(stringResource),
Toast.LENGTH_SHORT).show();
isLoading = false;
loadingIndicator.setVisibility(View.GONE);
setDoneLoading();
}
@Override
@@ -171,10 +183,21 @@ public class SearchInfoItemFragment extends Fragment {
//setListShown(true);
Toast.makeText(getActivity(), message,
Toast.LENGTH_LONG).show();
isLoading = false;
loadingIndicator.setVisibility(View.GONE);
setDoneLoading();
}
@Override
public void onReCaptchaChallenge() {
Toast.makeText(getActivity(), "ReCaptcha Challenge requested",
Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
startActivityForResult(
new Intent(getActivity(), ReCaptchaActivity.class),
RECAPTCHA_REQUEST);
}
});
}
@Override
@@ -184,7 +207,7 @@ public class SearchInfoItemFragment extends Fragment {
Context context = view.getContext();
loadingIndicator = (ProgressBar) view.findViewById(R.id.progressBar);
recyclerView = (RecyclerView) view.findViewById(R.id.list);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
streamInfoListLayoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(streamInfoListLayoutManager);
@@ -193,27 +216,23 @@ public class SearchInfoItemFragment extends Fragment {
infoListAdapter.setOnItemSelectedListener(new InfoItemBuilder.OnItemSelectedListener() {
@Override
public void selected(String url) {
Intent i = new Intent(getActivity(), VideoItemDetailActivity.class);
i.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
i.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
getActivity().startActivity(i);
startDetailActivity(url);
}
});
recyclerView.setAdapter(infoListAdapter);
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
recyclerView.clearOnScrollListeners();
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int pastVisiblesItems, visibleItemCount, totalItemCount;
super.onScrolled(recyclerView, dx, dy);
if(dy > 0) //check for scroll down
if (dy > 0) //check for scroll down
{
visibleItemCount = streamInfoListLayoutManager.getChildCount();
totalItemCount = streamInfoListLayoutManager.getItemCount();
pastVisiblesItems = streamInfoListLayoutManager.findFirstVisibleItemPosition();
if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading)
{
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading) {
pageNumber++;
search(searchQuery, pageNumber);
}
@@ -224,14 +243,26 @@ public class SearchInfoItemFragment extends Fragment {
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
private void startDetailActivity(String url) {
Intent i = new Intent(getActivity(), VideoItemDetailActivity.class);
i.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
i.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
getActivity().startActivity(i);
}
@Override
public void onDetach() {
super.onDetach();
public void onStart() {
super.onStart();
if(!searchQuery.isEmpty()) {
search(searchQuery);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(QUERY, searchQuery);
outState.putInt(STREAMING_SERVICE, streamingServiceId);
}
@Override
@@ -240,21 +271,16 @@ public class SearchInfoItemFragment extends Fragment {
inflater.inflate(R.menu.search_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
searchView = (SearchView) searchItem.getActionView();
SearchView searchView = (SearchView) searchItem.getActionView();
setupSearchView(searchView);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
private void setupSearchView(SearchView searchView) {
suggestionListAdapter = new SuggestionListAdapter(getActivity());
searchView.setSuggestionsAdapter(suggestionListAdapter);
searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView, suggestionListAdapter));
searchView.setOnQueryTextListener(new SearchQueryListener());
if(searchQuery != null && !searchQuery.isEmpty()) {
if (searchQuery != null && !searchQuery.isEmpty()) {
searchView.setQuery(searchQuery, false);
searchView.setIconifiedByDefault(false);
}
@@ -263,7 +289,9 @@ public class SearchInfoItemFragment extends Fragment {
private void search(String query) {
infoListAdapter.clearSteamItemList();
pageNumber = 0;
searchQuery = query;
search(query, pageNumber);
hideBackground();
loadingIndicator.setVisibility(View.VISIBLE);
}
@@ -273,10 +301,43 @@ public class SearchInfoItemFragment extends Fragment {
sw.search(streamingServiceId, query, page, getActivity());
}
private void setDoneLoading() {
this.isLoading = false;
loadingIndicator.setVisibility(View.GONE);
}
/**
* Hides the "dummy" background when no results are shown
*/
private void hideBackground() {
View view = getView();
if(view == null) return;
view.findViewById(R.id.mainBG).setVisibility(View.GONE);
}
private void searchSuggestions(String query) {
SuggestionSearchRunnable suggestionSearchRunnable =
new SuggestionSearchRunnable(streamingServiceId, query, getActivity(), suggestionListAdapter);
Thread suggestionThread = new Thread(suggestionSearchRunnable);
suggestionThread.start();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case RECAPTCHA_REQUEST:
if (resultCode == RESULT_OK) {
if (searchQuery.length() != 0) {
search(searchQuery);
}
} else {
Log.d(TAG, "ReCaptcha failed");
}
break;
default:
Log.e(TAG, "Request code from activity not supported [" + requestCode + "]");
break;
}
}
}

View File

@@ -25,8 +25,8 @@ import android.support.v7.widget.SearchView;
public class SearchSuggestionListener implements SearchView.OnSuggestionListener{
private SearchView searchView;
private SuggestionListAdapter adapter;
private final SearchView searchView;
private final SuggestionListAdapter adapter;
public SearchSuggestionListener(SearchView searchView, SuggestionListAdapter adapter) {
this.searchView = searchView;

View File

@@ -7,13 +7,13 @@ import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.SearchResult;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.NewPipe;
import java.io.IOException;
@@ -41,10 +41,11 @@ import java.io.IOException;
public class SearchWorker {
private static final String TAG = SearchWorker.class.toString();
public interface SearchWorkerResultListner {
public interface SearchWorkerResultListener {
void onResult(SearchResult result);
void onNothingFound(final int stringResource);
void onError(String message);
void onReCaptchaChallenge();
}
private class ResultRunnable implements Runnable {
@@ -57,7 +58,7 @@ public class SearchWorker {
@Override
public void run() {
if(this.requestId == SearchWorker.this.requestId) {
searchWorkerResultListner.onResult(result);
searchWorkerResultListener.onResult(result);
}
}
}
@@ -81,12 +82,13 @@ public class SearchWorker {
}
@Override
public void run() {
final String serviceName = NewPipe.getNameOfService(serviceId);
SearchResult result = null;
SearchEngine engine = null;
try {
engine = ServiceList.getService(serviceId)
.getSearchEngineInstance(new Downloader());
engine = NewPipe.getService(serviceId)
.getSearchEngineInstance();
} catch(ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
@@ -100,8 +102,7 @@ public class SearchWorker {
String searchLanguage = sp.getString(searchLanguageKey,
a.getString(R.string.default_language_value));
result = SearchResult
.getSearchResult(engine, query, page, searchLanguage, new Downloader());
.getSearchResult(engine, query, page, searchLanguage);
if(runs) {
h.post(new ResultRunnable(result, requestId));
}
@@ -119,15 +120,22 @@ public class SearchWorker {
View rootView = a.findViewById(android.R.id.content);
ErrorActivity.reportError(h, a, result.errors, null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.light_parsing_error));
serviceName, query, R.string.light_parsing_error));
}
// hard errors:
} catch (ReCaptchaException e) {
h.post(new Runnable() {
@Override
public void run() {
searchWorkerResultListener.onReCaptchaChallenge();
}
});
} catch(IOException e) {
h.post(new Runnable() {
@Override
public void run() {
searchWorkerResultListner.onNothingFound(R.string.network_error);
searchWorkerResultListener.onNothingFound(R.string.network_error);
}
});
e.printStackTrace();
@@ -135,14 +143,13 @@ public class SearchWorker {
h.post(new Runnable() {
@Override
public void run() {
searchWorkerResultListner.onError(e.getMessage());
searchWorkerResultListener.onError(e.getMessage());
}
});
} catch(ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */
YOUTUBE, query, R.string.parsing_error));
serviceName, query, R.string.parsing_error));
//postNewErrorToast(h, R.string.parsing_error);
e.printStackTrace();
@@ -157,7 +164,7 @@ public class SearchWorker {
}
private static SearchWorker searchWorker = null;
private SearchWorkerResultListner searchWorkerResultListner = null;
private SearchWorkerResultListener searchWorkerResultListener = null;
private SearchRunnable runnable = null;
private int requestId = 0; //prevents running requests that have already ben expired
@@ -165,8 +172,8 @@ public class SearchWorker {
return searchWorker == null ? (searchWorker = new SearchWorker()) : searchWorker;
}
public void setSearchWorkerResultListner(SearchWorkerResultListner listener) {
searchWorkerResultListner = listener;
public void setSearchWorkerResultListener(SearchWorkerResultListener listener) {
searchWorkerResultListener = listener;
}
private SearchWorker() {

View File

@@ -3,10 +3,8 @@ package org.schabi.newpipe.search_fragment;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.support.v4.widget.ResourceCursorAdapter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
@@ -31,52 +29,56 @@ import java.util.List;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SuggestionListAdapter extends CursorAdapter {
/**
* {@link ResourceCursorAdapter} to display suggestions.
*/
public class SuggestionListAdapter extends ResourceCursorAdapter {
private static final String[] columns = new String[]{"_id", "title"};
private static final int INDEX_ID = 0;
private static final int INDEX_TITLE = 1;
private String[] columns = new String[]{"_id", "title"};
public SuggestionListAdapter(Context context) {
super(context, null, false);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
ViewHolder viewHolder;
View view = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, parent, false);
viewHolder = new ViewHolder();
viewHolder.suggestionTitle = (TextView) view.findViewById(android.R.id.text1);
view.setTag(viewHolder);
return view;
super(context, android.R.layout.simple_list_item_1, null, 0);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder viewHolder = (ViewHolder) view.getTag();
viewHolder.suggestionTitle.setText(cursor.getString(1));
ViewHolder viewHolder = new ViewHolder(view);
viewHolder.suggestionTitle.setText(cursor.getString(INDEX_TITLE));
}
/**
* Update the suggestion list
* @param suggestions the list of suggestions
*/
public void updateAdapter(List<String> suggestions) {
MatrixCursor cursor = new MatrixCursor(columns);
MatrixCursor cursor = new MatrixCursor(columns, suggestions.size());
int i = 0;
for (String s : suggestions) {
String[] temp = new String[2];
temp[0] = Integer.toString(i);
temp[1] = s;
for (String suggestion : suggestions) {
String[] columnValues = new String[columns.length];
columnValues[INDEX_TITLE] = suggestion;
columnValues[INDEX_ID] = Integer.toString(i);
cursor.addRow(columnValues);
i++;
cursor.addRow(temp);
}
changeCursor(cursor);
}
/**
* Get the suggestion for a position
* @param position the position of the suggestion
* @return the suggestion
*/
public String getSuggestion(int position) {
return ((Cursor) getItem(position)).getString(1);
return ((Cursor) getItem(position)).getString(INDEX_TITLE);
}
private class ViewHolder {
public TextView suggestionTitle;
private final TextView suggestionTitle;
private ViewHolder(View view) {
this.suggestionTitle = (TextView) view.findViewById(android.R.id.text1);
}
}
}

View File

@@ -6,12 +6,11 @@ import android.os.Handler;
import android.preference.PreferenceManager;
import android.widget.Toast;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.search.SuggestionExtractor;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.ServiceList;
import java.io.IOException;
import java.util.List;
@@ -38,14 +37,15 @@ import java.util.List;
public class SuggestionSearchRunnable implements Runnable{
/**
* Runnable to update a {@link SuggestionListAdapter}
*/
private class SuggestionResultRunnable implements Runnable{
private List<String> suggestions;
private SuggestionListAdapter adapter;
private final List<String> suggestions;
private SuggestionResultRunnable(List<String> suggestions, SuggestionListAdapter adapter) {
private SuggestionResultRunnable(List<String> suggestions) {
this.suggestions = suggestions;
this.adapter = adapter;
}
@Override
@@ -56,9 +56,9 @@ public class SuggestionSearchRunnable implements Runnable{
private final int serviceId;
private final String query;
final Handler h = new Handler();
private Activity a = null;
private SuggestionListAdapter adapter;
private final Handler h = new Handler();
private final Activity a;
private final SuggestionListAdapter adapter;
public SuggestionSearchRunnable(int serviceId, String query,
Activity activity, SuggestionListAdapter adapter) {
this.serviceId = serviceId;
@@ -70,18 +70,18 @@ public class SuggestionSearchRunnable implements Runnable{
@Override
public void run() {
try {
SearchEngine engine =
ServiceList.getService(serviceId).getSearchEngineInstance(new Downloader());
SuggestionExtractor se =
NewPipe.getService(serviceId).getSuggestionExtractorInstance();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(a);
String searchLanguageKey = a.getString(R.string.search_language_key);
String searchLanguage = sp.getString(searchLanguageKey,
a.getString(R.string.default_language_value));
List<String> suggestions = engine.suggestionList(query,searchLanguage,new Downloader());
h.post(new SuggestionResultRunnable(suggestions, adapter));
List<String> suggestions = se.suggestionList(query, searchLanguage);
h.post(new SuggestionResultRunnable(suggestions));
} catch (ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
ServiceList.getNameOfService(serviceId), query, R.string.parsing_error));
NewPipe.getNameOfService(serviceId), query, R.string.parsing_error));
e.printStackTrace();
} catch (IOException e) {
postNewErrorToast(h, R.string.network_error);
@@ -89,7 +89,7 @@ public class SuggestionSearchRunnable implements Runnable{
} catch (Exception e) {
ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
ServiceList.getNameOfService(serviceId), query, R.string.general_error));
NewPipe.getNameOfService(serviceId), query, R.string.general_error));
}
}

View File

@@ -89,14 +89,6 @@ public class NewPipeSettings {
return downloadPath;
}
public static String getDownloadPath(Context context, String fileName)
{
if(Utility.isVideoFile(fileName)) {
return NewPipeSettings.getVideoDownloadPath(context);
}
return NewPipeSettings.getAudioDownloadPath(context);
}
private static File getFolder(Context context, int keyID, String defaultDirectoryName) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final String key = context.getString(keyID);

View File

@@ -5,6 +5,7 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
@@ -16,6 +17,8 @@ import android.view.ViewGroup;
import org.schabi.newpipe.R;
import java.util.Objects;
/**
* Created by Christian Schabesberger on 31.08.15.
@@ -43,6 +46,10 @@ public class SettingsActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceBundle) {
if (Objects.equals(PreferenceManager.getDefaultSharedPreferences(this)
.getString("theme", getResources().getString(R.string.light_theme_title)), getResources().getString(R.string.dark_theme_title))) {
setTheme(R.style.DarkTheme);
}
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceBundle);
super.onCreate(savedInstanceBundle);

View File

@@ -1,6 +1,7 @@
package org.schabi.newpipe.settings;
import android.app.Activity;
import android.app.ListActivity;
import android.content.ClipData;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -20,6 +21,7 @@ import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import java.util.ArrayList;
import java.util.Objects;
import info.guardianproject.netcipher.proxy.OrbotHelper;
@@ -55,6 +57,7 @@ public class SettingsFragment extends PreferenceFragment
String DOWNLOAD_PATH_PREFERENCE;
String DOWNLOAD_PATH_AUDIO_PREFERENCE;
String USE_TOR_KEY;
String THEME;
public static final int REQUEST_INSTALL_ORBOT = 0x1234;
@@ -63,11 +66,11 @@ public class SettingsFragment extends PreferenceFragment
private ListPreference searchLanguagePreference;
private Preference downloadPathPreference;
private Preference downloadPathAudioPreference;
private Preference themePreference;
private SharedPreferences defaultPreferences;
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
@@ -81,6 +84,7 @@ public class SettingsFragment extends PreferenceFragment
SEARCH_LANGUAGE_PREFERENCE = getString(R.string.search_language_key);
DOWNLOAD_PATH_PREFERENCE = getString(R.string.download_path_key);
DOWNLOAD_PATH_AUDIO_PREFERENCE = getString(R.string.download_path_audio_key);
THEME = getString(R.string.theme_key);
USE_TOR_KEY = getString(R.string.use_tor_key);
// get pref objects
@@ -92,6 +96,7 @@ public class SettingsFragment extends PreferenceFragment
(ListPreference) findPreference(SEARCH_LANGUAGE_PREFERENCE);
downloadPathPreference = findPreference(DOWNLOAD_PATH_PREFERENCE);
downloadPathAudioPreference = findPreference(DOWNLOAD_PATH_AUDIO_PREFERENCE);
themePreference = findPreference(THEME);
prefListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
@@ -132,6 +137,11 @@ public class SettingsFragment extends PreferenceFragment
downloadPathAudioPreference
.setSummary(downloadPath);
}
else if (key == THEME)
{
String theme = sharedPreferences.getString(THEME, "Light");
themePreference.setSummary(theme);
}
updateSummary();
}
};
@@ -161,7 +171,6 @@ public class SettingsFragment extends PreferenceFragment
activity.startActivityForResult(i, R.string.download_path_audio_key);
}
}
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
@@ -216,8 +225,8 @@ public class SettingsFragment extends PreferenceFragment
// installing the app does not necessarily return RESULT_OK
App.configureTor(requestCode == REQUEST_INSTALL_ORBOT
&& OrbotHelper.requestStartTor(a));
}
updateSummary();
super.onActivityResult(requestCode, resultCode, data);
}
@@ -239,6 +248,9 @@ public class SettingsFragment extends PreferenceFragment
downloadPathAudioPreference.setSummary(
defaultPreferences.getString(DOWNLOAD_PATH_AUDIO_PREFERENCE,
getString(R.string.download_path_audio_summary)));
themePreference.setSummary(
defaultPreferences.getString(THEME,
getString(R.string.light_theme_title)));
}
@Override

View File

@@ -0,0 +1,68 @@
package org.schabi.newpipe.util;
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
public class PermissionHelper {
public static final int PERMISSION_WRITE_STORAGE = 778;
public static final int PERMISSION_READ_STORAGE = 777;
public static boolean checkStoragePermissions(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if(!checkReadStoragePermissions(activity)) return false;
}
return checkWriteStoragePermissions(activity);
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
public static boolean checkReadStoragePermissions(Activity activity) {
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity,
new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSION_READ_STORAGE);
return false;
}
return true;
}
public static boolean checkWriteStoragePermissions(Activity activity) {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
/*if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {*/
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSION_WRITE_STORAGE);
// PERMISSION_WRITE_STORAGE is an
// app-defined int constant. The callback method gets the
// result of the request.
/*}*/
return false;
}
return true;
}
}

View File

@@ -0,0 +1,36 @@
package us.shandian.giga.get;
import java.util.List;
/**
* Provides access to the storage of {@link DownloadMission}s
*/
public interface DownloadDataSource {
/**
* Load all missions
* @return a list of download missions
*/
List<DownloadMission> loadMissions();
/**
* Add a downlaod mission to the storage
* @param downloadMission the download mission to add
* @return the identifier of the mission
*/
void addMission(DownloadMission downloadMission);
/**
* Update a download mission which exists in the storage
* @param downloadMission the download mission to update
* @throws IllegalArgumentException if the mission was not added to storage
*/
void updateMission(DownloadMission downloadMission);
/**
* Delete a download mission
* @param downloadMission the mission to delete
*/
void deleteMission(DownloadMission downloadMission);
}

View File

@@ -3,12 +3,46 @@ package us.shandian.giga.get;
public interface DownloadManager
{
int BLOCK_SIZE = 512 * 1024;
int startMission(String url, String name, int threads);
/**
* Start a new download mission
* @param url the url to download
* @param location the location
* @param name the name of the file to create
* @param isAudio true if the download is an audio file
* @param threads the number of threads maximal used to download chunks of the file. @return the identifier of the mission.
*/
int startMission(String url, String location, String name, boolean isAudio, int threads);
/**
* Resume the execution of a download mission.
* @param id the identifier of the mission to resume.
*/
void resumeMission(int id);
/**
* Pause the execution of a download mission.
* @param id the identifier of the mission to pause.
*/
void pauseMission(int id);
/**
* Deletes the mission from the downloaded list but keeps the downloaded file.
* @param id The mission identifier
*/
void deleteMission(int id);
/**
* Get the download mission by its identifier
* @param id the identifier of the download mission
* @return the download mission or null if the mission doesn't exist
*/
DownloadMission getMission(int id);
/**
* Get the number of download missions.
* @return the number of download missions.
*/
int getCount();
String getLocation();
}

View File

@@ -1,17 +1,21 @@
package us.shandian.giga.get;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.gson.Gson;
import org.schabi.newpipe.settings.NewPipeSettings;
import java.io.File;
import java.io.FilenameFilter;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import us.shandian.giga.util.Utility;
import static org.schabi.newpipe.BuildConfig.DEBUG;
@@ -19,29 +23,48 @@ import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadManagerImpl implements DownloadManager
{
private static final String TAG = DownloadManagerImpl.class.getSimpleName();
private Context mContext;
private String mLocation;
protected ArrayList<DownloadMission> mMissions = new ArrayList<DownloadMission>();
public DownloadManagerImpl(Context context, String location) {
mContext = context;
mLocation = location;
loadMissions();
private final DownloadDataSource mDownloadDataSource;
private final ArrayList<DownloadMission> mMissions = new ArrayList<DownloadMission>();
/**
* Create a new instance
* @param searchLocations the directories to search for unfinished downloads
* @param downloadDataSource the data source for finished downloads
*/
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource) {
mDownloadDataSource = downloadDataSource;
loadMissions(searchLocations);
}
@Override
public int startMission(String url, String name, int threads) {
DownloadMission mission = new DownloadMission();
mission.url = url;
mission.name = name;
mission.location = NewPipeSettings.getDownloadPath(mContext, name);
public int startMission(String url, String location, String name, boolean isAudio, int threads) {
DownloadMission existingMission = getMissionByLocation(location, name);
if(existingMission != null) {
// Already downloaded or downloading
if(existingMission.finished) {
// Overwrite mission
deleteMission(mMissions.indexOf(existingMission));
} else {
// Rename file (?)
try {
name = generateUniqueName(location, name);
}catch (Exception e) {
Log.e(TAG, "Unable to generate unique name", e);
name = System.currentTimeMillis() + name ;
Log.i(TAG, "Using " + name);
}
}
}
DownloadMission mission = new DownloadMission(name, url, location);
mission.timestamp = System.currentTimeMillis();
mission.threadCount = threads;
new Initializer(mContext, mission).start();
mission.addListener(new MissionListener(mission));
new Initializer(mission).start();
return insertMission(mission);
}
@Override
public void resumeMission(int i) {
DownloadMission d = getMission(i);
@@ -49,7 +72,7 @@ public class DownloadManagerImpl implements DownloadManager
d.start();
}
}
@Override
public void pauseMission(int i) {
DownloadMission d = getMission(i);
@@ -57,55 +80,94 @@ public class DownloadManagerImpl implements DownloadManager
d.pause();
}
}
@Override
public void deleteMission(int i) {
getMission(i).delete();
DownloadMission mission = getMission(i);
if(mission.finished) {
mDownloadDataSource.deleteMission(mission);
}
mission.delete();
mMissions.remove(i);
}
private void loadMissions() {
File f = new File(mLocation);
private void loadMissions(Iterable<String> searchLocations) {
mMissions.clear();
loadFinishedMissions();
for(String location: searchLocations) {
loadMissions(location);
}
}
/**
* Loads finished missions from the data source
*/
private void loadFinishedMissions() {
List<DownloadMission> finishedMissions = mDownloadDataSource.loadMissions();
if(finishedMissions == null) {
finishedMissions = new ArrayList<>();
}
// Ensure its sorted
Collections.sort(finishedMissions, new Comparator<DownloadMission>() {
@Override
public int compare(DownloadMission o1, DownloadMission o2) {
return (int) (o1.timestamp - o2.timestamp);
}
});
mMissions.ensureCapacity(mMissions.size() + finishedMissions.size());
for(DownloadMission mission: finishedMissions) {
File downloadedFile = mission.getDownloadedFile();
if(!downloadedFile.isFile()) {
if(DEBUG) {
Log.d(TAG, "downloaded file removed: " + downloadedFile.getAbsolutePath());
}
mDownloadDataSource.deleteMission(mission);
} else {
mission.length = downloadedFile.length();
mission.finished = true;
mission.running = false;
mMissions.add(mission);
}
}
}
private void loadMissions(String location) {
File f = new File(location);
if (f.exists() && f.isDirectory()) {
File[] subs = f.listFiles();
if(subs == null) {
Log.e(TAG, "listFiles() returned null");
return;
}
for (File sub : subs) {
if (sub.isDirectory()) {
continue;
}
if (sub.getName().endsWith(".giga")) {
if (sub.isFile() && sub.getName().endsWith(".giga")) {
String str = Utility.readFromFile(sub.getAbsolutePath());
if (str != null && !str.trim().equals("")) {
if (DEBUG) {
Log.d(TAG, "loading mission " + sub.getName());
Log.d(TAG, str);
}
DownloadMission mis = new Gson().fromJson(str, DownloadMission.class);
if (mis.finished) {
sub.delete();
if(!sub.delete()) {
Log.w(TAG, "Unable to delete .giga file: " + sub.getPath());
}
continue;
}
mis.running = false;
mis.recovered = true;
insertMission(mis);
}
} else if (!sub.getName().startsWith(".") && !new File(sub.getPath() + ".giga").exists()) {
// Add a dummy mission for downloaded files
DownloadMission mis = new DownloadMission();
mis.length = sub.length();
mis.done = mis.length;
mis.finished = true;
mis.running = false;
mis.name = sub.getName();
mis.location = mLocation;
mis.timestamp = sub.lastModified();
insertMission(mis);
}
}
}
@@ -140,18 +202,81 @@ public class DownloadManagerImpl implements DownloadManager
return i;
}
@Override
public String getLocation() {
return mLocation;
/**
* Get a mission by its location and name
* @param location the location
* @param name the name
* @return the mission or null if no such mission exists
*/
private @Nullable DownloadMission getMissionByLocation(String location, String name) {
for(DownloadMission mission: mMissions) {
if(location.equals(mission.location) && name.equals(mission.name)) {
return mission;
}
}
return null;
}
/**
* Splits the filename into name and extension
*
* Dots are ignored if they appear: not at all, at the beginning of the file,
* at the end of the file
*
* @param name the name to split
* @return a string array with a length of 2 containing the name and the extension
*/
private static String[] splitName(String name) {
int dotIndex = name.lastIndexOf('.');
if(dotIndex <= 0 || (dotIndex == name.length() - 1)) {
return new String[]{name, ""};
} else {
return new String[]{name.substring(0, dotIndex), name.substring(dotIndex + 1)};
}
}
/**
* Generates a unique file name.
*
* e.g. "myname (1).txt" if the name "myname.txt" exists.
* @param location the location (to check for existing files)
* @param name the name of the file
* @return the unique file name
* @throws IllegalArgumentException if the location is not a directory
* @throws SecurityException if the location is not readable
*/
private static String generateUniqueName(String location, String name) {
if(location == null) throw new NullPointerException("location is null");
if(name == null) throw new NullPointerException("name is null");
File destination = new File(location);
if(!destination.isDirectory()) {
throw new IllegalArgumentException("location is not a directory: " + location);
}
final String[] nameParts = splitName(name);
String[] existingName = destination.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(nameParts[0]);
}
});
Arrays.sort(existingName);
String newName;
int downloadIndex = 0;
do {
newName = nameParts[0] + " (" + downloadIndex + ")." + nameParts[1];
++downloadIndex;
if(downloadIndex == 1000) { // Probably an error on our side
throw new RuntimeException("Too many existing files");
}
} while (Arrays.binarySearch(existingName, newName) >= 0);
return newName;
}
private class Initializer extends Thread {
private Context context;
private DownloadMission mission;
public Initializer(Context context, DownloadMission mission) {
this.context = context;
public Initializer(DownloadMission mission) {
this.mission = mission;
}
@@ -213,4 +338,30 @@ public class DownloadManagerImpl implements DownloadManager
}
}
}
/**
* Waits for mission to finish to add it to the {@link #mDownloadDataSource}
*/
private class MissionListener implements DownloadMission.MissionListener {
private final DownloadMission mMission;
private MissionListener(DownloadMission mission) {
if(mission == null) throw new NullPointerException("mission is null");
// Could the mission be passed in onFinish()?
mMission = mission;
}
@Override
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
}
@Override
public void onFinish(DownloadMission downloadMission) {
mDownloadDataSource.addMission(mMission);
}
@Override
public void onError(DownloadMission downloadMission, int errCode) {
}
}
}

View File

@@ -1,6 +1,5 @@
package us.shandian.giga.get;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
@@ -10,39 +9,63 @@ import com.google.gson.Gson;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import us.shandian.giga.util.Utility;
import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadMission
{
private static final String TAG = DownloadMission.class.getSimpleName();
public interface MissionListener {
HashMap<MissionListener, Handler> handlerStore = new HashMap<>();
void onProgressUpdate(long done, long total);
void onFinish();
void onError(int errCode);
void onProgressUpdate(DownloadMission downloadMission, long done, long total);
void onFinish(DownloadMission downloadMission);
void onError(DownloadMission downloadMission, int errCode);
}
public static final int ERROR_SERVER_UNSUPPORTED = 206;
public static final int ERROR_UNKNOWN = 233;
public String name = "";
public String url = "";
public String location = "";
/**
* The filename
*/
public String name;
/**
* The url of the file to download
*/
public String url;
/**
* The directory to store the download
*/
public String location;
/**
* Number of blocks the size of {@link DownloadManager#BLOCK_SIZE}
*/
public long blocks;
/**
* Number of bytes
*/
public long length;
/**
* Number of bytes downloaded
*/
public long done;
public int threadCount = 3;
public int finishCount;
public List<Long> threadPositions = new ArrayList<Long>();
public Map<Long, Boolean> blockState = new HashMap<Long, Boolean>();
private List<Long> threadPositions = new ArrayList<Long>();
public final Map<Long, Boolean> blockState = new HashMap<Long, Boolean>();
public boolean running;
public boolean finished;
public boolean fallback;
@@ -53,23 +76,65 @@ public class DownloadMission
private transient ArrayList<WeakReference<MissionListener>> mListeners = new ArrayList<WeakReference<MissionListener>>();
private transient boolean mWritingToFile;
private static final int NO_IDENTIFIER = -1;
private long db_identifier = NO_IDENTIFIER;
public DownloadMission() {
}
public DownloadMission(String name, String url, String location) {
if(name == null) throw new NullPointerException("name is null");
if(name.isEmpty()) throw new IllegalArgumentException("name is empty");
if(url == null) throw new NullPointerException("url is null");
if(url.isEmpty()) throw new IllegalArgumentException("url is empty");
if(location == null) throw new NullPointerException("location is null");
if(location.isEmpty()) throw new IllegalArgumentException("location is empty");
this.url = url;
this.name = name;
this.location = location;
}
private void checkBlock(long block) {
if(block < 0 || block >= blocks) {
throw new IllegalArgumentException("illegal block identifier");
}
}
/**
* Check if a block is reserved
* @param block the block identifier
* @return true if the block is reserved and false if otherwise
*/
public boolean isBlockPreserved(long block) {
checkBlock(block);
return blockState.containsKey(block) ? blockState.get(block) : false;
}
public void preserveBlock(long block) {
checkBlock(block);
synchronized (blockState) {
blockState.put(block, true);
}
}
public void setPosition(int id, long position) {
threadPositions.set(id, position);
/**
* Set the download position of the file
* @param threadId the identifier of the thread
* @param position the download position of the thread
*/
public void setPosition(int threadId, long position) {
threadPositions.set(threadId, position);
}
public long getPosition(int id) {
return threadPositions.get(id);
/**
* Get the position of a thread
* @param threadId the identifier of the thread
* @return the position for the thread
*/
public long getPosition(int threadId) {
return threadPositions.get(threadId);
}
public synchronized void notifyProgress(long deltaLen) {
@@ -95,13 +160,16 @@ public class DownloadMission
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onProgressUpdate(done, length);
listener.onProgressUpdate(DownloadMission.this, done, length);
}
});
}
}
}
/**
* Called by a download thread when it finished.
*/
public synchronized void notifyFinished() {
if (errCode > 0) return;
@@ -111,7 +179,10 @@ public class DownloadMission
onFinish();
}
}
/**
* Called when all parts are downloaded
*/
private void onFinish() {
if (errCode > 0) return;
@@ -130,7 +201,7 @@ public class DownloadMission
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onFinish();
listener.onFinish(DownloadMission.this);
}
});
}
@@ -147,7 +218,7 @@ public class DownloadMission
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onError(errCode);
listener.onError(DownloadMission.this, errCode);
}
});
}
@@ -169,7 +240,10 @@ public class DownloadMission
}
}
}
/**
* Start downloading with multiple threads.
*/
public void start() {
if (!running && !finished) {
running = true;
@@ -200,12 +274,19 @@ public class DownloadMission
// if (err)
}
}
/**
* Removes the file and the meta file
*/
public void delete() {
deleteThisFromFile();
new File(location + "/" + name).delete();
new File(location, name).delete();
}
/**
* Write this {@link DownloadMission} to the meta file asynchronously
* if no thread is already running.
*/
public void writeThisToFile() {
if (!mWritingToFile) {
mWritingToFile = true;
@@ -218,14 +299,30 @@ public class DownloadMission
}.start();
}
}
/**
* Write this {@link DownloadMission} to the meta file.
*/
private void doWriteThisToFile() {
synchronized (blockState) {
Utility.writeToFile(location + "/" + name + ".giga", new Gson().toJson(this));
Utility.writeToFile(getMetaFilename(), new Gson().toJson(this));
}
}
private void deleteThisFromFile() {
new File(location + "/" + name + ".giga").delete();
new File(getMetaFilename()).delete();
}
/**
* Get the path of the meta file
* @return the path to the meta file
*/
private String getMetaFilename() {
return location + "/" + name + ".giga";
}
public File getDownloadedFile() {
return new File(location, name);
}
}

View File

@@ -9,14 +9,19 @@ import java.net.URL;
import static org.schabi.newpipe.BuildConfig.DEBUG;
/**
* Runnable to download blocks of a file until the file is completely downloaded,
* an error occurs or the process is stopped.
*/
public class DownloadRunnable implements Runnable
{
private static final String TAG = DownloadRunnable.class.getSimpleName();
private DownloadMission mMission;
private int mId;
private final DownloadMission mMission;
private final int mId;
public DownloadRunnable(DownloadMission mission, int id) {
if(mission == null) throw new NullPointerException("mission is null");
mMission = mission;
mId = id;
}
@@ -86,7 +91,7 @@ public class DownloadRunnable implements Runnable
Log.d(TAG, mId + ":Content-Length=" + conn.getContentLength() + " Code:" + conn.getResponseCode());
}
// A server may be ignoring the range requet
// A server may be ignoring the range request
if (conn.getResponseCode() != 206) {
mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
@@ -131,7 +136,7 @@ public class DownloadRunnable implements Runnable
notifyProgress(-total);
if (DEBUG) {
Log.d(TAG, mId + ":position " + position + " retrying");
Log.d(TAG, mId + ":position " + position + " retrying", e);
}
}
}

View File

@@ -8,10 +8,11 @@ import java.net.URL;
// Single-threaded fallback mode
public class DownloadRunnableFallback implements Runnable
{
private DownloadMission mMission;
private final DownloadMission mMission;
//private int mId;
public DownloadRunnableFallback(DownloadMission mission) {
if(mission == null) throw new NullPointerException("mission is null");
//mId = id;
mMission = mission;
}
@@ -35,7 +36,7 @@ public class DownloadRunnableFallback implements Runnable
f.write(buf, 0, len);
notifyProgress(len);
if (Thread.currentThread().interrupted()) {
if (Thread.interrupted()) {
break;
}

View File

@@ -0,0 +1,102 @@
package us.shandian.giga.get.sqlite;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import us.shandian.giga.get.DownloadMission;
/**
* SqliteHelper to store {@link us.shandian.giga.get.DownloadMission}
*/
public class DownloadMissionSQLiteHelper extends SQLiteOpenHelper {
private final String TAG = "DownloadMissionHelper";
// TODO: use NewPipeSQLiteHelper ('s constants) when playlist branch is merged (?)
private static final String DATABASE_NAME = "downloads.db";
private static final int DATABASE_VERSION = 2;
/**
* The table name of download missions
*/
static final String MISSIONS_TABLE_NAME = "download_missions";
/**
* The key to the directory location of the mission
*/
static final String KEY_LOCATION = "location";
/**
* The key to the url of a mission
*/
static final String KEY_URL = "url";
/**
* The key to the name of a mission
*/
static final String KEY_NAME = "name";
/**
* The key to the done.
*/
static final String KEY_DONE = "bytes_downloaded";
static final String KEY_TIMESTAMP = "timestamp";
/**
* The statement to create the table
*/
private static final String MISSIONS_CREATE_TABLE =
"CREATE TABLE " + MISSIONS_TABLE_NAME + " (" +
KEY_LOCATION + " TEXT NOT NULL, " +
KEY_NAME + " TEXT NOT NULL, " +
KEY_URL + " TEXT NOT NULL, " +
KEY_DONE + " INTEGER NOT NULL, " +
KEY_TIMESTAMP + " INTEGER NOT NULL, " +
" UNIQUE(" + KEY_LOCATION + ", " + KEY_NAME + "));";
DownloadMissionSQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* Returns all values of the download mission as ContentValues.
* @param downloadMission the download mission
* @return the content values
*/
public static ContentValues getValuesOfMission(DownloadMission downloadMission) {
ContentValues values = new ContentValues();
values.put(KEY_URL, downloadMission.url);
values.put(KEY_LOCATION, downloadMission.location);
values.put(KEY_NAME, downloadMission.name);
values.put(KEY_DONE, downloadMission.done);
values.put(KEY_TIMESTAMP, downloadMission.timestamp);
return values;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(MISSIONS_CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Currently nothing to do
}
public static DownloadMission getMissionFromCursor(Cursor cursor) {
if(cursor == null) throw new NullPointerException("cursor is null");
int pos;
String name = cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME));
String location = cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCATION));
String url = cursor.getString(cursor.getColumnIndexOrThrow(KEY_URL));
DownloadMission mission = new DownloadMission(name, url, location);
mission.done = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_DONE));
mission.timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_TIMESTAMP));
mission.finished = true;
return mission;
}
}

View File

@@ -0,0 +1,79 @@
package us.shandian.giga.get.sqlite;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import us.shandian.giga.get.DownloadDataSource;
import us.shandian.giga.get.DownloadMission;
import static us.shandian.giga.get.sqlite.DownloadMissionSQLiteHelper.KEY_LOCATION;
import static us.shandian.giga.get.sqlite.DownloadMissionSQLiteHelper.KEY_NAME;
import static us.shandian.giga.get.sqlite.DownloadMissionSQLiteHelper.MISSIONS_TABLE_NAME;
/**
* Non-thread-safe implementation of {@link DownloadDataSource}
*/
public class SQLiteDownloadDataSource implements DownloadDataSource {
private static final String TAG = "DownloadDataSourceImpl";
private final DownloadMissionSQLiteHelper downloadMissionSQLiteHelper;
public SQLiteDownloadDataSource(Context context) {
downloadMissionSQLiteHelper = new DownloadMissionSQLiteHelper(context);
}
@Override
public List<DownloadMission> loadMissions() {
ArrayList<DownloadMission> result;
SQLiteDatabase database = downloadMissionSQLiteHelper.getReadableDatabase();
Cursor cursor = database.query(MISSIONS_TABLE_NAME, null, null,
null, null, null, DownloadMissionSQLiteHelper.KEY_TIMESTAMP);
int count = cursor.getCount();
if(count == 0) return new ArrayList<>();
result = new ArrayList<>(count);
while (cursor.moveToNext()) {
result.add(DownloadMissionSQLiteHelper.getMissionFromCursor(cursor));
}
return result;
}
@Override
public void addMission(DownloadMission downloadMission) {
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
database.insert(MISSIONS_TABLE_NAME, null, values);
}
@Override
public void updateMission(DownloadMission downloadMission) {
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
String whereClause = KEY_LOCATION+ " = ? AND " +
KEY_NAME + " = ?";
int rowsAffected = database.update(MISSIONS_TABLE_NAME, values,
whereClause, new String[]{downloadMission.location, downloadMission.name});
if(rowsAffected != 1) {
Log.e(TAG, "Expected 1 row to be affected by update but got " + rowsAffected);
}
}
@Override
public void deleteMission(DownloadMission downloadMission) {
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
database.delete(MISSIONS_TABLE_NAME,
KEY_LOCATION + " = ? AND " +
KEY_NAME + " = ?",
new String[]{downloadMission.location, downloadMission.name});
}
}

View File

@@ -1,59 +1,99 @@
package us.shandian.giga.service;
import android.Manifest;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.content.PermissionChecker;
import android.util.Log;
import android.widget.Toast;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.R;
import org.schabi.newpipe.download.DownloadActivity;
import org.schabi.newpipe.settings.NewPipeSettings;
import java.util.ArrayList;
import us.shandian.giga.get.DownloadDataSource;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.get.DownloadManagerImpl;
import us.shandian.giga.get.DownloadMission;
import org.schabi.newpipe.download.MainActivity;
import us.shandian.giga.get.sqlite.SQLiteDownloadDataSource;
import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadManagerService extends Service implements DownloadMission.MissionListener
public class DownloadManagerService extends Service
{
private static final String TAG = DownloadManagerService.class.getSimpleName();
/**
* Message code of update messages stored as {@link Message#what}.
*/
private static final int UPDATE_MESSAGE = 0;
private static final int NOTIFICATION_ID = 1000;
private static final String EXTRA_NAME = "DownloadManagerService.extra.name";
private static final String EXTRA_LOCATION = "DownloadManagerService.extra.location";
private static final String EXTRA_IS_AUDIO = "DownloadManagerService.extra.is_audio";
private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads";
private DMBinder mBinder;
private DownloadManager mManager;
private Notification mNotification;
private Handler mHandler;
private long mLastTimeStamp = System.currentTimeMillis();
private DownloadDataSource mDataSource;
private MissionListener missionListener = new MissionListener();
private void notifyMediaScanner(DownloadMission mission) {
Uri uri = Uri.parse("file://" + mission.location + "/" + mission.name);
// notify media scanner on downloaded media file ...
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
}
@Override
public void onCreate() {
super.onCreate();
if (DEBUG) {
Log.d(TAG, "onCreate");
}
mBinder = new DMBinder();
if(mDataSource == null) {
mDataSource = new SQLiteDownloadDataSource(this);
}
if (mManager == null) {
String path = NewPipeSettings.getVideoDownloadPath(this);
mManager = new DownloadManagerImpl(this, path);
ArrayList<String> paths = new ArrayList<>(2);
paths.add(NewPipeSettings.getVideoDownloadPath(this));
paths.add(NewPipeSettings.getAudioDownloadPath(this));
mManager = new DownloadManagerImpl(paths, mDataSource);
if (DEBUG) {
Log.d(TAG, "mManager == null");
Log.d(TAG, "Download directory: " + path);
Log.d(TAG, "Download directory: " + paths);
}
}
Intent i = new Intent();
i.setAction(Intent.ACTION_MAIN);
i.setClass(this, MainActivity.class);
i.setClass(this, DownloadActivity.class);
Drawable icon = this.getResources().getDrawable(R.mipmap.ic_launcher);
@@ -68,8 +108,8 @@ public class DownloadManagerService extends Service implements DownloadMission.M
PendingIntent.getActivity(
this,
0,
new Intent(this, MainActivity.class)
.setAction(MainActivity.INTENT_LIST),
new Intent(this, DownloadActivity.class)
.setAction(DownloadActivity.INTENT_LIST),
PendingIntent.FLAG_UPDATE_CURRENT
);
@@ -83,28 +123,50 @@ public class DownloadManagerService extends Service implements DownloadMission.M
mHandler = new Handler(thread.getLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0) {
int runningCount = 0;
for (int i = 0; i < mManager.getCount(); i++) {
if (mManager.getMission(i).running) {
runningCount++;
switch (msg.what) {
case UPDATE_MESSAGE: {
int runningCount = 0;
for (int i = 0; i < mManager.getCount(); i++) {
if (mManager.getMission(i).running) {
runningCount++;
}
}
updateState(runningCount);
break;
}
updateState(runningCount);
}
}
};
}
private void startMissionAsync(final String url, final String location, final String name,
final boolean isAudio, final int threads) {
mHandler.post(new Runnable() {
@Override
public void run() {
int missionId = mManager.startMission(url, location, name, isAudio, threads);
mBinder.onMissionAdded(mManager.getMission(missionId));
}
});
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DEBUG) {
Log.d(TAG, "Starting");
}
Log.i(TAG, "Got intent: " + intent);
String action = intent.getAction();
if(action != null && action.equals(Intent.ACTION_RUN)) {
String name = intent.getStringExtra(EXTRA_NAME);
String location = intent.getStringExtra(EXTRA_LOCATION);
int threads = intent.getIntExtra(EXTRA_THREADS, 1);
boolean isAudio = intent.getBooleanExtra(EXTRA_IS_AUDIO, false);
String url = intent.getDataString();
startMissionAsync(url, location, name, isAudio, threads);
}
return START_NOT_STICKY;
}
@@ -119,52 +181,76 @@ public class DownloadManagerService extends Service implements DownloadMission.M
for (int i = 0; i < mManager.getCount(); i++) {
mManager.pauseMission(i);
}
stopForeground(true);
}
@Override
public IBinder onBind(Intent intent) {
int permissionCheck;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
if(permissionCheck == PermissionChecker.PERMISSION_DENIED) {
Toast.makeText(this, "Permission denied (read)", Toast.LENGTH_SHORT).show();
}
}
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if(permissionCheck == PermissionChecker.PERMISSION_DENIED) {
Toast.makeText(this, "Permission denied (write)", Toast.LENGTH_SHORT).show();
}
return mBinder;
}
@Override
public void onProgressUpdate(long done, long total) {
long now = System.currentTimeMillis();
long delta = now - mLastTimeStamp;
if (delta > 2000) {
postUpdateMessage();
mLastTimeStamp = now;
}
}
@Override
public void onFinish() {
postUpdateMessage();
}
@Override
public void onError(int errCode) {
postUpdateMessage();
}
private void postUpdateMessage() {
mHandler.sendEmptyMessage(0);
mHandler.sendEmptyMessage(UPDATE_MESSAGE);
}
private void updateState(int runningCount) {
if (runningCount == 0) {
stopForeground(true);
} else {
startForeground(1000, mNotification);
startForeground(NOTIFICATION_ID, mNotification);
}
}
public static void startMission(Context context, String url, String location, String name, boolean isAudio, int threads) {
Intent intent = new Intent(context, DownloadManagerService.class);
intent.setAction(Intent.ACTION_RUN);
intent.setData(Uri.parse(url));
intent.putExtra(EXTRA_NAME, name);
intent.putExtra(EXTRA_LOCATION, location);
intent.putExtra(EXTRA_IS_AUDIO, isAudio);
intent.putExtra(EXTRA_THREADS, threads);
context.startService(intent);
}
class MissionListener implements DownloadMission.MissionListener {
@Override
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
long now = System.currentTimeMillis();
long delta = now - mLastTimeStamp;
if (delta > 2000) {
postUpdateMessage();
mLastTimeStamp = now;
}
}
@Override
public void onFinish(DownloadMission downloadMission) {
postUpdateMessage();
notifyMediaScanner(downloadMission);
}
@Override
public void onError(DownloadMission downloadMission, int errCode) {
postUpdateMessage();
}
}
// Wrapper of DownloadManager
public class DMBinder extends Binder {
public DownloadManager getDownloadManager() {
@@ -172,15 +258,13 @@ public class DownloadManagerService extends Service implements DownloadMission.M
}
public void onMissionAdded(DownloadMission mission) {
mission.addListener(DownloadManagerService.this);
mission.addListener(missionListener);
postUpdateMessage();
}
public void onMissionRemoved(DownloadMission mission) {
mission.removeListener(DownloadManagerService.this);
mission.removeListener(missionListener);
postUpdateMessage();
}
}
}

View File

@@ -5,6 +5,9 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.support.v4.content.FileProvider;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -19,6 +22,7 @@ import android.support.v7.widget.RecyclerView;
import java.io.File;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.schabi.newpipe.R;
@@ -28,10 +32,14 @@ import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.ui.common.ProgressDrawable;
import us.shandian.giga.util.Utility;
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHolder>
{
private static final Map<Integer, String> ALGORITHMS = new HashMap<>();
private static final String TAG = "MissionAdapter";
static {
ALGORITHMS.put(R.id.md5, "MD5");
ALGORITHMS.put(R.id.sha1, "SHA1");
@@ -143,9 +151,8 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
h.status.setText(R.string.msg_error);
} else {
float progress = (float) h.mission.done / h.mission.length;
h.status.setText(String.format("%.2f%%", progress * 100));
h.status.setText(String.format(Locale.US, "%.2f%%", progress * 100));
h.progress.setProgress(progress);
}
}
@@ -212,23 +219,23 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
h.lastDone = -1;
return true;
case R.id.view:
Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
File f = new File(h.mission.location + "/" + h.mission.name);
File f = new File(h.mission.location, h.mission.name);
String ext = Utility.getFileExt(h.mission.name);
if (ext == null) return false;
Log.d(TAG, "Viewing file: " + f.getAbsolutePath() + " ext: " + ext);
if (ext == null) {
Log.w(TAG, "Can't view file because it has no extension: " +
h.mission.name);
return false;
}
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.substring(1));
Log.v(TAG, "Mime: " + mime + " package: " + mContext.getApplicationContext().getPackageName() + ".provider");
if (f.exists()) {
i.setDataAndType(Uri.fromFile(f), mime);
try {
mContext.startActivity(i);
} catch (Exception e) {
}
viewFileWithFileProvider(f, mime);
} else {
Log.w(TAG, "File doesn't exist");
}
return true;
@@ -249,6 +256,34 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
popup.show();
}
private void viewFile(File file, String mimetype) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), mimetype);
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION);
}
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
Log.v(TAG, "Starting intent: " + intent);
mContext.startActivity(intent);
}
private void viewFileWithFileProvider(File file, String mimetype) {
String ourPackage = mContext.getApplicationContext().getPackageName();
Uri uri = FileProvider.getUriForFile(mContext, ourPackage + ".provider", file);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(uri, mimetype);
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION);
}
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
Log.v(TAG, "Starting intent: " + intent);
mContext.startActivity(intent);
}
private class ChecksumTask extends AsyncTask<String, Void, String> {
ProgressDialog prog;
@@ -280,7 +315,7 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
static class ViewHolder extends RecyclerView.ViewHolder {
public DownloadMission mission;
public int position;
public TextView status;
public ImageView icon;
public TextView name;
@@ -289,7 +324,7 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
public ImageView menu;
public ProgressDrawable progress;
public MissionObserver observer;
public long lastTimeStamp = -1;
public long lastDone = -1;
public int colorId;
@@ -316,12 +351,12 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
}
@Override
public void onProgressUpdate(long done, long total) {
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
mAdapter.updateProgress(mHolder);
}
@Override
public void onFinish() {
public void onFinish(DownloadMission downloadMission) {
//mAdapter.mManager.deleteMission(mHolder.position);
// TODO Notification
//mAdapter.notifyDataSetChanged();
@@ -332,7 +367,7 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
}
@Override
public void onError(int errCode) {
public void onError(DownloadMission downloadMission, int errCode) {
mAdapter.updateProgress(mHolder);
}

View File

@@ -40,7 +40,7 @@ public abstract class MissionsFragment extends Fragment
private MissionAdapter mAdapter;
private GridLayoutManager mGridManager;
private LinearLayoutManager mLinearManager;
private Activity mActivity;
private Context mActivity;
private ServiceConnection mConnection = new ServiceConnection() {
@@ -65,7 +65,7 @@ public abstract class MissionsFragment extends Fragment
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
mLinear = mPrefs.getBoolean("linear", false);
// Bind the service
Intent i = new Intent();
i.setClass(getActivity(), DownloadManagerService.class);
@@ -85,14 +85,15 @@ public abstract class MissionsFragment extends Fragment
}
@Override
public void onAttach(Activity activity) {
public void onAttach(Context activity) {
super.onAttach(activity);
mActivity = activity;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onDestroyView() {
super.onDestroyView();
getActivity().unbindService(mConnection);
}
@Override

View File

@@ -27,13 +27,8 @@ public class Utility
{
public static enum FileType {
APP,
VIDEO,
EXCEL,
WORD,
POWERPOINT,
MUSIC,
ARCHIVE,
UNKNOWN
}
@@ -142,22 +137,11 @@ public class Utility
}
public static FileType getFileType(String file) {
if (file.endsWith(".apk")) {
return FileType.APP;
} else if (file.endsWith(".mp3") || file.endsWith(".wav") || file.endsWith(".flac") || file.endsWith(".m4a")) {
if (file.endsWith(".mp3") || file.endsWith(".wav") || file.endsWith(".flac") || file.endsWith(".m4a")) {
return FileType.MUSIC;
} else if (file.endsWith(".mp4") || file.endsWith(".mpeg") || file.endsWith(".rm") || file.endsWith(".rmvb")
|| file.endsWith(".flv") || file.endsWith(".webp") || file.endsWith(".webm")) {
return FileType.VIDEO;
} else if (file.endsWith(".doc") || file.endsWith(".docx")) {
return FileType.WORD;
} else if (file.endsWith(".xls") || file.endsWith(".xlsx")) {
return FileType.EXCEL;
} else if (file.endsWith(".ppt") || file.endsWith(".pptx")) {
return FileType.POWERPOINT;
} else if (file.endsWith(".zip") || file.endsWith(".rar") || file.endsWith(".7z") || file.endsWith(".gz")
|| file.endsWith("tar") || file.endsWith(".bz")) {
return FileType.ARCHIVE;
} else {
return FileType.UNKNOWN;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Some files were not shown because too many files have changed in this diff Show More