1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2026-01-14 20:47:55 +00:00

Compare commits

...

320 Commits

Author SHA1 Message Date
Christian Schabesberger
f65f2da890 moved on to v0.11.6 2018-02-04 18:40:45 +01:00
Christian Schabesberger
30ab58c33d added some more lambdas 2018-02-04 17:50:22 +01:00
Christian Schabesberger
87ba5a7eb6 Merge branch 'eximport' into dev 2018-02-04 13:40:03 +01:00
Christian Schabesberger
6e8593af91 add import database function
bla

remove unused restart function

add allert dialog and add time to filename
2018-02-04 13:38:58 +01:00
Weblate
0ab1d3fc40 Merge remote-tracking branch 'origin/dev' into dev 2018-02-04 03:36:15 +01:00
ezjerry liao
f22d13e695 Translated using Weblate (Chinese (Traditional))
Currently translated at 83.5% (223 of 267 strings)
2018-02-04 03:36:07 +01:00
Mauricio Colli
cdde61a460 Change service icons for now
- Use place holders while the legal discussion is happening
2018-02-03 22:32:01 -02:00
Mauricio Colli
989ce126f1 Improve up button behavior
- Closes #614
2018-02-03 09:03:55 -02:00
ScratchBuild
28618e822e Translated using Weblate (Japanese)
Currently translated at 74.5% (199 of 267 strings)
2018-02-03 08:38:18 +01:00
Weblate
6772381afc Merge remote-tracking branch 'origin/dev' into dev 2018-01-31 13:34:27 +01:00
孟武尼德霍格龍
75b45beabc Translated using Weblate (Chinese (Traditional))
Currently translated at 77.2% (207 of 268 strings)
2018-01-31 13:34:24 +01:00
ButterflyOfFire
56d53e9b01 Translated using Weblate (Arabic)
Currently translated at 95.1% (255 of 268 strings)
2018-01-31 13:34:15 +01:00
Mauricio Colli
3a8b04e2d1 Fix and improve service switching (introduced colors)
- Every service now have its own colors
- Fix bug navigation button and backstack count
- Fix and themed properly the icons and colors of the main fragment tabs
- Re-organized the styles and colors (too much in one file)
2018-01-30 05:07:40 -02:00
Christian Schabesberger
1ce7d66fb1 Merge pull request #1043 from TobiGr/preferred-string
Make preferred string not translatable
2018-01-29 00:47:55 +01:00
TobiGr
7b5a9b69fe make preferred string not translatable 2018-01-28 21:27:20 +01:00
Allan Nordhøy
837b22ccac Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (268 of 268 strings)
2018-01-28 19:24:39 +01:00
Christian Schabesberger
7146719393 add export newipe db function 2018-01-28 19:02:34 +01:00
Weblate
71ee604c69 Merge remote-tracking branch 'origin/dev' into dev 2018-01-27 21:19:32 +01:00
Mateusz
9945a5b813 Translated using Weblate (Polish)
Currently translated at 98.1% (263 of 268 strings)
2018-01-27 21:19:31 +01:00
Kompiuterių meistras +37060040
7254387042 Translated using Weblate (Lithuanian)
Currently translated at 91.0% (244 of 268 strings)
2018-01-27 21:19:29 +01:00
Jonas
3a7f2a94a6 Translated using Weblate (French)
Currently translated at 100.0% (268 of 268 strings)
2018-01-27 21:19:26 +01:00
Mauricio Colli
6bea4aa96b Fix player switching
- Background to popup wasn't asking for permission
- The new task flag is needed to switch from the background/popup UI to the main player
2018-01-27 02:57:00 -02:00
Mauricio Colli
fa262bbceb Improve settings theme color
- Closes #863
2018-01-27 02:56:10 -02:00
Eduardo Caron
3139fe0170 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.8% (265 of 268 strings)
2018-01-25 22:39:19 +01:00
Freddy Morán Jr
ef6c5de65b Translated using Weblate (Spanish)
Currently translated at 100.0% (268 of 268 strings)
2018-01-25 19:40:22 +01:00
M1ck
07799563b5 Translated using Weblate (French)
Currently translated at 99.6% (267 of 268 strings)
2018-01-25 18:36:21 +01:00
Nathan Follens
b1de4b7bd6 Translated using Weblate (Dutch)
Currently translated at 100.0% (268 of 268 strings)
2018-01-25 18:16:29 +01:00
E T
2b8ae9a5ea Translated using Weblate (Turkish)
Currently translated at 99.6% (267 of 268 strings)
2018-01-25 14:42:33 +01:00
Georg Rieger
7c52d3ec5d Translated using Weblate (German)
Currently translated at 99.6% (267 of 268 strings)
2018-01-25 14:37:17 +01:00
Osoitz
aefaa7619e Translated using Weblate (Basque)
Currently translated at 99.6% (267 of 268 strings)
2018-01-25 14:34:34 +01:00
Emanuele Petriglia
ca202290bf Translated using Weblate (Italian)
Currently translated at 100.0% (268 of 268 strings)
2018-01-24 14:46:59 +01:00
Weblate
cbdbc4cba2 Merge remote-tracking branch 'origin/dev' into dev 2018-01-24 13:10:42 +01:00
M1ck
8abf904a78 Translated using Weblate (French)
Currently translated at 100.0% (252 of 252 strings)
2018-01-24 13:10:32 +01:00
Mauricio Colli
ecf7969c46 Improve settings up button behavior
- Fix #736
2018-01-23 11:45:41 -02:00
Mauricio Colli
a473e3d623 Add preferred player 2018-01-23 11:15:36 -02:00
Weblate
1f8e90858e Merge remote-tracking branch 'origin/dev' into dev 2018-01-23 12:17:34 +01:00
Osoitz
2c2edca8fa Translated using Weblate (Spanish)
Currently translated at 100.0% (252 of 252 strings)
2018-01-23 12:17:33 +01:00
Rubix
50e86ff1ca Translated using Weblate (Romanian)
Currently translated at 87.6% (221 of 252 strings)
2018-01-23 12:17:33 +01:00
Rintaro matsuo
02ecc5011a Translated using Weblate (Japanese)
Currently translated at 78.9% (199 of 252 strings)
2018-01-23 12:17:32 +01:00
nautilusx
6e666a018b Translated using Weblate (German)
Currently translated at 96.8% (244 of 252 strings)
2018-01-23 12:17:31 +01:00
M2ck
66fbb2ce1e Translated using Weblate (French)
Currently translated at 100.0% (252 of 252 strings)
2018-01-23 12:17:30 +01:00
r2308145
b00722ec0a Translated using Weblate (Czech)
Currently translated at 100.0% (252 of 252 strings)
2018-01-23 12:17:29 +01:00
ButterflyOfFire
77b1413319 Translated using Weblate (Arabic)
Currently translated at 100.0% (252 of 252 strings)
2018-01-23 12:17:29 +01:00
Osoitz
87b8d60c9d Translated using Weblate (Basque)
Currently translated at 100.0% (252 of 252 strings)
2018-01-23 12:17:26 +01:00
Christian Schabesberger
db5203e1ff Merge pull request #1023 from TobiGr/update-screenshots
Update screenshots
2018-01-23 00:27:28 +01:00
TobiGr
ea022670c4 Update screenshots 2018-01-22 21:04:27 +01:00
r2308145
54b009cc49 Translated using Weblate (Czech)
Currently translated at 100.0% (252 of 252 strings)
2018-01-22 19:09:37 +01:00
E T
80c3acace9 Translated using Weblate (Turkish)
Currently translated at 100.0% (252 of 252 strings)
2018-01-21 17:40:52 +01:00
Nathan Follens
cea9428b47 Translated using Weblate (Dutch)
Currently translated at 100.0% (252 of 252 strings)
2018-01-21 17:34:51 +01:00
Emanuele Petriglia
f8ffbfabbe Translated using Weblate (Italian)
Currently translated at 100.0% (252 of 252 strings)
2018-01-21 12:12:04 +01:00
Allan Nordhøy
836a1e652b Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (252 of 252 strings)
2018-01-20 23:27:15 +01:00
Freddy Morán Jr
d8544e0b84 Translated using Weblate (Spanish)
Currently translated at 100.0% (252 of 252 strings)
2018-01-20 23:06:06 +01:00
M2ck
4817d7fddc Translated using Weblate (French)
Currently translated at 100.0% (252 of 252 strings)
2018-01-20 23:00:00 +01:00
ButterflyOfFire
e6a385a85e Translated using Weblate (Arabic)
Currently translated at 100.0% (252 of 252 strings)
2018-01-20 21:34:50 +01:00
Weblate
3634f68364 Merge remote-tracking branch 'origin/dev' into dev 2018-01-20 21:31:01 +01:00
Freddy Morán Jr
f17ffa94fe Translated using Weblate (Spanish)
Currently translated at 100.0% (249 of 249 strings)
2018-01-20 21:31:00 +01:00
Coffeemaker
d52bcd46a1 Translated using Weblate (German)
Currently translated at 97.5% (243 of 249 strings)
2018-01-20 21:30:56 +01:00
ButterflyOfFire
7e58b0b6fe Translated using Weblate (Arabic)
Currently translated at 100.0% (249 of 249 strings)
2018-01-20 21:30:49 +01:00
Christian Schabesberger
f451f1f65d resolve conflict 2018-01-20 18:06:34 +01:00
Christian Schabesberger
4d12e71fba move on to v0.11.5 2018-01-20 16:00:39 +01:00
Christian Schabesberger
66fde7a212 fix loop in channel 2018-01-20 15:59:34 +01:00
Christian Schabesberger
86eccf219d refactore some more lambda functions 2018-01-20 15:24:45 +01:00
Christian Schabesberger
677865f347 player popup menu icons for white theme 2018-01-20 14:46:10 +01:00
Christian Schabesberger
eb4b3810e9 refactor ExtractionHelper using lambda expression 2018-01-20 13:57:31 +01:00
Christian Schabesberger
5f26501ddf fix triangle play button when returning to app 2018-01-20 13:39:06 +01:00
Emanuele Petriglia
b33a72f864 Translated using Weblate (Italian)
Currently translated at 100.0% (249 of 249 strings)
2018-01-19 20:07:50 +01:00
Freddy Morán Jr
675f43b968 Translated using Weblate (Spanish)
Currently translated at 100.0% (249 of 249 strings)
2018-01-19 01:24:52 +01:00
M2ck
9f117a2e59 Translated using Weblate (French)
Currently translated at 100.0% (249 of 249 strings)
2018-01-19 00:33:40 +01:00
Yann Hodiesne
a0844229a3 Translated using Weblate (French)
Currently translated at 97.9% (244 of 249 strings)
2018-01-19 00:23:06 +01:00
M2ck
114fcc144c Translated using Weblate (French)
Currently translated at 97.9% (244 of 249 strings)
2018-01-19 00:19:11 +01:00
Weblate
43372ff648 Merge remote-tracking branch 'origin/dev' into dev 2018-01-19 00:17:53 +01:00
ScratchBuild
b8ebbc5404 Translated using Weblate (Japanese)
Currently translated at 79.5% (194 of 244 strings)
2018-01-19 00:17:51 +01:00
M2ck
28309f82f3 Translated using Weblate (French)
Currently translated at 100.0% (244 of 244 strings)
2018-01-19 00:17:45 +01:00
Christian Schabesberger
c70fa391b6 fix conflict with weblate 2018-01-18 20:43:30 +01:00
nautilusx
caab589dce Translated using Weblate (German)
Currently translated at 99.5% (243 of 244 strings)
2018-01-18 08:18:08 +01:00
maruyuki
20370054e7 Translated using Weblate (Japanese)
Currently translated at 56.9% (139 of 244 strings)
2018-01-18 08:18:04 +01:00
f1882cb1e1 Translated using Weblate (German)
Currently translated at 97.5% (238 of 244 strings)
2018-01-12 19:37:15 +01:00
Nick Undnick
2ed149852d Translated using Weblate (German)
Currently translated at 97.5% (238 of 244 strings)
2018-01-10 18:22:38 +01:00
Coffeemaker
ac7226a0df Translated using Weblate (German)
Currently translated at 97.5% (238 of 244 strings)
2018-01-10 18:22:00 +01:00
Coffeemaker
5705650ca8 Translated using Weblate (Telugu)
Currently translated at 53.6% (131 of 244 strings)
2018-01-09 17:53:16 +01:00
Coffeemaker
60855ca7c5 Translated using Weblate (Tamil)
Currently translated at 5.7% (14 of 244 strings)
2018-01-09 17:53:16 +01:00
Coffeemaker
b8d8d181f3 Translated using Weblate (Slovak)
Currently translated at 70.0% (171 of 244 strings)
2018-01-09 17:53:16 +01:00
Thomas Lavend'Homme
6309160fc6 Translated using Weblate (French)
Currently translated at 100.0% (244 of 244 strings)
2018-01-09 17:53:14 +01:00
ButterflyOfFire
fc0e6ed273 Translated using Weblate (Arabic)
Currently translated at 100.0% (244 of 244 strings)
2018-01-09 17:53:04 +01:00
Christian Schabesberger
ada0cee656 move on to v0.11.4 2018-01-09 14:11:37 +01:00
Christian Schabesberger
6dde524d2c Merge branch 'dev' of github.com:teamnewpipe/NewPipe into dev 2018-01-09 14:02:39 +01:00
Christian Schabesberger
6a631e1915 forece select activity when opening with browser 2018-01-09 14:02:32 +01:00
Christian Schabesberger
bb6fa343cf Merge pull request #976 from coffeemakr/feature-share-subject
Add subject to shared URL's (fixes #975)
2018-01-09 13:53:29 +01:00
Christian Schabesberger
7557acde6c Merge pull request #977 from TobiGr/TranslationChecker-Fix
Fix crash due to no "other" item in plurals
2018-01-09 13:51:14 +01:00
TobiGr
25ed8952f9 Fix crash due to no "other" item in plurals 2018-01-09 13:24:50 +01:00
Coffeemakr
b93d94b0bd Add subject to shared URL's (fixes #975) 2018-01-09 12:41:30 +01:00
Christian Schabesberger
33d75fd2fb switch languageCode to content country 2018-01-09 12:25:40 +01:00
Christian Schabesberger
28a9855fd2 add countrycodes 2018-01-09 11:33:17 +01:00
Christian Schabesberger
9aad07621c fix many/other problem 2018-01-09 10:28:28 +01:00
Christian Schabesberger
384cde6eaa fix weblate merge failure 2018-01-08 13:29:25 +01:00
Sebastian Rasmussen
5ae98661ad Translated using Weblate (Swedish)
Currently translated at 100.0% (244 of 244 strings)
2018-01-08 13:21:19 +01:00
Alberto Moshpirit
8f4d9ceca9 Translated using Weblate (Spanish)
Currently translated at 100.0% (244 of 244 strings)
2018-01-08 13:21:15 +01:00
Christian Schabesberger
83d9a1233e horrible hack for fixing channel load next page foo 2018-01-06 20:39:33 +01:00
thami simo
522a287d79 Translated using Weblate (Arabic)
Currently translated at 96.3% (235 of 244 strings)
2018-01-06 17:21:06 +01:00
Sebastian Rasmussen
e052d4660d Translated using Weblate (Swedish)
Currently translated at 100.0% (244 of 244 strings)
2018-01-06 17:21:03 +01:00
John Zhen Mo
39b0b2f032 -Added player conversion to background and popup players.
-[#919] Fixed custom notification does not trigger unlocking on lockscreen.
-[#947] Fixes player crashing on internet outage, issue partially addressed.
-Fixed main player losing state after destroy while in background.
-Fixed main player controls not hiding automatically after orientation change.
-Fixed dialog uploader not marqueeing when too long.
-Fixed popup permission throwing NPE on BaseList.
-Refactored popup permissions to start in NavigationHelper.
-Extracted hardcoded string for player menus.
-Bump Java version to 1.8.
-Some lambda conversions.
2018-01-03 22:53:38 -08:00
Christian Schabesberger
0223d6d200 moved on to v0.11.3 2018-01-04 05:34:19 +01:00
Christian Schabesberger
808ce72078 fix share menu for playlists 2018-01-04 05:28:01 +01:00
Christian Schabesberger
3a84c47176 hopefully fix plurals 2018-01-04 04:56:45 +01:00
Weblate
20c2426128 Merge remote-tracking branch 'origin/dev' into dev 2017-12-30 19:06:47 +01:00
Phạm Nguyễn Hoàng
bf11d4c9fa Translated using Weblate (Vietnamese)
Currently translated at 59.0% (144 of 244 strings)
2017-12-30 19:06:47 +01:00
Matej U
783c0f79d7 Translated using Weblate (Slovenian)
Currently translated at 91.8% (224 of 244 strings)
2017-12-30 19:06:46 +01:00
Ivan Krušlin
98b94bd9c4 Translated using Weblate (Croatian)
Currently translated at 97.1% (237 of 244 strings)
2017-12-30 19:06:44 +01:00
Rex_sa
ff0178f965 Translated using Weblate (Arabic)
Currently translated at 91.8% (224 of 244 strings)
2017-12-30 19:06:41 +01:00
Schabi
7f88c3d0a9 roleback wrong fix 2017-12-30 00:27:29 +01:00
Schabi
11e8e38f2c setup context for making notifications open via setContextIntent() 2017-12-29 17:29:15 +01:00
Schabi
50c5314eaf fix yt trending content language 2017-12-29 15:02:23 +01:00
Schabi
a7a76d4f58 add beta 2 logo 2017-12-26 14:48:06 +01:00
Schabi
4df4f68fe1 Merge branch 'dev' 2017-12-24 12:17:55 +01:00
Schabi
7db8d37137 update extractor and move on to version v0.11.2 2017-12-24 12:17:41 +01:00
Christian Schabesberger
b7503a7d81 Merge pull request #936 from TobiGr/readme-website
Add website, blog and press kit to README.MD
2017-12-23 17:47:59 +01:00
Schabi
9f5d4034e3 fix fullscreen button on popup 2017-12-23 17:34:47 +01:00
Tobias Groza
3c941f6c4b Add website, blog and press kit to README.MD 2017-12-23 16:35:40 +01:00
Christian Schabesberger
ec8fff421a fix avatar not vissible on info screen bug 2017-12-22 16:53:43 +01:00
Christian Schabesberger
a47a0b5432 Merge pull request #904 from Jawnnypoo/tweaks
Pull up support lib version so all always match
2017-12-22 15:25:33 +01:00
Christian Schabesberger
96ba46f21d Merge pull request #931 from TobiGr/notification-icon
Change notification bar icon for popup and background player
2017-12-21 16:03:15 +01:00
Ariel Shulman
90f48d5817 Translated using Weblate (Hebrew)
Currently translated at 100.0% (244 of 244 strings)
2017-12-20 23:47:19 +01:00
Jonas
7288dd097a Translated using Weblate (Spanish)
Currently translated at 100.0% (244 of 244 strings)
2017-12-20 17:02:16 +01:00
TobiGr
0119d62a35 change notification bar icon for popup and background player 2017-12-20 16:29:32 +01:00
Kompiuterių meistras +37060040
4d6ab73fa9 Translated using Weblate (Lithuanian)
Currently translated at 100.0% (244 of 244 strings)
2017-12-20 13:06:09 +01:00
f58c95840a Translated using Weblate (Lithuanian)
Currently translated at 60.6% (148 of 244 strings)
2017-12-20 11:04:43 +01:00
Weblate
ecf24f81ec Merge remote-tracking branch 'origin/dev' into dev 2017-12-18 14:15:55 +01:00
Rex_sa
dd3306a940 Translated using Weblate (Arabic)
Currently translated at 91.3% (223 of 244 strings)
2017-12-18 14:15:52 +01:00
Claudio Maradonna
bbb2b98f27 Translated using Weblate (Italian)
Currently translated at 100.0% (244 of 244 strings)
2017-12-18 14:15:32 +01:00
Christian Schabesberger
bc7332780d fix show rending before page was loaded 2017-12-17 12:55:30 +01:00
Joseph Kim
6f3bc3ac8f Translated using Weblate (Korean)
Currently translated at 99.5% (243 of 244 strings)
2017-12-14 11:48:42 +01:00
Weblate
cd04d869b7 Merge remote-tracking branch 'origin/dev' into dev 2017-12-12 15:49:41 +01:00
Eduardo Caron
909e15cbdd Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (244 of 244 strings)
2017-12-12 15:49:31 +01:00
Christian Schabesberger
33fa30ab78 Merge branch 'feature-small-code-improvements' of https://github.com/coffeemakr/NewPipe into coffe 2017-12-11 18:06:38 +01:00
Anthony MARGERAND
44933ac17a Translated using Weblate (French)
Currently translated at 100.0% (244 of 244 strings)
2017-12-10 16:35:06 +01:00
Coffeemakr
bb2af96deb Use getters for extractor items 2017-12-10 11:07:51 +01:00
Coffeemakr
1fe6da14ea Small code improvements 2017-12-10 11:07:08 +01:00
Weblate
b7b9653c21 Merge remote-tracking branch 'origin/dev' into dev 2017-12-09 18:07:20 +01:00
alekksander
8adc5918f8 Translated using Weblate (Polish)
Currently translated at 100.0% (244 of 244 strings)
2017-12-09 18:07:19 +01:00
Tobias Groza
0db593b1bb Translated using Weblate (German)
Currently translated at 100.0% (244 of 244 strings)
2017-12-09 18:07:10 +01:00
Christian Schabesberger
a0c9dbeb78 Merge pull request #910 from coffeemakr/bugfix-integrate-extractor
Fix failing test
2017-12-09 17:36:13 +01:00
Nathan Follens
61d5546d89 Translated using Weblate (Dutch)
Currently translated at 100.0% (244 of 244 strings)
2017-12-09 14:30:18 +01:00
alekksander
f97b7c943b Translated using Weblate (Polish)
Currently translated at 100.0% (244 of 244 strings)
2017-12-09 13:58:19 +01:00
Duppadaadadii
97549b633b Translated using Weblate (Finnish)
Currently translated at 100.0% (244 of 244 strings)
2017-12-09 10:54:55 +01:00
Sérgio Marques
1949e4a9d4 Translated using Weblate (Portuguese)
Currently translated at 93.4% (228 of 244 strings)
2017-12-09 02:49:06 +01:00
Coffeemakr
ecb5f7a5ba Try Travis' solution 2017-12-08 20:26:44 +01:00
Emanuele Petriglia
6021f72cf0 Translated using Weblate (Italian)
Currently translated at 99.1% (242 of 244 strings)
2017-12-08 16:49:42 +01:00
nailyk
c00ef74f96 Translated using Weblate (French)
Currently translated at 99.5% (243 of 244 strings)
2017-12-08 14:46:23 +01:00
Coffeemakr
962e070150 Use Mediaformat objects instead of ids for tests 2017-12-08 13:57:44 +01:00
r2308145
221cbf5e07 Translated using Weblate (Czech)
Currently translated at 100.0% (244 of 244 strings)
2017-12-08 13:45:20 +01:00
Weblate
a72d37ab69 Merge remote-tracking branch 'origin/dev' into dev 2017-12-07 12:40:55 +01:00
E T
f92227e5df Translated using Weblate (Turkish)
Currently translated at 100.0% (244 of 244 strings)
2017-12-07 12:40:54 +01:00
Eduardo Caron
c50617452f Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (244 of 244 strings)
2017-12-07 12:40:52 +01:00
Allan Nordhøy
3957eca94d Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (244 of 244 strings)
2017-12-07 12:40:51 +01:00
Tobias Groza
70111cf614 Translated using Weblate (German)
Currently translated at 99.5% (243 of 244 strings)
2017-12-07 12:40:49 +01:00
r2308145
f9e03c9a40 Translated using Weblate (Czech)
Currently translated at 100.0% (244 of 244 strings)
2017-12-07 12:40:45 +01:00
Christian Schabesberger
88fbdf1cc4 Merge branch 'dev' of github.com:teamnewpipe/NewPipe into dev 2017-12-07 11:36:05 +01:00
Christian Schabesberger
7b76bd79e8 fix swaped name/url for channel 2017-12-07 11:33:34 +01:00
Allan Nordhøy
f4b58e649d Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (244 of 244 strings)
2017-12-07 01:27:39 +01:00
John
91ff301d53 Pull up support lib version so all always match 2017-12-06 18:19:56 -06:00
Eduardo Caron
f5e1c99259 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (244 of 244 strings)
2017-12-07 01:12:44 +01:00
Enol P
abdcd3cc30 Translated using Weblate (Asturian)
Currently translated at 100.0% (244 of 244 strings)
2017-12-07 00:57:40 +01:00
Weblate
23615a39ac Merge remote-tracking branch 'origin/dev' into dev 2017-12-07 00:52:53 +01:00
r2308145
5fa5fc39fc Translated using Weblate (Czech)
Currently translated at 100.0% (239 of 239 strings)
2017-12-07 00:52:50 +01:00
Enol P
01b3c7e91b Translated using Weblate (Asturian)
Currently translated at 100.0% (239 of 239 strings)
2017-12-07 00:52:42 +01:00
Christian Schabesberger
e4e364af3f move on to v0.11.1 2017-12-06 14:49:48 +01:00
Christian Schabesberger
f2358692af update to latest newpipeextractor 2017-12-06 14:29:26 +01:00
TobiGr
26ed6299e3 - add donation hint and website to about activity
- move NewPipe's license to license tab
2017-12-05 17:07:31 +01:00
Christian Schabesberger
3a85187111 Merge pull request #895 from TeamNewPipe/remember
remember last screen orientation
2017-12-05 12:59:00 +01:00
Christian Schabesberger
4261a2eed3 remember last screen orientation 2017-12-05 12:51:23 +01:00
antfarmer
fef17163a9 Translated using Weblate (Bulgarian)
Currently translated at 76.1% (182 of 239 strings)
2017-12-01 11:44:51 +01:00
Deva
9dd447a14f Translated using Weblate (Tamil)
Currently translated at 5.8% (14 of 239 strings)
2017-11-27 15:50:18 +01:00
r2308145
ecf4407ba4 Translated using Weblate (Czech)
Currently translated at 100.0% (239 of 239 strings)
2017-11-27 13:42:16 +01:00
antfarmer
039c0d3ee6 Translated using Weblate (Bulgarian)
Currently translated at 45.6% (109 of 239 strings)
2017-11-25 19:44:55 +01:00
antfarmer
5808aead55 Added translation using Weblate (Bulgarian) 2017-11-23 21:00:38 +01:00
Eduardo Caron
e797e2e7f1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (239 of 239 strings)
2017-11-23 20:58:37 +01:00
Weblate
23cacbfe65 Merge remote-tracking branch 'origin/dev' into dev 2017-11-23 19:46:19 +01:00
Massedil
4f94ee9b72 Translated using Weblate (French)
Currently translated at 100.0% (239 of 239 strings)

https://github.com/TeamNewPipe/NewPipe/issues/852
2017-11-23 19:46:16 +01:00
TheAssassin
0d1a26298a Recreate LICENSE
git revert screwed up so there was no LICENSE file at all any more, hence we have to recreate it.
2017-11-21 21:18:33 +01:00
TheAssassin
8ccd0b23e9 Revert "Create LICENSE"
This reverts commit d273f69852.
2017-11-21 21:14:52 +01:00
TheAssassin
9d8b991354 Revert "Rename LICENSE to LICENSE.txt"
This reverts commit eee3ccafc3.
2017-11-21 21:14:40 +01:00
TheAssassin
d273f69852 Create LICENSE 2017-11-21 16:06:47 +01:00
TheAssassin
eee3ccafc3 Rename LICENSE to LICENSE.txt
Should satisfy GitHub's license detection.
See https://help.github.com/articles/licensing-a-repository/ for details.
2017-11-21 15:15:07 +01:00
Allan Nordhøy
3686e90e81 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (239 of 239 strings)
2017-11-21 05:26:38 +01:00
wb9688
f5f8371865 Remove setting 2017-11-20 19:31:33 +01:00
wb9688
1191455d37 Show selected service 2017-11-20 16:04:18 +01:00
Andrey mm
1a5d9da2bf Translated using Weblate (Russian)
Currently translated at 99.5% (238 of 239 strings)
2017-11-20 04:49:02 +01:00
wb9688
011e151c91 Merge branch 'dev' into multiple-services 2017-11-19 17:41:57 +01:00
wb9688
54aa40eac1 Add simple drawer for selecting service 2017-11-19 17:21:46 +01:00
Andrey mm
21a7a73f6d Translated using Weblate (Russian)
Currently translated at 93.7% (224 of 239 strings)

changed according to format of "default audio" title
2017-11-18 08:40:20 +01:00
Andrey mm
5090b41eef Translated using Weblate (Russian)
Currently translated at 93.7% (224 of 239 strings)
2017-11-18 08:28:56 +01:00
ktln
0e55aa6249 Translated using Weblate (Russian)
Currently translated at 93.7% (224 of 239 strings)
2017-11-18 06:28:54 +01:00
Andrey mm
dd2dcf4df2 Translated using Weblate (Russian)
Currently translated at 93.7% (224 of 239 strings)
2017-11-18 06:28:20 +01:00
Oscar Hemelaar
2e84b28998 Translated using Weblate (French)
Currently translated at 100.0% (239 of 239 strings)
2017-11-17 22:46:13 +01:00
Oscar Hemelaar
e6cbfea5a7 Translated using Weblate (Spanish)
Currently translated at 100.0% (239 of 239 strings)
2017-11-16 22:05:00 +01:00
Weblate
641d662944 Merge remote-tracking branch 'origin/dev' into dev 2017-11-16 22:01:48 +01:00
Oscar Hemelaar
09208e183b Translated using Weblate (French)
Currently translated at 100.0% (239 of 239 strings)
2017-11-16 22:01:43 +01:00
Christian Schabesberger
fba3ece688 resolve conflict 2017-11-16 12:20:03 +01:00
Christian Schabesberger
f7d849a3cc Merge branch 'expiring_lru' of https://github.com/karyogamy/NewPipe into info 2017-11-16 11:55:39 +01:00
Allan Nordhøy
709c700cc6 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.5% (238 of 239 strings)
2017-11-16 07:48:32 +01:00
John Zhen Mo
2da411c1ec -#836: Modified notifications texts to use custom colors instead of device-specific styles. 2017-11-15 20:42:05 -08:00
John Zhen Mo
d6e4f3c809 -Saves the recovery timestamp when player source fails during a valid stream playback. 2017-11-15 20:09:38 -08:00
Freddy Morán Jr
1bb08db8ba Translated using Weblate (Spanish)
Currently translated at 98.7% (236 of 239 strings)
2017-11-15 19:49:38 +01:00
Daria Szatan
2ba116b1e6 Translated using Weblate (Polish)
Currently translated at 69.8% (167 of 239 strings)
2017-11-15 16:48:24 +01:00
lolloCreator
7088de0fb9 Translated using Weblate (Italian)
Currently translated at 100.0% (239 of 239 strings)
2017-11-15 16:47:12 +01:00
Duppadaadadii
7084f73d6c Translated using Weblate (Finnish)
Currently translated at 100.0% (239 of 239 strings)
2017-11-15 13:45:56 +01:00
John Zhen Mo
69374e25fe -Fixed cursor position to point to end after search text insert.
-Reduced and slightly changed offset of search text insert image.
2017-11-14 15:58:13 -08:00
John Zhen Mo
5e16969d61 -Increased search item insert arrow click space. 2017-11-14 11:34:17 -08:00
John Zhen Mo
0fe5a44e5a -Removed todo for timeout feature on info cache. 2017-11-14 11:34:16 -08:00
John Zhen Mo
98e617001d -Added search suggestion insert per #840. 2017-11-14 11:34:16 -08:00
John Zhen Mo
979bd09b29 -Fixed popup player not in foreground when opened by external intent.
-Fixed popup overlay permission causing exception when opened externally.
2017-11-14 11:34:15 -08:00
John Zhen Mo
77678b8f31 -Modified InfoItem LRU cache expire to allow expiration (current default 4 hours).
-Modified info type display on InfoItemDialog to show uploader name if exists.
2017-11-14 11:34:15 -08:00
lolloCreator
3575cac9d7 Translated using Weblate (Italian)
Currently translated at 100.0% (239 of 239 strings)
2017-11-14 16:40:51 +01:00
Weblate
8cdeaf1b27 Merge remote-tracking branch 'origin/dev' into dev 2017-11-14 13:23:41 +01:00
E T
08a8c6c414 Translated using Weblate (Turkish)
Currently translated at 100.0% (239 of 239 strings)
2017-11-14 13:23:40 +01:00
CookieCaptain D
fa5c1b22ae Translated using Weblate (Portuguese)
Currently translated at 95.8% (229 of 239 strings)
2017-11-14 13:23:34 +01:00
vipul jurel
a99e7f3288 Translated using Weblate (Hindi)
Currently translated at 100.0% (239 of 239 strings)
2017-11-14 13:23:32 +01:00
Duppadaadadii
a29506ed2f Translated using Weblate (Finnish)
Currently translated at 100.0% (239 of 239 strings)
2017-11-14 13:23:16 +01:00
Christian Schabesberger
1434b40d03 fix not compilling because of icepick 2017-11-14 12:14:01 +01:00
marin
6d6609187b Add dropdown menu and switch to background option 2017-11-13 20:29:54 +01:00
9682eaae2a Translated using Weblate (Portuguese)
Currently translated at 92.4% (221 of 239 strings)
2017-11-13 19:48:08 +01:00
1cdb4ccc17 Translated using Weblate (Portuguese)
Currently translated at 90.7% (217 of 239 strings)
2017-11-13 19:47:03 +01:00
CookieCaptain D
2a878dffbc Translated using Weblate (Portuguese)
Currently translated at 90.3% (216 of 239 strings)
2017-11-13 19:46:47 +01:00
49f4fb7ed7 Translated using Weblate (Portuguese)
Currently translated at 89.9% (215 of 239 strings)
2017-11-13 19:46:31 +01:00
CookieCaptain D
caa985660a Translated using Weblate (Portuguese)
Currently translated at 89.1% (213 of 239 strings)
2017-11-13 19:45:46 +01:00
Weblate
3842a1e4fb Merge remote-tracking branch 'origin/dev' into dev 2017-11-13 18:41:49 +01:00
Matej U
e06d83cb93 Translated using Weblate (Slovenian)
Currently translated at 92.8% (222 of 239 strings)
2017-11-13 18:41:48 +01:00
Morten R
67df894448 Translated using Weblate (German)
Currently translated at 100.0% (239 of 239 strings)
2017-11-13 18:41:45 +01:00
Charles de Lacombe
d1ec6cf21b Translated using Weblate (French)
Currently translated at 94.5% (226 of 239 strings)
2017-11-13 18:41:43 +01:00
Enol P
537c561cee Translated using Weblate (Asturian)
Currently translated at 94.9% (227 of 239 strings)
2017-11-13 18:41:42 +01:00
Nathan Follens
a65ddc5b36 Translated using Weblate (Dutch)
Currently translated at 100.0% (239 of 239 strings)
2017-11-13 18:41:40 +01:00
Christian Schabesberger
1fb1b3a784 Merge branch 'dev' of github.com:teamnewpipe/NewPipe into dev 2017-11-12 23:58:29 +01:00
Christian Schabesberger
b80879765c Merge branch 'dev' of https://github.com/lawonga/NewPipe into fix 2017-11-12 23:58:08 +01:00
Weblate
10919fe15b Merge remote-tracking branch 'origin/dev' into dev 2017-11-12 21:52:51 +01:00
vipul jurel
9e5fe1edca Translated using Weblate (Hindi)
Currently translated at 100.0% (239 of 239 strings)
2017-11-12 21:52:50 +01:00
Mathias Norräng
03ee3f3d2a Translated using Weblate (Swedish)
Currently translated at 100.0% (239 of 239 strings)
2017-11-12 21:52:38 +01:00
Christian Schabesberger
4113283069 Merge pull request #833 from TeamNewPipe/fastlane
[WIP] add fastlane support
2017-11-12 21:34:10 +01:00
Mathias Norräng
56c5f696df Translated using Weblate (Swedish)
Currently translated at 61.0% (146 of 239 strings)
2017-11-12 21:12:20 +01:00
Weblate
9beb76e641 Merge remote-tracking branch 'origin/dev' into dev 2017-11-12 18:48:19 +01:00
YFdyh000
f71403be58 Translated using Weblate (Chinese (Simplified))
Currently translated at 87.4% (209 of 239 strings)
2017-11-12 18:48:16 +01:00
vipul jurel
d0e5d36b1b Translated using Weblate (Hindi)
Currently translated at 100.0% (239 of 239 strings)
2017-11-12 18:48:05 +01:00
Christian Schabesberger
fdeb7543ca fix channel rssbutton null pointer bug 2017-11-12 13:59:11 +01:00
Weblate
90716f4f5b Merge remote-tracking branch 'origin/dev' into dev 2017-11-12 13:22:06 +01:00
Ivan Krušlin
54d41bc288 Translated using Weblate (Croatian)
Currently translated at 100.0% (233 of 233 strings)
2017-11-12 13:22:05 +01:00
drgsh
d63c7a9042 Translated using Weblate (Hindi)
Currently translated at 25.7% (60 of 233 strings)
2017-11-12 13:22:01 +01:00
Christian Schabesberger
cd5b60cbed fix layout 2017-11-12 09:16:51 +01:00
Christian Schabesberger
6d60e6698a Merge branch 'play-queue-enhancement' of https://github.com/karyogamy/NewPipe into pqe 2017-11-12 08:33:05 +01:00
Christian Schabesberger
d53cb01396 add fastlane support
fix screenthots link

add description
2017-11-12 08:31:09 +01:00
lawonga
8baaecab1b #827 Fixes auto rotation bug with useAsFrontPage on by preserving the useAsFrontPage boolean setting 2017-11-11 19:51:20 -08:00
John Zhen Mo
1368f9f89e -Fixed channel header contrast color. 2017-11-11 15:05:48 -08:00
John Zhen Mo
ce36f3ae3b -Modified play queue action buttons on playlist and channel fragments.
-Added info item menu with custom title banner on long click.
-Enabled starting playlists and channels from middle.
-Enabled caching for play queue item thumbnails.
-Modified play queue to revert to first item on deleting last item.
2017-11-11 14:48:16 -08:00
John Zhen Mo
cf147aa161 -Reverted dropdown menu UI for list view info items. 2017-11-11 14:48:16 -08:00
John Zhen Mo
7700cff5e5 -Added play buttons to channel fragment similar to playlist fragment.
-Fixed abstract info play queue reloading the same initial page.
-Fixed OOB on get item for abstract play queue.
2017-11-11 14:48:16 -08:00
John Zhen Mo
b883f313ba -Fixed NPE when popup is updated during shutdown. 2017-11-11 14:48:16 -08:00
John Zhen Mo
b1ee22cde6 -Added scroll to fetch for external play queues.
-Modified service player activity scrolling to be instantaneous when difference is too large.
-Modified service player activity to no longer update metadata if nothing change when sync is called.
2017-11-11 14:48:16 -08:00
John Zhen Mo
b32f149a1b -Refactored Channel and Playlist PlayQueue into AbstractInfo playQueue.
-Increase list item action dropdown padding.
2017-11-11 14:48:16 -08:00
John Zhen Mo
1d136c6c35 -Added fast seeking on background notification when play queue size is 1.
-Fixed player intent with quality selection not used in detail fragment.
-Fixed window index not reset on sync when not playing.
-Fix dropdown play string for stream info item.
2017-11-11 14:48:16 -08:00
John Zhen Mo
b8a17580c5 -Added play queue dropdown to channel info items.
-Added play queue dropdown to playlist info items.
-Added Channel Play Queue.
-Renamed External Play Queue to Playlist Play Queue.
-Modified Playlist Play Queue to allow loading from initial state.
2017-11-11 14:48:16 -08:00
John Zhen Mo
87febf8679 -Added dropdown to start play for all StreamInfoItem.
-Refactored NavigationHelper to allow service player control to open anywhere.
-Refactored NavigationHelper to allow starting player at anywhere.
2017-11-11 14:48:16 -08:00
John Zhen Mo
38b2ffd450 -Added fling to toss popup view when velocity is below shutdown.
-Added string for unknown content.
-Fixed NPE when UI element is touched after player shuts down in service activity.
-Fixed shuffle reset caused by position discontinuity offsets index.
-Moved some more player shared preferences to PlayerHelper.
2017-11-11 14:48:16 -08:00
John Zhen Mo
01e031e7e7 -Modified recovery to not set if progress position is 0 or less.
-Modified queue item synchronization to no longer trigger update when the sync is run on the identical item.
2017-11-11 14:48:16 -08:00
John Zhen Mo
0b1eda3050 -Improved null checks in player stream resolution.
-Improved naming in MediaSourceManager
2017-11-11 14:48:16 -08:00
Weblate
52cdf96dfe Merge remote-tracking branch 'origin/dev' into dev 2017-11-11 18:55:55 +01:00
nautilusx
1f5eba59c5 Translated using Weblate (German)
Currently translated at 99.5% (232 of 233 strings)
2017-11-11 18:55:51 +01:00
r2308145
372c2f2be0 Translated using Weblate (Czech)
Currently translated at 100.0% (233 of 233 strings)
2017-11-11 18:55:48 +01:00
Ivan Krušlin
25e0b46396 Translated using Weblate (Croatian)
Currently translated at 100.0% (233 of 233 strings)
2017-11-11 18:55:45 +01:00
wb9688
621a1909ec Merge remote-tracking branch 'origin/dev' into multiple-services 2017-11-11 13:18:26 +01:00
Christian Schabesberger
d3332583b6 Merge branch 'dev' of github.com:teamnewpipe/NewPipe into dev 2017-11-10 21:31:39 +01:00
Christian Schabesberger
cb5cf9bb09 update support lib to 27.0.1 2017-11-10 21:31:19 +01:00
Nick Undnick
6cb2c2a84e Translated using Weblate (German)
Currently translated at 99.5% (232 of 233 strings)
2017-11-10 20:54:24 +01:00
nautilusx
633137fd79 Translated using Weblate (German)
Currently translated at 99.5% (232 of 233 strings)
2017-11-10 20:53:22 +01:00
Weblate
9627fdf33f Merge remote-tracking branch 'origin/dev' into dev 2017-11-10 20:46:38 +01:00
Praveen0899
b39366c80a Translated using Weblate (Telugu)
Currently translated at 72.5% (169 of 233 strings)
2017-11-10 20:46:38 +01:00
drgsh
ac01c49666 Translated using Weblate (Hindi)
Currently translated at 23.6% (55 of 233 strings)
2017-11-10 20:46:13 +01:00
r2308145
80d16ea407 Translated using Weblate (Czech)
Currently translated at 100.0% (233 of 233 strings)
2017-11-10 20:46:11 +01:00
Tobias Groza
4f44f26333 Translated using Weblate (German)
Currently translated at 99.5% (232 of 233 strings)
2017-11-10 20:46:08 +01:00
Christian Schabesberger
a21bdb1487 Merge branch 'studio-3' of https://github.com/wb9688/NewPipe into a 2017-11-10 20:45:05 +01:00
Christian Schabesberger
4d6a2f40d3 Merge pull request #830 from TobiGr/fix-ta-language-code
Fix Tamil language code
2017-11-10 19:47:12 +01:00
Nick Undnick
e6773aac0e Translated using Weblate (German)
Currently translated at 99.5% (232 of 233 strings)
2017-11-10 19:46:31 +01:00
TobiGr
997381d0c3 Fix Tamil language code 2017-11-10 16:57:58 +01:00
wb9688
ac53eeb76d Upgrade ACRA 2017-11-10 15:36:13 +01:00
wb9688
a09c8934fc Fix Travis 2017-11-10 15:34:57 +01:00
Ephraim Raj
b4120c39e6 Translated using Weblate (Hindi)
Currently translated at 21.8% (51 of 233 strings)
2017-11-10 13:46:37 +01:00
wb9688
5d6320d925 Upgrade to Studio 3 2017-11-10 10:33:59 +01:00
Xuacu Saturio
985bf50f7f Translated using Weblate (Asturian)
Currently translated at 96.5% (225 of 233 strings)
2017-11-09 22:44:19 +01:00
Allan Nordhøy
84d21af644 Translated using Weblate (Norwegian Bokmål)
Currently translated at 95.7% (223 of 233 strings)
2017-11-09 21:47:47 +01:00
Weblate
267cd99b04 Merge remote-tracking branch 'origin/dev' into dev 2017-11-08 19:49:19 +01:00
r2308145
401960079c Translated using Weblate (Czech)
Currently translated at 100.0% (233 of 233 strings)
2017-11-08 19:49:17 +01:00
Freddy Morán Jr
1fbc8a2850 Translated using Weblate (Spanish)
Currently translated at 98.7% (230 of 233 strings)
2017-11-08 19:49:08 +01:00
Christian Schabesberger
16fe5a94ac Merge pull request #821 from Stokkie64/patch-1
Update CONTRIBUTING.md
2017-11-08 13:02:34 +01:00
Arjen Singels
1c20a4d9eb Update CONTRIBUTING.md
fixed typo.
2017-11-08 13:58:01 +02:00
Christian Schabesberger
3a9f30d954 Merge pull request #812 from imallan/dev
Fixes ClassCastException when getting Bitmap from AdaptiveIcon
2017-11-08 12:55:07 +01:00
Christian Schabesberger
856aacf8ce Merge pull request #819 from TobiGr/fix-czech-plurals
Fix Czech plurals
2017-11-08 12:54:38 +01:00
TobiGr
441b510775 Fix plurals not showing number of views, videos and subscribers
Fix typo in pull request template
2017-11-07 14:48:48 +01:00
Weblate
88a10b5af1 Merge remote-tracking branch 'origin/dev' into dev 2017-11-07 11:44:38 +01:00
Duppadaadadii
65205ace95 Translated using Weblate (Finnish)
Currently translated at 100.0% (233 of 233 strings)
2017-11-07 11:44:37 +01:00
Nathan Follens
1a4ef06ee9 Translated using Weblate (Dutch)
Currently translated at 100.0% (233 of 233 strings)
2017-11-07 11:44:31 +01:00
r2308145
64ac631040 Translated using Weblate (Czech)
Currently translated at 100.0% (233 of 233 strings)
2017-11-07 11:44:30 +01:00
Omar
4e4cabb929 Translated using Weblate (Arabic)
Currently translated at 40.3% (94 of 233 strings)
2017-11-07 11:44:22 +01:00
Christian Schabesberger
b242c86869 add support for indian languages 2017-11-07 11:04:12 +01:00
vesp
d37fee346a Translated using Weblate (Czech)
Currently translated at 100.0% (233 of 233 strings)
2017-11-06 22:41:56 +01:00
r2308145
cc52d3b0af Translated using Weblate (Czech)
Currently translated at 100.0% (233 of 233 strings)
2017-11-06 22:41:07 +01:00
vesp
d5b1bae305 Translated using Weblate (Czech)
Currently translated at 100.0% (233 of 233 strings)
2017-11-06 22:39:44 +01:00
r2308145
04e22faf85 Translated using Weblate (Czech)
Currently translated at 100.0% (233 of 233 strings)
2017-11-06 22:38:51 +01:00
Nathan Follens
48cb3ed138 Translated using Weblate (Dutch)
Currently translated at 100.0% (233 of 233 strings)
2017-11-06 18:24:39 +01:00
wb9688
ebdeee8b3c Translated using Weblate (Dutch)
Currently translated at 100.0% (233 of 233 strings)
2017-11-06 18:21:29 +01:00
6449d7d4ee Translated using Weblate (Dutch)
Currently translated at 100.0% (233 of 233 strings)
2017-11-06 18:20:08 +01:00
yilun
4b775d15a2 Fixes ClassCastException when getting Bitmap from AdaptiveIcon on Android 8+ 2017-11-06 12:27:10 +00:00
wb9688
e4d6a453b0 Translated using Weblate (Dutch)
Currently translated at 100.0% (233 of 233 strings)
2017-11-06 11:44:55 +01:00
9dcbcd57cb Translated using Weblate (Arabic)
Currently translated at 33.4% (78 of 233 strings)
2017-11-06 10:36:44 +01:00
Omar
17aa44c88b Translated using Weblate (Arabic)
Currently translated at 33.4% (78 of 233 strings)
2017-11-06 10:36:32 +01:00
Weblate
60dc266e13 Merge remote-tracking branch 'origin/dev' into dev 2017-11-05 23:03:03 +01:00
Duppadaadadii
60ed308caa Translated using Weblate (Finnish)
Currently translated at 100.0% (233 of 233 strings)
2017-11-05 23:02:59 +01:00
Christian Schabesberger
c485e7e167 merge master 2017-11-05 21:00:05 +01:00
Christian Schabesberger
71a8361580 merge dev 2017-11-05 20:59:17 +01:00
Christian Schabesberger
3d1cc348c8 fix getUploaderName() and move on to v0.10.2 2017-10-31 19:47:46 +01:00
wb9688
25db3c2940 Update NewPipeExtractor 2017-09-16 10:25:54 +02:00
wb9688
442290d7f0 Hide spinner 2017-09-14 11:43:30 +02:00
wb9688
a6eb871f5e Change layout for audio-only streams 2017-09-14 10:49:39 +02:00
wb9688
b500c3f526 Add service setting 2017-09-14 09:31:01 +02:00
225 changed files with 8178 additions and 2088 deletions

View File

@@ -32,7 +32,7 @@ Do not report crashes in the GitHub issue tracker. NewPipe has an automated cras
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR description. Untested code will **not** be merged!
* Try to figure out yourself why builds on our CI fail.
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job, but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the maintainers' jobs way easier.
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again about sumission, or clearly state that in the description of your PR.
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again about submission, or clearly state that in the description of your PR.
* Respond yourselves if someone requests changes or otherwise raises issues about your PRs.
* Check if your contributions align with the [fdroid inclusion guidelines](https://f-droid.org/en/docs/Inclusion_Policy/).
* Check if your submission can be build with the current fdroid build server setup.

View File

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

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@
gradle.properties
*~
.weblate
*.class

View File

@@ -5,11 +5,13 @@ android:
components:
# The BuildTools version used by NewPipe
- tools
- build-tools-26.0.1
- build-tools-27.0.1
# The SDK version used to compile NewPipe
- android-26
- android-27
before_install:
- yes | sdkmanager "platforms;android-27"
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
licenses:

170
CheckTranslations.java Normal file
View File

@@ -0,0 +1,170 @@
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.regex.*;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.nio.file.Files;
import java.nio.charset.Charset;
public final class CheckTranslations {
private static boolean debug = false;
private static boolean plurals = false;
private static boolean empty = false;
private static boolean remove = false;
private static int checks = 0;
private static int matches = 0;
private static int changes = 0;
private static Pattern p, pb, pe, e, o;
/**
* Search translated strings.xml files for empty item / plural tags
* and remove them.
* @param args directories which contain string.xml files (in any subdirectory)
* -e option to find all empty string tags
* -p option to find all empty plurals and item tags
* -r option to remove all occurrences from the files
* -d option to see more details
*/
public static void main(String[] args) {
if (args.length < 1 || (args[0].equals("-d") && args.length < 2)) {
System.out.println("Not enough arguments");
return;
}
for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case "-d":
debug = true;
break;
case "-p":
plurals = true;
break;
case "-e":
empty = true;
break;
case "-r":
remove = true;
break;
}
}
if (!plurals && !empty) {
plurals = true;
empty = true;
}
p = Pattern.compile("(<item quantity=\")(zero|one|two|three|few|many|other)(\"></item>|\"/>)");
pb = Pattern.compile("(<plurals[\\sa-zA-Z=\"]*>)");
pe = Pattern.compile("(</plurals>)");
e = Pattern.compile("(<string[\\sa-z_\\\"=]*)((><\\/string>|\\/>){1})");
o = Pattern.compile("(<item quantity=\"other\">)[^</>]*(<\\/item>)");
for (int i = 0; i < args.length; i++) {
if (!args[i].equals("-d") && !args[i].equals("-p") && !args[i].equals("-e") && !args[i].equals("-r")) {
File f = new File(args[i]);
if (f.exists() && !f.isDirectory()) {
checkFile(f);
} else if (f.isDirectory()) {
checkFiles(f.listFiles());
} else {
System.out.println("'" + args[i] + "' does not exist!");
}
}
}
System.out.println(checks + " files were checked.");
System.out.println(matches + " corrupt lines detected.");
if (remove) {
System.out.println(matches + " corrupt lines removed and " + changes + " lines fixed.");
}
}
private static void checkFiles(File[] f) {
for (int i = 0; i < f.length; i++) {
if (f[i].exists() && !f[i].isDirectory()) {
if (f[i].toString().contains("strings.xml")) {
checkFile(f[i]);
}
} else if (f[i].isDirectory()) {
checkFiles(f[i].listFiles());
}
}
}
private static void checkFile(File f) {
// Do not check our original English strings to cause no unwanted changes
// Btw. there should not be empty plural/item tags
if (f.toString().contains("values/strings.xml")) {
return;
}
if (debug) System.out.println("Checking " + f.toString());
checks++;
List<String> lines = new ArrayList<String>();
boolean checkFailed = false;
boolean otherDetected = false;
boolean inPlurals = false;
try (BufferedReader br = new BufferedReader(new FileReader(f))) {
String line;
int ln = 0;
while ((line = br.readLine()) != null) {
ln++;
if (plurals && p.matcher(line).find()) {
matches++;
if (debug) System.out.println(" Line " + ln + " was " + ((remove) ? "removed" : "detected") + ": '" + line + "'");
checkFailed = true;
} else if (empty && e.matcher(line).find()) {
matches++;
checkFailed = true;
if (debug) System.out.println(" Line " + ln + " was " + ((remove) ? "removed" : "detected") + ": '" + line + "'");
} else {
if (remove) lines.add(line);
}
}
br.close();
int pluralsLine = 0;
for (int i = 0; i < lines.size(); i++) {
if (o.matcher(lines.get(i)).find()) {
otherDetected = true;
}
if (plurals && pb.matcher(lines.get(i)).find()) {
inPlurals = true;
pluralsLine = i;
} else if (plurals && pe.matcher(lines.get(i)).find()) {
inPlurals = false;
if (!otherDetected) {
boolean b = false;
check: for(int j = pluralsLine; j < i; j++) {
if (lines.get(j).contains("many")) {
b = true;
pluralsLine = j;
break check;
}
}
if (remove && b) {
if (debug) System.out.println(" Line " + (pluralsLine + 1) + " was " + ((remove) ? "changed" : "detected") + ": '" + lines.get(pluralsLine) + "'");
lines.set(pluralsLine, lines.get(pluralsLine).replace("many", "other"));
changes++;
checkFailed = true;
} else if (debug) {
if (debug) System.out.println(" WARNING: Line " + (i + 1) + " - No <item quantity=\"other\"> found!");
}
}
otherDetected = false;
}
}
if (remove && checkFailed) {
Files.write(f.toPath(), lines, Charset.forName("UTF-8"));
}
} catch (IOException e) {
System.out.println(e);
}
}
}

View File

@@ -13,21 +13,22 @@
</p>
<hr />
<p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#description">Description</a> &bull; <a href="#features">Features</a> &bull; <a href="#contribution">Contribution</a> &bull; <a href="#donate">Donate</a> &bull; <a href="#license">License</a></p>
<p align="center"><a href="https://newpipe.schabi.org">Website</a> &bull; <a href="https://newpipe.schabi.org/blog/">Blog</a> &bull; <a href="https://newpipe.schabi.org/press/">Press</a></p>
<hr />
WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.
## Screenshots
[<img src="screenshots/shot_1.png" width=160>](screenshots/shot_1.png)
[<img src="screenshots/shot_2.png" width=160>](screenshots/shot_2.png)
[<img src="screenshots/shot_3.png" width=160>](screenshots/shot_3.png)
[<img src="screenshots/shot_4.png" width=160>](screenshots/shot_4.png)
[<img src="screenshots/shot_5.png" width=160>](screenshots/shot_5.png)
[<img src="screenshots/shot_6.png" width=160>](screenshots/shot_6.png)
[<img src="screenshots/shot_7.png" width=160>](screenshots/shot_7.png)
[<img src="screenshots/shot_8.png" width=160>](screenshots/shot_8.png)
[<img src="screenshots/shot_9.png" width=160>](screenshots/shot_9.png)
[<img src="screenshots/shot_10.png" width=160>](screenshots/shot_10.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
## Description

View File

@@ -1,15 +1,15 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion '26.0.1'
compileSdkVersion 27
buildToolsVersion '27.0.1'
defaultConfig {
applicationId "org.schabi.newpipe"
minSdkVersion 15
targetSdkVersion 26
versionCode 41
versionName "0.11.0"
targetSdkVersion 27
versionCode 47
versionName "0.11.6"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
@@ -27,6 +27,10 @@ android {
applicationIdSuffix ".debug"
}
beta {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
applicationIdSuffix ".beta"
}
}
@@ -38,48 +42,51 @@ android {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
ext {
supportLibVersion = '27.0.2'
}
dependencies {
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
exclude module: 'support-annotations'
}
compile 'com.github.TeamNewPipe:NewPipeExtractor:b9d0941'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:7fd21ec08581d'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:1.10.19'
compile 'com.android.support:appcompat-v7:26.0.1'
compile 'com.android.support:support-v4:26.0.1'
compile 'com.android.support:design:26.0.1'
compile 'com.android.support:recyclerview-v7:26.0.1'
compile 'com.android.support:preference-v14:26.0.1'
implementation "com.android.support:appcompat-v7:$supportLibVersion"
implementation "com.android.support:support-v4:$supportLibVersion"
implementation "com.android.support:design:$supportLibVersion"
implementation "com.android.support:recyclerview-v7:$supportLibVersion"
implementation "com.android.support:preference-v14:$supportLibVersion"
compile 'com.google.code.gson:gson:2.7'
compile 'ch.acra:acra:4.9.0'
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'ch.acra:acra:4.9.2'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
compile 'de.hdodenhof:circleimageview:2.1.0'
compile 'com.github.nirhart:parallaxscroll:1.0'
compile 'com.nononsenseapps:filepicker:3.0.1'
compile 'com.google.android.exoplayer:exoplayer:r2.5.4'
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1'
implementation 'com.nononsenseapps:filepicker:3.0.1'
implementation 'com.google.android.exoplayer:exoplayer:r2.5.4'
debugCompile 'com.facebook.stetho:stetho:1.5.0'
debugCompile 'com.facebook.stetho:stetho-urlconnection:1.5.0'
debugCompile 'com.android.support:multidex:1.0.1'
debugImplementation 'com.facebook.stetho:stetho:1.5.0'
debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.5.0'
debugImplementation 'com.android.support:multidex:1.0.2'
compile 'io.reactivex.rxjava2:rxjava:2.1.2'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
implementation 'io.reactivex.rxjava2:rxjava:2.1.7'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
compile 'android.arch.persistence.room:runtime:1.0.0-alpha8'
compile 'android.arch.persistence.room:rxjava2:1.0.0-alpha8'
annotationProcessor 'android.arch.persistence.room:compiler:1.0.0-alpha8'
implementation 'android.arch.persistence.room:runtime:1.0.0'
implementation 'android.arch.persistence.room:rxjava2:1.0.0'
annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
compile 'frankiesardo:icepick:3.2.0'
provided 'frankiesardo:icepick-processor:3.2.0'
implementation 'frankiesardo:icepick:3.2.0'
annotationProcessor 'frankiesardo:icepick-processor:3.2.0'
}

View File

@@ -16,7 +16,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher"
android:theme="@style/DarkTheme"
android:theme="@style/OpeningTheme"
tools:ignore="AllowBackup">
<activity
android:name=".MainActivity"
@@ -31,7 +31,7 @@
<activity
android:name=".player.old.PlayVideoActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/VideoPlayerTheme"
android:theme="@style/OldVideoPlayerTheme"
tools:ignore="UnusedAttribute"/>
<service
@@ -56,8 +56,7 @@
android:name=".player.MainVideoPlayer"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/PlayerTheme"/>
android:launchMode="singleTask"/>
<activity
android:name=".settings.SettingsActivity"
@@ -124,7 +123,7 @@
<activity
android:name=".RouterActivity"
android:taskAffinity=""
android:theme="@android:style/Theme.NoDisplay">
android:theme="@style/RouterActivityThemeDark">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
@@ -175,17 +174,21 @@
</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>
<service
android:name=".RouterPlayerActivity$FetcherService"
android:exported="false"/>
<activity
android:name=".RouterPopupActivity"
android:label="@string/popup_mode_share_menu_title"
android:name=".RouterPlayerActivity"
android:excludeFromRecents="true"
android:label="@string/preferred_player_share_menu_title"
android:taskAffinity=""
android:theme="@android:style/Theme.NoDisplay">
android:theme="@style/RouterActivityThemeDark">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
@@ -204,6 +207,11 @@
<data android:pathPrefix="/embed/"/>
<data android:pathPrefix="/watch"/>
<data android:pathPrefix="/attribution_link"/>
<!-- channel prefix -->
<data android:pathPrefix="/channel/"/>
<data android:pathPrefix="/user/"/>
<!-- playlist prefix -->
<data android:pathPrefix="/playlist"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>

View File

@@ -1,9 +1,12 @@
package org.schabi.newpipe;
import android.app.AlarmManager;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
@@ -116,7 +119,6 @@ public class App extends Application {
});
}
private void initACRA() {
try {
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
@@ -149,4 +151,5 @@ public class App extends Application {
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannel(mChannel);
}
}

View File

@@ -77,17 +77,6 @@ public abstract class BaseFragment extends Fragment {
protected void initListeners() {
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected final int resolveResourceIdFromAttr(@AttrRes int attr) {
TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{attr});
int attributeResourceId = a.getResourceId(0, 0);
a.recycle();
return attributeResourceId;
}
/*//////////////////////////////////////////////////////////////////////////
// DisplayImageOptions default configurations
//////////////////////////////////////////////////////////////////////////*/

View File

@@ -28,8 +28,12 @@ import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
@@ -50,11 +54,13 @@ import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.history.HistoryListener;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.ThemeHelper;
@@ -67,8 +73,10 @@ import io.reactivex.subjects.PublishSubject;
public class MainActivity extends AppCompatActivity implements HistoryListener {
private static final String TAG = "MainActivity";
public static final boolean DEBUG = false;
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
private SharedPreferences sharedPreferences;
private ActionBarDrawerToggle toggle = null;
/*//////////////////////////////////////////////////////////////////////////
// Activity's LifeCycle
@@ -77,7 +85,10 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
ThemeHelper.setTheme(this);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
@@ -85,13 +96,53 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
initFragments();
}
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
setSupportActionBar(findViewById(R.id.toolbar));
setupDrawer();
initHistory();
}
private void setupDrawer() {
final Toolbar toolbar = findViewById(R.id.toolbar);
final DrawerLayout drawer = findViewById(R.id.drawer_layout);
final NavigationView drawerItems = findViewById(R.id.navigation);
//drawerItems.setItemIconTintList(null); // Set null to use the original icon
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
if (!BuildConfig.BUILD_TYPE.equals("release")) {
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close);
toggle.syncState();
drawer.addDrawerListener(toggle);
drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
private int lastService;
@Override
public void onDrawerOpened(View drawerView) {
lastService = ServiceHelper.getSelectedServiceId(MainActivity.this);
}
@Override
public void onDrawerClosed(View drawerView) {
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
}
}
});
drawerItems.setNavigationItemSelectedListener(item -> {
if (item.getGroupId() == R.id.menu_services_group) {
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false);
ServiceHelper.setSelectedServiceId(this, item.getTitle().toString());
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
}
drawer.closeDrawers();
return true;
});
} else {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
@@ -112,20 +163,14 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
// https://stackoverflow.com/questions/10844112/runtimeexception-performing-pause-of-activity-that-is-not-resumed
// Briefly, let the activity resume properly posting the recreate call to end of the message queue
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
MainActivity.this.recreate();
}
});
new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
}
if(sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) {
if (sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) {
if (DEBUG) Log.d(TAG, "main page has changed, recreating main fragment...");
sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply();
NavigationHelper.openMainActivity(this);
}
}
@Override
@@ -159,6 +204,38 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
} else super.onBackPressed();
}
/**
* Implement the following diagram behavior for the up button:
* <pre>
* +---------------+
* | Main Screen +----+
* +-------+-------+ |
* | |
* ▲ Up | Search Button
* | |
* +----+-----+ |
* +------------+ Search |◄-----+
* | +----+-----+
* | Open |
* | something ▲ Up
* | |
* | +------------+-------------+
* | | |
* | | Video <-> Channel |
* +---►| Channel <-> Playlist |
* | Video <-> .... |
* | |
* +--------------------------+
* </pre>
*/
private void onHomeButtonPressed() {
// If search fragment wasn't found in the backstack...
if (!NavigationHelper.tryGotoSearchFragment(getSupportFragmentManager())) {
// ...go to the main fragment
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
}
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@@ -184,6 +261,9 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(false);
}
updateDrawerNavigation();
return true;
}
@@ -194,7 +274,7 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
switch (id) {
case android.R.id.home:
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
onHomeButtonPressed();
return true;
case R.id.action_settings:
NavigationHelper.openSettings(this);
@@ -228,6 +308,27 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void updateDrawerNavigation() {
if (getSupportActionBar() == null) return;
final Toolbar toolbar = findViewById(R.id.toolbar);
final DrawerLayout drawer = findViewById(R.id.drawer_layout);
final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (fragment instanceof MainFragment) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
if (toggle != null) {
toggle.syncState();
toolbar.setNavigationOnClickListener(v -> drawer.openDrawer(GravityCompat.START));
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNDEFINED);
}
} else {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
toolbar.setNavigationOnClickListener(v -> onHomeButtonPressed());
}
}
private void handleIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");

View File

@@ -3,14 +3,25 @@ package org.schabi.newpipe;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.widget.Toast;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.Collection;
import java.util.HashSet;
/**
import icepick.Icepick;
import icepick.State;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
/*
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
* RouterActivity.java is part of NewPipe.
*
@@ -34,23 +45,71 @@ import java.util.HashSet;
*/
public class RouterActivity extends AppCompatActivity {
@State
protected String currentUrl;
protected CompositeDisposable disposables = new CompositeDisposable();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
String videoUrl = getUrl(getIntent());
handleUrl(videoUrl);
if (TextUtils.isEmpty(currentUrl)) {
currentUrl = getUrl(getIntent());
if (TextUtils.isEmpty(currentUrl)) {
Toast.makeText(this, R.string.invalid_url_toast, Toast.LENGTH_LONG).show();
finish();
}
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
@Override
protected void onStart() {
super.onStart();
handleUrl(currentUrl);
}
protected void handleUrl(String url) {
boolean success = NavigationHelper.openByLink(this, url);
if (!success) {
disposables.add(Observable
.fromCallable(() -> NavigationHelper.getIntentByLink(this, url))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}, this::handleError)
);
}
protected void handleError(Throwable error) {
error.printStackTrace();
if (error instanceof ExtractionException) {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
} else {
ExtractorHelper.handleGeneralException(this, -1, null, error, UserAction.SOMETHING_ELSE, null);
}
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
disposables.clear();
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@@ -71,7 +130,8 @@ public class RouterActivity extends AppCompatActivity {
} 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];
final String[] uris = getUris(extraText);
videoUrl = uris.length > 0 ? uris[0] : null;
}
return videoUrl;

View File

@@ -0,0 +1,413 @@
package org.schabi.newpipe;
import android.app.IntentService;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Toast;
import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.StreamingService.LinkType;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.playlist.ChannelPlayQueue;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlaylistPlayQueue;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
import java.util.Arrays;
import icepick.State;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
/**
* Get the url from the intent and open it in the chosen preferred player
*/
public class RouterPlayerActivity extends RouterActivity {
@State
protected int currentServiceId = -1;
private StreamingService currentService;
@State
protected LinkType currentLinkType;
@State
protected int selectedRadioPosition = -1;
protected int selectedPreviously = -1;
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
setTheme(ThemeHelper.isLightThemeSelected(this) ? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
}
@Override
protected void handleUrl(String url) {
disposables.add(Observable
.fromCallable(() -> {
if (currentServiceId == -1) {
currentService = NewPipe.getServiceByUrl(url);
currentServiceId = currentService.getServiceId();
currentLinkType = currentService.getLinkTypeByUrl(url);
currentUrl = NavigationHelper.getCleanUrl(currentService, url, currentLinkType);
} else {
currentService = NewPipe.getService(currentServiceId);
}
return currentLinkType != LinkType.NONE;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
if (result) {
onSuccess();
} else {
onError();
}
}, this::handleError));
}
protected void onError() {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
finish();
}
protected void onSuccess() {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
if ((isExtAudioEnabled || isExtVideoEnabled) && currentLinkType != LinkType.STREAM) {
Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show();
finish();
return;
}
// TODO: Add some sort of "capabilities" field to services (audio only, video and audio, etc.)
if (currentService == ServiceList.SoundCloud.getService()) {
handleChoice(getString(R.string.background_player_key));
return;
}
final String playerChoiceKey = preferences.getString(getString(R.string.preferred_player_key), getString(R.string.preferred_player_default));
final String alwaysAskKey = getString(R.string.always_ask_player_key);
if (playerChoiceKey.equals(alwaysAskKey)) {
showDialog();
} else {
handleChoice(playerChoiceKey);
}
}
private void showDialog() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(this,
ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme);
LayoutInflater inflater = LayoutInflater.from(themeWrapper);
final LinearLayout rootLayout = (LinearLayout) inflater.inflate(R.layout.preferred_player_dialog_view, null, false);
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
final AdapterChoiceItem[] choices = {
new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player),
resolveResourceIdFromAttr(themeWrapper, R.attr.play)),
new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player),
resolveResourceIdFromAttr(themeWrapper, R.attr.audio)),
new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player),
resolveResourceIdFromAttr(themeWrapper, R.attr.popup))
};
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
final int indexOfChild = radioGroup.indexOfChild(radioGroup.findViewById(radioGroup.getCheckedRadioButtonId()));
final AdapterChoiceItem choice = choices[indexOfChild];
handleChoice(choice.key);
if (which == DialogInterface.BUTTON_POSITIVE) {
preferences.edit().putString(getString(R.string.preferred_player_key), choice.key).apply();
}
};
final AlertDialog alertDialog = new AlertDialog.Builder(themeWrapper)
.setTitle(R.string.preferred_player_share_menu_title)
.setView(radioGroup)
.setCancelable(true)
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
.setPositiveButton(R.string.always, dialogButtonsClickListener)
.setOnDismissListener((dialog) -> finish())
.create();
alertDialog.setOnShowListener(dialog -> {
setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1);
});
radioGroup.setOnCheckedChangeListener((group, checkedId) -> setDialogButtonsState(alertDialog, true));
final View.OnClickListener radioButtonsClickListener = v -> {
final int indexOfChild = radioGroup.indexOfChild(v);
if (indexOfChild == -1) return;
selectedPreviously = selectedRadioPosition;
selectedRadioPosition = indexOfChild;
if (selectedPreviously == selectedRadioPosition) {
handleChoice(choices[selectedRadioPosition].key);
}
};
int id = 12345;
for (AdapterChoiceItem item : choices) {
final RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
radioButton.setText(item.description);
radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
radioButton.setChecked(false);
radioButton.setId(id++);
radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
radioButton.setOnClickListener(radioButtonsClickListener);
radioGroup.addView(radioButton);
}
if (selectedRadioPosition == -1) {
final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_player_last_selected_key), null);
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
for (int i = 0; i < choices.length; i++) {
AdapterChoiceItem c = choices[i];
if (lastSelectedPlayer.equals(c.key)) {
selectedRadioPosition = i;
break;
}
}
}
}
selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.length - 1);
if (selectedRadioPosition != -1) {
((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true);
}
selectedPreviously = selectedRadioPosition;
alertDialog.show();
}
private void setDialogButtonsState(AlertDialog dialog, boolean state) {
final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (negativeButton == null || positiveButton == null) return;
negativeButton.setEnabled(state);
positiveButton.setEnabled(state);
}
private void handleChoice(final String playerChoiceKey) {
if (Arrays.asList(getResources().getStringArray(R.array.preferred_player_values_list)).contains(playerChoiceKey)) {
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putString(getString(R.string.preferred_player_last_selected_key), playerChoiceKey).apply();
}
if (playerChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) {
PermissionHelper.showPopupEnablementToast(this);
finish();
return;
}
final Intent intent = new Intent(this, FetcherService.class);
intent.putExtra(FetcherService.KEY_CHOICE, new Choice(currentService.getServiceId(), currentLinkType, currentUrl, playerChoiceKey));
startService(intent);
finish();
}
private static class AdapterChoiceItem {
final String description, key;
@DrawableRes
final int icon;
AdapterChoiceItem(String key, String description, int icon) {
this.description = description;
this.key = key;
this.icon = icon;
}
}
private static class Choice implements Serializable {
final int serviceId;
final String url, playerChoice;
final LinkType linkType;
Choice(int serviceId, LinkType linkType, String url, String playerChoice) {
this.serviceId = serviceId;
this.linkType = linkType;
this.url = url;
this.playerChoice = playerChoice;
}
@Override
public String toString() {
return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Service Fetcher
//////////////////////////////////////////////////////////////////////////*/
public static class FetcherService extends IntentService {
private static final int ID = 456;
public static final String KEY_CHOICE = "key_choice";
private Disposable fetcher;
public FetcherService() {
super(FetcherService.class.getSimpleName());
}
@Override
public void onCreate() {
super.onCreate();
startForeground(ID, createNotification().build());
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent == null) return;
final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE);
if (!(serializable instanceof Choice)) return;
Choice playerChoice = (Choice) serializable;
handleChoice(playerChoice);
}
public void handleChoice(Choice choice) {
Single<? extends Info> single = null;
UserAction userAction = UserAction.SOMETHING_ELSE;
switch (choice.linkType) {
case STREAM:
single = ExtractorHelper.getStreamInfo(choice.serviceId, choice.url, false);
userAction = UserAction.REQUESTED_STREAM;
break;
case CHANNEL:
single = ExtractorHelper.getChannelInfo(choice.serviceId, choice.url, false);
userAction = UserAction.REQUESTED_CHANNEL;
break;
case PLAYLIST:
single = ExtractorHelper.getPlaylistInfo(choice.serviceId, choice.url, false);
userAction = UserAction.REQUESTED_PLAYLIST;
break;
}
if (single != null) {
final UserAction finalUserAction = userAction;
final Consumer<Info> resultHandler = getResultHandler(choice);
fetcher = single
.observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> {
resultHandler.accept(info);
if (fetcher != null) fetcher.dispose();
}, throwable -> ExtractorHelper.handleGeneralException(this,
choice.serviceId, choice.url, throwable, finalUserAction, ", opened with " + choice.playerChoice));
}
}
public Consumer<Info> getResultHandler(Choice choice) {
return info -> {
final String videoPlayerKey = getString(R.string.video_player_key);
final String backgroundPlayerKey = getString(R.string.background_player_key);
final String popupPlayerKey = getString(R.string.popup_player_key);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
boolean useOldVideoPlayer = PlayerHelper.isUsingOldPlayer(this);
PlayQueue playQueue;
String playerChoice = choice.playerChoice;
if (info instanceof StreamInfo) {
if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) {
NavigationHelper.playOnExternalAudioPlayer(this, (StreamInfo) info);
} else if (playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) {
NavigationHelper.playOnExternalVideoPlayer(this, (StreamInfo) info);
} else if (playerChoice.equals(videoPlayerKey) && useOldVideoPlayer) {
NavigationHelper.playOnOldVideoPlayer(this, (StreamInfo) info);
} else {
playQueue = new SinglePlayQueue((StreamInfo) info);
if (playerChoice.equals(videoPlayerKey)) {
NavigationHelper.playOnMainPlayer(this, playQueue);
} else if (playerChoice.equals(backgroundPlayerKey)) {
NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
} else if (playerChoice.equals(popupPlayerKey)) {
NavigationHelper.enqueueOnPopupPlayer(this, playQueue, true);
}
}
}
if (info instanceof ChannelInfo || info instanceof PlaylistInfo) {
playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info);
if (playerChoice.equals(videoPlayerKey)) {
NavigationHelper.playOnMainPlayer(this, playQueue);
} else if (playerChoice.equals(backgroundPlayerKey)) {
NavigationHelper.playOnBackgroundPlayer(this, playQueue);
} else if (playerChoice.equals(popupPlayerKey)) {
NavigationHelper.playOnPopupPlayer(this, playQueue);
}
}
};
}
@Override
public void onDestroy() {
super.onDestroy();
stopForeground(true);
if (fetcher != null) fetcher.dispose();
}
private NotificationCompat.Builder createNotification() {
return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentTitle(getString(R.string.preferred_player_fetcher_notification_title))
.setContentText(getString(R.string.preferred_player_fetcher_notification_message));
}
}
}

View File

@@ -1,49 +0,0 @@
package org.schabi.newpipe;
import android.content.Intent;
import android.os.Build;
import android.widget.Toast;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.PermissionHelper;
/**
* Get the url from the intent and open a popup player
*/
public class RouterPopupActivity extends RouterActivity {
@Override
protected void handleUrl(String url) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !PermissionHelper.checkSystemAlertWindowPermission(this)) {
Toast.makeText(this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
return;
}
StreamingService service;
try {
service = NewPipe.getServiceByUrl(url);
} catch (ExtractionException e) {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
return;
}
Intent callIntent = new Intent(this, PopupVideoPlayer.class);
switch (service.getLinkTypeByUrl(url)) {
case STREAM:
break;
default:
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
return;
}
callIntent.putExtra(Constants.KEY_URL, url);
callIntent.putExtra(Constants.KEY_SERVICE_ID, service.getServiceId());
startService(callIntent);
finish();
}
}

View File

@@ -135,8 +135,12 @@ public class AboutActivity extends AppCompatActivity {
View githubLink = rootView.findViewById(R.id.github_link);
githubLink.setOnClickListener(new OnGithubLinkClickListener());
View licenseLink = rootView.findViewById(R.id.app_read_license);
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
View donationLink = rootView.findViewById(R.id.donation_link);
donationLink.setOnClickListener(new OnDonationLinkClickListener());
View websiteLink = rootView.findViewById(R.id.website_link);
websiteLink.setOnClickListener(new OnWebsiteLinkClickListener());
return rootView;
}
@@ -149,10 +153,21 @@ public class AboutActivity extends AppCompatActivity {
}
}
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
private static class OnDonationLinkClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
public void onClick(final View view) {
final Context context = view.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.donation_url)));
context.startActivity(intent);
}
}
private static class OnWebsiteLinkClickListener implements View.OnClickListener {
@Override
public void onClick(final View view) {
final Context context = view.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.website_url)));
context.startActivity(intent);
}
}
}

View File

@@ -62,6 +62,9 @@ public class LicenseFragment extends Fragment {
View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
View licenseLink = rootView.findViewById(R.id.app_read_license);
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
for (final SoftwareComponent component : softwareComponents) {
View componentView = inflater.inflate(R.layout.item_software_component, container, false);
TextView softwareName = componentView.findViewById(R.id.name);
@@ -119,4 +122,11 @@ public class LicenseFragment extends Fragment {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink));
startActivity(browserIntent);
}
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
}
}
}

View File

@@ -48,7 +48,7 @@ public class WatchHistoryEntry extends HistoryEntry {
}
public WatchHistoryEntry(StreamInfo streamInfo) {
this(new Date(), streamInfo.service_id, streamInfo.name, streamInfo.url,
this(new Date(), streamInfo.getServiceId(), streamInfo.getName(), streamInfo.getUrl(),
streamInfo.id, streamInfo.thumbnail_url, streamInfo.uploader_name, streamInfo.duration);
}

View File

@@ -116,10 +116,7 @@ public class SubscriptionEntity {
@Ignore
public ChannelInfoItem toChannelInfoItem() {
ChannelInfoItem item = new ChannelInfoItem();
item.url = getUrl();
item.service_id = getServiceId();
item.name = getName();
ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
item.thumbnail_url = getAvatarUrl();
item.subscriber_count = getSubscriberCount();
item.description = getDescription();

View File

@@ -108,8 +108,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
nameEditText = view.findViewById(R.id.file_name);
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.name));
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(), currentInfo.audio_streams);
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName()));
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams());
streamsSpinner = view.findViewById(R.id.quality_spinner);
streamsSpinner.setOnItemSelectedListener(this);
@@ -183,7 +183,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
String[] items = new String[audioStreams.size()];
for (int i = 0; i < audioStreams.size(); i++) {
AudioStream audioStream = audioStreams.get(i);
items[i] = MediaFormat.getNameById(audioStream.format) + " " + audioStream.average_bitrate + "kbps";
items[i] = audioStream.getFormat().getName() + " " + audioStream.getAverageBitrate() + "kbps";
}
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, items);
@@ -242,7 +242,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
RadioButton audioButton = view.findViewById(R.id.audio_button);
RadioButton videoButton = view.findViewById(R.id.video_button);
if (currentInfo.audio_streams == null || currentInfo.audio_streams.size() == 0) {
if (currentInfo.getAudioStreams() == null || currentInfo.getAudioStreams().size() == 0) {
audioButton.setVisibility(View.GONE);
videoButton.setChecked(true);
} else if (sortedStreamVideosList == null || sortedStreamVideosList.size() == 0) {
@@ -256,14 +256,18 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
String url, location;
String fileName = nameEditText.getText().toString().trim();
if (fileName.isEmpty()) fileName = FilenameUtils.createFilename(getContext(), currentInfo.name);
if (fileName.isEmpty()) fileName = FilenameUtils.createFilename(getContext(), currentInfo.getName());
boolean isAudio = radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button;
url = isAudio ? currentInfo.audio_streams.get(selectedAudioIndex).url : sortedStreamVideosList.get(selectedVideoIndex).url;
location = isAudio ? NewPipeSettings.getAudioDownloadPath(getContext()) : NewPipeSettings.getVideoDownloadPath(getContext());
if (isAudio) fileName += "." + MediaFormat.getSuffixById(currentInfo.audio_streams.get(selectedAudioIndex).format);
else fileName += "." + MediaFormat.getSuffixById(sortedStreamVideosList.get(selectedVideoIndex).format);
if (isAudio) {
url = currentInfo.getAudioStreams().get(selectedAudioIndex).getUrl();
location = NewPipeSettings.getAudioDownloadPath(getContext());
fileName += "." + currentInfo.getAudioStreams().get(selectedAudioIndex).getFormat().getSuffix();
} else {
url = sortedStreamVideosList.get(selectedVideoIndex).getUrl();
location = NewPipeSettings.getVideoDownloadPath(getContext());
fileName += "." + sortedStreamVideosList.get(selectedVideoIndex).getFormat().getSuffix();
}
DownloadManagerService.startMission(getContext(), url, location, fileName, isAudio, threadsSeekBar.getProgress() + 1);
getDialog().dismiss();

View File

@@ -1,6 +1,7 @@
package org.schabi.newpipe.fragments;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
@@ -50,6 +51,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
protected Button errorButtonRetry;
protected TextView errorTextView;
@State
protected boolean useAsFrontPage = false;
@Override
@@ -218,6 +220,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
if (serviceName == null) serviceName = "none";
if (request == null) request = "none";
ErrorActivity.reportError(getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
}
@@ -238,4 +241,28 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView, ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
public void setTitle(String title) {
if (DEBUG) Log.d(TAG, "setTitle() called with: title = [" + title + "]");
if (activity != null && activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setTitle(title);
}
}
protected void openUrlInBrowser(String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
}
protected void shareUrl(String subject, String url) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, url);
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
}
}

View File

@@ -2,6 +2,7 @@ package org.schabi.newpipe.fragments;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
@@ -22,6 +23,7 @@ import android.view.ViewGroup;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.kiosk.KioskList;
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
@@ -30,30 +32,25 @@ import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
import org.schabi.newpipe.fragments.subscription.SubscriptionFragment;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.concurrent.ExecutionException;
public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener {
private ViewPager viewPager;
private boolean showBlankTab = false;
private static final int FALLBACK_SERVICE_ID = 0; // Youtbe
private static final String FALLBACK_CHANNEL_URL =
"https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ";
private static final String FALLBACK_CHANNEL_NAME = "Music";
private static final String FALLBACK_KIOSK_ID = "Trending";
public int currentServiceId = -1;
private ViewPager viewPager;
/*//////////////////////////////////////////////////////////////////////////
// Konst
// Constants
//////////////////////////////////////////////////////////////////////////*/
private static final int KIOSK_MENU_OFFSETT = 2000;
private static final int FALLBACK_SERVICE_ID = ServiceList.YouTube.getId();
private static final String FALLBACK_CHANNEL_URL = "https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ";
private static final String FALLBACK_CHANNEL_NAME = "Music";
private static final String FALLBACK_KIOSK_ID = "Trending";
private static final int KIOSK_MENU_OFFSET = 2000;
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
@@ -66,9 +63,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
currentServiceId = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(getActivity())
.getString(getString(R.string.current_service_key), "0"));
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
currentServiceId = ServiceHelper.getSelectedServiceId(activity);
return inflater.inflate(R.layout.fragment_main, container, false);
}
@@ -86,27 +82,15 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
tabLayout.setupWithViewPager(viewPager);
if(ThemeHelper.isLightThemeSelected(getActivity())) {
tabLayout.setBackgroundColor(getResources().getColor(R.color.light_youtube_primary_color));
}
int channelIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_channel);
int whatsHotIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_hot);
if(PreferenceManager.getDefaultSharedPreferences(getActivity())
.getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key))
.equals(getString(R.string.subscription_page_key))) {
if(ThemeHelper.isLightThemeSelected(getActivity())) {
tabLayout.getTabAt(0).setIcon(R.drawable.ic_channel_black_24dp);
} else{
tabLayout.getTabAt(0).setIcon(R.drawable.ic_channel_white_24dp);
}
} else {
if(ThemeHelper.isLightThemeSelected(getActivity())) {
tabLayout.getTabAt(0).setIcon(R.drawable.ic_whatshot_black_24dp);
tabLayout.getTabAt(1).setIcon(R.drawable.ic_channel_black_24dp);
} else {
tabLayout.getTabAt(0).setIcon(R.drawable.ic_whatshot_white_24dp);
tabLayout.getTabAt(1).setIcon(R.drawable.ic_channel_white_24dp);
}
}
if (isSubscriptionsPageOnlySelected()) {
tabLayout.getTabAt(0).setIcon(channelIcon);
} else {
tabLayout.getTabAt(0).setIcon(whatsHotIcon);
tabLayout.getTabAt(1).setIcon(channelIcon);
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -139,7 +123,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_search:
NavigationHelper.openSearchFragment(getFragmentManager(), 0, "");
NavigationHelper.openSearchFragment(getFragmentManager(), ServiceHelper.getSelectedServiceId(activity), "");
return true;
}
return super.onOptionsItemSelected(item);
@@ -164,11 +148,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
private class PagerAdapter extends FragmentPagerAdapter {
private int[] tabTitles = new int[]{
R.string.tab_main,
R.string.tab_subscriptions
};
PagerAdapter(FragmentManager fm) {
super(fm);
}
@@ -177,13 +156,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public Fragment getItem(int position) {
switch (position) {
case 0:
if(PreferenceManager.getDefaultSharedPreferences(getActivity())
.getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key))
.equals(getString(R.string.subscription_page_key))) {
return new SubscriptionFragment();
} else {
return getMainPageFramgent();
}
return isSubscriptionsPageOnlySelected() ? new SubscriptionFragment() : getMainPageFragment();
case 1:
return new SubscriptionFragment();
default:
@@ -199,13 +172,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
@Override
public int getCount() {
if(PreferenceManager.getDefaultSharedPreferences(getActivity())
.getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key))
.equals(getString(R.string.subscription_page_key))) {
return 1;
} else {
return 2;
}
return isSubscriptionsPageOnlySelected() ? 1 : 2;
}
}
@@ -213,28 +180,33 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
// Main page content
//////////////////////////////////////////////////////////////////////////*/
private Fragment getMainPageFramgent() {
private boolean isSubscriptionsPageOnlySelected() {
return PreferenceManager.getDefaultSharedPreferences(activity)
.getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key))
.equals(getString(R.string.subscription_page_key));
}
private Fragment getMainPageFragment() {
try {
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(getActivity());
final String setMainPage = preferences.getString(getString(R.string.main_page_content_key),
getString(R.string.main_page_selectd_kiosk_id));
if(setMainPage.equals(getString(R.string.blank_page_key))) {
getString(R.string.main_page_selectd_kiosk_id));
if (setMainPage.equals(getString(R.string.blank_page_key))) {
return new BlankFragment();
} else if(setMainPage.equals(getString(R.string.kiosk_page_key))) {
} else if (setMainPage.equals(getString(R.string.kiosk_page_key))) {
int serviceId = preferences.getInt(getString(R.string.main_page_selected_service),
FALLBACK_SERVICE_ID);
String kioskId = preferences.getString(getString(R.string.main_page_selectd_kiosk_id),
FALLBACK_KIOSK_ID);
KioskFragment fragment = KioskFragment.getInstance(serviceId, kioskId
);
KioskFragment fragment = KioskFragment.getInstance(serviceId, kioskId);
fragment.useAsFrontPage(true);
return fragment;
} else if(setMainPage.equals(getString(R.string.feed_page_key))) {
} else if (setMainPage.equals(getString(R.string.feed_page_key))) {
FeedFragment fragment = new FeedFragment();
fragment.useAsFrontPage(true);
return fragment;
} else if(setMainPage.equals(getString(R.string.channel_page_key))) {
} else if (setMainPage.equals(getString(R.string.channel_page_key))) {
int serviceId = preferences.getInt(getString(R.string.main_page_selected_service),
FALLBACK_SERVICE_ID);
String url = preferences.getString(getString(R.string.main_page_selected_channel_url),
@@ -267,8 +239,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
StreamingService service = NewPipe.getService(currentServiceId);
KioskList kl = service.getKioskList();
int i = 0;
for(final String ks : kl.getAvailableKiosks()) {
menu.add(0, KIOSK_MENU_OFFSETT + i, Menu.NONE,
for (final String ks : kl.getAvailableKiosks()) {
menu.add(0, KIOSK_MENU_OFFSET + i, Menu.NONE,
KioskTranslator.getTranslatedKioskName(ks, getContext()))
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override

View File

@@ -60,7 +60,7 @@ public class SpinnerToolbarAdapter extends BaseAdapter {
ImageView woSoundIcon = convertView.findViewById(R.id.wo_sound_icon);
TextView text = convertView.findViewById(android.R.id.text1);
VideoStream item = (VideoStream) getItem(position);
text.setText(MediaFormat.getNameById(item.format) + " " + item.resolution);
text.setText(item.getFormat().getName() + " " + item.getResolution());
int visibility = !showIconNoAudio ? View.GONE
: item.isVideoOnly ? View.VISIBLE

View File

@@ -4,7 +4,8 @@ import java.io.Serializable;
class StackItem implements Serializable {
private int serviceId;
private String title, url;
private String title;
private String url;
StackItem(int serviceId, String url, String title) {
this.serviceId = serviceId;

View File

@@ -24,7 +24,6 @@ import android.text.util.Linkify;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -49,7 +48,6 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
@@ -62,9 +60,10 @@ import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.history.HistoryListener;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.old.PlayVideoActivity;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.SinglePlayQueue;
@@ -77,6 +76,7 @@ import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
import java.util.ArrayList;
@@ -281,7 +281,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
// Check if the next video label and video is visible,
// if it is, include the two elements in the next check
int nextCount = currentInfo != null && currentInfo.next_video != null ? 2 : 0;
int nextCount = currentInfo != null && currentInfo.getNextVideo() != null ? 2 : 0;
if (relatedStreamsView != null && relatedStreamsView.getChildCount() > INITIAL_RELATED_VIDEOS + nextCount) {
outState.putSerializable(WAS_RELATED_EXPANDED_KEY, true);
}
@@ -328,14 +328,22 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
openPopupPlayer(false);
break;
case R.id.detail_uploader_root_layout:
if (currentInfo.uploader_url == null || currentInfo.uploader_url.isEmpty()) {
if (TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
Log.w(TAG, "Can't open channel because we got no channel URL");
} else {
NavigationHelper.openChannelFragment(getFragmentManager(), currentInfo.service_id, currentInfo.uploader_url, currentInfo.uploader_name);
NavigationHelper.openChannelFragment(
getFragmentManager(),
currentInfo.getServiceId(),
currentInfo.getUploaderUrl(),
currentInfo.getUploaderName());
}
break;
case R.id.detail_thumbnail_root_layout:
openVideoPlayer();
if (currentInfo.video_streams.isEmpty() && currentInfo.video_only_streams.isEmpty()) {
openBackgroundPlayer(false);
} else {
openVideoPlayer();
}
break;
case R.id.detail_title_root_layout:
toggleTitleAndDescription();
@@ -378,22 +386,22 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
if (DEBUG) Log.d(TAG, "toggleExpandRelatedVideos() called with: info = [" + info + "]");
if (!showRelatedStreams) return;
int nextCount = info.next_video != null ? 2 : 0;
int nextCount = info.getNextVideo() != null ? 2 : 0;
int initialCount = INITIAL_RELATED_VIDEOS + nextCount;
if (relatedStreamsView.getChildCount() > initialCount) {
relatedStreamsView.removeViews(initialCount, relatedStreamsView.getChildCount() - (initialCount));
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, resolveResourceIdFromAttr(R.attr.expand)));
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand)));
return;
}
//Log.d(TAG, "toggleExpandRelatedVideos() called with: info = [" + info + "], from = [" + INITIAL_RELATED_VIDEOS + "]");
for (int i = INITIAL_RELATED_VIDEOS; i < info.related_streams.size(); i++) {
InfoItem item = info.related_streams.get(i);
for (int i = INITIAL_RELATED_VIDEOS; i < info.getRelatedStreams().size(); i++) {
InfoItem item = info.getRelatedStreams().get(i);
//Log.d(TAG, "i = " + i);
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
}
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, resolveResourceIdFromAttr(R.attr.collapse)));
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.collapse)));
}
/*//////////////////////////////////////////////////////////////////////////
@@ -457,7 +465,12 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
infoItemBuilder.setOnStreamSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener<StreamInfoItem>() {
@Override
public void selected(StreamInfoItem selectedItem) {
selectAndLoadVideo(selectedItem.service_id, selectedItem.url, selectedItem.name);
selectAndLoadVideo(selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
}
@Override
public void held(StreamInfoItem selectedItem) {
showStreamDialog(selectedItem);
}
});
@@ -476,6 +489,34 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
detailControlsPopup.setOnTouchListener(getOnControlsTouchListener());
}
private void showStreamDialog(final StreamInfoItem item) {
final Context context = getContext();
if (context == null || context.getResources() == null || getActivity() == null) return;
final String[] commands = new String[]{
context.getResources().getString(R.string.enqueue_on_background),
context.getResources().getString(R.string.enqueue_on_popup)
};
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(getActivity(), new SinglePlayQueue(item));
break;
default:
break;
}
}
};
new InfoItemDialog(getActivity(), item, commands, actions).show();
}
private View.OnTouchListener getOnControlsTouchListener() {
return new View.OnTouchListener() {
@Override
@@ -497,35 +538,35 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
private void initThumbnailViews(StreamInfo info) {
thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
if (info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.thumbnail_url, thumbnailImageView, DISPLAY_THUMBNAIL_OPTIONS, new SimpleImageLoadingListener() {
if (!TextUtils.isEmpty(info.getThumbnailUrl())) {
imageLoader.displayImage(info.getThumbnailUrl(), thumbnailImageView, DISPLAY_THUMBNAIL_OPTIONS, new SimpleImageLoadingListener() {
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
ErrorActivity.reportError(activity, failReason.getCause(), null, activity.findViewById(android.R.id.content), ErrorActivity.ErrorInfo.make(UserAction.LOAD_IMAGE, NewPipe.getNameOfService(currentInfo.service_id), imageUri, R.string.could_not_load_thumbnails));
ErrorActivity.reportError(activity, failReason.getCause(), null, activity.findViewById(android.R.id.content), ErrorActivity.ErrorInfo.make(UserAction.LOAD_IMAGE, NewPipe.getNameOfService(currentInfo.getServiceId()), imageUri, R.string.could_not_load_thumbnails));
}
});
}
if (info.uploader_avatar_url != null && !info.uploader_avatar_url.isEmpty()) {
imageLoader.displayImage(info.uploader_avatar_url, uploaderThumb, DISPLAY_AVATAR_OPTIONS);
if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) {
imageLoader.displayImage(info.getUploaderAvatarUrl(), uploaderThumb, DISPLAY_AVATAR_OPTIONS);
}
}
private void initRelatedVideos(StreamInfo info) {
if (relatedStreamsView.getChildCount() > 0) relatedStreamsView.removeAllViews();
if (info.next_video != null && showRelatedStreams) {
if (info.getNextVideo() != null && showRelatedStreams) {
nextStreamTitle.setVisibility(View.VISIBLE);
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, info.next_video));
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, info.getNextVideo()));
relatedStreamsView.addView(getSeparatorView());
relatedStreamRootLayout.setVisibility(View.VISIBLE);
} else nextStreamTitle.setVisibility(View.GONE);
if (info.related_streams != null && !info.related_streams.isEmpty() && showRelatedStreams) {
//long first = System.nanoTime(), each;
int to = info.related_streams.size() >= INITIAL_RELATED_VIDEOS ? INITIAL_RELATED_VIDEOS : info.related_streams.size();
int to = info.getRelatedStreams().size() >= INITIAL_RELATED_VIDEOS ? INITIAL_RELATED_VIDEOS : info.getRelatedStreams().size();
for (int i = 0; i < to; i++) {
InfoItem item = info.related_streams.get(i);
InfoItem item = info.getRelatedStreams().get(i);
//each = System.nanoTime();
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
//if (DEBUG) Log.d(TAG, "each took " + ((System.nanoTime() - each) / 1000000L) + "ms");
@@ -535,9 +576,9 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
relatedStreamRootLayout.setVisibility(View.VISIBLE);
relatedStreamExpandButton.setVisibility(View.VISIBLE);
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, resolveResourceIdFromAttr(R.attr.expand)));
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand)));
} else {
if (info.next_video == null) relatedStreamRootLayout.setVisibility(View.GONE);
if (info.getNextVideo() == null) relatedStreamRootLayout.setVisibility(View.GONE);
relatedStreamExpandButton.setVisibility(View.GONE);
}
}
@@ -581,26 +622,14 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
private void setupActionBarHandler(final StreamInfo info) {
if (DEBUG) Log.d(TAG, "setupActionBarHandler() called with: info = [" + info + "]");
sortedStreamVideosList = new ArrayList<>(ListHelper.getSortedStreamVideosList(activity, info.video_streams, info.video_only_streams, false));
sortedStreamVideosList = new ArrayList<>(ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false));
actionBarHandler.setupStreamList(sortedStreamVideosList, spinnerToolbar);
actionBarHandler.setOnShareListener(new ActionBarHandler.OnActionListener() {
@Override
public void onActionSelected(int selectedStreamId) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info.url);
intent.setType("text/plain");
startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
}
});
actionBarHandler.setOnShareListener(selectedStreamId -> shareUrl(info.name, info.url));
actionBarHandler.setOnOpenInBrowserListener(new ActionBarHandler.OnActionListener() {
@Override
public void onActionSelected(int selectedStreamId) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(info.url));
startActivity(Intent.createChooser(intent, activity.getString(R.string.choose_browser)));
openUrlInBrowser(info.getUrl());
}
});
@@ -608,7 +637,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
@Override
public void onActionSelected(int selectedStreamId) {
try {
NavigationHelper.playWithKore(activity, Uri.parse(info.url.replace("https", "http")));
NavigationHelper.playWithKore(activity, Uri.parse(info.getUrl().replace("https", "http")));
if(activity instanceof HistoryListener) {
((HistoryListener) activity).onVideoPlayed(info, null);
}
@@ -707,7 +736,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
public void prepareAndHandleInfo(final StreamInfo info, boolean scrollToTop) {
if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = [" + info + "], scrollToTop = [" + scrollToTop + "]");
setInitialData(info.service_id, info.url, info.name);
setInitialData(info.getServiceId(), info.getUrl(), info.getName());
pushToStack(serviceId, url, name);
showLoading();
@@ -763,7 +792,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
//////////////////////////////////////////////////////////////////////////*/
private void openBackgroundPlayer(final boolean append) {
AudioStream audioStream = currentInfo.audio_streams.get(ListHelper.getDefaultAudioFormat(activity, currentInfo.audio_streams));
AudioStream audioStream = currentInfo.getAudioStreams().get(ListHelper.getDefaultAudioFormat(activity, currentInfo.getAudioStreams()));
if (activity instanceof HistoryListener) {
((HistoryListener) activity).onAudioPlayed(currentInfo, audioStream);
@@ -775,16 +804,13 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 16) {
openNormalBackgroundPlayer(append);
} else {
openExternalBackgroundPlayer(audioStream);
NavigationHelper.playOnExternalPlayer(activity, currentInfo.getName(), currentInfo.getUploaderName(), audioStream);
}
}
private void openPopupPlayer(final boolean append) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(activity)) {
Toast toast = Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG);
TextView messageView = toast.getView().findViewById(android.R.id.message);
if (messageView != null) messageView.setGravity(Gravity.CENTER);
toast.show();
if (!PermissionHelper.isPopupEnabled(activity)) {
PermissionHelper.showPopupEnablementToast(activity);
return;
}
@@ -792,16 +818,16 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
((HistoryListener) activity).onVideoPlayed(currentInfo, getSelectedVideoStream());
}
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
final Intent intent;
final PlayQueue itemQueue = new SinglePlayQueue(currentInfo);
if (append) {
Toast.makeText(activity, R.string.popup_playing_append, Toast.LENGTH_SHORT).show();
intent = NavigationHelper.getPlayerEnqueueIntent(activity, PopupVideoPlayer.class, playQueue);
NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue);
} else {
Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue, getSelectedVideoStream().resolution);
final Intent intent = NavigationHelper.getPlayerIntent(
activity, PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution
);
activity.startService(intent);
}
activity.startService(intent);
}
private void openVideoPlayer() {
@@ -812,104 +838,39 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
}
if (PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
openExternalVideoPlayer(selectedVideoStream);
NavigationHelper.playOnExternalPlayer(activity, currentInfo.getName(), currentInfo.getUploaderName(), selectedVideoStream);
} else {
openNormalPlayer(selectedVideoStream);
}
}
private void openNormalBackgroundPlayer(final boolean append) {
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
final PlayQueue itemQueue = new SinglePlayQueue(currentInfo);
if (append) {
activity.startService(NavigationHelper.getPlayerEnqueueIntent(activity, BackgroundPlayer.class, playQueue));
Toast.makeText(activity, R.string.background_player_append, Toast.LENGTH_SHORT).show();
NavigationHelper.enqueueOnBackgroundPlayer(activity, itemQueue);
} else {
activity.startService(NavigationHelper.getPlayerIntent(activity, BackgroundPlayer.class, playQueue));
Toast.makeText(activity, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
}
}
private void openExternalBackgroundPlayer(AudioStream audioStream) {
Intent intent;
intent = new Intent();
try {
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(audioStream.url), MediaFormat.getMimeById(audioStream.format));
intent.putExtra(Intent.EXTRA_TITLE, currentInfo.name);
intent.putExtra("title", currentInfo.name);
activity.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.no_player_found)
.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url)));
activity.startActivity(intent);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "You unlocked a secret unicorn.");
}
});
builder.create().show();
Log.e(TAG, "Either no Streaming player for audio was installed, or something important crashed:");
e.printStackTrace();
NavigationHelper.playOnBackgroundPlayer(activity, itemQueue);
}
}
private void openNormalPlayer(VideoStream selectedVideoStream) {
Intent mIntent;
boolean useOldPlayer = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(getString(R.string.use_old_player_key), false)
|| (Build.VERSION.SDK_INT < 16);
boolean useOldPlayer = PlayerHelper.isUsingOldPlayer(activity) || (Build.VERSION.SDK_INT < 16);
if (!useOldPlayer) {
// ExoPlayer
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue, getSelectedVideoStream().resolution);
mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue, getSelectedVideoStream().getResolution());
} else {
// Internal Player
mIntent = new Intent(activity, PlayVideoActivity.class)
.putExtra(PlayVideoActivity.VIDEO_TITLE, currentInfo.name)
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url)
.putExtra(PlayVideoActivity.VIDEO_URL, currentInfo.url)
.putExtra(PlayVideoActivity.START_POSITION, currentInfo.start_position);
.putExtra(PlayVideoActivity.VIDEO_TITLE, currentInfo.getName())
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.getUrl())
.putExtra(PlayVideoActivity.VIDEO_URL, currentInfo.getUrl())
.putExtra(PlayVideoActivity.START_POSITION, currentInfo.getStartPosition());
}
startActivity(mIntent);
}
private void openExternalVideoPlayer(VideoStream selectedVideoStream) {
// External Player
Intent intent = new Intent();
try {
intent.setAction(Intent.ACTION_VIEW)
.setDataAndType(Uri.parse(selectedVideoStream.url), MediaFormat.getMimeById(selectedVideoStream.format))
.putExtra(Intent.EXTRA_TITLE, currentInfo.name)
.putExtra("title", currentInfo.name);
this.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.no_player_found)
.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent()
.setAction(Intent.ACTION_VIEW)
.setData(Uri.parse(getString(R.string.fdroid_vlc_url)));
startActivity(intent);
}
})
.setNegativeButton(R.string.cancel, null);
builder.create().show();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@@ -1063,20 +1024,28 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
public void handleResult(@NonNull StreamInfo info) {
super.handleResult(info);
setInitialData(info.service_id, info.url, info.name);
setInitialData(info.getServiceId(), info.getUrl(), info.getName());
pushToStack(serviceId, url, name);
animateView(thumbnailPlayButton, true, 200);
videoTitleTextView.setText(name);
if (!TextUtils.isEmpty(info.uploader_name)) uploaderTextView.setText(info.uploader_name);
uploaderTextView.setVisibility(!TextUtils.isEmpty(info.uploader_name) ? View.VISIBLE : View.GONE);
if (!TextUtils.isEmpty(info.getUploaderName())) {
uploaderTextView.setText(info.getUploaderName());
uploaderTextView.setVisibility(View.VISIBLE);
} else {
uploaderTextView.setVisibility(View.GONE);
}
uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
if (info.view_count >= 0) videoCountView.setText(Localization.localizeViewCount(activity, info.view_count));
videoCountView.setVisibility(info.view_count >= 0 ? View.VISIBLE : View.GONE);
if (info.getViewCount() >= 0) {
videoCountView.setText(Localization.localizeViewCount(activity, info.getViewCount()));
videoCountView.setVisibility(View.VISIBLE);
} else {
videoCountView.setVisibility(View.GONE);
}
if (info.dislike_count == -1 && info.like_count == -1) {
if (info.getDislikeCount() == -1 && info.getLikeCount() == -1) {
thumbsDownImageView.setVisibility(View.VISIBLE);
thumbsUpImageView.setVisibility(View.VISIBLE);
thumbsUpTextView.setVisibility(View.GONE);
@@ -1084,14 +1053,23 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
thumbsDisabledTextView.setVisibility(View.VISIBLE);
} else {
if (info.dislike_count >= 0) thumbsDownTextView.setText(Localization.shortCount(activity, info.dislike_count));
thumbsDownTextView.setVisibility(info.dislike_count >= 0 ? View.VISIBLE : View.GONE);
thumbsDownImageView.setVisibility(info.dislike_count >= 0 ? View.VISIBLE : View.GONE);
if (info.like_count >= 0) thumbsUpTextView.setText(Localization.shortCount(activity, info.like_count));
thumbsUpTextView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
thumbsUpImageView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
if (info.getDislikeCount() >= 0) {
thumbsDownTextView.setText(Localization.shortCount(activity, info.getDislikeCount()));
thumbsDownTextView.setVisibility(View.VISIBLE);
thumbsDownImageView.setVisibility(View.VISIBLE);
} else {
thumbsDownTextView.setVisibility(View.GONE);
thumbsDownImageView.setVisibility(View.GONE);
}
if (info.getLikeCount() >= 0) {
thumbsUpTextView.setText(Localization.shortCount(activity, info.getLikeCount()));
thumbsUpTextView.setVisibility(View.VISIBLE);
thumbsUpImageView.setVisibility(View.VISIBLE);
} else {
thumbsUpTextView.setVisibility(View.GONE);
thumbsUpImageView.setVisibility(View.GONE);
}
thumbsDisabledTextView.setVisibility(View.GONE);
}
@@ -1100,10 +1078,10 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoDescriptionView.setVisibility(View.GONE);
videoDescriptionRootLayout.setVisibility(View.GONE);
if (!TextUtils.isEmpty(info.upload_date)) {
videoUploadDateView.setText(Localization.localizeDate(activity, info.upload_date));
if (!TextUtils.isEmpty(info.getUploadDate())) {
videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate()));
}
prepareDescription(info.description);
prepareDescription(info.getDescription());
animateView(spinnerToolbar, true, 500);
setupActionBarHandler(info);
@@ -1113,10 +1091,17 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
toggleExpandRelatedVideos(currentInfo);
wasRelatedStreamsExpanded = false;
}
setTitleToUrl(info.service_id, info.url, info.name);
setTitleToUrl(info.getServiceId(), info.getUrl(), info.getName());
if (!info.errors.isEmpty()) {
showSnackBarError(info.errors, UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(info.service_id), info.url, 0);
if (!info.getErrors().isEmpty()) {
showSnackBarError(info.getErrors(), UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(info.getServiceId()), info.getUrl(), 0);
}
if (info.video_streams.isEmpty() && info.video_only_streams.isEmpty()) {
detailControlsBackground.setVisibility(View.GONE);
detailControlsPopup.setVisibility(View.GONE);
spinnerToolbar.setVisibility(View.GONE);
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_white_24dp);
}
if (autoPlayEnabled) {

View File

@@ -1,15 +1,21 @@
package org.schabi.newpipe.fragments.list;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
@@ -19,8 +25,11 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.StateSaver;
import java.util.List;
@@ -137,7 +146,12 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
onItemSelected(selectedItem);
NavigationHelper.openVideoDetailFragment(
useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(),
selectedItem.service_id, selectedItem.url, selectedItem.name);
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
}
@Override
public void held(StreamInfoItem selectedItem) {
showStreamDialog(selectedItem);
}
});
@@ -147,8 +161,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
onItemSelected(selectedItem);
NavigationHelper.openChannelFragment(
useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(),
selectedItem.service_id, selectedItem.url, selectedItem.name);
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
}
@Override
public void held(ChannelInfoItem selectedItem) {}
});
infoListAdapter.setOnPlaylistSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener<PlaylistInfoItem>() {
@@ -157,8 +174,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
onItemSelected(selectedItem);
NavigationHelper.openPlaylistFragment(
useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(),
selectedItem.service_id, selectedItem.url, selectedItem.name);
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
}
@Override
public void held(PlaylistInfoItem selectedItem) {}
});
itemsList.clearOnScrollListeners();
@@ -176,6 +196,34 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
}
protected void showStreamDialog(final StreamInfoItem item) {
final Context context = getContext();
final Activity activity = getActivity();
if (context == null || context.getResources() == null || getActivity() == null) return;
final String[] commands = new String[]{
context.getResources().getString(R.string.enqueue_on_background),
context.getResources().getString(R.string.enqueue_on_popup)
};
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
break;
default:
break;
}
}
};
new InfoItemDialog(getActivity(), item, commands, actions).show();
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/

View File

@@ -84,17 +84,6 @@ public abstract class BaseListInfoFragment<I extends ListInfo> extends BaseListF
currentNextItemsUrl = (String) savedObjects.poll();
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
public void setTitle(String title) {
Log.d(TAG, "setTitle() called with: title = [" + title + "]");
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setTitle(title);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Load and handle
//////////////////////////////////////////////////////////////////////////*/
@@ -124,20 +113,12 @@ public abstract class BaseListInfoFragment<I extends ListInfo> extends BaseListF
currentWorker = loadResult(forceLoad)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<I>() {
@Override
public void accept(@NonNull I result) throws Exception {
isLoading.set(false);
currentInfo = result;
currentNextItemsUrl = result.next_streams_url;
handleResult(result);
}
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
onError(throwable);
}
});
.subscribe((@NonNull I result) -> {
isLoading.set(false);
currentInfo = result;
currentNextItemsUrl = result.next_streams_url;
handleResult(result);
}, (@NonNull Throwable throwable) -> onError(throwable));
}
/**
@@ -153,18 +134,12 @@ public abstract class BaseListInfoFragment<I extends ListInfo> extends BaseListF
currentWorker = loadMoreItemsLogic()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ListExtractor.NextItemsResult>() {
@Override
public void accept(@io.reactivex.annotations.NonNull ListExtractor.NextItemsResult nextItemsResult) throws Exception {
isLoading.set(false);
handleNextItems(nextItemsResult);
}
}, new Consumer<Throwable>() {
@Override
public void accept(@io.reactivex.annotations.NonNull Throwable throwable) throws Exception {
isLoading.set(false);
onError(throwable);
}
.subscribe((@io.reactivex.annotations.NonNull ListExtractor.NextItemsResult nextItemsResult) -> {
isLoading.set(false);
handleNextItems(nextItemsResult);
}, (@io.reactivex.annotations.NonNull Throwable throwable) -> {
isLoading.set(false);
onError(throwable);
});
}
@@ -190,8 +165,8 @@ public abstract class BaseListInfoFragment<I extends ListInfo> extends BaseListF
public void handleResult(@NonNull I result) {
super.handleResult(result);
url = result.url;
name = result.name;
url = result.getUrl();
name = result.getName();
setTitle(name);
if (infoListAdapter.getItemsList().size() == 0) {

View File

@@ -1,6 +1,8 @@
package org.schabi.newpipe.fragments.list.channel;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -18,6 +20,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.jakewharton.rxbinding2.view.RxView;
@@ -28,12 +31,18 @@ import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.fragments.subscription.SubscriptionService;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.playlist.ChannelPlayQueue;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -68,6 +77,11 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private TextView headerTitleView;
private TextView headerSubscribersTextView;
private Button headerSubscribeButton;
private View playlistCtrl;
private LinearLayout headerPlayAllButton;
private LinearLayout headerPopupButton;
private LinearLayout headerBackgroundButton;
private MenuItem menuRssButton;
@@ -87,11 +101,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
if(activity != null
&& useAsFrontPage
&& isVisibleToUser) {
try {
activity.getSupportActionBar().setTitle(currentInfo.name);
} catch (Exception e) {
onError(e);
}
setTitle(currentInfo != null ? currentInfo.getName() : name);
}
}
@@ -124,10 +134,58 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view);
headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view);
headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button);
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
return headerRootLayout;
}
@Override
protected void showStreamDialog(final StreamInfoItem item) {
final Activity activity = getActivity();
final Context context = getContext();
if (context == null || context.getResources() == null || getActivity() == null) return;
final String[] commands = new String[]{
context.getResources().getString(R.string.enqueue_on_background),
context.getResources().getString(R.string.enqueue_on_popup),
context.getResources().getString(R.string.start_here_on_main),
context.getResources().getString(R.string.start_here_on_background),
context.getResources().getString(R.string.start_here_on_popup),
};
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
break;
case 2:
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
break;
case 3:
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
break;
case 4:
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
break;
default:
break;
}
}
};
new InfoItemDialog(getActivity(), item, commands, actions).show();
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@@ -144,7 +202,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
menuRssButton = menu.findItem(R.id.menu_item_rss);
if (currentInfo != null) {
menuRssButton.setVisible(!TextUtils.isEmpty(currentInfo.feed_url));
menuRssButton.setVisible(!TextUtils.isEmpty(currentInfo.getFeedUrl()));
}
}
@@ -153,23 +211,11 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private void openRssFeed() {
final ChannelInfo info = currentInfo;
if(info != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.feed_url));
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl()));
startActivity(intent);
}
}
private void openChannelUriInBrowser() {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
}
private void shareChannelUri() {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, url);
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@@ -177,10 +223,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
openRssFeed();
break;
case R.id.menu_item_openInBrowser:
openChannelUriInBrowser();
openUrlInBrowser(url);
break;
case R.id.menu_item_share: {
shareChannelUri();
shareUrl(name, url);
break;
}
default:
@@ -200,12 +246,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
@Override
public void accept(Throwable throwable) throws Exception {
animateView(headerSubscribeButton, false, 100);
showSnackBarError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.service_id), "Get subscription status", 0);
showSnackBarError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Get subscription status", 0);
}
};
final Observable<List<SubscriptionEntity>> observable = subscriptionService.subscriptionTable()
.getSubscription(info.service_id, info.url)
.getSubscription(info.getServiceId(), info.getUrl())
.toObservable();
disposables.add(observable
@@ -251,14 +297,14 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
final Action onComplete = new Action() {
@Override
public void run() throws Exception {
if (DEBUG) Log.d(TAG, "Updated subscription: " + info.url);
if (DEBUG) Log.d(TAG, "Updated subscription: " + info.getUrl());
}
};
final Consumer<Throwable> onError = new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(info.service_id), "Updating Subscription for " + info.url, R.string.subscription_update_failed);
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(info.getServiceId()), "Updating Subscription for " + info.getUrl(), R.string.subscription_update_failed);
}
};
@@ -279,7 +325,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
final Consumer<Throwable> onError = new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.service_id), "Subscription Change", R.string.subscription_change_failed);
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Subscription Change", R.string.subscription_change_failed);
}
};
@@ -303,9 +349,9 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
if (subscriptionEntities.isEmpty()) {
if (DEBUG) Log.d(TAG, "No subscription to this channel!");
SubscriptionEntity channel = new SubscriptionEntity();
channel.setServiceId(info.service_id);
channel.setUrl(info.url);
channel.setData(info.name, info.avatar_url, info.description, info.subscriber_count);
channel.setServiceId(info.getServiceId());
channel.setUrl(info.getUrl());
channel.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount());
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel));
} else {
if (DEBUG) Log.d(TAG, "Found subscription to this channel!");
@@ -376,29 +422,63 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
imageLoader.displayImage(result.banner_url, headerChannelBanner, DISPLAY_BANNER_OPTIONS);
imageLoader.displayImage(result.avatar_url, headerAvatarView, DISPLAY_AVATAR_OPTIONS);
if (result.subscriber_count != -1) {
headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.subscriber_count));
if (result.getSubscriberCount() != -1) {
headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.getSubscriberCount()));
headerSubscribersTextView.setVisibility(View.VISIBLE);
} else headerSubscribersTextView.setVisibility(View.GONE);
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.feed_url));
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
playlistCtrl.setVisibility(View.VISIBLE);
if (!result.errors.isEmpty()) {
showSnackBarError(result.errors, UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(result.service_id), result.url, 0);
showSnackBarError(result.errors, UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
}
if (disposables != null) disposables.clear();
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
updateSubscription(result);
monitorSubscription(result);
headerPlayAllButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
NavigationHelper.playOnMainPlayer(activity, getPlayQueue());
}
});
headerPopupButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue());
}
});
headerBackgroundButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue());
}
});
}
private PlayQueue getPlayQueue() {
return getPlayQueue(0);
}
private PlayQueue getPlayQueue(final int index) {
return new ChannelPlayQueue(
currentInfo.getServiceId(),
currentInfo.getUrl(),
currentInfo.getNextStreamsUrl(),
infoListAdapter.getItemsList(),
index
);
}
@Override
public void handleNextItems(ListExtractor.NextItemsResult result) {
super.handleNextItems(result);
if (!result.errors.isEmpty()) {
showSnackBarError(result.errors, UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(serviceId),
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(serviceId),
"Get next page of: " + url, R.string.general_error);
}
}

View File

@@ -297,12 +297,12 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
// Called only when response is non-empty
@Override
public void onSuccess(final ChannelInfo channelInfo) {
if (infoListAdapter == null || channelInfo.related_streams.isEmpty()) {
if (infoListAdapter == null || channelInfo.getRelatedStreams().isEmpty()) {
onDone();
return;
}
final InfoItem item = channelInfo.related_streams.get(0);
final InfoItem item = channelInfo.getRelatedStreams().get(0);
// Keep requesting new items if the current one already exists
boolean itemExists = doesItemExist(infoListAdapter.getItemsList(), item);
if (!itemExists) {
@@ -412,9 +412,9 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
private boolean doesItemExist(final List<InfoItem> items, final InfoItem item) {
for (final InfoItem existingItem : items) {
if (existingItem.info_type == item.info_type &&
existingItem.service_id == item.service_id &&
existingItem.name.equals(item.name) &&
existingItem.url.equals(item.url)) return true;
existingItem.getServiceId() == item.getServiceId() &&
existingItem.getName().equals(item.getName()) &&
existingItem.getUrl().equals(item.getUrl())) return true;
}
return false;
}

View File

@@ -5,6 +5,7 @@ import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -27,6 +28,7 @@ import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.NavigationHelper;
import icepick.State;
import io.reactivex.Single;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -53,16 +55,14 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
private String kioskId = "";
@State
protected String kioskId = "";
protected String kioskTranslatedName;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private View headerRootLayout;
private TextView headerTitleView;
public static KioskFragment getInstance(int serviceId)
throws ExtractionException {
return getInstance(serviceId, NewPipe.getService(serviceId)
@@ -87,20 +87,30 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
kioskTranslatedName = KioskTranslator.getTranslatedKioskName(kioskId, activity);
name = kioskTranslatedName;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(useAsFrontPage && isVisibleToUser) {
if(useAsFrontPage && isVisibleToUser && activity != null) {
try {
activity.getSupportActionBar().setTitle(KioskTranslator.getTranslatedKioskName(kioskId, getActivity()));
setTitle(kioskTranslatedName);
} catch (Exception e) {
onError(e);
onUnrecoverableError(e, UserAction.UI_ERROR,
"none",
"none", R.string.app_ui_crash);
}
}
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_kiosk, container, false);
}
@@ -125,14 +135,18 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
public Single<KioskInfo> loadResult(boolean forceReload) {
String contentCountry = PreferenceManager
.getDefaultSharedPreferences(activity)
.getString(getString(R.string.search_language_key),
getString(R.string.default_language_value));
.getString(getString(R.string.content_country_key),
getString(R.string.default_country_value));
return ExtractorHelper.getKioskInfo(serviceId, url, contentCountry, forceReload);
}
@Override
public Single<ListExtractor.NextItemsResult> loadMoreItemsLogic() {
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextItemsUrl);
String contentCountry = PreferenceManager
.getDefaultSharedPreferences(activity)
.getString(getString(R.string.content_country_key),
getString(R.string.default_country_value));
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextItemsUrl, contentCountry);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -149,14 +163,13 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
public void handleResult(@NonNull final KioskInfo result) {
super.handleResult(result);
String title = KioskTranslator.getTranslatedKioskName(result.id, getActivity());
ActionBar supportActionBar = activity.getSupportActionBar();
supportActionBar.setTitle(title);
name = kioskTranslatedName;
setTitle(kioskTranslatedName);
if (!result.errors.isEmpty()) {
showSnackBarError(result.errors,
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(),
UserAction.REQUESTED_PLAYLIST,
NewPipe.getNameOfService(result.service_id), result.url, 0);
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
}
}
@@ -164,8 +177,8 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
public void handleNextItems(ListExtractor.NextItemsResult result) {
super.handleNextItems(result);
if (!result.errors.isEmpty()) {
showSnackBarError(result.errors,
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(),
UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId)
, "Get next page of: " + url, 0);
}

View File

@@ -1,38 +1,36 @@
package org.schabi.newpipe.fragments.list.playlist;
import android.content.Intent;
import android.os.Build;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.playlist.ExternalPlayQueue;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlaylistPlayQueue;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import io.reactivex.Single;
@@ -50,10 +48,11 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
private TextView headerUploaderName;
private ImageView headerUploaderAvatar;
private TextView headerStreamCount;
private View playlistCtrl;
private Button headerPlayAllButton;
private Button headerPopupButton;
private Button headerBackgroundButton;
private View headerPlayAllButton;
private View headerPopupButton;
private View headerBackgroundButton;
public static PlaylistFragment getInstance(int serviceId, String url, String name) {
PlaylistFragment instance = new PlaylistFragment();
@@ -81,10 +80,11 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
headerUploaderName = headerRootLayout.findViewById(R.id.uploader_name);
headerUploaderAvatar = headerRootLayout.findViewById(R.id.uploader_avatar_view);
headerStreamCount = headerRootLayout.findViewById(R.id.playlist_stream_count);
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_play_all_button);
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_play_popup_button);
headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_play_bg_button);
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
return headerRootLayout;
}
@@ -96,6 +96,49 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
infoListAdapter.useMiniItemVariants(true);
}
@Override
protected void showStreamDialog(final StreamInfoItem item) {
final Context context = getContext();
final Activity activity = getActivity();
if (context == null || context.getResources() == null || getActivity() == null) return;
final String[] commands = new String[]{
context.getResources().getString(R.string.enqueue_on_background),
context.getResources().getString(R.string.enqueue_on_popup),
context.getResources().getString(R.string.start_here_on_main),
context.getResources().getString(R.string.start_here_on_background),
context.getResources().getString(R.string.start_here_on_popup),
};
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
break;
case 2:
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
break;
case 3:
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
break;
case 4:
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
break;
default:
break;
}
}
};
new InfoItemDialog(getActivity(), item, commands, actions).show();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
@@ -117,6 +160,23 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
return ExtractorHelper.getPlaylistInfo(serviceId, url, forceLoad);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_openInBrowser:
openUrlInBrowser(url);
break;
case R.id.menu_item_share: {
shareUrl(name, url);
break;
}
default:
return super.onOptionsItemSelected(item);
}
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@@ -138,69 +198,67 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
animateView(headerRootLayout, true, 100);
animateView(headerUploaderLayout, true, 300);
headerUploaderLayout.setOnClickListener(null);
if (!TextUtils.isEmpty(result.uploader_name)) {
headerUploaderName.setText(result.uploader_name);
if (!TextUtils.isEmpty(result.uploader_url)) {
if (!TextUtils.isEmpty(result.getUploaderName())) {
headerUploaderName.setText(result.getUploaderName());
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
headerUploaderLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NavigationHelper.openChannelFragment(getFragmentManager(), result.service_id, result.uploader_url, result.uploader_name);
NavigationHelper.openChannelFragment(getFragmentManager(), result.getServiceId(), result.getUploaderUrl(), result.getUploaderName());
}
});
}
}
imageLoader.displayImage(result.uploader_avatar_url, headerUploaderAvatar, DISPLAY_AVATAR_OPTIONS);
playlistCtrl.setVisibility(View.VISIBLE);
imageLoader.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar, DISPLAY_AVATAR_OPTIONS);
headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, (int) result.stream_count, (int) result.stream_count));
if (!result.errors.isEmpty()) {
showSnackBarError(result.errors, UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(result.service_id), result.url, 0);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
}
headerPlayAllButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(buildPlaylistIntent(MainVideoPlayer.class));
NavigationHelper.playOnMainPlayer(activity, getPlayQueue());
}
});
headerPopupButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(activity)) {
Toast toast = Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG);
TextView messageView = toast.getView().findViewById(android.R.id.message);
if (messageView != null) messageView.setGravity(Gravity.CENTER);
toast.show();
return;
}
activity.startService(buildPlaylistIntent(PopupVideoPlayer.class));
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue());
}
});
headerBackgroundButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
activity.startService(buildPlaylistIntent(BackgroundPlayer.class));
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue());
}
});
}
private Intent buildPlaylistIntent(final Class targetClazz) {
final PlayQueue playQueue = new ExternalPlayQueue(
currentInfo.service_id,
currentInfo.url,
currentInfo.next_streams_url,
private PlayQueue getPlayQueue() {
return getPlayQueue(0);
}
private PlayQueue getPlayQueue(final int index) {
return new PlaylistPlayQueue(
currentInfo.getServiceId(),
currentInfo.getUrl(),
currentInfo.getNextStreamsUrl(),
infoListAdapter.getItemsList(),
0
index
);
return NavigationHelper.getPlayerIntent(activity, targetClazz, playQueue);
}
@Override
public void handleNextItems(ListExtractor.NextItemsResult result) {
super.handleNextItems(result);
if (!result.errors.isEmpty()) {
showSnackBarError(result.errors, UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId)
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId)
, "Get next page of: " + url, 0);
}
}

View File

@@ -111,7 +111,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
private int currentPage = 0;
private int currentNextPage = 0;
private String searchLanguage;
private String contentCountry;
private boolean isSuggestionsEnabled = true;
private boolean isSearchHistoryEnabled = true;
@@ -165,7 +165,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
suggestionListAdapter = new SuggestionListAdapter(activity);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true);
suggestionListAdapter.setShowSugestinHistory(isSearchHistoryEnabled);
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
searchHistoryDAO = NewPipeDatabase.getInstance().searchHistoryDAO();
}
@@ -176,7 +176,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
isSuggestionsEnabled = preferences.getBoolean(getString(R.string.show_search_suggestions_key), true);
searchLanguage = preferences.getString(getString(R.string.search_language_key), getString(R.string.default_language_value));
contentCountry = preferences.getString(getString(R.string.content_country_key), getString(R.string.default_country_value));
}
@Override
@@ -446,6 +446,12 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
searchEditText.setText(item.query);
}
@Override
public void onSuggestionItemInserted(SuggestionItem item) {
searchEditText.setText(item.query);
searchEditText.setSelection(searchEditText.getText().length());
}
@Override
public void onSuggestionItemLongClick(SuggestionItem item) {
if (item.fromHistory) showDeleteSuggestionDialog(item);
@@ -613,7 +619,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
return local.materialize();
}
final Observable<List<SuggestionItem>> network = ExtractorHelper.suggestionsFor(serviceId, query, searchLanguage).toObservable()
final Observable<List<SuggestionItem>> network = ExtractorHelper.suggestionsFor(serviceId, query, contentCountry).toObservable()
.map(new Function<List<String>, List<SuggestionItem>>() {
@Override
public List<SuggestionItem> apply(@io.reactivex.annotations.NonNull List<String> strings) throws Exception {
@@ -725,7 +731,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
super.startLoading(forceLoad);
if (disposables != null) disposables.clear();
if (searchDisposable != null) searchDisposable.dispose();
searchDisposable = ExtractorHelper.searchFor(serviceId, searchQuery, currentPage, searchLanguage, filter)
searchDisposable = ExtractorHelper.searchFor(serviceId, searchQuery, currentPage, contentCountry, filter)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<SearchResult>() {
@@ -749,7 +755,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
showListFooter(true);
if (searchDisposable != null) searchDisposable.dispose();
currentNextPage = currentPage + 1;
searchDisposable = ExtractorHelper.getMoreSearchItems(serviceId, searchQuery, currentNextPage, searchLanguage, filter)
searchDisposable = ExtractorHelper.getMoreSearchItems(serviceId, searchQuery, currentNextPage, contentCountry, filter)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ListExtractor.NextItemsResult>() {
@@ -855,8 +861,8 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
lastSearchedQuery = searchQuery;
if (infoListAdapter.getItemsList().size() == 0) {
if (result.resultList.size() > 0) {
infoListAdapter.addInfoItemList(result.resultList);
if (!result.getResults().isEmpty()) {
infoListAdapter.addInfoItemList(result.getResults());
} else {
infoListAdapter.clearStreamItemList();
showEmptyState();
@@ -870,11 +876,11 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
@Override
public void handleNextItems(ListExtractor.NextItemsResult result) {
showListFooter(false);
currentPage = Integer.parseInt(result.nextItemsUrl);
infoListAdapter.addInfoItemList(result.nextItemsList);
currentPage = Integer.parseInt(result.getNextItemsUrl());
infoListAdapter.addInfoItemList(result.getNextItemsList());
if (!result.errors.isEmpty()) {
showSnackBarError(result.errors, UserAction.SEARCHED, NewPipe.getNameOfService(serviceId)
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.SEARCHED, NewPipe.getNameOfService(serviceId)
, "\"" + searchQuery + "\" → page " + currentPage, 0);
}
super.handleNextItems(result);

View File

@@ -19,10 +19,11 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
private final ArrayList<SuggestionItem> items = new ArrayList<>();
private final Context context;
private OnSuggestionItemSelected listener;
private boolean showSugestinHistory = true;
private boolean showSuggestionHistory = true;
public interface OnSuggestionItemSelected {
void onSuggestionItemSelected(SuggestionItem item);
void onSuggestionItemInserted(SuggestionItem item);
void onSuggestionItemLongClick(SuggestionItem item);
}
@@ -32,7 +33,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
public void setItems(List<SuggestionItem> items) {
this.items.clear();
if (showSugestinHistory) {
if (showSuggestionHistory) {
this.items.addAll(items);
} else {
// remove history items if history is disabled
@@ -49,8 +50,8 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
this.listener = listener;
}
public void setShowSugestinHistory(boolean v) {
showSugestinHistory = v;
public void setShowSuggestionHistory(boolean v) {
showSuggestionHistory = v;
}
@Override
@@ -62,19 +63,25 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
public void onBindViewHolder(SuggestionItemHolder holder, int position) {
final SuggestionItem currentItem = getItem(position);
holder.updateFrom(currentItem);
holder.itemView.setOnClickListener(new View.OnClickListener() {
holder.queryView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) listener.onSuggestionItemSelected(currentItem);
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
holder.queryView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (listener != null) listener.onSuggestionItemLongClick(currentItem);
return true;
}
});
holder.insertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) listener.onSuggestionItemInserted(currentItem);
}
});
}
private SuggestionItem getItem(int position) {
@@ -93,6 +100,8 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
public static class SuggestionItemHolder extends RecyclerView.ViewHolder {
private final TextView itemSuggestionQuery;
private final ImageView suggestionIcon;
private final View queryView;
private final View insertView;
// Cache some ids, as they can potentially be constantly updated/recycled
private final int historyResId;
@@ -103,6 +112,9 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon);
itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query);
queryView = rootView.findViewById(R.id.suggestion_search);
insertView = rootView.findViewById(R.id.suggestion_insert);
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.history);
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search);
}

View File

@@ -19,8 +19,6 @@ import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.ArrayList;
@@ -131,9 +129,12 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
@Override
public void selected(ChannelInfoItem selectedItem) {
// Requires the parent fragment to find holder for fragment replacement
NavigationHelper.openChannelFragment(getParentFragment().getFragmentManager(), selectedItem.service_id, selectedItem.url, selectedItem.name);
NavigationHelper.openChannelFragment(getParentFragment().getFragmentManager(), selectedItem.getServiceId(), selectedItem.url, selectedItem.getName());
}
@Override
public void held(ChannelInfoItem selectedItem) {}
});
headerRootLayout.setOnClickListener(new View.OnClickListener() {

View File

@@ -112,7 +112,7 @@ public class SubscriptionService {
// Subscriber count changes very often, making this check almost unnecessary.
// Consider removing it later.
if (!isSubscriptionUpToDate(info, subscription)) {
subscription.setData(info.name, info.avatar_url, info.description, info.subscriber_count);
subscription.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount());
return update(subscription);
}
@@ -122,7 +122,7 @@ public class SubscriptionService {
}
};
return subscriptionTable().getSubscription(info.service_id, info.url)
return subscriptionTable().getSubscription(info.getServiceId(), info.getUrl())
.firstOrError()
.flatMapCompletable(update);
}
@@ -137,11 +137,11 @@ public class SubscriptionService {
}
private boolean isSubscriptionUpToDate(final ChannelInfo info, final SubscriptionEntity entity) {
return info.url.equals(entity.getUrl()) &&
info.service_id == entity.getServiceId() &&
info.name.equals(entity.getName()) &&
info.avatar_url.equals(entity.getAvatarUrl()) &&
info.description.equals(entity.getDescription()) &&
info.subscriber_count == entity.getSubscriberCount();
return info.getUrl().equals(entity.getUrl()) &&
info.getServiceId() == entity.getServiceId() &&
info.getName().equals(entity.getName()) &&
info.getAvatarUrl().equals(entity.getAvatarUrl()) &&
info.getDescription().equals(entity.getDescription()) &&
info.getSubscriberCount() == entity.getSubscriberCount();
}
}

View File

@@ -44,6 +44,7 @@ public class InfoItemBuilder {
public interface OnInfoItemSelectedListener<T extends InfoItem> {
void selected(T selectedItem);
void held(T selectedItem);
}
private final Context context;

View File

@@ -0,0 +1,55 @@
package org.schabi.newpipe.info_list;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
public class InfoItemDialog {
private final AlertDialog dialog;
public InfoItemDialog(@NonNull final Activity activity,
@NonNull final StreamInfoItem info,
@NonNull final String[] commands,
@NonNull final DialogInterface.OnClickListener actions) {
this(activity, commands, actions, info.getName(), info.uploader_name);
}
public InfoItemDialog(@NonNull final Activity activity,
@NonNull final String[] commands,
@NonNull final DialogInterface.OnClickListener actions,
@NonNull final String title,
@Nullable final String additionalDetail) {
final LayoutInflater inflater = activity.getLayoutInflater();
final View bannerView = inflater.inflate(R.layout.dialog_title, null);
bannerView.setSelected(true);
TextView titleView = bannerView.findViewById(R.id.itemTitleView);
titleView.setText(title);
TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
if (additionalDetail != null) {
detailsView.setText(additionalDetail);
detailsView.setVisibility(View.VISIBLE);
} else {
detailsView.setVisibility(View.GONE);
}
dialog = new AlertDialog.Builder(activity)
.setCustomTitle(bannerView)
.setItems(commands, actions)
.create();
}
public void show() {
dialog.show();
}
}

View File

@@ -36,7 +36,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
if (!(infoItem instanceof ChannelInfoItem)) return;
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
itemTitleView.setText(item.name);
itemTitleView.setText(item.getName());
itemAdditionalDetailView.setText(getDetailLine(item));
itemBuilder.getImageLoader()

View File

@@ -32,7 +32,7 @@ public class PlaylistInfoItemHolder extends InfoItemHolder {
if (!(infoItem instanceof PlaylistInfoItem)) return;
final PlaylistInfoItem item = (PlaylistInfoItem) infoItem;
itemTitleView.setText(item.name);
itemTitleView.setText(item.getName());
itemStreamCountView.setText(item.stream_count + "");
itemUploaderView.setText(item.uploader_name);

View File

@@ -40,7 +40,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
if (!(infoItem instanceof StreamInfoItem)) return;
final StreamInfoItem item = (StreamInfoItem) infoItem;
itemVideoTitleView.setText(item.name);
itemVideoTitleView.setText(item.getName());
itemUploaderView.setText(item.uploader_name);
if (item.duration > 0) {
@@ -67,6 +67,38 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
}
}
});
switch (item.stream_type) {
case AUDIO_STREAM:
case VIDEO_STREAM:
case FILE:
enableLongClick(item);
break;
case LIVE_STREAM:
case AUDIO_LIVE_STREAM:
case NONE:
default:
disableLongClick();
break;
}
}
private void enableLongClick(final StreamInfoItem item) {
itemView.setLongClickable(true);
itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (itemBuilder.getOnStreamSelectedListener() != null) {
itemBuilder.getOnStreamSelectedListener().held(item);
}
return true;
}
});
}
private void disableLongClick() {
itemView.setLongClickable(false);
itemView.setOnLongClickListener(null);
}
/**

View File

@@ -48,6 +48,7 @@ import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
@@ -64,10 +65,13 @@ public final class BackgroundPlayer extends Service {
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.BackgroundPlayer.CLOSE";
public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE";
public static final String ACTION_OPEN_CONTROLS = "org.schabi.newpipe.player.BackgroundPlayer.OPEN_CONTROLS";
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
public static final String ACTION_PLAY_NEXT = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT";
public static final String ACTION_PLAY_PREVIOUS = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS";
public static final String ACTION_FAST_REWIND = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
public static final String ACTION_FAST_FORWARD = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
public static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
private BasePlayerImpl basePlayerImpl;
private LockManager lockManager;
@@ -130,16 +134,6 @@ public final class BackgroundPlayer extends Service {
/*//////////////////////////////////////////////////////////////////////////
// Actions
//////////////////////////////////////////////////////////////////////////*/
public void openControl(final Context context) {
Intent intent = new Intent(context, BackgroundPlayerActivity.class);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
private void onClose() {
if (DEBUG) Log.d(TAG, "onClose() called");
@@ -182,7 +176,7 @@ public final class BackgroundPlayer extends Service {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true)
.setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCustomContentView(notRemoteView)
.setCustomBigContentView(bigNotRemoteView);
@@ -191,6 +185,8 @@ public final class BackgroundPlayer extends Service {
}
private void setupNotification(RemoteViews remoteViews) {
if (basePlayerImpl == null) return;
remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle());
remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName());
@@ -198,15 +194,29 @@ public final class BackgroundPlayer extends Service {
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationStop,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationContent,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_OPEN_CONTROLS), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationRepeat,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
// Starts background player activity -- attempts to unlock lockscreen
final Intent intent = NavigationHelper.getBackgroundPlayerActivityIntent(this);
remoteViews.setOnClickPendingIntent(R.id.notificationContent,
PendingIntent.getActivity(this, NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT));
if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) {
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_previous);
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_next);
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
} else {
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_rewind);
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_fastforward);
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
}
setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode());
}
@@ -241,17 +251,15 @@ public final class BackgroundPlayer extends Service {
//////////////////////////////////////////////////////////////////////////*/
private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) {
final String methodName = "setImageResource";
switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_off);
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_off);
break;
case Player.REPEAT_MODE_ONE:
remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_one);
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_one);
break;
case Player.REPEAT_MODE_ALL:
remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_all);
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_all);
break;
}
}
@@ -372,6 +380,7 @@ public final class BackgroundPlayer extends Service {
@Override
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
if (currentItem == item && currentInfo == info) return;
super.sync(item, info);
resetNotification();
@@ -380,12 +389,13 @@ public final class BackgroundPlayer extends Service {
}
@Override
@Nullable
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
final int index = ListHelper.getDefaultAudioFormat(context, info.audio_streams);
if (index < 0) return null;
if (index < 0 || index >= info.audio_streams.size()) return null;
final AudioStream audio = info.audio_streams.get(index);
return buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format));
return buildMediaSource(audio.getUrl(), MediaFormat.getSuffixById(audio.getFormatId()));
}
@Override
@@ -445,10 +455,11 @@ public final class BackgroundPlayer extends Service {
super.setupBroadcastReceiver(intentFilter);
intentFilter.addAction(ACTION_CLOSE);
intentFilter.addAction(ACTION_PLAY_PAUSE);
intentFilter.addAction(ACTION_OPEN_CONTROLS);
intentFilter.addAction(ACTION_REPEAT);
intentFilter.addAction(ACTION_PLAY_PREVIOUS);
intentFilter.addAction(ACTION_PLAY_NEXT);
intentFilter.addAction(ACTION_FAST_REWIND);
intentFilter.addAction(ACTION_FAST_FORWARD);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
@@ -468,9 +479,6 @@ public final class BackgroundPlayer extends Service {
case ACTION_PLAY_PAUSE:
onVideoPlayPause();
break;
case ACTION_OPEN_CONTROLS:
openControl(getApplicationContext());
break;
case ACTION_REPEAT:
onRepeatClicked();
break;
@@ -480,6 +488,12 @@ public final class BackgroundPlayer extends Service {
case ACTION_PLAY_PREVIOUS:
onPlayPrevious();
break;
case ACTION_FAST_FORWARD:
onFastForward();
break;
case ACTION_FAST_REWIND:
onFastRewind();
break;
case Intent.ACTION_SCREEN_ON:
onScreenOnOff(true);
break;

View File

@@ -1,8 +1,12 @@
package org.schabi.newpipe.player;
import android.content.Intent;
import android.view.MenuItem;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.PermissionHelper;
import static org.schabi.newpipe.player.BackgroundPlayer.ACTION_CLOSE;
public final class BackgroundPlayerActivity extends ServicePlayerActivity {
@@ -36,4 +40,31 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
((BackgroundPlayer.BasePlayerImpl) player).removeActivityListener(this);
}
}
@Override
public int getPlayerOptionMenuResource() {
return R.menu.menu_play_queue_bg;
}
@Override
public boolean onPlayerOptionSelected(MenuItem item) {
if (item.getItemId() == R.id.action_switch_popup) {
if (!PermissionHelper.isPopupEnabled(this)) {
PermissionHelper.showPopupEnablementToast(this);
return true;
}
this.player.setRecovery();
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
getApplicationContext().startService(getSwitchIntent(PopupVideoPlayer.class));
return true;
}
return false;
}
@Override
public Intent getPlayerShutdownIntent() {
return new Intent(ACTION_CLOSE);
}
}

View File

@@ -26,6 +26,7 @@ import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
@@ -76,7 +77,6 @@ import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Predicate;
@@ -111,6 +111,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
public static final String PLAYBACK_QUALITY = "playback_quality";
public static final String PLAY_QUEUE = "play_queue";
public static final String APPEND_ONLY = "append_only";
public static final String SELECT_ON_APPEND = "select_on_append";
/*//////////////////////////////////////////////////////////////////////////
// Playback
@@ -134,6 +135,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
protected final static int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
protected final static int PLAY_PREV_ACTIVATION_LIMIT = 5000; // 5 seconds
protected final static int PROGRESS_LOOP_INTERVAL = 500;
protected final static int RECOVERY_SKIP_THRESHOLD = 3000; // 3 seconds
protected SimpleExoPlayer simpleExoPlayer;
protected AudioReactor audioReactor;
@@ -193,7 +195,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
.observeOn(AndroidSchedulers.mainThread())
.filter(new Predicate<Long>() {
@Override
public boolean test(@NonNull Long aLong) throws Exception {
public boolean test(Long aLong) throws Exception {
return isProgressLoopRunning();
}
})
@@ -217,7 +219,13 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
// Resolve append intents
if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) {
int sizeBeforeAppend = playQueue.size();
playQueue.append(queue.getStreams());
if (intent.getBooleanExtra(SELECT_ON_APPEND, false) && queue.getStreams().size() > 0) {
playQueue.setIndex(sizeBeforeAppend);
}
return;
}
@@ -235,7 +243,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
initPlayback(queue);
}
protected void initPlayback(@NonNull final PlayQueue queue) {
protected void initPlayback(final PlayQueue queue) {
playQueue = queue;
playQueue.init();
playbackManager = new MediaSourceManager(this, playQueue);
@@ -453,16 +461,20 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
final PlayQueueItem currentSourceItem = playQueue.getItem();
// Check if already playing correct window
final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
final boolean isCurrentWindowCorrect =
simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
// Check if recovering
if (isCurrentWindowCorrect && currentSourceItem != null &&
currentSourceItem.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
if (isCurrentWindowCorrect && currentSourceItem != null) {
/* Recovering with sub-second position may cause a long buffer delay in ExoPlayer,
* rounding this position to the nearest second will help alleviate this.*/
final long position = currentSourceItem.getRecoveryPosition();
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)position));
/* Skip recovering if the recovery position is not set.*/
if (position == PlayQueueItem.RECOVERY_UNSET) return;
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex +
" at: " + getTimeString((int)position));
simpleExoPlayer.seekTo(currentSourceItem.getRecoveryPosition());
playQueue.unsetRecovery(currentSourceIndex);
}
@@ -515,7 +527,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
break;
case Player.STATE_READY: //3
recover();
if (!isPrepared) {
isPrepared = true;
onPrepared(playWhenReady);
@@ -545,14 +556,18 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
* an error to the play queue based on if the current error can be skipped.
*
* This is done because ExoPlayer reports the source exceptions before window is
* transitioned on seamless playback.
* transitioned on seamless playback. Because player error causes ExoPlayer to go
* back to {@link Player#STATE_IDLE STATE_IDLE}, we reset and prepare the media source
* again to resume playback.
*
* Because player error causes ExoPlayer to go back to {@link Player#STATE_IDLE STATE_IDLE},
* we reset and prepare the media source again to resume playback.<br><br>
* In the event that this error is produced during a valid stream playback, we save the
* current position so the playback may be recovered and resumed manually by the user. This
* happens only if the playback is {@link #RECOVERY_SKIP_THRESHOLD} milliseconds until complete.
* <br><br>
*
* {@link ExoPlaybackException#TYPE_UNEXPECTED TYPE_UNEXPECTED}: <br><br>
* If a runtime error occurred, then we can try to recover it by restarting the playback
* after setting the timestamp recovery.
* after setting the timestamp recovery. <br><br>
*
* {@link ExoPlaybackException#TYPE_RENDERER TYPE_RENDERER}: <br><br>
* If the renderer failed, treat the error as unrecoverable.
@@ -569,6 +584,10 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
switch (error.type) {
case ExoPlaybackException.TYPE_SOURCE:
if (simpleExoPlayer.getCurrentPosition() <
simpleExoPlayer.getDuration() - RECOVERY_SKIP_THRESHOLD) {
setRecovery();
}
playQueue.error(isCurrentWindowValid());
showStreamError(error);
break;
@@ -591,12 +610,12 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with window index = [" + newWindowIndex + "]");
// If the user selects a new track, then the discontinuity occurs after the index is changed.
// Therefore, the only source that causes a discrepancy would be autoplay,
// Therefore, the only source that causes a discrepancy would be gapless transition,
// which can only offset the current track by +1.
if (newWindowIndex != playQueue.getIndex() && playbackManager != null) {
if (newWindowIndex == playQueue.getIndex() + 1) {
playQueue.offsetIndex(+1);
playbackManager.load();
}
playbackManager.load();
}
@Override
@@ -613,6 +632,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Blocking...");
currentItem = null;
currentInfo = null;
simpleExoPlayer.stop();
isPrepared = false;
@@ -631,17 +652,21 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
}
@Override
public void sync(@android.support.annotation.NonNull final PlayQueueItem item,
public void sync(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info) {
if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Syncing...");
if (currentItem == item && currentInfo == info) return;
currentItem = item;
currentInfo = info;
if (DEBUG) Log.d(TAG, "Syncing...");
if (simpleExoPlayer == null) return;
// Check if on wrong window
final int currentSourceIndex = playQueue.getIndex();
if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex) {
final int currentSourceIndex = playQueue.indexOf(item);
if (currentSourceIndex != playQueue.getIndex()) {
Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex +
"], queue index=[" + playQueue.getIndex() + "]");
} else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) {
final long startPos = info != null ? info.start_position : 0;
if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos));
simpleExoPlayer.seekTo(currentSourceIndex, startPos);
@@ -756,10 +781,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
} else {
playQueue.setIndex(index);
}
if (!isPlaying()) {
onVideoPlayPause();
}
}
public void seekBy(int milliSeconds) {
@@ -826,15 +847,15 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
}
public String getVideoUrl() {
return currentItem == null ? null : currentItem.getUrl();
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUrl();
}
public String getVideoTitle() {
return currentItem == null ? null : currentItem.getTitle();
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getTitle();
}
public String getUploaderName() {
return currentItem == null ? null : currentItem.getUploader();
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader();
}
public boolean isCompleted() {
@@ -870,8 +891,10 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
}
public PlaybackParameters getPlaybackParameters() {
final PlaybackParameters defaultParameters = new PlaybackParameters(1f, 1f);
if (simpleExoPlayer == null) return defaultParameters;
final PlaybackParameters parameters = simpleExoPlayer.getPlaybackParameters();
return parameters == null ? new PlaybackParameters(1f, 1f) : parameters;
return parameters == null ? defaultParameters : parameters;
}
public void setPlaybackParameters(float speed, float pitch) {
@@ -900,7 +923,9 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
final int queuePos = playQueue.getIndex();
final long windowPos = simpleExoPlayer.getCurrentPosition();
setRecovery(queuePos, windowPos);
if (windowPos > 0 && windowPos <= simpleExoPlayer.getDuration()) {
setRecovery(queuePos, windowPos);
}
}
public void setRecovery(final int queuePos, final long windowPos) {

View File

@@ -22,11 +22,15 @@ package org.schabi.newpipe.player;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Color;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
@@ -48,6 +52,7 @@ import com.google.android.exoplayer2.Player;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
@@ -56,6 +61,7 @@ import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.PopupMenuIconHacker;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.List;
@@ -70,12 +76,15 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
public final class MainVideoPlayer extends Activity {
private static final String TAG = ".MainVideoPlayer";
private static final boolean DEBUG = BasePlayer.DEBUG;
private static final String PLAYER_STATE_INTENT = "player_state_intent";
private GestureDetector gestureDetector;
private boolean activityPaused;
private VideoPlayerImpl playerImpl;
private SharedPreferences defaultPreferences;
/*//////////////////////////////////////////////////////////////////////////
// Activity LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@@ -84,11 +93,19 @@ public final class MainVideoPlayer extends Activity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(this);
ThemeHelper.setTheme(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) getWindow().setStatusBarColor(Color.BLACK);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
if (getIntent() == null) {
final Intent intent;
if (savedInstanceState != null && savedInstanceState.getParcelable(PLAYER_STATE_INTENT) != null) {
intent = savedInstanceState.getParcelable(PLAYER_STATE_INTENT);
} else {
intent = getIntent();
}
if (intent == null) {
Toast.makeText(this, R.string.general_error, Toast.LENGTH_SHORT).show();
finish();
return;
@@ -98,7 +115,24 @@ public final class MainVideoPlayer extends Activity {
setContentView(R.layout.activity_main_player);
playerImpl = new VideoPlayerImpl(this);
playerImpl.setup(findViewById(android.R.id.content));
playerImpl.handleIntent(getIntent());
playerImpl.handleIntent(intent);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (this.playerImpl == null) return;
final Intent intent = NavigationHelper.getPlayerIntent(
getApplicationContext(),
this.getClass(),
this.playerImpl.getPlayQueue(),
this.playerImpl.getRepeatMode(),
this.playerImpl.getPlaybackSpeed(),
this.playerImpl.getPlaybackPitch(),
this.playerImpl.getPlaybackQuality()
);
outState.putParcelable(PLAYER_STATE_INTENT, intent);
}
@Override
@@ -141,6 +175,11 @@ public final class MainVideoPlayer extends Activity {
activityPaused = false;
}
if(globalScreenOrientationLocked()) {
boolean lastOrientationWasLandscape
= defaultPreferences.getBoolean(getString(R.string.last_orientation_landscape_key), false);
setLandScape(lastOrientationWasLandscape);
}
}
@Override
@@ -150,6 +189,17 @@ public final class MainVideoPlayer extends Activity {
if (playerImpl != null) playerImpl.destroy();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (playerImpl.isSomePopupMenuVisible()) {
playerImpl.moreOptionsPopupMenu.dismiss();
playerImpl.getQualityPopupMenu().dismiss();
playerImpl.getPlaybackSpeedPopupMenu().dismiss();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@@ -182,11 +232,28 @@ public final class MainVideoPlayer extends Activity {
}
private void toggleOrientation() {
setRequestedOrientation(getResources().getDisplayMetrics().heightPixels > getResources().getDisplayMetrics().widthPixels
setLandScape(!isLandScape());
defaultPreferences.edit()
.putBoolean(getString(R.string.last_orientation_landscape_key), !isLandScape())
.apply();
}
private boolean isLandScape() {
return getResources().getDisplayMetrics().heightPixels < getResources().getDisplayMetrics().widthPixels;
}
private void setLandScape(boolean v) {
setRequestedOrientation(v
? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
: ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
}
private boolean globalScreenOrientationLocked() {
// 1: Screen orientation changes using acelerometer
// 0: Screen orientatino is locked
return !(android.provider.Settings.System.getInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 1);
}
protected void setRepeatModeButton(final ImageButton imageButton, final int repeatMode) {
switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
@@ -222,7 +289,6 @@ public final class MainVideoPlayer extends Activity {
private ImageButton repeatButton;
private ImageButton shuffleButton;
private ImageButton screenRotationButton;
private ImageButton playPauseButton;
private ImageButton playPreviousButton;
private ImageButton playNextButton;
@@ -234,6 +300,10 @@ public final class MainVideoPlayer extends Activity {
private boolean queueVisible;
private ImageButton moreOptionsButton;
public int moreOptionsPopupMenuGroupId = 89;
public PopupMenu moreOptionsPopupMenu;
VideoPlayerImpl(final Context context) {
super("VideoPlayerImpl" + MainVideoPlayer.TAG, context);
}
@@ -249,10 +319,12 @@ public final class MainVideoPlayer extends Activity {
this.repeatButton = rootView.findViewById(R.id.repeatButton);
this.shuffleButton = rootView.findViewById(R.id.shuffleButton);
this.screenRotationButton = rootView.findViewById(R.id.screenRotationButton);
this.playPauseButton = rootView.findViewById(R.id.playPauseButton);
this.playPreviousButton = rootView.findViewById(R.id.playPreviousButton);
this.playNextButton = rootView.findViewById(R.id.playNextButton);
this.moreOptionsButton = rootView.findViewById(R.id.moreOptionsButton);
this.moreOptionsPopupMenu = new PopupMenu(context, moreOptionsButton);
buildMoreOptionsMenu();
titleTextView.setSelected(true);
channelTextView.setSelected(true);
@@ -276,7 +348,7 @@ public final class MainVideoPlayer extends Activity {
playPauseButton.setOnClickListener(this);
playPreviousButton.setOnClickListener(this);
playNextButton.setOnClickListener(this);
screenRotationButton.setOnClickListener(this);
moreOptionsButton.setOnClickListener(this);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -305,7 +377,7 @@ public final class MainVideoPlayer extends Activity {
titleTextView.setText(getVideoTitle());
channelTextView.setText(getUploaderName());
playPauseButton.setImageResource(R.drawable.ic_pause_white);
//playPauseButton.setImageResource(R.drawable.ic_pause_white);
}
@Override
@@ -325,9 +397,8 @@ public final class MainVideoPlayer extends Activity {
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
if (simpleExoPlayer == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !PermissionHelper.checkSystemAlertWindowPermission(MainVideoPlayer.this)) {
Toast.makeText(MainVideoPlayer.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
if (!PermissionHelper.isPopupEnabled(context)) {
PermissionHelper.showPopupEnablementToast(context);
return;
}
@@ -348,6 +419,28 @@ public final class MainVideoPlayer extends Activity {
finish();
}
public void onPlayBackgroundButtonClicked() {
if (DEBUG) Log.d(TAG, "onPlayBackgroundButtonClicked() called");
if (playerImpl.getPlayer() == null) return;
setRecovery();
final Intent intent = NavigationHelper.getPlayerIntent(
context,
BackgroundPlayer.class,
this.getPlayQueue(),
this.getRepeatMode(),
this.getPlaybackSpeed(),
this.getPlaybackPitch(),
this.getPlaybackQuality()
);
context.startService(intent);
((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
destroy();
finish();
}
@Override
public void onClick(View v) {
super.onClick(v);
@@ -360,9 +453,6 @@ public final class MainVideoPlayer extends Activity {
} else if (v.getId() == playNextButton.getId()) {
onPlayNext();
} else if (v.getId() == screenRotationButton.getId()) {
onScreenRotationClicked();
} else if (v.getId() == queueButton.getId()) {
onQueueClicked();
return;
@@ -372,16 +462,15 @@ public final class MainVideoPlayer extends Activity {
} else if (v.getId() == shuffleButton.getId()) {
onShuffleClicked();
return;
} else if (v.getId() == moreOptionsButton.getId()) {
onMoreOptionsClicked();
}
if (getCurrentState() != STATE_COMPLETED) {
getControlsVisibilityHandler().removeCallbacksAndMessages(null);
animateView(getControlsRoot(), true, 300, 0, new Runnable() {
@Override
public void run() {
if (getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible()) {
hideControls(300, DEFAULT_CONTROLS_HIDE_TIME);
}
animateView(getControlsRoot(), true, 300, 0, () -> {
if (getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible()) {
hideControls(300, DEFAULT_CONTROLS_HIDE_TIME);
}
});
}
@@ -397,7 +486,7 @@ public final class MainVideoPlayer extends Activity {
getControlsRoot().setVisibility(View.INVISIBLE);
queueLayout.setVisibility(View.VISIBLE);
itemsList.smoothScrollToPosition(playQueue.getIndex());
itemsList.scrollToPosition(playQueue.getIndex());
}
private void onQueueClosed() {
@@ -405,9 +494,18 @@ public final class MainVideoPlayer extends Activity {
queueVisible = false;
}
private void onMoreOptionsClicked() {
if (DEBUG) Log.d(TAG, "onMoreOptionsClicked() called");
moreOptionsPopupMenu.show();
isSomePopupMenuVisible = true;
showControls(300);
}
private void onScreenRotationClicked() {
if (DEBUG) Log.d(TAG, "onScreenRotationClicked() called");
toggleOrientation();
showControlsThenHide();
}
@Override
@@ -463,12 +561,9 @@ public final class MainVideoPlayer extends Activity {
@Override
public void onPlaying() {
super.onPlaying();
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, new Runnable() {
@Override
public void run() {
playPauseButton.setImageResource(R.drawable.ic_pause_white);
animatePlayButtons(true, 200);
}
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
playPauseButton.setImageResource(R.drawable.ic_pause_white);
animatePlayButtons(true, 200);
});
showSystemUi();
getRootView().setKeepScreenOn(true);
@@ -477,12 +572,9 @@ public final class MainVideoPlayer extends Activity {
@Override
public void onPaused() {
super.onPaused();
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, new Runnable() {
@Override
public void run() {
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
animatePlayButtons(true, 200);
}
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
animatePlayButtons(true, 200);
});
showSystemUi();
@@ -500,12 +592,9 @@ public final class MainVideoPlayer extends Activity {
@Override
public void onCompleted() {
showSystemUi();
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, new Runnable() {
@Override
public void run() {
playPauseButton.setImageResource(R.drawable.ic_replay_white);
animatePlayButtons(true, 300);
}
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, () -> {
playPauseButton.setImageResource(R.drawable.ic_replay_white);
animatePlayButtons(true, 300);
});
getRootView().setKeepScreenOn(false);
@@ -534,17 +623,10 @@ public final class MainVideoPlayer extends Activity {
public void hideControls(final long duration, long delay) {
if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
getControlsVisibilityHandler().removeCallbacksAndMessages(null);
getControlsVisibilityHandler().postDelayed(new Runnable() {
@Override
public void run() {
animateView(getControlsRoot(), false, duration, 0, new Runnable() {
@Override
public void run() {
hideSystemUi();
}
});
}
}, delay);
getControlsVisibilityHandler().postDelayed(() ->
animateView(getControlsRoot(), false, duration, 0, MainVideoPlayer.this::hideSystemUi),
delay
);
}
private void updatePlaybackButtons() {
@@ -555,6 +637,42 @@ public final class MainVideoPlayer extends Activity {
setShuffleButton(shuffleButton, playQueue.isShuffled());
}
private void buildMoreOptionsMenu() {
this.moreOptionsPopupMenu.getMenuInflater().inflate(R.menu.menu_videooptions,
moreOptionsPopupMenu.getMenu());
moreOptionsPopupMenu.setOnMenuItemClickListener(menuItem -> {
switch (menuItem.getItemId()) {
case R.id.toggleOrientation:
onScreenRotationClicked();
break;
case R.id.switchPopup:
onFullScreenButtonClicked();
break;
case R.id.switchBackground:
onPlayBackgroundButtonClicked();
break;
}
return false;
});
try {
PopupMenuIconHacker.setShowPopupIcon(moreOptionsPopupMenu);
} catch (Exception e) {
e.printStackTrace();
}
// fix icon theme
if(ThemeHelper.isLightThemeSelected(MainVideoPlayer.this)) {
moreOptionsPopupMenu.getMenu()
.findItem(R.id.toggleOrientation)
.setIcon(R.drawable.ic_screen_rotation_black_24dp);
moreOptionsPopupMenu.getMenu()
.findItem(R.id.switchPopup)
.setIcon(R.drawable.ic_fullscreen_exit_black_24dp);
}
}
private void buildQueue() {
queueLayout = findViewById(R.id.playQueuePanel);
@@ -565,17 +683,28 @@ public final class MainVideoPlayer extends Activity {
itemsList.setClickable(true);
itemsList.setLongClickable(true);
itemsList.clearOnScrollListeners();
itemsList.addOnScrollListener(getQueueScrollListener());
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
itemTouchHelper.attachToRecyclerView(itemsList);
playQueueAdapter.setSelectedListener(getOnSelectedListener());
itemsListCloseButton.setOnClickListener(new View.OnClickListener() {
itemsListCloseButton.setOnClickListener(view -> onQueueClosed());
}
private OnScrollBelowItemsListener getQueueScrollListener() {
return new OnScrollBelowItemsListener() {
@Override
public void onClick(View view) {
onQueueClosed();
public void onScrolledDown(RecyclerView recyclerView) {
if (playQueue != null && !playQueue.isComplete()) {
playQueue.fetch();
} else if (itemsList != null) {
itemsList.clearOnScrollListeners();
}
}
});
};
}
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
@@ -785,4 +914,4 @@ public final class MainVideoPlayer extends Activity {
}
}
}
}

View File

@@ -31,7 +31,6 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
@@ -44,47 +43,30 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.PopupMenu;
import android.widget.RemoteViews;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.old.PlayVideoActivity;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.old.PlayVideoActivity;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.IOException;
import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.player.helper.PlayerHelper.isUsingOldPlayer;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -96,12 +78,10 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
public final class PopupVideoPlayer extends Service {
private static final String TAG = ".PopupVideoPlayer";
private static final boolean DEBUG = BasePlayer.DEBUG;
private static final int SHUTDOWN_FLING_VELOCITY = 10000;
private static final int NOTIFICATION_ID = 40028922;
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.PopupVideoPlayer.PLAY_PAUSE";
public static final String ACTION_OPEN_CONTROLS = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_CONTROLS";
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
private static final String POPUP_SAVED_WIDTH = "popup_saved_width";
@@ -112,6 +92,9 @@ public final class PopupVideoPlayer extends Service {
private WindowManager.LayoutParams windowLayoutParams;
private GestureDetector gestureDetector;
private int shutdownFlingVelocity;
private int tossFlingVelocity;
private float screenWidth, screenHeight;
private float popupWidth, popupHeight;
@@ -123,8 +106,8 @@ public final class PopupVideoPlayer extends Service {
private RemoteViews notRemoteView;
private VideoPlayerImpl playerImpl;
private Disposable currentWorker;
private LockManager lockManager;
/*//////////////////////////////////////////////////////////////////////////
// Service-Activity Binder
//////////////////////////////////////////////////////////////////////////*/
@@ -155,31 +138,8 @@ public final class PopupVideoPlayer extends Service {
if (playerImpl.getPlayer() == null) initPopup();
if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true);
if (intent != null && intent.getStringExtra(Constants.KEY_URL) != null) {
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
final String url = intent.getStringExtra(Constants.KEY_URL);
playerImpl.handleIntent(intent);
playerImpl.setStartedFromNewPipe(false);
final FetcherHandler fetcherRunnable = new FetcherHandler(this, serviceId, url);
currentWorker = ExtractorHelper.getStreamInfo(serviceId,url,false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<StreamInfo>() {
@Override
public void accept(@NonNull StreamInfo info) throws Exception {
fetcherRunnable.onReceive(info);
}
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
fetcherRunnable.onError(throwable);
}
});
} else {
playerImpl.setStartedFromNewPipe(true);
playerImpl.handleIntent(intent);
}
return START_NOT_STICKY;
}
@@ -211,12 +171,14 @@ public final class PopupVideoPlayer extends Service {
View rootView = View.inflate(this, R.layout.player_popup, null);
playerImpl.setup(rootView);
shutdownFlingVelocity = PlayerHelper.getShutdownFlingVelocity(this);
tossFlingVelocity = PlayerHelper.getTossFlingVelocity(this);
updateScreenSize();
final boolean popupRememberSizeAndPos = PlayerHelper.isRememberingPopupDimensions(this);
final float defaultSize = getResources().getDimension(R.dimen.popup_default_width);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean popupRememberSizeAndPos = sharedPreferences.getBoolean(getString(R.string.popup_remember_size_pos_key), true);
float defaultSize = getResources().getDimension(R.dimen.popup_default_width);
popupWidth = popupRememberSizeAndPos ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize;
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_PHONE : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -261,16 +223,19 @@ public final class PopupVideoPlayer extends Service {
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
notRemoteView.setOnClickPendingIntent(R.id.notificationStop,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
notRemoteView.setOnClickPendingIntent(R.id.notificationContent,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_OPEN_CONTROLS), PendingIntent.FLAG_UPDATE_CURRENT));
notRemoteView.setOnClickPendingIntent(R.id.notificationRepeat,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
// Starts popup player activity -- attempts to unlock lockscreen
final Intent intent = NavigationHelper.getPopupPlayerActivityIntent(this);
notRemoteView.setOnClickPendingIntent(R.id.notificationContent,
PendingIntent.getActivity(this, NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT));
setRepeatModeRemote(notRemoteView, playerImpl.getRepeatMode());
return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true)
.setSmallIcon(R.drawable.ic_play_arrow_white)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContent(notRemoteView);
}
@@ -305,7 +270,6 @@ public final class PopupVideoPlayer extends Service {
}
if (lockManager != null) lockManager.releaseWifiAndCpu();
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
if (currentWorker != null) currentWorker.dispose();
mBinder = null;
playerImpl = null;
@@ -313,15 +277,6 @@ public final class PopupVideoPlayer extends Service {
stopSelf();
}
public void openControl(final Context context) {
Intent intent = new Intent(context, PopupVideoPlayerActivity.class);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@@ -366,6 +321,7 @@ public final class PopupVideoPlayer extends Service {
}
private void updatePopupSize(int width, int height) {
if (playerImpl == null) return;
if (DEBUG) Log.d(TAG, "updatePopupSize() called with: width = [" + width + "], height = [" + height + "]");
width = (int) (width > maximumWidth ? maximumWidth : width < minimumWidth ? minimumWidth : width);
@@ -404,6 +360,7 @@ public final class PopupVideoPlayer extends Service {
protected class VideoPlayerImpl extends VideoPlayer {
private TextView resizingIndicator;
private ImageButton fullScreenButton;
@Override
public void handleIntent(Intent intent) {
@@ -421,6 +378,8 @@ public final class PopupVideoPlayer extends Service {
public void initViews(View rootView) {
super.initViews(rootView);
resizingIndicator = rootView.findViewById(R.id.resizing_indicator);
fullScreenButton = rootView.findViewById(R.id.fullScreenButton);
fullScreenButton.setOnClickListener(v -> onFullScreenButtonClicked());
}
@Override
@@ -460,12 +419,11 @@ public final class PopupVideoPlayer extends Service {
this.getPlaybackPitch(),
this.getPlaybackQuality()
);
if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else {
intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
.putExtra(PlayVideoActivity.VIDEO_TITLE, getVideoTitle())
.putExtra(PlayVideoActivity.STREAM_URL, getSelectedVideoStream().url)
.putExtra(PlayVideoActivity.STREAM_URL, getSelectedVideoStream().getUrl())
.putExtra(PlayVideoActivity.VIDEO_URL, getVideoUrl())
.putExtra(PlayVideoActivity.START_POSITION, Math.round(getPlayer().getCurrentPosition() / 1000f));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -577,6 +535,7 @@ public final class PopupVideoPlayer extends Service {
@Override
public void sync(@NonNull PlayQueueItem item, @Nullable StreamInfo info) {
if (currentItem == item && currentInfo == info) return;
super.sync(item, info);
updateMetadata();
}
@@ -597,7 +556,6 @@ public final class PopupVideoPlayer extends Service {
if (DEBUG) Log.d(TAG, "setupBroadcastReceiver() called with: intentFilter = [" + intentFilter + "]");
intentFilter.addAction(ACTION_CLOSE);
intentFilter.addAction(ACTION_PLAY_PAUSE);
intentFilter.addAction(ACTION_OPEN_CONTROLS);
intentFilter.addAction(ACTION_REPEAT);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
@@ -616,9 +574,6 @@ public final class PopupVideoPlayer extends Service {
case ACTION_PLAY_PAUSE:
onVideoPlayPause();
break;
case ACTION_OPEN_CONTROLS:
openControl(getApplicationContext());
break;
case ACTION_REPEAT:
onRepeatClicked();
break;
@@ -791,11 +746,20 @@ public final class PopupVideoPlayer extends Service {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (DEBUG) Log.d(TAG, "Fling velocity: dX=[" + velocityX + "], dY=[" + velocityY + "]");
if (playerImpl == null) return false;
if (Math.abs(velocityX) > SHUTDOWN_FLING_VELOCITY) {
if (DEBUG) Log.d(TAG, "Popup close fling velocity= " + velocityX);
final float absVelocityX = Math.abs(velocityX);
final float absVelocityY = Math.abs(velocityY);
if (absVelocityX > shutdownFlingVelocity) {
onClose();
return true;
} else if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
if (absVelocityX > tossFlingVelocity) windowLayoutParams.x = (int) velocityX;
if (absVelocityY > tossFlingVelocity) windowLayoutParams.y = (int) velocityY;
checkPositionBounds();
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
return true;
}
return false;
}
@@ -835,6 +799,8 @@ public final class PopupVideoPlayer extends Service {
}
savePositionAndSize();
}
v.performClick();
return true;
}
@@ -862,67 +828,4 @@ public final class PopupVideoPlayer extends Service {
return true;
}
}
/**
* Fetcher handler used if open by a link out of NewPipe
*/
private class FetcherHandler {
private final int serviceId;
private final String url;
private final Context context;
private final Handler mainHandler;
FetcherHandler(Context context, int serviceId, String url) {
this.mainHandler = new Handler(PopupVideoPlayer.this.getMainLooper());
this.context = context;
this.url = url;
this.serviceId = serviceId;
}
/*package-private*/ void onReceive(final StreamInfo info) {
mainHandler.post(new Runnable() {
@Override
public void run() {
playerImpl.initPlayback(new SinglePlayQueue(info));
}
});
}
protected void onError(final Throwable exception) {
if (DEBUG) Log.d(TAG, "onError() called with: exception = [" + exception + "]");
exception.printStackTrace();
mainHandler.post(new Runnable() {
@Override
public void run() {
if (exception instanceof ReCaptchaException) {
onReCaptchaException();
} else if (exception instanceof IOException) {
Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show();
} else if (exception instanceof YoutubeStreamExtractor.GemaException) {
Toast.makeText(context, R.string.blocked_by_gema, Toast.LENGTH_LONG).show();
} else if (exception instanceof YoutubeStreamExtractor.LiveStreamException) {
Toast.makeText(context, R.string.live_streams_not_supported, Toast.LENGTH_LONG).show();
} else if (exception instanceof ContentNotAvailableException) {
Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show();
} else {
int errorId = exception instanceof YoutubeStreamExtractor.DecryptException ? R.string.youtube_signature_decryption_error :
exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error;
ErrorActivity.reportError(mainHandler, context, exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(serviceId), url, errorId));
}
}
});
stopSelf();
}
/*package-private*/ void onReCaptchaException() {
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
Intent intent = new Intent(context, ReCaptchaActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
stopSelf();
}
}
}

View File

@@ -1,9 +1,12 @@
package org.schabi.newpipe.player;
import android.content.Intent;
import android.view.MenuItem;
import org.schabi.newpipe.R;
import static org.schabi.newpipe.player.PopupVideoPlayer.ACTION_CLOSE;
public final class PopupVideoPlayerActivity extends ServicePlayerActivity {
private static final String TAG = "PopupVideoPlayerActivity";
@@ -36,4 +39,25 @@ public final class PopupVideoPlayerActivity extends ServicePlayerActivity {
((PopupVideoPlayer.VideoPlayerImpl) player).removeActivityListener(this);
}
}
@Override
public int getPlayerOptionMenuResource() {
return R.menu.menu_play_queue_popup;
}
@Override
public boolean onPlayerOptionSelected(MenuItem item) {
if (item.getItemId() == R.id.action_switch_background) {
this.player.setRecovery();
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
getApplicationContext().startService(getSwitchIntent(BackgroundPlayer.class));
return true;
}
return false;
}
@Override
public Intent getPlayerShutdownIntent() {
return new Intent(ACTION_CLOSE);
}
}

View File

@@ -28,6 +28,7 @@ import com.google.android.exoplayer2.Player;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
@@ -57,6 +58,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private static final int PLAYBACK_SPEED_POPUP_MENU_GROUP_ID = 61;
private static final int PLAYBACK_PITCH_POPUP_MENU_GROUP_ID = 97;
private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80;
private View rootView;
private RecyclerView itemsList;
@@ -97,6 +100,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
public abstract void stopPlayerListener();
public abstract int getPlayerOptionMenuResource();
public abstract boolean onPlayerOptionSelected(MenuItem item);
public abstract Intent getPlayerShutdownIntent();
////////////////////////////////////////////////////////////////////////////
// Activity Lifecycle
////////////////////////////////////////////////////////////////////////////
@@ -131,6 +139,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_play_queue, menu);
getMenuInflater().inflate(getPlayerOptionMenuResource(), menu);
return true;
}
@@ -150,8 +159,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
case R.id.action_system_audio:
startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS));
return true;
case R.id.action_switch_main:
this.player.setRecovery();
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
getApplicationContext().startActivity(getSwitchIntent(MainVideoPlayer.class));
return true;
}
return super.onOptionsItemSelected(item);
return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item);
}
@Override
@@ -160,6 +174,17 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
unbind();
}
protected Intent getSwitchIntent(final Class clazz) {
return NavigationHelper.getPlayerIntent(
getApplicationContext(),
clazz,
this.player.getPlayQueue(),
this.player.getRepeatMode(),
this.player.getPlaybackSpeed(),
this.player.getPlaybackPitch(),
null
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
////////////////////////////////////////////////////////////////////////////
// Service Connection
////////////////////////////////////////////////////////////////////////////
@@ -225,6 +250,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
itemsList.setAdapter(player.getPlayQueueAdapter());
itemsList.setClickable(true);
itemsList.setLongClickable(true);
itemsList.clearOnScrollListeners();
itemsList.addOnScrollListener(getQueueScrollListener());
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
itemTouchHelper.attachToRecyclerView(itemsList);
@@ -283,12 +310,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
final float playbackSpeed = BasePlayer.PLAYBACK_SPEEDS[i];
final String formattedSpeed = formatSpeed(playbackSpeed);
final MenuItem item = playbackSpeedPopupMenu.getMenu().add(PLAYBACK_SPEED_POPUP_MENU_GROUP_ID, i, Menu.NONE, formattedSpeed);
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
player.setPlaybackSpeed(playbackSpeed);
return true;
}
item.setOnMenuItemClickListener(menuItem -> {
if (player == null) return false;
player.setPlaybackSpeed(playbackSpeed);
return true;
});
}
}
@@ -301,12 +327,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
final float playbackPitch = BasePlayer.PLAYBACK_PITCHES[i];
final String formattedPitch = formatPitch(playbackPitch);
final MenuItem item = playbackPitchPopupMenu.getMenu().add(PLAYBACK_PITCH_POPUP_MENU_GROUP_ID, i, Menu.NONE, formattedPitch);
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
player.setPlaybackPitch(playbackPitch);
return true;
}
item.setOnMenuItemClickListener(menuItem -> {
if (player == null) return false;
player.setPlaybackPitch(playbackPitch);
return true;
});
}
}
@@ -314,22 +339,18 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
final PopupMenu menu = new PopupMenu(this, view);
final MenuItem remove = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 0, Menu.NONE, R.string.play_queue_remove);
remove.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
final int index = player.getPlayQueue().indexOf(item);
if (index != -1) player.getPlayQueue().remove(index);
return true;
}
remove.setOnMenuItemClickListener(menuItem -> {
if (player == null) return false;
final int index = player.getPlayQueue().indexOf(item);
if (index != -1) player.getPlayQueue().remove(index);
return true;
});
final MenuItem detail = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 1, Menu.NONE, R.string.play_queue_stream_detail);
detail.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
onOpenDetail(item.getServiceId(), item.getUrl(), item.getTitle());
return true;
}
detail.setOnMenuItemClickListener(menuItem -> {
onOpenDetail(item.getServiceId(), item.getUrl(), item.getTitle());
return true;
});
menu.show();
@@ -339,6 +360,19 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
// Component Helpers
////////////////////////////////////////////////////////////////////////////
private OnScrollBelowItemsListener getQueueScrollListener() {
return new OnScrollBelowItemsListener() {
@Override
public void onScrolledDown(RecyclerView recyclerView) {
if (player != null && player.getPlayQueue() != null && !player.getPlayQueue().isComplete()) {
player.getPlayQueue().fetch();
} else if (itemsList != null) {
itemsList.clearOnScrollListeners();
}
}
};
}
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) {
@Override
@@ -349,7 +383,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
final int sourceIndex = source.getLayoutPosition();
final int targetIndex = target.getLayoutPosition();
player.getPlayQueue().move(sourceIndex, targetIndex);
if (player != null) player.getPlayQueue().move(sourceIndex, targetIndex);
return true;
}
@@ -372,11 +406,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return new PlayQueueItemBuilder.OnSelectedListener() {
@Override
public void selected(PlayQueueItem item, View view) {
player.onSelected(item);
if (player != null) player.onSelected(item);
}
@Override
public void held(PlayQueueItem item, View view) {
if (player == null) return;
final int index = player.getPlayQueue().indexOf(item);
if (index != -1) buildItemPopupMenu(item, view);
}
@@ -393,7 +429,23 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
}
private void scrollToSelected() {
itemsList.smoothScrollToPosition(player.getPlayQueue().getIndex());
if (player == null) return;
final int currentPlayingIndex = player.getPlayQueue().getIndex();
final int currentVisibleIndex;
if (itemsList.getLayoutManager() instanceof LinearLayoutManager) {
final LinearLayoutManager layout = ((LinearLayoutManager) itemsList.getLayoutManager());
currentVisibleIndex = layout.findFirstVisibleItemPosition();
} else {
currentVisibleIndex = 0;
}
final int distance = Math.abs(currentPlayingIndex - currentVisibleIndex);
if (distance < SMOOTH_SCROLL_MAXIMUM_DISTANCE) {
itemsList.smoothScrollToPosition(currentPlayingIndex);
} else {
itemsList.scrollToPosition(currentPlayingIndex);
}
}
////////////////////////////////////////////////////////////////////////////
@@ -402,6 +454,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
@Override
public void onClick(View view) {
if (player == null) return;
if (view.getId() == repeatButton.getId()) {
player.onRepeatClicked();
@@ -450,7 +504,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
player.simpleExoPlayer.seekTo(seekBar.getProgress());
if (player != null) player.simpleExoPlayer.seekTo(seekBar.getProgress());
seekDisplay.setVisibility(View.GONE);
seeking = false;
}
@@ -485,7 +539,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
@Override
public void onMetadataUpdate(StreamInfo info) {
if (info != null) {
metadataTitle.setText(info.name);
metadataTitle.setText(info.getName());
metadataArtist.setText(info.uploader_name);
scrollToSelected();
}

View File

@@ -39,7 +39,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceView;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
@@ -75,16 +74,16 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
* @author mauriciocolli
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.VideoListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener, Player.EventListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
public abstract class VideoPlayer extends BasePlayer
implements SimpleExoPlayer.VideoListener,
SeekBar.OnSeekBarChangeListener,
View.OnClickListener,
Player.EventListener,
PopupMenu.OnMenuItemClickListener,
PopupMenu.OnDismissListener {
public static final boolean DEBUG = BasePlayer.DEBUG;
public final String TAG;
/*//////////////////////////////////////////////////////////////////////////
// Intent
//////////////////////////////////////////////////////////////////////////*/
public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe";
/*//////////////////////////////////////////////////////////////////////////
// Player
//////////////////////////////////////////////////////////////////////////*/
@@ -96,7 +95,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
protected String playbackQuality;
private boolean startedFromNewPipe = true;
protected boolean wasPlaying = false;
/*//////////////////////////////////////////////////////////////////////////
@@ -124,12 +122,11 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
private View topControlsRoot;
private TextView qualityTextView;
private ImageButton fullScreenButton;
private ValueAnimator controlViewAnimator;
private Handler controlsVisibilityHandler = new Handler();
private boolean isSomePopupMenuVisible = false;
boolean isSomePopupMenuVisible = false;
private int qualityPopupMenuGroupId = 69;
private PopupMenu qualityPopupMenu;
@@ -166,7 +163,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls);
this.topControlsRoot = rootView.findViewById(R.id.topControls);
this.qualityTextView = rootView.findViewById(R.id.qualityTextView);
this.fullScreenButton = rootView.findViewById(R.id.fullScreenButton);
//this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f);
@@ -186,7 +182,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
super.initListeners();
playbackSeekBar.setOnSeekBarChangeListener(this);
playbackSpeedTextView.setOnClickListener(this);
fullScreenButton.setOnClickListener(this);
qualityTextView.setOnClickListener(this);
}
@@ -272,24 +267,25 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
}
@Override
@Nullable
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
final VideoStream video;
final int index;
if (playbackQuality == null) {
final int index = getDefaultResolutionIndex(videos);
video = videos.get(index);
index = getDefaultResolutionIndex(videos);
} else {
final int index = getOverrideResolutionIndex(videos, getPlaybackQuality());
video = videos.get(index);
index = getOverrideResolutionIndex(videos, getPlaybackQuality());
}
if (index < 0 || index >= videos.size()) return null;
final VideoStream video = videos.get(index);
final MediaSource streamSource = buildMediaSource(video.url, MediaFormat.getSuffixById(video.format));
final MediaSource streamSource = buildMediaSource(video.getUrl(), MediaFormat.getSuffixById(video.format));
final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams);
if (!video.isVideoOnly || audio == null) return streamSource;
// Merge with audio stream in case if video does not contain audio
final MediaSource audioSource = buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format));
final MediaSource audioSource = buildMediaSource(audio.getUrl(), MediaFormat.getSuffixById(audio.format));
return new MergingMediaSource(streamSource, audioSource);
}
@@ -453,9 +449,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
@Override
public void onClick(View v) {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
if (v.getId() == fullScreenButton.getId()) {
onFullScreenButtonClicked();
} else if (v.getId() == qualityTextView.getId()) {
if (v.getId() == qualityTextView.getId()) {
onQualitySelectorClicked();
} else if (v.getId() == playbackSpeedTextView.getId()) {
onPlaybackSpeedClicked();
@@ -693,14 +687,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
return availableStreams.get(selectedStreamIndex);
}
public boolean isStartedFromNewPipe() {
return startedFromNewPipe;
}
public void setStartedFromNewPipe(boolean startedFromNewPipe) {
this.startedFromNewPipe = startedFromNewPipe;
}
public Handler getControlsVisibilityHandler() {
return controlsVisibilityHandler;
}
@@ -753,14 +739,14 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
return qualityTextView;
}
public ImageButton getFullScreenButton() {
return fullScreenButton;
}
public PopupMenu getQualityPopupMenu() {
return qualityPopupMenu;
}
public PopupMenu getPlaybackSpeedPopupMenu() {
return playbackSpeedPopupMenu;
}
public View getSurfaceForeground() {
return surfaceForeground;
}

View File

@@ -12,6 +12,8 @@ import java.text.NumberFormat;
import java.util.Formatter;
import java.util.Locale;
import javax.annotation.Nonnull;
public class PlayerHelper {
private PlayerHelper() {}
@@ -56,6 +58,10 @@ public class PlayerHelper {
return isUsingOldPlayer(context, false);
}
public static boolean isRememberingPopupDimensions(@Nonnull final Context context) {
return isRememberingPopupDimensions(context, true);
}
public static long getPreferredCacheSize(@NonNull final Context context) {
return 64 * 1024 * 1024L;
}
@@ -83,6 +89,15 @@ public class PlayerHelper {
public static boolean isUsingDSP(@NonNull final Context context) {
return true;
}
public static int getShutdownFlingVelocity(@Nonnull final Context context) {
return 10000;
}
public static int getTossFlingVelocity(@Nonnull final Context context) {
return 2500;
}
////////////////////////////////////////////////////////////////////////////
// Private helpers
////////////////////////////////////////////////////////////////////////////
@@ -103,4 +118,8 @@ public class PlayerHelper {
private static boolean isUsingOldPlayer(@NonNull final Context context, final boolean b) {
return getPreferences(context).getBoolean(context.getString(R.string.use_old_player_key), b);
}
private static boolean isRememberingPopupDimensions(@Nonnull final Context context, final boolean b) {
return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
}
}

View File

@@ -29,7 +29,7 @@ import io.reactivex.subjects.PublishSubject;
public class MediaSourceManager {
private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode());
// One-side rolling window size for default loading
// Effectively loads windowSize * 2 + 1 streams, must be greater than 0
// Effectively loads windowSize * 2 + 1 streams per call to load, must be greater than 0
private final int windowSize;
private final PlaybackListener playbackListener;
private final PlayQueue playQueue;
@@ -38,7 +38,7 @@ public class MediaSourceManager {
// The higher it is, the less loading occurs during rapid noncritical timeline changes
// Not recommended to go below 100ms
private final long loadDebounceMillis;
private final PublishSubject<Long> loadSignal;
private final PublishSubject<Long> debouncedLoadSignal;
private final Disposable debouncedLoader;
private final DeferredMediaSource.Callback sourceBuilder;
@@ -52,7 +52,7 @@ public class MediaSourceManager {
public MediaSourceManager(@NonNull final PlaybackListener listener,
@NonNull final PlayQueue playQueue) {
this(listener, playQueue, 1, 1000L);
this(listener, playQueue, 1, 400L);
}
private MediaSourceManager(@NonNull final PlaybackListener listener,
@@ -69,7 +69,7 @@ public class MediaSourceManager {
this.loadDebounceMillis = loadDebounceMillis;
this.syncReactor = new SerialDisposable();
this.loadSignal = PublishSubject.create();
this.debouncedLoadSignal = PublishSubject.create();
this.debouncedLoader = getDebouncedLoader();
this.sourceBuilder = getSourceBuilder();
@@ -101,7 +101,7 @@ public class MediaSourceManager {
* Dispose the manager and releases all message buses and loaders.
* */
public void dispose() {
if (loadSignal != null) loadSignal.onComplete();
if (debouncedLoadSignal != null) debouncedLoadSignal.onComplete();
if (debouncedLoader != null) debouncedLoader.dispose();
if (playQueueReactor != null) playQueueReactor.cancel();
if (syncReactor != null) syncReactor.dispose();
@@ -118,7 +118,7 @@ public class MediaSourceManager {
* Unblocks the player once the item at the current index is loaded.
* */
public void load() {
loadSignal.onNext(System.currentTimeMillis());
loadDebounced();
}
/**
@@ -157,12 +157,12 @@ public class MediaSourceManager {
}
private void onPlayQueueChanged(final PlayQueueEvent event) {
if (playQueue.isEmpty()) {
if (playQueue.isEmpty() && playQueue.isComplete()) {
playbackListener.shutdown();
return;
}
// why no pattern matching in Java =(
// Event specific action
switch (event.type()) {
case INIT:
case REORDER:
@@ -172,37 +172,34 @@ public class MediaSourceManager {
case APPEND:
populateSources();
break;
case SELECT:
sync();
break;
case REMOVE:
final RemoveEvent removeEvent = (RemoveEvent) event;
remove(removeEvent.getRemoveIndex());
// Sync only when the currently playing is removed
if (removeEvent.getQueueIndex() == removeEvent.getRemoveIndex()) sync();
break;
case MOVE:
final MoveEvent moveEvent = (MoveEvent) event;
move(moveEvent.getFromIndex(), moveEvent.getToIndex());
break;
case SELECT:
case RECOVERY:
default:
break;
}
// Loading and Syncing
switch (event.type()) {
case INIT:
case REORDER:
case ERROR:
case APPEND:
loadInternal(); // low frequency, critical events
loadImmediate(); // low frequency, critical events
break;
case APPEND:
case REMOVE:
case SELECT:
case MOVE:
case RECOVERY:
default:
load(); // high frequency or noncritical events
loadDebounced(); // high frequency or noncritical events
break;
}
@@ -262,7 +259,11 @@ public class MediaSourceManager {
syncReactor.set(currentItem.getStream().subscribe(syncPlayback, onError));
}
private void loadInternal() {
private void loadDebounced() {
debouncedLoadSignal.onNext(System.currentTimeMillis());
}
private void loadImmediate() {
// The current item has higher priority
final int currentIndex = playQueue.getIndex();
final PlayQueueItem currentItem = playQueue.getItem(currentIndex);
@@ -290,7 +291,9 @@ public class MediaSourceManager {
final DeferredMediaSource mediaSource = (DeferredMediaSource) sources.getMediaSource(playQueue.indexOf(item));
if (mediaSource.state() == DeferredMediaSource.STATE_PREPARED) mediaSource.load();
if (tryUnblock()) sync();
tryUnblock();
if (!isBlocked) sync();
}
private void resetSources() {
@@ -307,13 +310,13 @@ public class MediaSourceManager {
}
private Disposable getDebouncedLoader() {
return loadSignal
return debouncedLoadSignal
.debounce(loadDebounceMillis, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long timestamp) throws Exception {
loadInternal();
loadImmediate();
}
});
}

View File

@@ -43,6 +43,7 @@ public interface PlaybackListener {
*
* May be called at any time.
* */
@Nullable
MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info);
/**

View File

@@ -0,0 +1,132 @@
package org.schabi.newpipe.playlist;
import android.util.Log;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.reactivex.SingleObserver;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> extends PlayQueue {
boolean isInitial;
boolean isComplete;
int serviceId;
String baseUrl;
String nextUrl;
transient Disposable fetchReactor;
AbstractInfoPlayQueue(final U item) {
this(item.getServiceId(), item.getUrl(), null, Collections.<InfoItem>emptyList(), 0);
}
AbstractInfoPlayQueue(final int serviceId,
final String url,
final String nextPageUrl,
final List<InfoItem> streams,
final int index) {
super(index, extractListItems(streams));
this.baseUrl = url;
this.nextUrl = nextPageUrl;
this.serviceId = serviceId;
this.isInitial = streams.isEmpty();
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
}
abstract protected String getTag();
@Override
public boolean isComplete() {
return isComplete;
}
SingleObserver<T> getHeadListObserver() {
return new SingleObserver<T>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
if (isComplete || !isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) {
d.dispose();
} else {
fetchReactor = d;
}
}
@Override
public void onSuccess(@NonNull T result) {
isInitial = false;
if (!result.has_more_streams) isComplete = true;
nextUrl = result.next_streams_url;
append(extractListItems(result.related_streams));
fetchReactor.dispose();
fetchReactor = null;
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
isComplete = true;
append(); // Notify change
}
};
}
SingleObserver<ListExtractor.NextItemsResult> getNextItemsObserver() {
return new SingleObserver<ListExtractor.NextItemsResult>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
if (isComplete || isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) {
d.dispose();
} else {
fetchReactor = d;
}
}
@Override
public void onSuccess(@NonNull ListExtractor.NextItemsResult result) {
if (!result.hasMoreStreams()) isComplete = true;
nextUrl = result.nextItemsUrl;
append(extractListItems(result.nextItemsList));
fetchReactor.dispose();
fetchReactor = null;
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
isComplete = true;
append(); // Notify change
}
};
}
@Override
public void dispose() {
super.dispose();
if (fetchReactor != null) fetchReactor.dispose();
}
private static List<PlayQueueItem> extractListItems(final List<InfoItem> infos) {
List<PlayQueueItem> result = new ArrayList<>();
for (final InfoItem stream : infos) {
if (stream instanceof StreamInfoItem) {
result.add(new PlayQueueItem((StreamInfoItem) stream));
}
}
return result;
}
}

View File

@@ -0,0 +1,49 @@
package org.schabi.newpipe.playlist;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.util.ExtractorHelper;
import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, ChannelInfoItem> {
public ChannelPlayQueue(final ChannelInfoItem item) {
super(item);
}
public ChannelPlayQueue(final ChannelInfo info) {
this(info.getServiceId(), info.getUrl(), info.getNextStreamsUrl(), info.getRelatedStreams(), 0);
}
public ChannelPlayQueue(final int serviceId,
final String url,
final String nextPageUrl,
final List<InfoItem> streams,
final int index) {
super(serviceId, url, nextPageUrl, streams, index);
}
@Override
protected String getTag() {
return "ChannelPlayQueue@" + Integer.toHexString(hashCode());
}
@Override
public void fetch() {
if (this.isInitial) {
ExtractorHelper.getChannelInfo(this.serviceId, this.baseUrl, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getHeadListObserver());
} else {
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getNextItemsObserver());
}
}
}

View File

@@ -1,104 +0,0 @@
package org.schabi.newpipe.playlist;
import android.util.Log;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.util.ExtractorHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.reactivex.SingleObserver;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public final class ExternalPlayQueue extends PlayQueue {
private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode());
private boolean isComplete;
private int serviceId;
private String baseUrl;
private String nextUrl;
private transient Disposable fetchReactor;
public ExternalPlayQueue(final int serviceId,
final String url,
final String nextPageUrl,
final List<InfoItem> streams,
final int index) {
super(index, extractPlaylistItems(streams));
this.baseUrl = url;
this.nextUrl = nextPageUrl;
this.serviceId = serviceId;
this.isComplete = nextPageUrl == null || nextPageUrl.isEmpty();
}
@Override
public boolean isComplete() {
return isComplete;
}
@Override
public void fetch() {
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getPlaylistObserver());
}
private SingleObserver<ListExtractor.NextItemsResult> getPlaylistObserver() {
return new SingleObserver<ListExtractor.NextItemsResult>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) {
d.dispose();
} else {
fetchReactor = d;
}
}
@Override
public void onSuccess(@NonNull ListExtractor.NextItemsResult result) {
if (!result.hasMoreStreams()) isComplete = true;
nextUrl = result.nextItemsUrl;
append(extractPlaylistItems(result.nextItemsList));
fetchReactor.dispose();
fetchReactor = null;
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e);
isComplete = true;
append(); // Notify change
}
};
}
@Override
public void dispose() {
super.dispose();
if (fetchReactor != null) fetchReactor.dispose();
}
private static List<PlayQueueItem> extractPlaylistItems(final List<InfoItem> infos) {
List<PlayQueueItem> result = new ArrayList<>();
for (final InfoItem stream : infos) {
if (stream instanceof StreamInfoItem) {
result.add(new PlayQueueItem((StreamInfoItem) stream));
}
}
return result;
}
}

View File

@@ -123,7 +123,7 @@ public abstract class PlayQueue implements Serializable {
* May throw {@link IndexOutOfBoundsException}.
* */
public PlayQueueItem getItem(int index) {
if (index >= streams.size() || streams.get(index) == null) return null;
if (index < 0 || index >= streams.size() || streams.get(index) == null) return null;
return streams.get(index);
}
@@ -279,7 +279,7 @@ public abstract class PlayQueue implements Serializable {
queueIndex.set(currentIndex % (size - 1));
} else if (currentIndex == removeIndex && currentIndex == size - 1){
queueIndex.set(removeIndex - 1);
queueIndex.set(0);
}
if (backup != null) {

View File

@@ -30,12 +30,12 @@ public class PlayQueueItem implements Serializable {
private transient Single<StreamInfo> stream;
PlayQueueItem(@NonNull final StreamInfo info) {
this(info.name, info.url, info.service_id, info.duration, info.thumbnail_url, info.uploader_name);
this(info.getName(), info.getUrl(), info.getServiceId(), info.duration, info.thumbnail_url, info.uploader_name);
this.stream = Single.just(info);
}
PlayQueueItem(@NonNull final StreamInfoItem item) {
this(item.name, item.url, item.service_id, item.duration, item.thumbnail_url, item.uploader_name);
this(item.getName(), item.getUrl(), item.getServiceId(), item.duration, item.thumbnail_url, item.uploader_name);
}
private PlayQueueItem(final String name, final String url, final int serviceId,

View File

@@ -107,6 +107,8 @@ public class PlayQueueItemBuilder {
.bitmapConfig(Bitmap.Config.RGB_565) // Users won't be able to see much anyways
.preProcessor(bitmapProcessor)
.imageScaleType(ImageScaleType.EXACTLY)
.cacheInMemory(true)
.cacheOnDisk(true)
.build();
}
}

View File

@@ -0,0 +1,49 @@
package org.schabi.newpipe.playlist;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.util.ExtractorHelper;
import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo, PlaylistInfoItem> {
public PlaylistPlayQueue(final PlaylistInfoItem item) {
super(item);
}
public PlaylistPlayQueue(final PlaylistInfo info) {
this(info.getServiceId(), info.getUrl(), info.getNextStreamsUrl(), info.getRelatedStreams(), 0);
}
public PlaylistPlayQueue(final int serviceId,
final String url,
final String nextPageUrl,
final List<InfoItem> streams,
final int index) {
super(serviceId, url, nextPageUrl, streams, index);
}
@Override
protected String getTag() {
return "PlaylistPlayQueue@" + Integer.toHexString(hashCode());
}
@Override
public void fetch() {
if (this.isInitial) {
ExtractorHelper.getPlaylistInfo(this.serviceId, this.baseUrl, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getHeadListObserver());
} else {
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getNextItemsObserver());
}
}
}

View File

@@ -1,12 +1,21 @@
package org.schabi.newpipe.playlist;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import java.util.Collections;
public final class SinglePlayQueue extends PlayQueue {
public SinglePlayQueue(final StreamInfoItem item) {
this(new PlayQueueItem(item));
}
public SinglePlayQueue(final StreamInfo info) {
super(0, Collections.singletonList(new PlayQueueItem(info)));
this(new PlayQueueItem(info));
}
private SinglePlayQueue(final PlayQueueItem playQueueItem) {
super(0, Collections.singletonList(playQueueItem));
}
@Override

View File

@@ -44,6 +44,7 @@ import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Vector;
@@ -144,12 +145,7 @@ public class ErrorActivity extends AppCompatActivity {
// async call
public static void reportError(Handler handler, final Context context, final List<Throwable> el,
final Class returnActivity, final View rootView, final ErrorInfo errorInfo) {
handler.post(new Runnable() {
@Override
public void run() {
reportError(context, el, returnActivity, rootView, errorInfo);
}
});
handler.post(() -> reportError(context, el, returnActivity, rootView, errorInfo));
}
public static void reportError(final Context context, final CrashReportData report, final ErrorInfo errorInfo) {
@@ -160,7 +156,7 @@ public class ErrorActivity extends AppCompatActivity {
key = k;
}
}
String[] el = new String[]{report.get(key)};
String[] el = new String[]{report.get(key).toString()};
Intent intent = new Intent(context, ErrorActivity.class);
intent.putExtra(ERROR_INFO, errorInfo);
@@ -218,17 +214,13 @@ public class ErrorActivity extends AppCompatActivity {
addGuruMeditaion();
currentTimeStamp = getCurrentTimeStamp();
reportButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
reportButton.setOnClickListener((View v) -> {
Intent i = new Intent(Intent.ACTION_SENDTO);
i.setData(Uri.parse("mailto:" + ERROR_EMAIL_ADDRESS))
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
.putExtra(Intent.EXTRA_TEXT, buildJson());
Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("mailto:" + ERROR_EMAIL_ADDRESS))
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
.putExtra(Intent.EXTRA_TEXT, buildJson());
startActivity(Intent.createChooser(intent, "Send Email"));
}
startActivity(Intent.createChooser(i, "Send Email"));
});
reportButton.setEnabled(false);
@@ -382,7 +374,7 @@ public class ErrorActivity extends AppCompatActivity {
private String getContentLangString() {
return PreferenceManager.getDefaultSharedPreferences(this)
.getString(this.getString(R.string.search_language_key), "none");
.getString(this.getString(R.string.content_country_key), "none");
}
private String getOsString() {

View File

@@ -1,9 +1,14 @@
package org.schabi.newpipe.settings;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.util.Log;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
@@ -12,91 +17,208 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.ZipHelper;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import javax.annotation.Nonnull;
public class ContentSettingsFragment extends BasePreferenceFragment {
private static final int REQUEST_IMPORT_PATH = 8945;
private static final int REQUEST_EXPORT_PATH = 30945;
private String homeDir;
private File databasesDir;
private File newpipe_db;
private File newpipe_db_journal;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
homeDir = getActivity().getApplicationInfo().dataDir;
databasesDir = new File(homeDir + "/databases");
newpipe_db = new File(homeDir + "/databases/newpipe.db");
newpipe_db_journal = new File(homeDir + "/databases/newpipe.db-journal");
addPreferencesFromResource(R.xml.content_settings);
final ListPreference mainPageContentPref = (ListPreference) findPreference(getString(R.string.main_page_content_key));
mainPageContentPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValueO) {
final String newValue = newValueO.toString();
mainPageContentPref.setOnPreferenceChangeListener((Preference preference, Object newValueO) -> {
final String newValue = newValueO.toString();
final String mainPrefOldValue =
defaultPreferences.getString(getString(R.string.main_page_content_key), "blank_page");
final String mainPrefOldSummary = getMainPagePrefSummery(mainPrefOldValue, mainPageContentPref);
final String mainPrefOldValue =
defaultPreferences.getString(getString(R.string.main_page_content_key), "blank_page");
final String mainPrefOldSummary = getMainPagePrefSummery(mainPrefOldValue, mainPageContentPref);
if(newValue.equals(getString(R.string.kiosk_page_key))) {
SelectKioskFragment selectKioskFragment = new SelectKioskFragment();
selectKioskFragment.setOnSelectedLisener(new SelectKioskFragment.OnSelectedLisener() {
@Override
public void onKioskSelected(String kioskId, int service_id) {
defaultPreferences.edit()
.putInt(getString(R.string.main_page_selected_service), service_id).apply();
defaultPreferences.edit()
.putString(getString(R.string.main_page_selectd_kiosk_id), kioskId).apply();
String serviceName = "";
try {
serviceName = NewPipe.getService(service_id).getServiceInfo().name;
} catch (ExtractionException e) {
onError(e);
}
String kioskName = KioskTranslator.getTranslatedKioskName(kioskId,
getContext());
String summary =
String.format(getString(R.string.service_kiosk_string),
serviceName,
kioskName);
mainPageContentPref.setSummary(summary);
}
});
selectKioskFragment.setOnCancelListener(new SelectKioskFragment.OnCancelListener() {
@Override
public void onCancel() {
mainPageContentPref.setSummary(mainPrefOldSummary);
mainPageContentPref.setValue(mainPrefOldValue);
}
});
selectKioskFragment.show(getFragmentManager(), "select_kiosk");
} else if(newValue.equals(getString(R.string.channel_page_key))) {
SelectChannelFragment selectChannelFragment = new SelectChannelFragment();
selectChannelFragment.setOnSelectedLisener(new SelectChannelFragment.OnSelectedLisener() {
@Override
public void onChannelSelected(String url, String name, int service) {
defaultPreferences.edit()
.putInt(getString(R.string.main_page_selected_service), service).apply();
defaultPreferences.edit()
.putString(getString(R.string.main_page_selected_channel_url), url).apply();
defaultPreferences.edit()
.putString(getString(R.string.main_page_selected_channel_name), name).apply();
mainPageContentPref.setSummary(name);
}
});
selectChannelFragment.setOnCancelListener(new SelectChannelFragment.OnCancelListener() {
@Override
public void onCancel() {
mainPageContentPref.setSummary(mainPrefOldSummary);
mainPageContentPref.setValue(mainPrefOldValue);
}
});
selectChannelFragment.show(getFragmentManager(), "select_channel");
} else {
mainPageContentPref.setSummary(getMainPageSummeryByKey(newValue));
}
defaultPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply();
return true;
if(newValue.equals(getString(R.string.kiosk_page_key))) {
SelectKioskFragment selectKioskFragment = new SelectKioskFragment();
selectKioskFragment.setOnSelectedLisener((String kioskId, int service_id) -> {
defaultPreferences.edit()
.putInt(getString(R.string.main_page_selected_service), service_id).apply();
defaultPreferences.edit()
.putString(getString(R.string.main_page_selectd_kiosk_id), kioskId).apply();
String serviceName = "";
try {
serviceName = NewPipe.getService(service_id).getServiceInfo().name;
} catch (ExtractionException e) {
onError(e);
}
String kioskName = KioskTranslator.getTranslatedKioskName(kioskId,
getContext());
String summary =
String.format(getString(R.string.service_kiosk_string),
serviceName,
kioskName);
mainPageContentPref.setSummary(summary);
});
selectKioskFragment.setOnCancelListener(() -> {
mainPageContentPref.setSummary(mainPrefOldSummary);
mainPageContentPref.setValue(mainPrefOldValue);
});
selectKioskFragment.show(getFragmentManager(), "select_kiosk");
} else if(newValue.equals(getString(R.string.channel_page_key))) {
SelectChannelFragment selectChannelFragment = new SelectChannelFragment();
selectChannelFragment.setOnSelectedLisener((String url, String name, int service) -> {
defaultPreferences.edit()
.putInt(getString(R.string.main_page_selected_service), service).apply();
defaultPreferences.edit()
.putString(getString(R.string.main_page_selected_channel_url), url).apply();
defaultPreferences.edit()
.putString(getString(R.string.main_page_selected_channel_name), name).apply();
mainPageContentPref.setSummary(name);
});
selectChannelFragment.setOnCancelListener(() -> {
mainPageContentPref.setSummary(mainPrefOldSummary);
mainPageContentPref.setValue(mainPrefOldValue);
});
selectChannelFragment.show(getFragmentManager(), "select_channel");
} else {
mainPageContentPref.setSummary(getMainPageSummeryByKey(newValue));
}
defaultPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply();
return true;
});
Preference importDataPreference = findPreference(getString(R.string.import_data));
importDataPreference.setOnPreferenceClickListener((Preference p) -> {
Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false)
.putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_FILE);
startActivityForResult(i, REQUEST_IMPORT_PATH);
return true;
});
Preference exportDataPreference = findPreference(getString(R.string.export_data));
exportDataPreference.setOnPreferenceClickListener((Preference p) -> {
Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true)
.putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_DIR);
startActivityForResult(i, REQUEST_EXPORT_PATH);
return true;
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nonnull Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (DEBUG) {
Log.d(TAG, "onActivityResult() called with: requestCode = [" + requestCode + "], resultCode = [" + resultCode + "], data = [" + data + "]");
}
if ((requestCode == REQUEST_IMPORT_PATH || requestCode == REQUEST_EXPORT_PATH)
&& resultCode == Activity.RESULT_OK) {
String path = data.getData().getPath();
if (requestCode == REQUEST_EXPORT_PATH) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
exportDatabase(path + "/NewPipeData-" + sdf.format(new Date()) + ".zip");
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.override_current_data)
.setPositiveButton(android.R.string.ok,
(DialogInterface d, int id) -> importDatabase(path))
.setNegativeButton(android.R.string.cancel,
(DialogInterface d, int id) -> d.cancel());
builder.create().show();
}
}
}
private void exportDatabase(String path) {
try {
ZipOutputStream outZip = new ZipOutputStream(
new BufferedOutputStream(
new FileOutputStream(path)));
ZipHelper.addFileToZip(outZip, newpipe_db.getPath(), "newpipe.db");
ZipHelper.addFileToZip(outZip, newpipe_db_journal.getPath(), "newpipe.db-journal");
outZip.close();
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT)
.show();
} catch (Exception e) {
onError(e);
}
}
private void importDatabase(String filePath) {
// check if file is supported
ZipFile zipFile = null;
try {
zipFile = new ZipFile(filePath);
} catch (IOException ioe) {
Toast.makeText(getContext(), R.string.no_valid_zip_file, Toast.LENGTH_SHORT)
.show();
return;
} finally {
try {
zipFile.close();
} catch (Exception e){}
}
try {
ZipInputStream zipIn = new ZipInputStream(
new BufferedInputStream(
new FileInputStream(filePath)));
if (!databasesDir.exists() && !databasesDir.mkdir()) {
throw new Exception("Could not create databases dir");
}
if(!(ZipHelper.extractFileFromZip(zipIn, newpipe_db.getPath(), "newpipe.db")
&& ZipHelper.extractFileFromZip(zipIn, newpipe_db_journal.getPath(), "newpipe.db-journal"))) {
Toast.makeText(getContext(), R.string.could_not_import_all_files, Toast.LENGTH_LONG)
.show();
}
zipIn.close();
// restart app to properly load db
//App.restart(getContext());
System.exit(0);
} catch (Exception e) {
onError(e);
}
}
@Override
@@ -117,8 +239,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
getString(R.string.main_page_selected_service), 0));
String kioskName = KioskTranslator.getTranslatedKioskName(
defaultPreferences.getString(
getString(R.string.main_page_selectd_kiosk_id), "Trending"),
defaultPreferences.getString(
getString(R.string.main_page_selectd_kiosk_id), "Trending"),
getContext());
String summary =

View File

@@ -46,7 +46,8 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
Log.d(TAG, "onPreferenceTreeClick() called with: preference = [" + preference + "]");
}
if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE) || preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) {
if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE)
|| preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) {
Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true)

View File

@@ -14,21 +14,17 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.fragments.subscription.SubscriptionService;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.ServiceIconMapper;
import org.schabi.newpipe.util.ServiceHelper;
import java.util.List;
import java.util.Vector;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
/**
* Created by Christian Schabesberger on 09.10.17.
* SelectKioskFragment.java is part of NewPipe.
@@ -125,13 +121,15 @@ public class SelectKioskFragment extends DialogFragment {
throws Exception {
for(StreamingService service : NewPipe.getServices()) {
//TODO: Multi-service support
if (service.getServiceId() != ServiceList.YouTube.getId()) continue;
for(String kioskId : service.getKioskList().getAvailableKiosks()) {
String name = String.format(getString(R.string.service_kiosk_string),
service.getServiceInfo().name,
KioskTranslator.getTranslatedKioskName(kioskId, getContext()));
kioskList.add(new Entry(
//ServiceIconMapper.getIconResource(service.getServiceId()),
ServiceIconMapper.getIconResource(-1),
ServiceHelper.getIcon(service.getServiceId()),
service.getServiceId(),
kioskId,
name));
@@ -140,9 +138,7 @@ public class SelectKioskFragment extends DialogFragment {
}
public int getItemCount() {
//todo: uncommend this line on multyservice support
//return kioskList.size();
return 1;
return kioskList.size();
}
public SelectKioskItemHolder onCreateViewHolder(ViewGroup parent, int type) {

View File

@@ -43,7 +43,8 @@ public class SettingsActivity extends AppCompatActivity implements BasePreferenc
@Override
protected void onCreate(Bundle savedInstanceBundle) {
ThemeHelper.setTheme(this);
setTheme(ThemeHelper.getSettingsThemeStyle(this));
super.onCreate(savedInstanceBundle);
setContentView(R.layout.settings_layout);
@@ -72,7 +73,9 @@ public class SettingsActivity extends AppCompatActivity implements BasePreferenc
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
finish();
} else getSupportFragmentManager().popBackStack();
}
return true;
}

View File

@@ -19,29 +19,38 @@
package org.schabi.newpipe.util;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.ListExtractor.NextItemsResult;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.List;
import java.util.concurrent.Callable;
import io.reactivex.Maybe;
import io.reactivex.MaybeSource;
import io.reactivex.Single;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
public final class ExtractorHelper {
private static final String TAG = ExtractorHelper.class.getSimpleName();
@@ -57,104 +66,96 @@ public final class ExtractorHelper {
}
}
public static Single<SearchResult> searchFor(final int serviceId, final String query, final int pageNumber, final String searchLanguage, final SearchEngine.Filter filter) {
public static Single<SearchResult> searchFor(final int serviceId,
final String query,
final int pageNumber,
final String contentCountry,
final SearchEngine.Filter filter) {
checkServiceId(serviceId);
return Single.fromCallable(new Callable<SearchResult>() {
@Override
public SearchResult call() throws Exception {
return SearchResult.getSearchResult(NewPipe.getService(serviceId).getSearchEngine(),
query, pageNumber, searchLanguage, filter);
}
});
return Single.fromCallable(() ->
SearchResult.getSearchResult(NewPipe.getService(serviceId).getSearchEngine(),
query, pageNumber, contentCountry, filter)
);
}
public static Single<NextItemsResult> getMoreSearchItems(final int serviceId, final String query, final int nextPageNumber, final String searchLanguage, final SearchEngine.Filter filter) {
public static Single<NextItemsResult> getMoreSearchItems(final int serviceId,
final String query,
final int nextPageNumber,
final String searchLanguage,
final SearchEngine.Filter filter) {
checkServiceId(serviceId);
return searchFor(serviceId, query, nextPageNumber, searchLanguage, filter)
.map(new Function<SearchResult, NextItemsResult>() {
@Override
public NextItemsResult apply(@NonNull SearchResult searchResult) throws Exception {
return new NextItemsResult(searchResult.resultList, nextPageNumber + "", searchResult.errors);
}
});
.map((@NonNull SearchResult searchResult) ->
new NextItemsResult(searchResult.resultList,
nextPageNumber + "",
searchResult.errors));
}
public static Single<List<String>> suggestionsFor(final int serviceId, final String query, final String searchLanguage) {
public static Single<List<String>> suggestionsFor(final int serviceId,
final String query,
final String contentCountry) {
checkServiceId(serviceId);
return Single.fromCallable(new Callable<List<String>>() {
@Override
public List<String> call() throws Exception {
return NewPipe.getService(serviceId).getSuggestionExtractor().suggestionList(query, searchLanguage);
}
});
return Single.fromCallable(() ->
NewPipe.getService(serviceId)
.getSuggestionExtractor()
.suggestionList(query, contentCountry));
}
public static Single<StreamInfo> getStreamInfo(final int serviceId, final String url, boolean forceLoad) {
public static Single<StreamInfo> getStreamInfo(final int serviceId,
final String url,
boolean forceLoad) {
checkServiceId(serviceId);
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<StreamInfo>() {
@Override
public StreamInfo call() throws Exception {
return StreamInfo.getInfo(NewPipe.getService(serviceId), url);
}
}));
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
StreamInfo.getInfo(NewPipe.getService(serviceId), url)));
}
public static Single<ChannelInfo> getChannelInfo(final int serviceId, final String url, boolean forceLoad) {
public static Single<ChannelInfo> getChannelInfo(final int serviceId,
final String url,
boolean forceLoad) {
checkServiceId(serviceId);
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<ChannelInfo>() {
@Override
public ChannelInfo call() throws Exception {
return ChannelInfo.getInfo(NewPipe.getService(serviceId), url);
}
}));
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
ChannelInfo.getInfo(NewPipe.getService(serviceId), url)));
}
public static Single<NextItemsResult> getMoreChannelItems(final int serviceId, final String url, final String nextStreamsUrl) {
public static Single<NextItemsResult> getMoreChannelItems(final int serviceId,
final String url,
final String nextStreamsUrl) {
checkServiceId(serviceId);
return Single.fromCallable(new Callable<NextItemsResult>() {
@Override
public NextItemsResult call() throws Exception {
return ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl);
}
});
return Single.fromCallable(() ->
ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl));
}
public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId, final String url, boolean forceLoad) {
public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId,
final String url,
boolean forceLoad) {
checkServiceId(serviceId);
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<PlaylistInfo>() {
@Override
public PlaylistInfo call() throws Exception {
return PlaylistInfo.getInfo(NewPipe.getService(serviceId), url);
}
}));
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
PlaylistInfo.getInfo(NewPipe.getService(serviceId), url)));
}
public static Single<NextItemsResult> getMorePlaylistItems(final int serviceId, final String url, final String nextStreamsUrl) {
public static Single<NextItemsResult> getMorePlaylistItems(final int serviceId,
final String url,
final String nextStreamsUrl) {
checkServiceId(serviceId);
return Single.fromCallable(new Callable<NextItemsResult>() {
@Override
public NextItemsResult call() throws Exception {
return PlaylistInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl);
}
});
return Single.fromCallable(() ->
PlaylistInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl));
}
public static Single<KioskInfo> getKioskInfo(final int serviceId, final String url, final String contentCountry, boolean forceLoad) {
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<KioskInfo>() {
@Override
public KioskInfo call() throws Exception {
return KioskInfo.getInfo(NewPipe.getService(serviceId), url, contentCountry);
}
}));
public static Single<KioskInfo> getKioskInfo(final int serviceId,
final String url,
final String contentCountry,
boolean forceLoad) {
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
KioskInfo.getInfo(NewPipe.getService(serviceId), url, contentCountry)));
}
public static Single<NextItemsResult> getMoreKioskItems(final int serviceId, final String url, final String nextStreamsUrl) {
return Single.fromCallable(new Callable<NextItemsResult>() {
@Override
public NextItemsResult call() throws Exception {
return KioskInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl);
}
});
public static Single<NextItemsResult> getMoreKioskItems(final int serviceId,
final String url,
final String nextStreamsUrl,
final String contentCountry) {
return Single.fromCallable(() ->
KioskInfo.getMoreItems(NewPipe.getService(serviceId),
url, nextStreamsUrl, contentCountry));
}
/*//////////////////////////////////////////////////////////////////////////
@@ -162,24 +163,24 @@ public final class ExtractorHelper {
//////////////////////////////////////////////////////////////////////////*/
/**
* Check if we can load it from the cache (forceLoad parameter), if we can't, load from the network (Single loadFromNetwork)
* Check if we can load it from the cache (forceLoad parameter), if we can't,
* load from the network (Single loadFromNetwork)
* and put the results in the cache.
*/
private static <I extends Info> Single<I> checkCache(boolean forceLoad, int serviceId, String url, Single<I> loadFromNetwork) {
private static <I extends Info> Single<I> checkCache(boolean forceLoad,
int serviceId,
String url,
Single<I> loadFromNetwork) {
checkServiceId(serviceId);
loadFromNetwork = loadFromNetwork.doOnSuccess(new Consumer<I>() {
@Override
public void accept(@NonNull I i) throws Exception {
cache.putInfo(i);
}
});
loadFromNetwork = loadFromNetwork.doOnSuccess((@NonNull I i) -> cache.putInfo(i));
Single<I> load;
if (forceLoad) {
cache.removeInfo(serviceId, url);
load = loadFromNetwork;
} else {
load = Maybe.concat(ExtractorHelper.<I>loadFromCache(serviceId, url), loadFromNetwork.toMaybe())
load = Maybe.concat(ExtractorHelper.<I>loadFromCache(serviceId, url),
loadFromNetwork.toMaybe())
.firstElement() //Take the first valid
.toSingle();
}
@@ -192,9 +193,7 @@ public final class ExtractorHelper {
*/
public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url) {
checkServiceId(serviceId);
return Maybe.defer(new Callable<MaybeSource<? extends I>>() {
@Override
public MaybeSource<? extends I> call() throws Exception {
return Maybe.defer(() -> {
//noinspection unchecked
I info = (I) cache.getFromKey(serviceId, url);
if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info);
@@ -205,8 +204,38 @@ public final class ExtractorHelper {
}
return Maybe.empty();
});
}
/**
* A simple and general error handler that show a Toast for known exceptions, and for others, opens the report error activity with the (optional) error message.
*/
public static void handleGeneralException(Context context, int serviceId, String url, Throwable exception, UserAction userAction, String optionalErrorMessage) {
final Handler handler = new Handler(context.getMainLooper());
handler.post(() -> {
if (exception instanceof ReCaptchaException) {
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
Intent intent = new Intent(context, ReCaptchaActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} else if (exception instanceof IOException) {
Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show();
} else if (exception instanceof YoutubeStreamExtractor.GemaException) {
Toast.makeText(context, R.string.blocked_by_gema, Toast.LENGTH_LONG).show();
} else if (exception instanceof YoutubeStreamExtractor.LiveStreamException) {
Toast.makeText(context, R.string.live_streams_not_supported, Toast.LENGTH_LONG).show();
} else if (exception instanceof ContentNotAvailableException) {
Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show();
} else {
int errorId = exception instanceof YoutubeStreamExtractor.DecryptException ? R.string.youtube_signature_decryption_error :
exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error;
ErrorActivity.reportError(handler, context, exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(userAction,
serviceId == -1 ? "none" : NewPipe.getNameOfService(serviceId), url + (optionalErrorMessage == null ? "" : optionalErrorMessage), errorId));
}
});
}
/**
@@ -214,21 +243,26 @@ public final class ExtractorHelper {
*
* @see Class#isAssignableFrom(Class)
*/
public static boolean hasAssignableCauseThrowable(Throwable throwable, Class<?>... causesToCheck) {
public static boolean hasAssignableCauseThrowable(Throwable throwable,
Class<?>... causesToCheck) {
// Check if getCause is not the same as cause (the getCause is already the root),
// as it will cause a infinite loop if it is
Throwable cause, getCause = throwable;
// Check if throwable is a subclass of any of the filtered classes
final Class throwableClass = throwable.getClass();
for (Class<?> causesEl : causesToCheck) {
if (throwable.getClass().isAssignableFrom(causesEl)) {
if (causesEl.isAssignableFrom(throwableClass)) {
return true;
}
}
// Iteratively checks if the root cause of the throwable is a subclass of the filtered class
while ((cause = throwable.getCause()) != null && getCause != cause) {
getCause = cause;
final Class causeClass = cause.getClass();
for (Class<?> causesEl : causesToCheck) {
if (cause.getClass().isAssignableFrom(causesEl)) {
if (causesEl.isAssignableFrom(causeClass)) {
return true;
}
}
@@ -265,6 +299,8 @@ public final class ExtractorHelper {
* Check if throwable have Interrupted* exception as one of its causes.
*/
public static boolean isInterruptedCaused(Throwable throwable) {
return ExtractorHelper.hasExactCauseThrowable(throwable, InterruptedIOException.class, InterruptedException.class);
return ExtractorHelper.hasExactCauseThrowable(throwable,
InterruptedIOException.class,
InterruptedException.class);
}
}

View File

@@ -26,6 +26,9 @@ import android.util.Log;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.extractor.Info;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public final class InfoCache {
private static final boolean DEBUG = MainActivity.DEBUG;
@@ -37,9 +40,9 @@ public final class InfoCache {
* Trim the cache to this size
*/
private static final int TRIM_CACHE_TO = 30;
private static final int DEFAULT_TIMEOUT_HOURS = 4;
// TODO: Replace to one with timeout (like the one from guava)
private static final LruCache<String, Info> lruCache = new LruCache<>(MAX_ITEMS_ON_CACHE);
private static final LruCache<String, CacheData> lruCache = new LruCache<>(MAX_ITEMS_ON_CACHE);
private InfoCache() {
//no instance
@@ -52,28 +55,29 @@ public final class InfoCache {
public Info getFromKey(int serviceId, @NonNull String url) {
if (DEBUG) Log.d(TAG, "getFromKey() called with: serviceId = [" + serviceId + "], url = [" + url + "]");
synchronized (lruCache) {
return lruCache.get(serviceId + url);
return getInfo(lruCache, keyOf(serviceId, url));
}
}
public void putInfo(@NonNull Info info) {
if (DEBUG) Log.d(TAG, "putInfo() called with: info = [" + info + "]");
synchronized (lruCache) {
lruCache.put(info.service_id + info.url, info);
final CacheData data = new CacheData(info, DEFAULT_TIMEOUT_HOURS, TimeUnit.HOURS);
lruCache.put(keyOf(info), data);
}
}
public void removeInfo(@NonNull Info info) {
if (DEBUG) Log.d(TAG, "removeInfo() called with: info = [" + info + "]");
synchronized (lruCache) {
lruCache.remove(info.service_id + info.url);
lruCache.remove(keyOf(info));
}
}
public void removeInfo(int serviceId, @NonNull String url) {
if (DEBUG) Log.d(TAG, "removeInfo() called with: serviceId = [" + serviceId + "], url = [" + url + "]");
synchronized (lruCache) {
lruCache.remove(serviceId + url);
lruCache.remove(keyOf(serviceId, url));
}
}
@@ -87,6 +91,7 @@ public final class InfoCache {
public void trimCache() {
if (DEBUG) Log.d(TAG, "trimCache() called");
synchronized (lruCache) {
removeStaleCache(lruCache);
lruCache.trimToSize(TRIM_CACHE_TO);
}
}
@@ -97,4 +102,51 @@ public final class InfoCache {
}
}
private static String keyOf(@NonNull final Info info) {
return keyOf(info.getServiceId(), info.getUrl());
}
private static String keyOf(final int serviceId, @NonNull final String url) {
return serviceId + url;
}
private static void removeStaleCache(@NonNull final LruCache<String, CacheData> cache) {
for (Map.Entry<String, CacheData> entry : cache.snapshot().entrySet()) {
final CacheData data = entry.getValue();
if (data != null && data.isExpired()) {
cache.remove(entry.getKey());
}
}
}
private static Info getInfo(@NonNull final LruCache<String, CacheData> cache,
@NonNull final String key) {
final CacheData data = cache.get(key);
if (data == null) return null;
if (data.isExpired()) {
cache.remove(key);
return null;
}
return data.info;
}
final private static class CacheData {
final private long expireTimestamp;
final private Info info;
private CacheData(@NonNull final Info info,
final long timeout,
@NonNull final TimeUnit timeUnit) {
this.expireTimestamp = System.currentTimeMillis() +
TimeUnit.MILLISECONDS.convert(timeout, timeUnit);
this.info = info;
}
private boolean isExpired() {
return System.currentTimeMillis() > expireTimestamp;
}
}
}

View File

@@ -95,7 +95,7 @@ public final class ListHelper {
int highestQualityIndex = 0;
if (audioStreams.size() > 1) for (int i = 1; i < audioStreams.size(); i++) {
AudioStream audioStream = audioStreams.get(i);
if (audioStream.average_bitrate >= audioStreams.get(highestQualityIndex).average_bitrate) highestQualityIndex = i;
if (audioStream.getAverageBitrate() >= audioStreams.get(highestQualityIndex).getAverageBitrate()) highestQualityIndex = i;
}
return highestQualityIndex;
}
@@ -124,10 +124,10 @@ public final class ListHelper {
int highestQualityIndex = -1;
for (int i = 0; i < audioStreams.size(); i++) {
AudioStream audioStream = audioStreams.get(i);
if (highestQualityIndex == -1 && audioStream.format == defaultFormat.id) highestQualityIndex = i;
if (highestQualityIndex == -1 && audioStream.getFormat() == defaultFormat) highestQualityIndex = i;
if (highestQualityIndex != -1 && audioStream.format == defaultFormat.id
&& audioStream.average_bitrate > audioStreams.get(highestQualityIndex).average_bitrate) {
if (highestQualityIndex != -1 && audioStream.getFormat() == defaultFormat
&& audioStream.getAverageBitrate() > audioStreams.get(highestQualityIndex).getAverageBitrate()) {
highestQualityIndex = i;
}
}
@@ -171,23 +171,23 @@ public final class ListHelper {
if (videoOnlyStreams != null) {
for (VideoStream stream : videoOnlyStreams) {
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue;
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.getResolution())) continue;
retList.add(stream);
}
}
if (videoStreams != null) {
for (VideoStream stream : videoStreams) {
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue;
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.getResolution())) continue;
retList.add(stream);
}
}
// Add all to the hashmap
for (VideoStream videoStream : retList) hashMap.put(videoStream.resolution, videoStream);
for (VideoStream videoStream : retList) hashMap.put(videoStream.getResolution(), videoStream);
// Override the values when the key == resolution, with the defaultFormat
for (VideoStream videoStream : retList) {
if (videoStream.format == defaultFormat.id) hashMap.put(videoStream.resolution, videoStream);
if (videoStream.getFormat() == defaultFormat) hashMap.put(videoStream.getResolution(), videoStream);
}
retList.clear();
@@ -219,8 +219,8 @@ public final class ListHelper {
Collections.sort(videoStreams, new Comparator<VideoStream>() {
@Override
public int compare(VideoStream o1, VideoStream o2) {
int res1 = Integer.parseInt(o1.resolution.replace("0p60", "1").replaceAll("[^\\d.]", ""));
int res2 = Integer.parseInt(o2.resolution.replace("0p60", "1").replaceAll("[^\\d.]", ""));
int res1 = Integer.parseInt(o1.getResolution().replace("0p60", "1").replaceAll("[^\\d.]", ""));
int res2 = Integer.parseInt(o2.getResolution().replace("0p60", "1").replaceAll("[^\\d.]", ""));
return ascendingOrder ? res1 - res2 : res2 - res1;
}
@@ -235,9 +235,9 @@ public final class ListHelper {
int defaultStreamIndex = -1;
for (int i = 0; i < videoStreams.size(); i++) {
VideoStream stream = videoStreams.get(i);
if (defaultStreamIndex == -1 && stream.resolution.equals(defaultResolution)) defaultStreamIndex = i;
if (defaultStreamIndex == -1 && stream.getResolution().equals(defaultResolution)) defaultStreamIndex = i;
if (stream.format == defaultFormat.id && stream.resolution.equals(defaultResolution)) {
if (stream.getFormat() == defaultFormat && stream.getResolution().equals(defaultResolution)) {
return i;
}
}

View File

@@ -5,10 +5,13 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.ImageLoader;
@@ -20,6 +23,10 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
@@ -28,18 +35,28 @@ import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.history.HistoryActivity;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.BackgroundPlayerActivity;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.player.PopupVideoPlayerActivity;
import org.schabi.newpipe.player.VideoPlayer;
import org.schabi.newpipe.player.old.PlayVideoActivity;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.settings.SettingsActivity;
import java.util.ArrayList;
@SuppressWarnings({"unused", "WeakerAccess"})
public class NavigationHelper {
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag";
/*//////////////////////////////////////////////////////////////////////////
// Players
//////////////////////////////////////////////////////////////////////////*/
public static Intent getPlayerIntent(final Context context,
final Class targetClazz,
final PlayQueue playQueue,
@@ -59,9 +76,11 @@ public class NavigationHelper {
public static Intent getPlayerEnqueueIntent(final Context context,
final Class targetClazz,
final PlayQueue playQueue) {
final PlayQueue playQueue,
final boolean selectOnAppend) {
return getPlayerIntent(context, targetClazz, playQueue)
.putExtra(BasePlayer.APPEND_ONLY, true);
.putExtra(BasePlayer.APPEND_ONLY, true)
.putExtra(BasePlayer.SELECT_ON_APPEND, selectOnAppend);
}
public static Intent getPlayerIntent(final Context context,
@@ -77,6 +96,133 @@ public class NavigationHelper {
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch);
}
public static void playOnMainPlayer(final Context context, final PlayQueue queue) {
final Intent playerIntent = getPlayerIntent(context, MainVideoPlayer.class, queue);
playerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(playerIntent);
}
public static void playOnOldVideoPlayer(Context context, StreamInfo info) {
ArrayList<VideoStream> videoStreamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false));
int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList);
if (index == -1) {
Toast.makeText(context, R.string.video_streams_empty, Toast.LENGTH_SHORT).show();
return;
}
VideoStream videoStream = videoStreamsList.get(index);
Intent intent = new Intent(context, PlayVideoActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(PlayVideoActivity.VIDEO_TITLE, info.getName())
.putExtra(PlayVideoActivity.STREAM_URL, videoStream.getUrl())
.putExtra(PlayVideoActivity.VIDEO_URL, info.getUrl())
.putExtra(PlayVideoActivity.START_POSITION, info.getStartPosition());
context.startActivity(intent);
}
public static void playOnPopupPlayer(final Context context, final PlayQueue queue) {
if (!PermissionHelper.isPopupEnabled(context)) {
PermissionHelper.showPopupEnablementToast(context);
return;
}
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
context.startService(getPlayerIntent(context, PopupVideoPlayer.class, queue));
}
public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue) {
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
context.startService(getPlayerIntent(context, BackgroundPlayer.class, queue));
}
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue) {
enqueueOnPopupPlayer(context, queue, false);
}
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend) {
if (!PermissionHelper.isPopupEnabled(context)) {
PermissionHelper.showPopupEnablementToast(context);
return;
}
Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show();
context.startService(getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue, selectOnAppend));
}
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue) {
enqueueOnBackgroundPlayer(context, queue, false);
}
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend) {
Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show();
context.startService(getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue, selectOnAppend));
}
/*//////////////////////////////////////////////////////////////////////////
// External Players
//////////////////////////////////////////////////////////////////////////*/
public static void playOnExternalAudioPlayer(Context context, StreamInfo info) {
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
if (index == -1) {
Toast.makeText(context, R.string.audio_streams_empty, Toast.LENGTH_SHORT).show();
return;
}
AudioStream audioStream = info.getAudioStreams().get(index);
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream);
}
public static void playOnExternalVideoPlayer(Context context, StreamInfo info) {
ArrayList<VideoStream> videoStreamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false));
int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList);
if (index == -1) {
Toast.makeText(context, R.string.video_streams_empty, Toast.LENGTH_SHORT).show();
return;
}
VideoStream videoStream = videoStreamsList.get(index);
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), videoStream);
}
public static void playOnExternalPlayer(Context context, String name, String artist, Stream stream) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(stream.getUrl()), stream.getFormat().getMimeType());
intent.putExtra(Intent.EXTRA_TITLE, name);
intent.putExtra("title", name);
intent.putExtra("artist", artist);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
resolveActivityOrAskToInstall(context, intent);
}
public static void resolveActivityOrAskToInstall(Context context, Intent intent) {
if (intent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(intent);
} else {
if (context instanceof Activity) {
new AlertDialog.Builder(context)
.setMessage(R.string.no_player_found)
.setPositiveButton(R.string.install, (dialog, which) -> {
Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
i.setData(Uri.parse(context.getString(R.string.fdroid_vlc_url)));
context.startActivity(i);
})
.setNegativeButton(R.string.cancel, (dialog, which) -> Log.i("NavigationHelper", "You unlocked a secret unicorn."))
.show();
//Log.e("NavigationHelper", "Either no Streaming player for audio was installed, or something important crashed:");
} else {
Toast.makeText(context, R.string.no_player_found_toast, Toast.LENGTH_LONG).show();
}
}
}
/*//////////////////////////////////////////////////////////////////////////
// Through FragmentManager
//////////////////////////////////////////////////////////////////////////*/
@@ -99,11 +245,21 @@ public class NavigationHelper {
.commit();
}
public static boolean tryGotoSearchFragment(FragmentManager fragmentManager) {
if (MainActivity.DEBUG) {
for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
Log.d("NavigationHelper", "tryGoToSearchFragment() [" + i + "] = [" + fragmentManager.getBackStackEntryAt(i) + "]");
}
}
return fragmentManager.popBackStackImmediate(SEARCH_FRAGMENT_TAG, 0);
}
public static void openSearchFragment(FragmentManager fragmentManager, int serviceId, String query) {
fragmentManager.beginTransaction()
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out)
.replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, query))
.addToBackStack(null)
.addToBackStack(SEARCH_FRAGMENT_TAG)
.commit();
}
@@ -230,23 +386,26 @@ public class NavigationHelper {
return true;
}
public static Intent getBackgroundPlayerActivityIntent(final Context context) {
return getServicePlayerActivityIntent(context, BackgroundPlayerActivity.class);
}
public static Intent getPopupPlayerActivityIntent(final Context context) {
return getServicePlayerActivityIntent(context, PopupVideoPlayerActivity.class);
}
private static Intent getServicePlayerActivityIntent(final Context context,
final Class activityClass) {
Intent intent = new Intent(context, activityClass);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
return intent;
}
/*//////////////////////////////////////////////////////////////////////////
// Link handling
//////////////////////////////////////////////////////////////////////////*/
public static boolean openByLink(Context context, String url) {
Intent intentByLink;
try {
intentByLink = getIntentByLink(context, url);
} catch (ExtractionException e) {
return false;
}
intentByLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentByLink.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intentByLink);
return true;
}
private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) {
Intent mIntent = new Intent(context, MainActivity.class);
mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
@@ -264,15 +423,14 @@ public class NavigationHelper {
throw new ExtractionException("Service not supported at the moment");
}
int serviceId = service.getServiceId();
StreamingService.LinkType linkType = service.getLinkTypeByUrl(url);
if (linkType == StreamingService.LinkType.NONE) {
throw new ExtractionException("Url not known to service. service=" + serviceId + " url=" + url);
throw new ExtractionException("Url not known to service. service=" + service + " url=" + url);
}
url = getCleanUrl(service, url, linkType);
Intent rIntent = getOpenIntent(context, url, serviceId, linkType);
Intent rIntent = getOpenIntent(context, url, service.getServiceId(), linkType);
switch (linkType) {
case STREAM:
@@ -284,7 +442,7 @@ public class NavigationHelper {
return rIntent;
}
private static String getCleanUrl(StreamingService service, String dirtyUrl, StreamingService.LinkType linkType) throws ExtractionException {
public static String getCleanUrl(StreamingService service, String dirtyUrl, StreamingService.LinkType linkType) throws ExtractionException {
switch (linkType) {
case STREAM:
return service.getStreamUrlIdHandler().cleanUrl(dirtyUrl);
@@ -298,7 +456,6 @@ public class NavigationHelper {
return null;
}
private static Uri openMarketUrl(String packageName) {
return Uri.parse("market://details")
.buildUpon()

View File

@@ -11,11 +11,15 @@ import android.provider.Settings;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.view.Gravity;
import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.R;
public class PermissionHelper {
public static final int PERMISSION_WRITE_STORAGE = 778;
public static final int PERMISSION_READ_STORAGE = 777;
public static final int PERMISSION_SYSTEM_ALERT_WINDOW = 779;
public static boolean checkStoragePermissions(Activity activity) {
@@ -75,21 +79,31 @@ public class PermissionHelper {
* In order to be able to draw over other apps, the permission android.permission.SYSTEM_ALERT_WINDOW have to be granted.
* <p>
* On < API 23 (MarshMallow) the permission was granted when the user installed the application (via AndroidManifest),
* on > 23, however, it have to start a activity asking the user if he agree.
* on > 23, however, it have to start a activity asking the user if he agrees.
* <p>
* This method just return if canDraw over other apps, if it doesn't, try to get the permission,
* it does not get the result of the startActivityForResult, if the user accept, the next time that he tries to open
* it will return true.
* This method just return if the app has permission to draw over other apps, and if it doesn't, it will try to get the permission.
*
* @param activity context to startActivityForResult
* @return returns {@link Settings#canDrawOverlays(Context)}
**/
@RequiresApi(api = Build.VERSION_CODES.M)
public static boolean checkSystemAlertWindowPermission(Activity activity) {
if (!Settings.canDrawOverlays(activity)) {
Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + activity.getPackageName()));
activity.startActivityForResult(i, PERMISSION_SYSTEM_ALERT_WINDOW);
public static boolean checkSystemAlertWindowPermission(Context context) {
if (!Settings.canDrawOverlays(context)) {
Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName()));
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
return false;
}else return true;
}
public static boolean isPopupEnabled(Context context) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
PermissionHelper.checkSystemAlertWindowPermission(context);
}
public static void showPopupEnablementToast(Context context) {
Toast toast = Toast.makeText(context, R.string.msg_popup_permission, Toast.LENGTH_LONG);
TextView messageView = toast.getView().findViewById(android.R.id.message);
if (messageView != null) messageView.setGravity(Gravity.CENTER);
toast.show();
}
}

View File

@@ -0,0 +1,48 @@
package org.schabi.newpipe.util;
import android.widget.PopupMenu;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Created by Christian Schabesberger on 20.01.18.
* Copyright 2018 Christian Schabesberger <chris.schabesberger@mailbox.org>
* PopupMenuIconHacker.java is part of NewPipe
*
* License: GPL-3.0+
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
public class PopupMenuIconHacker {
public static void setShowPopupIcon(PopupMenu menu) throws Exception {
try {
Field[] fields = menu.getClass().getDeclaredFields();
for (Field field : fields) {
if ("mPopup".equals(field.getName())) {
field.setAccessible(true);
Object menuPopupHelper = field.get(menu);
Class<?> classPopupHelper = Class.forName(menuPopupHelper
.getClass().getName());
Method setForceIcons = classPopupHelper.getMethod(
"setForceShowIcon", boolean.class);
setForceIcons.invoke(menuPopupHelper, true);
break;
}
}
} catch (Exception e) {
throw new Exception("Could not make Popup menu show Icons", e);
}
}
}

View File

@@ -0,0 +1,67 @@
package org.schabi.newpipe.util;
import android.content.Context;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
public class ServiceHelper {
private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube.getService();
@DrawableRes
public static int getIcon(int serviceId) {
switch (serviceId) {
case 0:
return R.drawable.place_holder_youtube;
case 1:
return R.drawable.place_holder_circle;
default:
return R.drawable.service;
}
}
public static int getSelectedServiceId(Context context) {
if (BuildConfig.BUILD_TYPE.equals("release")) return DEFAULT_FALLBACK_SERVICE.getServiceId();
final String serviceName = PreferenceManager.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.current_service_key), context.getString(R.string.default_service_value));
int serviceId;
try {
serviceId = NewPipe.getService(serviceName).getServiceId();
} catch (ExtractionException e) {
serviceId = DEFAULT_FALLBACK_SERVICE.getServiceId();
}
return serviceId;
}
public static void setSelectedServiceId(Context context, int serviceId) {
String serviceName;
try {
serviceName = NewPipe.getService(serviceId).getServiceInfo().name;
} catch (ExtractionException e) {
serviceName = DEFAULT_FALLBACK_SERVICE.getServiceInfo().name;
}
setSelectedServicePreferences(context, serviceName);
}
public static void setSelectedServiceId(Context context, String serviceName) {
int serviceId = NewPipe.getIdOfService(serviceName);
if (serviceId == -1) serviceName = DEFAULT_FALLBACK_SERVICE.getServiceInfo().name;
setSelectedServicePreferences(context, serviceName);
}
private static void setSelectedServicePreferences(Context context, String serviceName) {
PreferenceManager.getDefaultSharedPreferences(context).edit().
putString(context.getString(R.string.current_service_key), serviceName).apply();
}
}

View File

@@ -1,35 +0,0 @@
package org.schabi.newpipe.util;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
/**
* Created by Chrsitian Schabesberger on 09.10.17.
* ServiceIconMapper.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 ServiceIconMapper {
public static int getIconResource(int service_id) {
switch(service_id) {
case 0:
return R.drawable.youtube;
case 1:
return R.drawable.soud_cloud;
default:
return R.drawable.service;
}
}
}

View File

@@ -1,29 +1,38 @@
package org.schabi.newpipe.util;
import android.content.Context;
import android.content.res.TypedArray;
import android.preference.PreferenceManager;
import android.support.annotation.AttrRes;
import android.support.annotation.StyleRes;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
public class ThemeHelper {
/**
* Apply the selected theme (on NewPipe settings) in the context
* with the default style (see {@link #setTheme(Context, int)}).
*
* @param context context that the theme will be applied
*/
public static void setTheme(Context context) {
String lightTheme = context.getResources().getString(R.string.light_theme_key);
String darkTheme = context.getResources().getString(R.string.dark_theme_key);
String blackTheme = context.getResources().getString(R.string.black_theme_key);
setTheme(context, -1);
}
String selectedTheme = getSelectedTheme(context);
if (selectedTheme.equals(lightTheme)) context.setTheme(R.style.LightTheme);
else if (selectedTheme.equals(blackTheme)) context.setTheme(R.style.BlackTheme);
else if (selectedTheme.equals(darkTheme)) context.setTheme(R.style.DarkTheme);
// Fallback
else context.setTheme(R.style.DarkTheme);
/**
* Apply the selected theme (on NewPipe settings) in the context,
* themed according with the styles defined for the service .
*
* @param context context that the theme will be applied
* @param serviceId the theme will be styled to the service with this id,
* pass -1 to get the default style
*/
public static void setTheme(Context context, int serviceId) {
context.setTheme(getThemeForService(context, serviceId));
}
/**
@@ -35,9 +44,73 @@ public class ThemeHelper {
return getSelectedTheme(context).equals(context.getResources().getString(R.string.light_theme_key));
}
@StyleRes
public static int getThemeForService(Context context, int serviceId) {
String lightTheme = context.getResources().getString(R.string.light_theme_key);
String darkTheme = context.getResources().getString(R.string.dark_theme_key);
String blackTheme = context.getResources().getString(R.string.black_theme_key);
String selectedTheme = getSelectedTheme(context);
int defaultTheme = R.style.DarkTheme;
if (selectedTheme.equals(lightTheme)) defaultTheme = R.style.LightTheme;
else if (selectedTheme.equals(blackTheme)) defaultTheme = R.style.BlackTheme;
else if (selectedTheme.equals(darkTheme)) defaultTheme = R.style.DarkTheme;
if (serviceId <= -1) {
return defaultTheme;
}
final StreamingService service;
try {
service = NewPipe.getService(serviceId);
} catch (ExtractionException ignored) {
return defaultTheme;
}
String themeName = "DarkTheme";
if (selectedTheme.equals(lightTheme)) themeName = "LightTheme";
else if (selectedTheme.equals(blackTheme)) themeName = "BlackTheme";
else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme";
themeName += "." + service.getServiceInfo().name;
int resourceId = context.getResources().getIdentifier(themeName, "style", context.getPackageName());
if (resourceId > 0) {
return resourceId;
}
return defaultTheme;
}
public static String getSelectedTheme(Context context) {
String themeKey = context.getString(R.string.theme_key);
String defaultTheme = context.getResources().getString(R.string.default_theme_value);
return PreferenceManager.getDefaultSharedPreferences(context).getString(themeKey, defaultTheme);
}
@StyleRes
public static int getSettingsThemeStyle(Context context) {
String lightTheme = context.getResources().getString(R.string.light_theme_key);
String darkTheme = context.getResources().getString(R.string.dark_theme_key);
String blackTheme = context.getResources().getString(R.string.black_theme_key);
String selectedTheme = getSelectedTheme(context);
if (selectedTheme.equals(lightTheme)) return R.style.LightSettingsTheme;
else if (selectedTheme.equals(blackTheme)) return R.style.BlackSettingsTheme;
else if (selectedTheme.equals(darkTheme)) return R.style.DarkSettingsTheme;
// Fallback
else return R.style.DarkSettingsTheme;
}
/**
* Get a resource id from a resource styled according to the the context's theme.
*/
public static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) {
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
int attributeResourceId = a.getResourceId(0, 0);
a.recycle();
return attributeResourceId;
}
}

View File

@@ -0,0 +1,94 @@
package org.schabi.newpipe.util;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* Created by Christian Schabesberger on 28.01.18.
* Copyright 2018 Christian Schabesberger <chris.schabesberger@mailbox.org>
* ZipHelper.java is part of NewPipe
*
* License: GPL-3.0+
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
public class ZipHelper {
private static final int BUFFER_SIZE = 2048;
/**
* This function helps to create zip files.
* Caution this will override the original file.
* @param outZip The ZipOutputStream where the data should be stored in
* @param file The path of the file that should be added to zip.
* @param name The path of the file inside the zip.
* @throws Exception
*/
public static void addFileToZip(ZipOutputStream outZip, String file, String name) throws Exception {
byte data[] = new byte[BUFFER_SIZE];
FileInputStream fi = new FileInputStream(file);
BufferedInputStream inputStream = new BufferedInputStream(fi, BUFFER_SIZE);
ZipEntry entry = new ZipEntry(name);
outZip.putNextEntry(entry);
int count;
while((count = inputStream.read(data, 0, BUFFER_SIZE)) != -1) {
outZip.write(data, 0, count);
}
inputStream.close();
}
/**
* This will extract data from Zipfiles.
* Caution this will override the original file.
* @param inZip The ZipOutputStream where the data is stored in
* @param file The path of the file on the disk where the data should be extracted to.
* @param name The path of the file inside the zip.
* @return will return true if the file was found within the zip file
* @throws Exception
*/
public static boolean extractFileFromZip(ZipInputStream inZip, String file, String name) throws Exception {
byte data[] = new byte[BUFFER_SIZE];
boolean found = false;
ZipEntry ze;
while((ze = inZip.getNextEntry()) != null) {
if(ze.getName().equals(name)) {
found = true;
// delete old file first
File oldFile = new File(file);
if(oldFile.exists()) {
if(!oldFile.delete()) {
throw new Exception("Could not delete " + file);
}
}
FileOutputStream outFile = new FileOutputStream(file);
int count = 0;
while((count = inZip.read(data)) != -1) {
outFile.write(data, 0, count);
}
outFile.close();
inZip.closeEntry();
}
}
return true;
}
}

View File

@@ -6,8 +6,8 @@ import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
@@ -15,7 +15,6 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.PermissionChecker;
import android.util.Log;
import android.widget.Toast;
@@ -96,12 +95,12 @@ public class DownloadManagerService extends Service {
openDownloadListIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
Drawable icon = ContextCompat.getDrawable(this, R.mipmap.ic_launcher);
Bitmap iconBitmap = BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher);
Builder builder = new Builder(this, getString(R.string.notification_channel_id))
.setContentIntent(pendingIntent)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
.setLargeIcon(iconBitmap)
.setContentTitle(getString(R.string.msg_running))
.setContentText(getString(R.string.msg_running_detail));

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/decelerate_quint">
<alpha
android:duration="150"
android:fromAlpha="0.00"
android:toAlpha="1.0"/>
</set>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/accelerate_quint">
<alpha
android:duration="350"
android:fromAlpha="1.0"
android:toAlpha="0.00"/>
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

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