1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2026-01-14 21:48:00 +00:00

Compare commits

...

447 Commits

Author SHA1 Message Date
Christian Schabesberger
3bb95ad44c add changelog for version v0.13.4 2018-05-25 18:38:07 +02:00
Christian Schabesberger
0a6572c282 roll back to more stable version of newpipe extractor 2018-05-25 18:29:30 +02:00
Christian Schabesberger
3937067be1 move on to version 0.13.4 2018-05-25 09:45:22 +02:00
Christian Schabesberger
48e4eb44f2 remove unused imports 2018-05-25 09:43:28 +02:00
TobiGr
c78cc6f2fd Add dialog to accept privacy policy before sending crash report
Add link to privacy policy in about fragment
Replace some onClickListeners with Lamdas
2018-05-25 09:29:14 +02:00
Bogdan Khomutsky
73a71e0f5c Translated using Weblate (Russian)
Currently translated at 100.0% (365 of 365 strings)
2018-05-25 08:22:43 +02:00
SINUS (সাইনাস)
93605774f0 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 40.0% (146 of 365 strings)
2018-05-24 18:34:46 +02:00
Daria Szatan
2834e5d78f Translated using Weblate (Polish)
Currently translated at 100.0% (365 of 365 strings)
2018-05-23 19:53:07 +02:00
Allan Nordhøy
dd467b4d63 Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.1% (351 of 365 strings)
2018-05-22 11:40:49 +02:00
SN
1fa541776b Translated using Weblate (Hindi)
Currently translated at 80.0% (292 of 365 strings)
2018-05-22 08:37:59 +02:00
Weblate
f6f67c7b0a Merge branch 'dev' into weblate-merge-tmp 2018-05-21 06:37:51 +02:00
lartial
9b3f19c19b Translated using Weblate (Indonesian)
Currently translated at 100.0% (365 of 365 strings)
2018-05-21 06:37:49 +02:00
Christian Schabesberger
f4a9ec15e8 Merge pull request #1407 from DafabHoid/dev
Downloader: Fix crash on loading unfinished downloads from .giga files
2018-05-19 16:19:33 +02:00
Osoitz
006c4ecb02 Translated using Weblate (Basque)
Currently translated at 100.0% (365 of 365 strings)
2018-05-19 10:34:39 +02:00
Nishargo Nigar
1c752b0e18 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 34.2% (125 of 365 strings)
2018-05-19 08:34:45 +02:00
AB
f84aff63e3 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (365 of 365 strings)
2018-05-18 19:06:33 +02:00
DafabHoid
ae8121b680 Utility: Buffer the output to files when serializing 2018-05-18 18:23:32 +02:00
DafabHoid
882fbf9275 Fix crash on loading not yet finished downloads from .giga files 2018-05-18 18:18:37 +02:00
Dual Natan
0a1743251e Translated using Weblate (Macedonian)
Currently translated at 100.0% (365 of 365 strings)
2018-05-18 11:49:10 +02:00
Osoitz
3403a127c1 Translated using Weblate (Basque)
Currently translated at 100.0% (365 of 365 strings)
2018-05-18 10:24:13 +02:00
Robson Cassiano
9c5ca9f09d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (365 of 365 strings)
2018-05-17 16:40:02 +02:00
David Adrião
73eea5608a Translated using Weblate (Portuguese)
Currently translated at 86.3% (315 of 365 strings)
2018-05-17 13:40:38 +02:00
Osoitz
f48aeb91f4 Translated using Weblate (Basque)
Currently translated at 98.9% (361 of 365 strings)
2018-05-17 11:34:34 +02:00
Robson Cassiano
0bda964ece Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (365 of 365 strings)
2018-05-16 16:21:22 +02:00
Ale-Ma
14f5d54b50 Translated using Weblate (Italian)
Currently translated at 100.0% (365 of 365 strings)
2018-05-16 00:13:39 +02:00
Freddy Morán Jr
105ac2f6ff Translated using Weblate (Spanish)
Currently translated at 99.4% (363 of 365 strings)
2018-05-15 20:43:45 +02:00
HashikDonthineni
160560f1fd Translated using Weblate (Telugu)
Currently translated at 35.3% (129 of 365 strings)
2018-05-15 17:42:23 +02:00
ditokp
deeb667d6f Translated using Weblate (Indonesian)
Currently translated at 100.0% (365 of 365 strings)
2018-05-15 13:48:13 +02:00
Ali Demirtas
78123ff6f5 Translated using Weblate (Turkish)
Currently translated at 100.0% (365 of 365 strings)
2018-05-14 21:09:52 +02:00
9b1fdff22f Translated using Weblate (Romanian)
Currently translated at 82.7% (302 of 365 strings)
2018-05-14 19:46:37 +02:00
Ciprian
0275502796 Translated using Weblate (Romanian)
Currently translated at 82.7% (302 of 365 strings)
2018-05-14 19:46:32 +02:00
Weblate
8af475e319 Merge branch 'dev' into weblate-merge-tmp 2018-05-14 13:34:29 +02:00
Allan Nordhøy
2202c8f09e Translated using Weblate (Norwegian Bokmål)
Currently translated at 95.8% (350 of 365 strings)
2018-05-14 13:34:28 +02:00
ScratchBuild
adf309d3a8 Translated using Weblate (Japanese)
Currently translated at 77.5% (283 of 365 strings)
2018-05-14 13:34:26 +02:00
Marc Riera
3071314586 Translated using Weblate (Catalan)
Currently translated at 100.0% (365 of 365 strings)
2018-05-14 13:34:24 +02:00
thami simo
88e1df840d Translated using Weblate (Arabic)
Currently translated at 100.0% (365 of 365 strings)
2018-05-14 13:34:21 +02:00
Christian Schabesberger
23c1b66f6c add note to contribution description 2018-05-14 13:24:35 +02:00
ezjerry liao
b0318a1cce Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (365 of 365 strings)
2018-05-14 11:52:01 +02:00
Nathan Follens
c130a66e4d Translated using Weblate (Flemish)
Currently translated at 100.0% (365 of 365 strings)
2018-05-13 18:44:26 +02:00
ssantos
3386ba6d1b Translated using Weblate (German)
Currently translated at 100.0% (365 of 365 strings)
2018-05-13 16:41:08 +02:00
Marc Riera
9275569fa6 Translated using Weblate (Catalan)
Currently translated at 100.0% (365 of 365 strings)
2018-05-13 13:50:07 +02:00
Heimen Stoffels
2b23dfd0a6 Translated using Weblate (Dutch)
Currently translated at 100.0% (365 of 365 strings)
2018-05-13 13:46:49 +02:00
thami simo
2a13d9990e Translated using Weblate (Arabic)
Currently translated at 100.0% (365 of 365 strings)
2018-05-13 12:37:46 +02:00
Weblate
c9669b51c6 Merge branch 'dev' into weblate-merge-tmp 2018-05-13 11:34:46 +02:00
Osoitz
486c180b3c Translated using Weblate (Basque)
Currently translated at 98.9% (360 of 364 strings)
2018-05-13 11:34:43 +02:00
Christian Schabesberger
9eb5bf9b87 Merge pull request #1375 from acrosca/code_inspection
Code inspection
2018-05-12 14:21:37 +02:00
Christian Schabesberger
953a89f3a1 Merge branch 'settingsExport' of https://github.com/Somethingweirdhere/NewPipe into test 2018-05-12 13:34:05 +02:00
Christian Schabesberger
d638fa1434 use commit from newpipeextractor master 2018-05-11 18:00:28 +02:00
Christian Schabesberger
e6d700288c fix afiliate parse link failure 2018-05-11 18:00:28 +02:00
Christian Schabesberger
371f14cdc9 make compartible to yoututbe service restructure 2018-05-11 18:00:28 +02:00
Christian Schabesberger
0733ae2404 make compatible with encosing urlidhandler commit 2018-05-11 18:00:28 +02:00
ButterflyOfFire
b1731ebd49 Translated using Weblate (French)
Currently translated at 99.1% (361 of 364 strings)
2018-05-11 17:37:14 +02:00
Somethingweirdhere
342b3191ac Changed to lambda convention 2018-05-11 17:17:07 +02:00
zmni
cd39445245 Translated using Weblate (Indonesian)
Currently translated at 91.2% (332 of 364 strings)
2018-05-11 16:39:32 +02:00
Freddy Morán Jr
92eac67367 Translated using Weblate (Spanish)
Currently translated at 97.2% (354 of 364 strings)
2018-05-10 19:41:12 +02:00
Edwar Tikhonov
0e31a0c704 Translated using Weblate (Russian)
Currently translated at 100.0% (364 of 364 strings)
2018-05-10 11:42:55 +02:00
gensitu
d60c117a70 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (364 of 364 strings)
2018-05-10 11:36:24 +02:00
thami simo
69ccad5998 Translated using Weblate (Arabic)
Currently translated at 100.0% (364 of 364 strings)
2018-05-10 11:34:28 +02:00
Edwar Tikhonov
b6d22320e6 Translated using Weblate (Russian)
Currently translated at 100.0% (364 of 364 strings)
2018-05-09 11:12:45 +02:00
Weblate
d3bf948dba Merge remote-tracking branch 'origin/dev' into dev 2018-05-09 09:50:28 +02:00
ditokp
5de3d96b31 Translated using Weblate (Indonesian)
Currently translated at 85.7% (312 of 364 strings)
2018-05-09 09:50:28 +02:00
Florian
d30dd64322 Translated using Weblate (French)
Currently translated at 99.1% (361 of 364 strings)
2018-05-09 09:50:27 +02:00
thami simo
386df10a5a Translated using Weblate (Arabic)
Currently translated at 100.0% (364 of 364 strings)
2018-05-09 09:50:25 +02:00
Christian Schabesberger
af147de547 upgrade gradle 2018-05-08 20:56:11 +02:00
23cd0e5a5e Translated using Weblate (French)
Currently translated at 98.3% (358 of 364 strings)
2018-05-08 17:30:05 +02:00
c5a566657c Translated using Weblate (French)
Currently translated at 98.3% (358 of 364 strings)
2018-05-08 16:21:31 +02:00
Alexander Sparzt
ce2018c864 Translated using Weblate (French)
Currently translated at 98.3% (358 of 364 strings)
2018-05-08 16:21:26 +02:00
472ab46af2 Translated using Weblate (French)
Currently translated at 98.3% (358 of 364 strings)
2018-05-08 16:18:30 +02:00
Alexander Sparzt
7f87d45bb5 Translated using Weblate (French)
Currently translated at 98.3% (358 of 364 strings)
2018-05-08 16:18:25 +02:00
e9f7ab18bb Translated using Weblate (French)
Currently translated at 98.3% (358 of 364 strings)
2018-05-08 16:16:29 +02:00
Alexander Sparzt
fba83a8afe Translated using Weblate (French)
Currently translated at 98.3% (358 of 364 strings)
2018-05-08 16:16:24 +02:00
nailyk
05089abddc Translated using Weblate (French)
Currently translated at 96.4% (351 of 364 strings)
2018-05-08 14:43:20 +02:00
Prabjot Singh
ccd70aac51 Translated using Weblate (Punjabi)
Currently translated at 5.2% (19 of 364 strings)
2018-05-08 07:41:59 +02:00
nailyk
5df8445d04 Translated using Weblate (French)
Currently translated at 96.1% (350 of 364 strings)

Peut-être existe des traductions existantes mais je ne les aies pas trouvées.
2018-05-07 18:17:34 +02:00
nailyk
8c43674fa4 Translated using Weblate (French)
Currently translated at 95.8% (349 of 364 strings)
2018-05-07 18:14:23 +02:00
Florent Peterschmitt
f162316a6b Translated using Weblate (French)
Currently translated at 94.5% (344 of 364 strings)
2018-05-07 14:37:09 +02:00
Andrea Troiano
670596ed88 Translated using Weblate (Italian)
Currently translated at 100.0% (364 of 364 strings)
2018-05-07 10:08:51 +02:00
Prabjot Singh
6b3eb716c4 Added translation using Weblate (Punjabi) 2018-05-07 06:39:45 +02:00
Emin Tufan Çetin
6a5180d94c Translated using Weblate (Turkish)
Currently translated at 100.0% (364 of 364 strings)
2018-05-06 17:16:30 +02:00
Florent Peterschmitt
83faaedfcc Translated using Weblate (French)
Currently translated at 93.4% (340 of 364 strings)
2018-05-06 13:54:09 +02:00
Florian
d98d790a7a Translated using Weblate (French)
Currently translated at 93.4% (340 of 364 strings)
2018-05-06 13:54:02 +02:00
Florian
36b5833a3a Translated using Weblate (French)
Currently translated at 93.4% (340 of 364 strings)
2018-05-06 13:51:12 +02:00
5e86781a79 Translated using Weblate (French)
Currently translated at 89.5% (326 of 364 strings)
2018-05-06 13:27:25 +02:00
Florian
6a780504b4 Translated using Weblate (French)
Currently translated at 89.5% (326 of 364 strings)
2018-05-06 13:27:20 +02:00
Weblate
a0d8212136 Merge remote-tracking branch 'origin/dev' into dev 2018-05-06 13:01:58 +02:00
Marian Hanzel
0e1e6a9d62 Translated using Weblate (Slovak)
Currently translated at 95.8% (349 of 364 strings)
2018-05-06 13:01:58 +02:00
thami simo
812282a332 Translated using Weblate (Arabic)
Currently translated at 89.5% (326 of 364 strings)
2018-05-06 13:01:56 +02:00
r2308145
a8a4c9e97f Translated using Weblate (Czech)
Currently translated at 100.0% (364 of 364 strings)
2018-05-06 13:01:53 +02:00
Andrei.Rosca
24c293e335 fix context leaks 2018-05-06 10:50:02 +02:00
Andrei.Rosca
0a596df497 default ViewHolder 2018-05-06 10:14:24 +02:00
Andrei.Rosca
3d66c6572b prevent infinite loop 2018-05-06 10:08:56 +02:00
Andrei.Rosca
f45769cbb2 Reduce overdraw 2018-05-05 10:26:35 +02:00
Weblate
ef51f93c6f Merge remote-tracking branch 'origin/dev' into dev 2018-05-05 09:02:21 +02:00
ssantos
35af68f148 Translated using Weblate (German)
Currently translated at 100.0% (364 of 364 strings)
2018-05-05 09:02:19 +02:00
Christian Schabesberger
ff21430b43 move on to v0.13.3
also:
- reduce snack bar error visibility time
- fix metadata error
2018-05-04 16:21:36 +02:00
Weblate
fcb67f5119 Merge remote-tracking branch 'origin/dev' into dev 2018-05-04 09:40:15 +02:00
AB
0a41fbd185 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (364 of 364 strings)
2018-05-04 09:40:14 +02:00
Oleh Ilnytskyi
d32aaf488f Translated using Weblate (Russian)
Currently translated at 89.8% (327 of 364 strings)
2018-05-04 09:40:13 +02:00
Tobias Groza
1254798013 Translated using Weblate (German)
Currently translated at 92.5% (337 of 364 strings)
2018-05-04 09:40:12 +02:00
Heimen Stoffels
c72d2a2308 Translated using Weblate (Dutch)
Currently translated at 100.0% (364 of 364 strings)
2018-05-04 09:40:11 +02:00
Marc Riera
703181655b Translated using Weblate (Catalan)
Currently translated at 100.0% (364 of 364 strings)
2018-05-04 09:40:10 +02:00
Allan Nordhøy
da4a1c5bf0 Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.5% (344 of 364 strings)
2018-05-04 09:40:06 +02:00
Christian Schabesberger
1130bd502e Merge pull request #1342 from Somethingweirdhere/popupplayerdont
PopUpPlayer now also has a Play and Pause button
2018-05-03 17:25:11 +02:00
AB
27ea4ee679 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (364 of 364 strings)
2018-05-03 14:36:57 +02:00
Heimen Stoffels
56e3b66d06 Translated using Weblate (Dutch)
Currently translated at 100.0% (364 of 364 strings)
2018-05-03 11:36:33 +02:00
Nathan Follens
7d4768e151 Translated using Weblate (Dutch)
Currently translated at 100.0% (364 of 364 strings)
2018-05-03 11:36:28 +02:00
Marc Riera
7ec1011610 Translated using Weblate (Catalan)
Currently translated at 100.0% (364 of 364 strings)
2018-05-03 11:18:20 +02:00
Nathan Follens
e1bbd2055c Translated using Weblate (Flemish)
Currently translated at 100.0% (364 of 364 strings)
2018-05-03 08:58:01 +02:00
Nathan Follens
8a19547d9f Translated using Weblate (Dutch)
Currently translated at 100.0% (364 of 364 strings)
2018-05-03 08:47:28 +02:00
Jeff Huang
6e6922dab8 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (364 of 364 strings)
2018-05-03 02:27:38 +02:00
Weblate
e009ade922 Merge remote-tracking branch 'origin/dev' into dev 2018-05-02 23:38:15 +02:00
Freddy Morán Jr
90e15bcab9 Translated using Weblate (Spanish)
Currently translated at 99.7% (362 of 363 strings)
2018-05-02 23:38:15 +02:00
Allan Nordhøy
2a040cea4b Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.4% (343 of 363 strings)
2018-05-02 23:38:14 +02:00
Yoav
72f2a7f8db Translated using Weblate (Hebrew)
Currently translated at 86.7% (315 of 363 strings)
2018-05-02 23:38:11 +02:00
Somethingweirdhere
8a8022afe6 Now the play/pause button also has the correct scale! 2018-05-02 22:08:14 +02:00
Christian Schabesberger
b692bec310 Merge pull request #1350 from comradekingu/patch-6
Spelling: Rework new strings
2018-05-02 21:03:20 +02:00
Andrea Troiano
536c01c70d Translated using Weblate (Italian)
Currently translated at 100.0% (363 of 363 strings)
2018-05-02 10:15:53 +02:00
Allan Nordhøy
ec8e14e977 reverted the other "History & Cache" 2018-05-02 09:18:04 +02:00
Allan Nordhøy
85b34f8809 New desc, "& Cache" reverted 2018-05-02 09:16:53 +02:00
Lukas Wiedemann
22951a56a5 Translated using Weblate (German)
Currently translated at 100.0% (363 of 363 strings)
2018-05-01 23:37:09 +02:00
r2308145
46ad84b101 Translated using Weblate (Czech)
Currently translated at 100.0% (363 of 363 strings)
2018-05-01 22:58:31 +02:00
Marc Riera
5efb77e520 Translated using Weblate (Catalan)
Currently translated at 100.0% (363 of 363 strings)
2018-05-01 18:34:55 +02:00
Lukas Wiedemann
76903102b8 Translated using Weblate (German)
Currently translated at 100.0% (363 of 363 strings)
2018-04-30 23:26:35 +02:00
Nathan Follens
c8e26b429c Translated using Weblate (Flemish)
Currently translated at 100.0% (363 of 363 strings)
2018-04-30 21:49:04 +02:00
Marc Riera
55c1310f74 Translated using Weblate (Catalan)
Currently translated at 100.0% (363 of 363 strings)
2018-04-30 17:32:00 +02:00
Heimen Stoffels
b8278d91e0 Translated using Weblate (Dutch)
Currently translated at 100.0% (363 of 363 strings)
2018-04-30 16:58:57 +02:00
Marian Hanzel
b032502148 Translated using Weblate (Slovak)
Currently translated at 100.0% (363 of 363 strings)
2018-04-30 16:42:39 +02:00
Jeff Huang
bec1a4dd1a Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (363 of 363 strings)
2018-04-30 16:22:48 +02:00
AB
4dfb9e7977 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (363 of 363 strings)
2018-04-30 15:06:19 +02:00
Weblate
2bfa165cdc Merge remote-tracking branch 'origin/dev' into dev 2018-04-30 14:42:49 +02:00
AB
658666276d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (354 of 354 strings)
2018-04-30 14:42:44 +02:00
Christian Schabesberger
62f91b9084 Merge pull request #1356 from DafabHoid/doubletap-playpause
Double-tap the middle of the player screen to pause the video
2018-04-29 17:16:09 +02:00
Christian Schabesberger
719140ab78 resolve conflict 2018-04-29 17:13:45 +02:00
Christian Schabesberger
0471fd8145 add clear orphans 2018-04-29 17:06:54 +02:00
Christian Schabesberger
a079a0c901 fix requested changes part 1 2018-04-29 13:15:52 +02:00
Christian Schabesberger
ac2fa74c8f merge chagnes with dev 2018-04-29 13:01:37 +02:00
Christian Schabesberger
4c10ef65f5 add delete while history
add delete whole history
2018-04-29 12:51:57 +02:00
Christian Schabesberger
cfa697fab2 make history ui more consistent 2018-04-29 12:51:57 +02:00
Christian Schabesberger
a09b9d3e4d made items actually deltable 2018-04-29 12:51:57 +02:00
Christian Schabesberger
c470909f19 add delete options for StatisticPlaylistFragment 2018-04-29 12:51:57 +02:00
Christian Schabesberger
5e59cfcf9d remove HistoryInfoItem again
blub
2018-04-29 12:51:57 +02:00
Christian Schabesberger
a099fe35d2 reorder playqueue/localPlaylist classes 2018-04-29 12:51:57 +02:00
Christian Schabesberger
bcfd8a2450 rename playlist to player.playqueue 2018-04-29 12:49:52 +02:00
Christian Schabesberger
8ed9d71e14 put local foo into local foo folder 2018-04-29 12:47:12 +02:00
Christian Schabesberger
004c2fa55a Project restructure for history part 1
- add HistoryInfoItem (deriving from StreamInfoItem)
  in order to add a special options menu for the list items
- delete HistoryActivity and everything that belongs to its UI (not the
  manager tho)
- put everything that is local into local. (subscriptions still missing)
2018-04-29 12:47:12 +02:00
DafabHoid
3dd63d03cb Double-tap the middle of the player screen to pause the video 2018-04-29 11:23:21 +02:00
AB
cceedab864 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (354 of 354 strings)
2018-04-29 10:34:40 +02:00
Andrei.Rosca
b494b2ea39 Remove GSON lib 2018-04-29 01:06:34 +02:00
Rubix
0b29cf086b Translated using Weblate (Romanian)
Currently translated at 86.7% (307 of 354 strings)
2018-04-27 21:40:56 +02:00
r2308145
11d33097f7 Translated using Weblate (Czech)
Currently translated at 100.0% (354 of 354 strings)
2018-04-27 11:35:54 +02:00
ButterflyOfFire
3ae61645de Translated using Weblate (Arabic)
Currently translated at 87.8% (311 of 354 strings)
2018-04-26 15:34:21 +02:00
Weblate
4711befffa Merge remote-tracking branch 'origin/dev' into dev 2018-04-25 02:40:49 +02:00
Robson Cassiano
5673d53a20 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (354 of 354 strings)
2018-04-25 02:40:47 +02:00
Christian Schabesberger
90ca4a5e92 Merge pull request #1346 from naXa777/dev
Fix #1345 Gradle build fails
2018-04-24 22:03:36 +02:00
Allan Nordhøy
ad252956ab Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.3% (334 of 354 strings)
2018-04-24 21:39:29 +02:00
Marian Hanzel
1b2c091c39 Translated using Weblate (Slovak)
Currently translated at 100.0% (354 of 354 strings)
2018-04-24 09:40:59 +02:00
9031bc0c7b Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.7% (353 of 354 strings)
2018-04-24 02:28:17 +02:00
1d85e0ea63 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.7% (353 of 354 strings)
2018-04-24 02:27:25 +02:00
Robson Cassiano
458774aadb Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.7% (353 of 354 strings)
2018-04-24 02:27:20 +02:00
ButterflyOfFire
ae89f7bea3 Translated using Weblate (Arabic)
Currently translated at 83.8% (297 of 354 strings)
2018-04-23 23:34:19 +02:00
Allan Nordhøy
fd77b8552b Rework new strings 2018-04-23 22:07:27 +02:00
Marian Hanzel
bae9f5e844 Translated using Weblate (Slovak)
Currently translated at 100.0% (354 of 354 strings)
2018-04-23 08:05:45 +02:00
Paul
e3f3d90b68 Translated using Weblate (Russian)
Currently translated at 99.1% (351 of 354 strings)
2018-04-22 19:41:32 +02:00
naXa!
7f3bd8aec2 Fix #1345 Gradle build fails with "error: unescaped apostrophe in string" 2018-04-22 14:31:59 +03:00
CaptainCrumble
4501203a7a Translated using Weblate (Portuguese)
Currently translated at 93.2% (330 of 354 strings)
2018-04-22 01:40:38 +02:00
Somethingweirdhere
06292bceb2 PopUpPlayer now has a play and pause button. Tapping now doesnt pause or unpause the video and instead shows this button. 2018-04-22 00:16:07 +02:00
Somethingweirdhere
f94f14ab65 Added settings export 2018-04-21 21:11:52 +02:00
AB
7145c68e03 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (354 of 354 strings)
2018-04-21 19:11:00 +02:00
wellinkstein
bac3825c87 Translated using Weblate (French)
Currently translated at 100.0% (354 of 354 strings)
2018-04-21 15:37:08 +02:00
Dante
fc1d283414 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (354 of 354 strings)
2018-04-21 09:35:29 +02:00
c0652daa97 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (354 of 354 strings)
2018-04-21 09:35:24 +02:00
Q. A
553903bd9d Translated using Weblate (Albanian)
Currently translated at 12.7% (45 of 354 strings)
2018-04-21 09:34:14 +02:00
Freddy Morán Jr
a43ec25b7e Translated using Weblate (Spanish)
Currently translated at 99.7% (353 of 354 strings)
2018-04-20 16:42:38 +02:00
wellinkstein
bb2a66fd02 Translated using Weblate (French)
Currently translated at 100.0% (354 of 354 strings)
2018-04-20 14:41:22 +02:00
Ale-Ma
71ac830bfa Translated using Weblate (Italian)
Currently translated at 100.0% (354 of 354 strings)
2018-04-20 12:15:48 +02:00
r2308145
82bce80c62 Translated using Weblate (Czech)
Currently translated at 100.0% (354 of 354 strings)
2018-04-20 11:48:04 +02:00
Dante
67ddf78e18 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (354 of 354 strings)
2018-04-20 09:25:12 +02:00
Marc Riera
8285df0f3f Translated using Weblate (Catalan)
Currently translated at 99.1% (351 of 354 strings)
2018-04-19 22:35:00 +02:00
Somethingweirdhere
bdb45295b9 Merge pull request #2 from TeamNewPipe/dev
d
2018-04-19 15:30:12 +02:00
Emin Tufan Çetin
f330ee8f8d Translated using Weblate (Turkish)
Currently translated at 100.0% (354 of 354 strings)
2018-04-19 10:51:45 +02:00
Joseph Kim
7331e4a7f2 Translated using Weblate (Korean)
Currently translated at 100.0% (354 of 354 strings)
2018-04-19 04:40:40 +02:00
Jeff Huang
51252d3b61 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (354 of 354 strings)
2018-04-19 01:33:35 +02:00
Somethingweirdhere
dcdb2c323e Added settings export 2018-04-19 01:31:25 +02:00
Q. A
c4ac901c67 Added translation using Weblate (Albanian) 2018-04-18 23:38:25 +02:00
Tobias Groza
a7ce072ca2 Translated using Weblate (German)
Currently translated at 100.0% (354 of 354 strings)
2018-04-18 23:34:48 +02:00
Nathan Follens
a9b427b877 Translated using Weblate (Flemish)
Currently translated at 100.0% (354 of 354 strings)
2018-04-18 22:37:17 +02:00
Nathan Follens
67a9f3a4ad Added translation using Weblate (Flemish) 2018-04-18 22:12:00 +02:00
Nathan Follens
1a4905f36a Translated using Weblate (Dutch)
Currently translated at 100.0% (354 of 354 strings)
2018-04-18 21:57:15 +02:00
Weblate
cbb1fde7b0 Merge remote-tracking branch 'origin/dev' into dev 2018-04-18 21:41:20 +02:00
DafabHoid
b86bd019a7 Translated using Weblate (German)
Currently translated at 100.0% (353 of 353 strings)
2018-04-18 21:41:20 +02:00
6a0bada9d2 Translated using Weblate (Polish)
Currently translated at 100.0% (353 of 353 strings)
2018-04-18 21:41:18 +02:00
Christian Schabesberger
a708278cf0 Merge pull request #1327 from Somethingweirdhere/dev
Fixed crash when trying to open a downloaded file without a player
2018-04-18 20:35:44 +02:00
Mauricio Colli
119462cbc9 Fix RouterActivity choice selection
- Improve behavior when external player is enabled
- Fixes #1324
2018-04-18 11:44:46 -03:00
Tobias Groza
0324a4928c Translated using Weblate (German)
Currently translated at 100.0% (353 of 353 strings)
2018-04-18 12:00:09 +02:00
syed muzammil
895a2a56b5 Translated using Weblate (Urdu)
Currently translated at 2.5% (9 of 353 strings)
2018-04-18 01:41:26 +02:00
Somethingweirdhere
d9e616beee Fixed crash when trying to open a downloaded file without a player 2018-04-17 22:26:24 +02:00
horyzont
aa5d5d2b6d Translated using Weblate (Polish)
Currently translated at 100.0% (353 of 353 strings)
2018-04-17 21:20:47 +02:00
Somethingweirdhere
85dc555358 Fixed crash when trying to open a downloaded file without a player 2018-04-17 19:47:17 +02:00
Somethingweirdhere
15b4a7d055 Fixed crash when trying to open a downloaded file without a player 2018-04-17 19:19:12 +02:00
Andrea Troiano
696c94050d Translated using Weblate (Italian)
Currently translated at 100.0% (353 of 353 strings)
2018-04-17 09:38:03 +02:00
DafabHoid
b222614c4a Translated using Weblate (German)
Currently translated at 100.0% (353 of 353 strings)
2018-04-16 22:58:47 +02:00
Emin Tufan Çetin
edff694bf3 Translated using Weblate (Turkish)
Currently translated at 100.0% (353 of 353 strings)
2018-04-16 22:42:33 +02:00
Jeff Huang
be430a6ac0 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (353 of 353 strings)
2018-04-16 13:03:02 +02:00
AB
937d40c5f7 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (353 of 353 strings)
2018-04-16 11:42:15 +02:00
Rubix
d3979676ab Translated using Weblate (Romanian)
Currently translated at 84.1% (297 of 353 strings)
2018-04-16 10:40:03 +02:00
Emin Tufan Çetin
6716262a28 Translated using Weblate (Turkish)
Currently translated at 100.0% (353 of 353 strings)
2018-04-15 22:11:00 +02:00
r2308145
06d8bafce6 Translated using Weblate (Czech)
Currently translated at 100.0% (353 of 353 strings)
2018-04-15 19:35:47 +02:00
Anton Shestakov
f814755908 Translated using Weblate (Russian)
Currently translated at 98.8% (349 of 353 strings)
2018-04-15 16:39:47 +02:00
AB
f355fd2551 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (353 of 353 strings)
2018-04-15 10:40:28 +02:00
Andrea Troiano
ea84c62d76 Translated using Weblate (Italian)
Currently translated at 100.0% (353 of 353 strings)
2018-04-14 21:24:40 +02:00
r2308145
acda71cebb Translated using Weblate (Czech)
Currently translated at 100.0% (353 of 353 strings)
2018-04-14 19:05:22 +02:00
Heimen Stoffels
67dcd2e5c6 Translated using Weblate (Dutch)
Currently translated at 100.0% (353 of 353 strings)
2018-04-14 17:42:09 +02:00
Marc Riera
c996644613 Translated using Weblate (Catalan)
Currently translated at 100.0% (353 of 353 strings)
2018-04-14 16:00:44 +02:00
M1ck
171c3e492d Translated using Weblate (French)
Currently translated at 100.0% (353 of 353 strings)
2018-04-14 12:53:15 +02:00
Weblate
8834195cc6 Merge remote-tracking branch 'origin/dev' into dev 2018-04-14 11:39:08 +02:00
M1ck
ed57e72fa1 Translated using Weblate (French)
Currently translated at 96.9% (344 of 355 strings)
2018-04-14 11:39:07 +02:00
Dar Mackani
7e84c3e167 Translated using Weblate (Esperanto)
Currently translated at 18.3% (65 of 355 strings)
2018-04-14 11:39:07 +02:00
gensitu
4adc33471b Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (355 of 355 strings)
2018-04-14 11:39:06 +02:00
gensitu
31d07cc1e2 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (355 of 355 strings)
2018-04-14 11:39:05 +02:00
Andrea Troiano
a349a66d5a Translated using Weblate (Italian)
Currently translated at 100.0% (355 of 355 strings)
2018-04-14 11:38:59 +02:00
Christian Schabesberger
7d427b4cc4 move on to v0.13.2 2018-04-14 11:22:26 +02:00
Christian Schabesberger
6c7e54868d Merge branch 'playlist_to_next' of https://github.com/Shan11812/NewPipe into test 2018-04-14 11:04:08 +02:00
Christian Schabesberger
9484a5e2ee fix conflict 2018-04-14 11:02:31 +02:00
Christian Schabesberger
3cbd2057e3 Merge branch 'qol-follow-ups' of https://github.com/karyogamy/NewPipe into test 2018-04-14 10:53:09 +02:00
Stanisław Sikorski
71ff1cf713 Translated using Weblate (Polish)
Currently translated at 100.0% (355 of 355 strings)
2018-04-14 00:27:38 +02:00
Rintaro matsuo
c737d891bc Translated using Weblate (Japanese)
Currently translated at 81.1% (288 of 355 strings)
2018-04-13 04:39:41 +02:00
ScratchBuild
82a53343fc Translated using Weblate (Japanese)
Currently translated at 81.1% (288 of 355 strings)
2018-04-13 04:39:36 +02:00
Andrea Troiano
03fdc60018 Translated using Weblate (Italian)
Currently translated at 100.0% (355 of 355 strings)
2018-04-12 12:15:57 +02:00
John Zhen Mo
42d19d98ad -Changed media source manager near edge loading to no longer load while player position is not progressing.
-Changed main video player to always self-destruct on stop.
-Extracted main video player lifecycle states into separate data class.
-Fixed play queue in full repeat mode does not load first item after expiring.
2018-04-11 20:27:39 -07:00
John Zhen Mo
c9915bba18 -Reversed special seek logic for short buffer livestreams.
-Fixed loader cleaning potentially canceling existing correct loading items.
-Updated ExoPlayer to 2.7.3.
2018-04-11 20:27:39 -07:00
John Zhen Mo
a275d7ff50 -Rollbacks the original main player UI to display nav and status bar on click.
-Changed system UI color to translucent on Lollipop and above.
2018-04-11 20:27:39 -07:00
John Zhen Mo
74199c8624 -Designated background player as default media button receiver.
-Fixed media button intent causing illegal state exception when sent from external apps.
2018-04-11 20:27:38 -07:00
John Zhen Mo
238bff1fee -Modified adaptive track selection to upgrade with 1 second of buffer.
-Modified dynamic track updates to seek to default time instead of clamping to 0 time when negative progress is reached.
2018-04-11 20:27:38 -07:00
John Zhen Mo
de534b58c5 -Removed playlist cleaning. 2018-04-11 20:27:38 -07:00
John Zhen Mo
111a0f9f17 -Modified playback parameter dialog to use translucent background. (reverted from commit 0d25254d4831aca92ad6cf6c0c772279b32b4a07) 2018-04-11 20:27:38 -07:00
John Zhen Mo
50392ed67d -Changed failed media source exception to use cause instead of top level exception. 2018-04-11 20:27:38 -07:00
John Zhen Mo
3de9da0528 -Bump exoplayer dependency to 2.7.2. 2018-04-11 20:27:38 -07:00
John Zhen Mo
ece93cadd5 -Added better player exception handling to player.
-Added expired media source cleaning to media source manager.
2018-04-11 20:27:38 -07:00
John Zhen Mo
7219c8d33c -Fixed main player multiwindow pauses when focus changes. 2018-04-11 20:27:38 -07:00
John Zhen Mo
b0a09c7876 -Added "add to playlist" button to service player play queue item drop down.
-Refactored playlist manipulations out from media source manager.
-Fixed potential ArrayOutOfBound exception when checking if player window is live.
-Fixed service player play queue potentially not refreshing when current play queue is replaced.
2018-04-11 20:27:38 -07:00
John Zhen Mo
1d017d3cbc -Modified LIVE button to be untranslatable on all players. 2018-04-11 20:27:38 -07:00
John Zhen Mo
5fa56f2aca -Modified playback parameter dialog to use translucent background. 2018-04-11 20:27:37 -07:00
Freddy Morán Jr
5ded1b139f Translated using Weblate (Spanish)
Currently translated at 99.7% (354 of 355 strings)
2018-04-11 18:41:45 +02:00
r2308145
2abb0db272 Translated using Weblate (Czech)
Currently translated at 100.0% (355 of 355 strings)
2018-04-11 18:35:42 +02:00
Marc Riera
674c041c13 Translated using Weblate (Catalan)
Currently translated at 100.0% (354 of 354 strings)
2018-04-11 10:09:56 +02:00
Marc Riera
03c89a69a2 Translated using Weblate (Catalan)
Currently translated at 95.7% (339 of 354 strings)
2018-04-11 09:30:46 +02:00
Marc Riera
dbed5e2a62 Translated using Weblate (Catalan)
Currently translated at 76.8% (272 of 354 strings)
2018-04-11 09:30:08 +02:00
Sérgio Marques
d7519903a1 Translated using Weblate (Portuguese)
Currently translated at 80.0% (284 of 355 strings)
2018-04-11 00:40:00 +02:00
E T
ab14f0d7ac Translated using Weblate (Turkish)
Currently translated at 100.0% (355 of 355 strings)
2018-04-10 23:03:32 +02:00
Marc Riera
3b6e43b7ca Translated using Weblate (Catalan)
Currently translated at 76.8% (272 of 354 strings)
2018-04-10 22:41:59 +02:00
Marc Riera
7d3b7af874 Translated using Weblate (Catalan)
Currently translated at 32.2% (114 of 354 strings)
2018-04-10 19:28:24 +02:00
r2308145
2acc348062 Translated using Weblate (Czech)
Currently translated at 100.0% (355 of 355 strings)
2018-04-10 18:01:41 +02:00
Freddy Morán Jr
33ba8acd05 Translated using Weblate (Spanish)
Currently translated at 100.0% (355 of 355 strings)
2018-04-10 17:49:03 +02:00
ezjerry liao
0c56af7090 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (355 of 355 strings)
2018-04-10 15:01:23 +02:00
Marc Riera
4375ecaad7 Translated using Weblate (Catalan)
Currently translated at 32.2% (114 of 354 strings)
2018-04-10 13:52:48 +02:00
nagizade
90c538d4b6 Translated using Weblate (Azerbaijani)
Currently translated at 12.9% (46 of 355 strings)
2018-04-10 11:34:35 +02:00
Dante
1ffa8329d0 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (355 of 355 strings)
2018-04-10 05:32:24 +02:00
Dante
b404b7974a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (355 of 355 strings)
2018-04-10 05:20:16 +02:00
E T
de44b580f9 Translated using Weblate (Turkish)
Currently translated at 100.0% (355 of 355 strings)
2018-04-09 22:53:10 +02:00
Roger
2a630cd745 Added translation using Weblate (Chinese (Mandarin)) 2018-04-09 22:31:57 +02:00
ssantos
1ccccc5e39 Translated using Weblate (German)
Currently translated at 100.0% (355 of 355 strings)
2018-04-09 22:31:35 +02:00
Roger
aec851acfa Added translation using Weblate (Urdu) 2018-04-09 22:28:18 +02:00
nagizade
ad5ac479fd Added translation using Weblate (Azerbaijani) 2018-04-09 10:55:09 +02:00
Heimen Stoffels
67b82a404c Translated using Weblate (Dutch)
Currently translated at 100.0% (355 of 355 strings)
2018-04-09 10:45:45 +02:00
Weblate
f7f86a2f62 Merge remote-tracking branch 'origin/dev' into dev 2018-04-09 00:13:42 +02:00
Anton Shestakov
6e42b4af27 Translated using Weblate (Russian)
Currently translated at 98.3% (348 of 354 strings)
2018-04-09 00:13:42 +02:00
Filip Sebastian
fe9ed8af76 Translated using Weblate (Romanian)
Currently translated at 74.8% (265 of 354 strings)
2018-04-09 00:13:40 +02:00
Eduardo Caron
c4c3f369c0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.8% (350 of 354 strings)
2018-04-09 00:13:40 +02:00
M1ck
9b9bf40e47 Translated using Weblate (French)
Currently translated at 97.1% (344 of 354 strings)
2018-04-09 00:13:38 +02:00
Marc Riera
14a888a610 Added translation using Weblate (Catalan) 2018-04-09 00:13:35 +02:00
John Zhen Mo
f3d777c65c -Added accessibility caption style and text color resolver.
-Added button to open captioning settings activity from appearance settings.
-Modified player captions to use custom caption style when captioning manager is enabled.
-Removed caption size settings from appearance settings.
2018-04-08 13:58:55 -07:00
Mauricio Colli
d494b6c934 Long-click to open the downloads activity
- Closes #1263
2018-04-08 16:53:22 -03:00
Mauricio Colli
7294220727 Revert removal of menu items 2018-04-08 16:26:47 -03:00
Christian Schabesberger
665b9087b1 fix conflict 2018-04-08 18:49:27 +02:00
Christian Schabesberger
f1691050cd Merge branch 'download-permissions-screen' of https://github.com/arispoloway/NewPipe into perm 2018-04-08 18:21:27 +02:00
Aris Poloway
169b6acd24 Clean up download dialog opening 2018-04-08 11:47:11 -04:00
Mauricio Colli
35b3bf2edb Show download size preview 2018-04-08 08:45:15 -03:00
Christian Schabesberger
8f73c8c98b Merge pull request #1288 from jaller94/shot-to-short
Rename shot_description.txt to short_description.txt
2018-04-08 12:33:37 +02:00
Christian Schabesberger
94ea8c21eb Merge pull request #1289 from jaller94/german-translation
Add German translation of the description for fastlane
2018-04-08 12:32:53 +02:00
b6a19c3e2d Translated using Weblate (French)
Currently translated at 97.1% (344 of 354 strings)
2018-04-08 12:06:00 +02:00
M1ck
21455d58a6 Translated using Weblate (French)
Currently translated at 97.1% (344 of 354 strings)
2018-04-08 12:05:55 +02:00
Christian Paul
239e9bd3db Rename shot_description.txt to short_description.txt 2018-04-08 01:46:17 -07:00
Christian Paul
0832171a2b Add German translation of the description for fastlane 2018-04-08 01:38:25 -07:00
Anton Shestakov
1edfcc629c Translated using Weblate (Russian)
Currently translated at 97.7% (346 of 354 strings)
2018-04-08 09:34:00 +02:00
ezjerry liao
9a2a8698ef Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (354 of 354 strings)
2018-04-08 03:28:46 +02:00
Nathan Follens
54c31422d9 Translated using Weblate (Dutch)
Currently translated at 100.0% (354 of 354 strings)
2018-04-08 01:49:32 +02:00
Filip Sebastian
a33464284c Translated using Weblate (Romanian)
Currently translated at 74.8% (265 of 354 strings)
2018-04-08 00:57:40 +02:00
Filip Sebastian
4842caf426 Translated using Weblate (Romanian)
Currently translated at 74.8% (265 of 354 strings)
2018-04-08 00:53:04 +02:00
Filip Sebastian
02b6b4d8eb Translated using Weblate (Romanian)
Currently translated at 74.2% (263 of 354 strings)
2018-04-08 00:18:05 +02:00
Filip Sebastian
4b0d7c7d88 Translated using Weblate (Romanian)
Currently translated at 74.2% (263 of 354 strings)
2018-04-08 00:17:57 +02:00
Filip Sebastian
559b53acc3 Translated using Weblate (Romanian)
Currently translated at 74.2% (263 of 354 strings)
2018-04-08 00:17:51 +02:00
Filip Sebastian
4233c18dbb Translated using Weblate (Romanian)
Currently translated at 70.3% (249 of 354 strings)
2018-04-08 00:11:00 +02:00
Filip Sebastian
adec2c9fcc Translated using Weblate (Romanian)
Currently translated at 70.0% (248 of 354 strings)
2018-04-08 00:10:48 +02:00
Filip Sebastian
6ad2406262 Translated using Weblate (Romanian)
Currently translated at 70.0% (248 of 354 strings)
2018-04-08 00:10:43 +02:00
Filip Sebastian
bfeb8ca104 Translated using Weblate (Romanian)
Currently translated at 69.7% (247 of 354 strings)
2018-04-08 00:10:34 +02:00
Filip Sebastian
5614edfa2f Translated using Weblate (Romanian)
Currently translated at 68.9% (244 of 354 strings)
2018-04-08 00:00:14 +02:00
Filip Sebastian
946f7b2305 Translated using Weblate (Romanian)
Currently translated at 68.9% (244 of 354 strings)
2018-04-07 23:59:49 +02:00
Filip Sebastian
dfcf435f88 Translated using Weblate (Romanian)
Currently translated at 67.7% (240 of 354 strings)
2018-04-07 23:53:12 +02:00
Filip Sebastian
0e734c267c Translated using Weblate (Romanian)
Currently translated at 66.9% (237 of 354 strings)
2018-04-07 23:49:06 +02:00
Filip Sebastian
faadcec4d1 Translated using Weblate (Romanian)
Currently translated at 66.6% (236 of 354 strings)
2018-04-07 23:47:13 +02:00
Filip Sebastian
eb81784818 Translated using Weblate (Romanian)
Currently translated at 66.3% (235 of 354 strings)
2018-04-07 23:45:01 +02:00
Filip Sebastian
327010f76b Translated using Weblate (Romanian)
Currently translated at 66.3% (235 of 354 strings)
2018-04-07 23:44:26 +02:00
Filip Sebastian
371669dcb6 Translated using Weblate (Romanian)
Currently translated at 66.3% (235 of 354 strings)
2018-04-07 23:44:19 +02:00
Filip Sebastian
fb702b93ca Translated using Weblate (Romanian)
Currently translated at 66.3% (235 of 354 strings)
2018-04-07 23:37:57 +02:00
Filip Sebastian
50c8453785 Translated using Weblate (Romanian)
Currently translated at 66.3% (235 of 354 strings)
2018-04-07 23:37:43 +02:00
Filip Sebastian
14fb5ee6c4 Translated using Weblate (Romanian)
Currently translated at 66.3% (235 of 354 strings)
2018-04-07 23:37:29 +02:00
Filip Sebastian
46c2343ec6 Translated using Weblate (Romanian)
Currently translated at 66.3% (235 of 354 strings)
2018-04-07 23:35:42 +02:00
Filip Sebastian
f5c226362f Translated using Weblate (Romanian)
Currently translated at 65.8% (233 of 354 strings)
2018-04-07 23:24:43 +02:00
Filip Sebastian
9114c1157d Translated using Weblate (Romanian)
Currently translated at 64.9% (230 of 354 strings)
2018-04-07 23:22:38 +02:00
Filip Sebastian
a4f4230275 Translated using Weblate (Romanian)
Currently translated at 64.9% (230 of 354 strings)
2018-04-07 23:22:21 +02:00
Filip Sebastian
46a6147c08 Translated using Weblate (Romanian)
Currently translated at 64.9% (230 of 354 strings)
2018-04-07 23:21:19 +02:00
Filip Sebastian
0d26923d79 Translated using Weblate (Romanian)
Currently translated at 64.9% (230 of 354 strings)
2018-04-07 23:21:09 +02:00
Filip Sebastian
b57c93cf03 Translated using Weblate (Romanian)
Currently translated at 64.6% (229 of 354 strings)
2018-04-07 23:20:04 +02:00
Filip Sebastian
ec19e4bc8f Translated using Weblate (Romanian)
Currently translated at 64.6% (229 of 354 strings)
2018-04-07 23:19:36 +02:00
Filip Sebastian
cb68a9c2ce Translated using Weblate (Romanian)
Currently translated at 64.1% (227 of 354 strings)
2018-04-07 23:16:44 +02:00
Filip Sebastian
b51ded1580 Translated using Weblate (Romanian)
Currently translated at 64.1% (227 of 354 strings)
2018-04-07 23:16:38 +02:00
Filip Sebastian
4b6fb5bfeb Translated using Weblate (Romanian)
Currently translated at 63.8% (226 of 354 strings)
2018-04-07 23:08:51 +02:00
Filip Sebastian
9b8175b5be Translated using Weblate (Romanian)
Currently translated at 63.5% (225 of 354 strings)
2018-04-07 23:07:58 +02:00
Filip Sebastian
3ecfc622e2 Translated using Weblate (Romanian)
Currently translated at 63.5% (225 of 354 strings)
2018-04-07 23:07:35 +02:00
Filip Sebastian
d8d2ee4e09 Translated using Weblate (Romanian)
Currently translated at 63.5% (225 of 354 strings)
2018-04-07 23:06:38 +02:00
Filip Sebastian
be478cb088 Translated using Weblate (Romanian)
Currently translated at 62.7% (222 of 354 strings)
2018-04-07 22:57:24 +02:00
Filip Sebastian
c187b4487f Translated using Weblate (Romanian)
Currently translated at 62.7% (222 of 354 strings)
2018-04-07 22:54:09 +02:00
Weblate
f6d21e74cb Merge remote-tracking branch 'origin/dev' into dev 2018-04-07 22:50:27 +02:00
Filip Sebastian
bde4c4bd25 Translated using Weblate (Romanian)
Currently translated at 62.4% (221 of 354 strings)
2018-04-07 22:50:25 +02:00
Mauricio Colli
d9aaceea95 Fix naming inconsistency 2018-04-07 16:09:34 -03:00
Aris Poloway
676d64a24a Open download dialog if video download is clicked instead 2018-04-07 13:32:31 -04:00
Weblate
8ae1768f71 Merge remote-tracking branch 'origin/dev' into dev 2018-04-07 13:37:23 +02:00
212705e7e3 Translated using Weblate (German)
Currently translated at 99.4% (352 of 354 strings)
2018-04-07 13:37:22 +02:00
Andreas Kromke
b3f6524e5c Translated using Weblate (German)
Currently translated at 99.4% (352 of 354 strings)
2018-04-07 13:37:20 +02:00
Aris Poloway
5ca0a0adf2 Open downloads after permission granted 2018-04-06 17:29:58 -04:00
Mauricio Colli
669d2c44c9 Dynamically create the opening choices
- Fixes TODO
2018-04-06 06:02:51 -03:00
Mauricio Colli
604bc5b4c1 Merge pull request #1255 from Poussinou/patch-1
Add distributionSha256Sum property to gradle-wrapper.properties
2018-04-06 04:50:19 -03:00
Mauricio Colli
88268ae569 Merge pull request #1268 from arispoloway/dev
Handle links with time specified (ex. &t=xxx)
2018-04-06 04:49:02 -03:00
Mauricio Colli
140fb86401 Fix Info's start time when using VideoDetailFragment
- Update extractor dependency
2018-04-06 04:35:44 -03:00
Aris Poloway
2b281c4357 Don't save stream and store recovery position from StreamInfo 2018-04-05 23:47:20 -04:00
MohammedSR Vevo
7b6dbfb456 Translated using Weblate (Kurdish)
Currently translated at 18.0% (64 of 354 strings)
2018-04-06 00:37:32 +02:00
Aris Poloway
b3c49ac86b Revert unnecessary url clean 2018-04-05 16:45:00 -04:00
Dalibor Klobučarić
d309fd9c97 Translated using Weblate (Croatian)
Currently translated at 77.4% (274 of 354 strings)
2018-04-05 11:35:13 +02:00
Aris Poloway
f3a280dcb6 Don't preemptively clean url and save StreamInfo with PlayQueueItem 2018-04-04 20:43:14 -04:00
MORTI
43894687a2 Translated using Weblate (French)
Currently translated at 96.3% (341 of 354 strings)
2018-04-05 01:40:08 +02:00
MohammedSR Vevo
8dd8928dab Translated using Weblate (Kurdish)
Currently translated at 4.2% (15 of 354 strings)
2018-04-04 23:26:31 +02:00
MohammedSR Vevo
282a4d15c2 Translated using Weblate (Kurdish)
Currently translated at 3.3% (12 of 354 strings)
2018-04-04 23:25:25 +02:00
MohammedSR Vevo
83a2ffc67e Added translation using Weblate (Kurdish) 2018-04-04 23:13:05 +02:00
Eduardo Caron
e9a6e3fae0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.0% (347 of 354 strings)
2018-04-04 17:55:55 +02:00
Eduardo Caron
97944b9793 Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.6% (342 of 354 strings)
2018-04-04 17:43:44 +02:00
Çağdaş Tatar
0b5d995b6d Translated using Weblate (Turkish)
Currently translated at 100.0% (354 of 354 strings)
2018-04-04 16:48:18 +02:00
Çağdaş Tatar
1781a9fe9a Translated using Weblate (Turkish)
Currently translated at 99.4% (352 of 354 strings)
2018-04-04 15:53:38 +02:00
Çağdaş Tatar
a0479bf7ca Translated using Weblate (Turkish)
Currently translated at 99.4% (352 of 354 strings)
2018-04-04 15:48:54 +02:00
r2308145
2affa31edf Translated using Weblate (Czech)
Currently translated at 100.0% (354 of 354 strings)
2018-04-04 13:35:50 +02:00
Poussinou
0bd040a851 Update gradle to 4.6 (and add corresponding distributionSha256Sum) 2018-04-04 13:33:21 +02:00
Tobias Groza
616c6a1607 Translated using Weblate (German)
Currently translated at 99.4% (352 of 354 strings)
2018-04-04 12:38:10 +02:00
a5176fbf80 Translated using Weblate (German)
Currently translated at 99.4% (352 of 354 strings)
2018-04-04 12:38:01 +02:00
Nick Undnick
f2b76b7582 Translated using Weblate (German)
Currently translated at 99.4% (352 of 354 strings)
2018-04-04 12:37:56 +02:00
ssantos
f4847f31ae Translated using Weblate (German)
Currently translated at 99.4% (352 of 354 strings)
2018-04-04 12:37:51 +02:00
Dalibor Klobučarić
05adcf2c12 Translated using Weblate (Croatian)
Currently translated at 76.2% (270 of 354 strings)
2018-04-04 10:39:56 +02:00
Dalibor Klobučarić
ea301be3c1 Translated using Weblate (Croatian)
Currently translated at 76.2% (270 of 354 strings)
2018-04-04 10:39:45 +02:00
Dalibor Klobučarić
88e9785b01 Translated using Weblate (Croatian)
Currently translated at 72.8% (258 of 354 strings)
2018-04-04 10:36:04 +02:00
Dual Natan
78efabfc1c Translated using Weblate (Macedonian)
Currently translated at 100.0% (354 of 354 strings)
2018-04-04 05:38:53 +02:00
Poussinou
9d3e22200c Update gradle-wrapper.properties 2018-04-03 21:59:42 +02:00
ssantos
183f9701fd Translated using Weblate (German)
Currently translated at 99.4% (352 of 354 strings)
2018-04-03 12:16:27 +02:00
ssantos
ebbba134fe Translated using Weblate (German)
Currently translated at 98.8% (350 of 354 strings)
2018-04-03 12:07:17 +02:00
ssantos
c257be8176 Translated using Weblate (German)
Currently translated at 98.3% (348 of 354 strings)
2018-04-03 12:00:19 +02:00
Dual Natan
afb0aea660 Translated using Weblate (Macedonian)
Currently translated at 100.0% (354 of 354 strings)
2018-04-03 05:17:57 +02:00
Dual Natan
960bf46f44 Translated using Weblate (Macedonian)
Currently translated at 77.9% (276 of 354 strings)
2018-04-03 04:42:16 +02:00
Dual Natan
16db799e88 Translated using Weblate (Macedonian)
Currently translated at 57.0% (202 of 354 strings)
2018-04-03 04:12:55 +02:00
Dual Natan
f8f2cdcfcc Translated using Weblate (Macedonian)
Currently translated at 42.0% (149 of 354 strings)
2018-04-03 03:40:03 +02:00
Dual Natan
a1f7862f96 Translated using Weblate (Macedonian)
Currently translated at 7.9% (28 of 354 strings)
2018-04-03 02:29:01 +02:00
Dual Natan
3ff9284f2a Added translation using Weblate (Macedonian) 2018-04-03 02:07:39 +02:00
Weblate
edf8f27c0f Merge remote-tracking branch 'origin/dev' into dev 2018-04-02 23:39:25 +02:00
horyzont
a720953ff3 Translated using Weblate (Polish)
Currently translated at 100.0% (354 of 354 strings)
2018-04-02 23:39:23 +02:00
Christian Schabesberger
f79dd26a82 remove outdated ccc slides 2018-04-02 20:54:26 +02:00
Christian Schabesberger
36b5fce4dd remove translation stuff 2018-04-02 20:53:56 +02:00
Christian Schabesberger
89537322fd fix email button not working 2018-04-02 20:53:32 +02:00
r2308145
2229ce1fe9 Translated using Weblate (Czech)
Currently translated at 100.0% (354 of 354 strings)
2018-04-02 16:36:12 +02:00
r2308145
f257b2177a Translated using Weblate (Czech)
Currently translated at 98.5% (349 of 354 strings)
2018-04-02 16:33:35 +02:00
r2308145
056c1fd43e Translated using Weblate (Czech)
Currently translated at 97.7% (346 of 354 strings)
2018-04-02 14:59:50 +02:00
r2308145
acacd3134f Translated using Weblate (Czech)
Currently translated at 96.8% (343 of 354 strings)
2018-04-02 14:58:11 +02:00
r2308145
1fe0ba1a6e Translated using Weblate (Czech)
Currently translated at 96.3% (341 of 354 strings)
2018-04-02 14:43:01 +02:00
r2308145
e639f4b5de Translated using Weblate (Czech)
Currently translated at 95.4% (338 of 354 strings)
2018-04-02 14:35:57 +02:00
r2308145
5a234bd989 Translated using Weblate (Czech)
Currently translated at 94.6% (335 of 354 strings)
2018-04-02 14:34:55 +02:00
r2308145
661227266e Translated using Weblate (Czech)
Currently translated at 88.9% (315 of 354 strings)
2018-04-02 14:22:33 +02:00
horyzont
e01556fd9a Translated using Weblate (Polish)
Currently translated at 100.0% (354 of 354 strings)
2018-04-01 23:03:18 +02:00
horyzont
5cb53ffc18 Translated using Weblate (Polish)
Currently translated at 100.0% (354 of 354 strings)
2018-04-01 23:01:44 +02:00
horyzont
e7178626ab Translated using Weblate (Polish)
Currently translated at 100.0% (354 of 354 strings)
2018-04-01 22:56:07 +02:00
horyzont
7f0948b0ed Translated using Weblate (Polish)
Currently translated at 99.7% (353 of 354 strings)
2018-04-01 22:50:49 +02:00
Weblate
4baee67781 Merge remote-tracking branch 'origin/dev' into dev 2018-04-01 22:41:06 +02:00
Heimen Stoffels
7061f38abe Translated using Weblate (Dutch)
Currently translated at 100.0% (354 of 354 strings)
2018-04-01 22:41:06 +02:00
horyzont
2aa259c0b2 Translated using Weblate (Polish)
Currently translated at 98.3% (348 of 354 strings)
2018-04-01 22:41:02 +02:00
Christian Schabesberger
a0f74e715a fix bugreport for kiosk 2018-04-01 21:54:00 +02:00
Christian Schabesberger
c13e761c3f remove global ip range label 2018-04-01 19:58:07 +02:00
shan11812
2837e44bab Adds 'Add to Playlist' option to Related and Channel lists 2018-04-01 20:47:24 +05:30
Heimen Stoffels
d41e3bb41e Translated using Weblate (Dutch)
Currently translated at 100.0% (354 of 354 strings)
2018-04-01 16:43:09 +02:00
Weblate
4160bbb8c4 Merge remote-tracking branch 'origin/dev' into dev 2018-04-01 15:38:23 +02:00
Veli Tasalı
404a20f280 Translated using Weblate (Turkish)
Currently translated at 99.4% (352 of 354 strings)
2018-04-01 15:38:23 +02:00
se7entime
2241146b9f Translated using Weblate (Indonesian)
Currently translated at 89.2% (316 of 354 strings)
2018-04-01 15:38:20 +02:00
Christian Schabesberger
bcb26c5721 move on to version 0.13.1 2018-04-01 15:29:57 +02:00
Christian Schabesberger
a681d8c1ba Merge pull request #1227 from TeamNewPipe/removebeta
remove beta
2018-04-01 15:28:49 +02:00
cozyplanes
0515b74f75 Translated using Weblate (Korean)
Currently translated at 100.0% (354 of 354 strings)
2018-04-01 13:40:13 +02:00
Veli Tasalı
7eeb96d88e Translated using Weblate (Turkish)
Currently translated at 99.1% (351 of 354 strings)
2018-03-31 18:01:21 +02:00
se7entime
5b6c73ef06 Translated using Weblate (Indonesian)
Currently translated at 89.5% (317 of 354 strings)
2018-03-31 15:30:26 +02:00
se7entime
c70b866d16 Translated using Weblate (Indonesian)
Currently translated at 87.2% (309 of 354 strings)
2018-03-31 15:28:10 +02:00
se7entime
b84f2874dc Translated using Weblate (Indonesian)
Currently translated at 87.2% (309 of 354 strings)
2018-03-31 15:28:03 +02:00
se7entime
c372b5529b Translated using Weblate (Indonesian)
Currently translated at 80.5% (285 of 354 strings)
2018-03-31 15:25:16 +02:00
se7entime
a43156c38d Translated using Weblate (Indonesian)
Currently translated at 74.5% (264 of 354 strings)
2018-03-31 15:22:37 +02:00
se7entime
a15112febc Translated using Weblate (Indonesian)
Currently translated at 68.9% (244 of 354 strings)
2018-03-31 15:19:45 +02:00
se7entime
1c6a677a39 Translated using Weblate (Indonesian)
Currently translated at 67.7% (240 of 354 strings)
2018-03-31 15:18:34 +02:00
se7entime
9bf749a2c8 Translated using Weblate (Indonesian)
Currently translated at 65.5% (232 of 354 strings)
2018-03-31 15:15:16 +02:00
se7entime
f6c9d9df20 Translated using Weblate (Indonesian)
Currently translated at 57.3% (203 of 354 strings)
2018-03-31 15:04:54 +02:00
se7entime
5386e0ded9 Translated using Weblate (Indonesian)
Currently translated at 56.7% (201 of 354 strings)
2018-03-31 15:04:27 +02:00
se7entime
8fab405a3a Translated using Weblate (Indonesian)
Currently translated at 55.0% (195 of 354 strings)
2018-03-31 15:03:32 +02:00
se7entime
ee409f3ca9 Translated using Weblate (Indonesian)
Currently translated at 49.7% (176 of 354 strings)
2018-03-31 15:00:23 +02:00
Weblate
bda9beacaa Merge remote-tracking branch 'origin/dev' into dev 2018-03-31 14:59:25 +02:00
Veli Tasalı
5d8c7e5733 Translated using Weblate (Turkish)
Currently translated at 96.8% (343 of 354 strings)
2018-03-31 14:59:24 +02:00
Stnby
a68c763125 Translated using Weblate (Lithuanian)
Currently translated at 87.8% (311 of 354 strings)
2018-03-31 14:59:23 +02:00
se7entime
31ac89d9d6 Translated using Weblate (Indonesian)
Currently translated at 49.4% (175 of 354 strings)
2018-03-31 14:59:22 +02:00
se7entime
8b258cbbe4 Translated using Weblate (Indonesian)
Currently translated at 47.7% (169 of 354 strings)
2018-03-31 14:55:56 +02:00
se7entime
53676fc0fd Translated using Weblate (Indonesian)
Currently translated at 47.1% (167 of 354 strings)
2018-03-31 14:53:54 +02:00
Christian Schabesberger
42c561af9e update build tools to 27.0.3 2018-03-30 18:45:27 +02:00
Christian Schabesberger
fae777c14c make service items in drawer be dynamicly generated 2018-03-30 18:41:11 +02:00
Christian Schabesberger
1af6dc614a remove global ip range label 2018-03-30 14:46:26 +02:00
Christian Schabesberger
2986004638 remove entry points in three dot menu in main screen 2018-03-30 11:52:05 +02:00
Christian Schabesberger
d6f7b4706b remove beta build 2018-03-30 11:09:07 +02:00
Christian Schabesberger
b40dd3e5c0 update gradle 2018-03-30 10:58:32 +02:00
Schabi
2f4097ca9d Merge branch 'dev' of github.com:teamnewpipe/NewPipe into dev 2018-03-28 09:58:18 +02:00
Schabi
c8a91fbb25 add note to check if bugs exist in latest version 2018-03-28 09:58:09 +02:00
Víctor Manuel Tapia Ramírez
0ec259a5fc Translated using Weblate (Spanish)
Currently translated at 100.0% (354 of 354 strings)
2018-03-28 09:50:20 +02:00
Weblate
2fda99bdb7 Merge remote-tracking branch 'origin/dev' into dev 2018-03-28 05:36:47 +02:00
nautilusx
1bb4ac4e9c Translated using Weblate (German)
Currently translated at 95.7% (339 of 354 strings)
2018-03-28 05:36:45 +02:00
MORTI
f376b98c09 Translated using Weblate (French)
Currently translated at 96.3% (341 of 354 strings)
2018-03-28 05:36:41 +02:00
Christian Schabesberger
7382fdb2d2 Merge pull request #1217 from Grammost/patch-1
Fix broken Librepay link in README.md
2018-03-27 11:53:08 +02:00
Edwar Tikhonov
aead9592cf Translated using Weblate (Russian)
Currently translated at 94.9% (336 of 354 strings)
2018-03-27 11:39:53 +02:00
MORTI
bedacf29d9 Translated using Weblate (French)
Currently translated at 96.0% (340 of 354 strings)
2018-03-27 04:49:28 +02:00
570e9a8307 Translated using Weblate (French)
Currently translated at 89.2% (316 of 354 strings)
2018-03-27 01:15:17 +02:00
justanidea
fcb94ec603 Translated using Weblate (French)
Currently translated at 88.9% (315 of 354 strings)
2018-03-27 01:13:44 +02:00
69886ed58b Translated using Weblate (French)
Currently translated at 88.7% (314 of 354 strings)
2018-03-27 01:13:05 +02:00
MORTI
798235cd21 Translated using Weblate (French)
Currently translated at 88.4% (313 of 354 strings)
2018-03-27 01:12:25 +02:00
Grammost
b38b6b6f35 Update README.md 2018-03-26 15:10:03 +00:00
Grammost
2dba13c52e Fix broken Librepay link in README.md 2018-03-26 14:49:59 +00:00
ezjerry liao
ff366cb2c5 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (354 of 354 strings)
2018-03-26 11:33:45 +02:00
Weblate
a87861a993 Merge remote-tracking branch 'origin/dev' into dev 2018-03-25 23:34:16 +02:00
AB
65476356c9 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (354 of 354 strings)
2018-03-25 23:34:12 +02:00
Christian Schabesberger
8af6667f3e Merge pull request #1212 from karyogamy/sub-quick-fix
Subscription Front Page Crash Fix
2018-03-25 20:31:36 +02:00
John Zhen Mo
b546df7b95 -Fixed activity not available exception when setting title on subscription fragments when used as front pager. 2018-03-25 10:29:37 -07:00
Emanuele Petriglia
20c1f12da8 Translated using Weblate (Italian)
Currently translated at 100.0% (354 of 354 strings)
2018-03-25 13:21:40 +02:00
Weblate
e65ff201af Merge remote-tracking branch 'origin/dev' into dev 2018-03-25 11:36:18 +02:00
Tobias Groza
4f367a3dcd Translated using Weblate (German)
Currently translated at 89.8% (318 of 354 strings)
2018-03-25 11:36:16 +02:00
Heimen Stoffels
d7093bce4d Translated using Weblate (Dutch)
Currently translated at 100.0% (354 of 354 strings)
2018-03-25 11:36:13 +02:00
202 changed files with 6667 additions and 3307 deletions

View File

@@ -11,6 +11,7 @@ Do not report crashes in the GitHub issue tracker. NewPipe has an automated cras
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature hasn't been reported/requested before
* Check whether your issue/feature is already fixed/implemented
* Check if the issue still exists in the latest release/beta version
* If you are an Android/Java developer, you are always welcome to fix/implement an issue/a feature yourself. PRs welcome!
* We use English for development. Issues in other languages will be closed and ignored.
* Please only add *one* issue at a time. Do not put multiple issues into one thread.
@@ -27,7 +28,7 @@ Do not report crashes in the GitHub issue tracker. NewPipe has an automated cras
* Stick to NewPipe's style conventions (well, just look the other code and then do it the same way :))
* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google libraries.
* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy)
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might not be considered, GitHub is the primary platform.
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might not be considered, GitHub is the primary platform. (This only affects you if you are a member of TeamNewPipe)
* When submitting changes, you confirm that your code is licensed under the terms of the [GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR description. Untested code will **not** be merged!
* Try to figure out yourself why builds on our CI fail.

View File

@@ -5,7 +5,7 @@ android:
components:
# The BuildTools version used by NewPipe
- tools
- build-tools-27.0.1
- build-tools-27.0.3
# The SDK version used to compile NewPipe
- android-27

Binary file not shown.

View File

@@ -1,170 +0,0 @@
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

@@ -88,7 +88,7 @@ If you like NewPipe we'd be happy about a donation. You can either donate via Bi
<tr>
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="https://upload.wikimedia.org/wikipedia/commons/2/27/Liberapay_logo_v2_white-on-yellow.svg" alt="Liberapay" width="80px" /></a></td>
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="assets/liberapay_qr_code.png" alt="Visit NewPipe at liberapay.com" width="100px"/></a></td>
<td><a href="https://liberapay.com/TeamNewPipe/donate/"><img src="assets/liberapay_donate_button.svg" alt="Donate via Liberapay" height="35px" /></a></td>
<td><a href="https://liberapay.com/TeamNewPipe/donate"><img src="assets/liberapay_donate_button.svg" alt="Donate via Liberapay" height="35px" /></a></td>
</tr>
<tr>
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Bountysource.png/320px-Bountysource.png" alt="Bountysource" width="190px" /></a></td>

View File

@@ -2,14 +2,14 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 27
buildToolsVersion '27.0.1'
buildToolsVersion '27.0.3'
defaultConfig {
applicationId "org.schabi.newpipe"
minSdkVersion 15
targetSdkVersion 27
versionCode 49
versionName "0.13.0"
versionCode 63
versionName "0.13.4"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
@@ -26,13 +26,6 @@ android {
debuggable true
applicationIdSuffix ".debug"
}
beta {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
applicationIdSuffix ".beta"
}
}
lintOptions {
@@ -48,8 +41,8 @@ android {
}
ext {
supportLibVersion = '27.1.0'
exoPlayerLibVersion = '2.7.1'
supportLibVersion = '27.1.1'
exoPlayerLibVersion = '2.7.3'
roomDbLibVersion = '1.0.0'
leakCanaryLibVersion = '1.5.4'
okHttpLibVersion = '1.5.0'
@@ -61,7 +54,7 @@ dependencies {
exclude module: 'support-annotations'
}
implementation 'com.github.TeamNewPipe:NewPipeExtractor:f787b375e5fb6d'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:bf1c771'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:1.10.19'
@@ -72,13 +65,13 @@ dependencies {
implementation "com.android.support:recyclerview-v7:$supportLibVersion"
implementation "com.android.support:preference-v14:$supportLibVersion"
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'ch.acra:acra:4.9.2'
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:4.2.1'
implementation "com.google.android.exoplayer:exoplayer:$exoPlayerLibVersion"
implementation "com.google.android.exoplayer:extension-mediasession:$exoPlayerLibVersion"
@@ -98,7 +91,6 @@ dependencies {
annotationProcessor "frankiesardo:icepick-processor:$icepickLibVersion"
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryLibVersion"
betaImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryLibVersion"
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryLibVersion"
implementation 'com.squareup.okhttp3:okhttp:3.9.1'

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:label="NewPipe Beta"
tools:replace="android:label">
</application>
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -42,7 +42,11 @@
<service
android:name=".player.BackgroundPlayer"
android:exported="false"/>
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service>
<activity
android:name=".player.BackgroundPlayerActivity"
@@ -76,8 +80,8 @@
android:name=".history.HistoryActivity"
android:label="@string/title_activity_history"/>
<service android:name=".subscription.services.SubscriptionsImportService"/>
<service android:name=".subscription.services.SubscriptionsExportService"/>
<service android:name=".local.subscription.services.SubscriptionsImportService"/>
<service android:name=".local.subscription.services.SubscriptionsExportService"/>
<activity
android:name=".PanicResponderActivity"
@@ -132,7 +136,7 @@
<activity
android:name=".RouterActivity"
android:excludeFromRecents="true"
android:label="@string/preferred_player_share_menu_title"
android:label="@string/preferred_open_action_share_menu_title"
android:taskAffinity=""
android:theme="@style/RouterActivityThemeDark">

View File

@@ -73,6 +73,31 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
mCookies = cookies;
}
/**
* Get the size of the content that the url is pointing by firing a HEAD request.
*
* @param url an url pointing to the content
* @return the size of the content, in bytes
*/
public long getContentLength(String url) throws IOException {
Response response = null;
try {
final Request request = new Request.Builder()
.head().url(url)
.addHeader("User-Agent", USER_AGENT)
.build();
response = client.newCall(request).execute();
return Long.parseLong(response.header("Content-Length"));
} catch (NumberFormatException e) {
throw new IOException("Invalid content length", e);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* Download the text file at the supplied URL as in download(String),
* but set the HTTP header field "Accept-Language" to the supplied string.

View File

@@ -22,11 +22,13 @@ package org.schabi.newpipe;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.view.GravityCompat;
@@ -44,7 +46,6 @@ import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
@@ -55,6 +56,7 @@ import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.ThemeHelper;
@@ -94,44 +96,47 @@ public class MainActivity extends AppCompatActivity {
drawer = findViewById(R.id.drawer_layout);
drawerItems = findViewById(R.id.navigation);
//drawerItems.setItemIconTintList(null); // Set null to use the original icon
for(StreamingService s : NewPipe.getServices()) {
final String title = s.getServiceInfo().getName() +
(ServiceHelper.isBeta(s) ? " (beta)" : "");
final MenuItem item = drawerItems.getMenu()
.add(R.id.menu_services_group, s.getServiceId(), 0, title);
item.setIcon(ServiceHelper.getIcon(s.getServiceId()));
}
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;
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 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);
}
}
});
@Override
public void onDrawerClosed(View drawerView) {
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
}
}
});
drawerItems.setNavigationItemSelectedListener(this::changeService);
drawerItems.setNavigationItemSelectedListener(this::changeService);
setupDrawerFooter();
setupDrawerHeader();
} else {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
}
setupDrawerFooter();
setupDrawerHeader();
}
private boolean changeService(MenuItem item) {
if (item.getGroupId() == R.id.menu_services_group) {
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false);
ServiceHelper.setSelectedServiceId(this, item.getTitle().toString());
ServiceHelper.setSelectedServiceId(this, item.getItemId());
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
headerServiceView.setText("gurken");
} else {
return false;
}
@@ -146,7 +151,8 @@ public class MainActivity extends AppCompatActivity {
settings.setOnClickListener(view -> NavigationHelper.openSettings(this));
downloads.setOnClickListener(view ->NavigationHelper.openDownloads(this));
history.setOnClickListener(view -> NavigationHelper.openHistory(this));
history.setOnClickListener(view ->
NavigationHelper.openStatisticFragment(getSupportFragmentManager()));
}
private void setupDrawerHeader() {
@@ -176,11 +182,9 @@ public class MainActivity extends AppCompatActivity {
// when the user returns to MainActivity
drawer.closeDrawer(Gravity.START, false);
try {
if(BuildConfig.BUILD_TYPE != "release" ) {
String selectedServiceName = NewPipe.getService(
ServiceHelper.getSelectedServiceId(this)).getServiceInfo().getName();
headerServiceView.setText(selectedServiceName);
}
String selectedServiceName = NewPipe.getService(
ServiceHelper.getSelectedServiceId(this)).getServiceInfo().getName();
headerServiceView.setText(selectedServiceName);
} catch (Exception e) {
ErrorActivity.reportUiError(this, e);
}
@@ -232,6 +236,26 @@ public class MainActivity extends AppCompatActivity {
} else super.onBackPressed();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
for (int i: grantResults){
if (i == PackageManager.PERMISSION_DENIED){
return;
}
}
switch (requestCode) {
case PermissionHelper.DOWNLOADS_REQUEST_CODE:
NavigationHelper.openDownloads(this);
break;
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (fragment instanceof VideoDetailFragment) {
((VideoDetailFragment) fragment).openDownloadDialog();
}
break;
}
}
/**
* Implement the following diagram behavior for the up button:
* <pre>
@@ -304,16 +328,16 @@ public class MainActivity extends AppCompatActivity {
case android.R.id.home:
onHomeButtonPressed();
return true;
case R.id.action_settings:
NavigationHelper.openSettings(this);
return true;
case R.id.action_show_downloads:
return NavigationHelper.openDownloads(this);
case R.id.action_history:
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
return true;
case R.id.action_about:
NavigationHelper.openAbout(this);
return true;
case R.id.action_history:
NavigationHelper.openHistory(this);
case R.id.action_settings:
NavigationHelper.openSettings(this);
return true;
default:
return super.onOptionsItemSelected(item);

View File

@@ -1,6 +1,7 @@
package org.schabi.newpipe;
import android.app.IntentService;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -24,7 +25,6 @@ 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;
@@ -32,10 +32,10 @@ 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.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
@@ -43,9 +43,11 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import icepick.Icepick;
import icepick.State;
@@ -57,6 +59,8 @@ import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO;
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
/**
@@ -64,13 +68,10 @@ import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
*/
public class RouterActivity extends AppCompatActivity {
@State
protected int currentServiceId = -1;
@State protected int currentServiceId = -1;
private StreamingService currentService;
@State
protected LinkType currentLinkType;
@State
protected int selectedRadioPosition = -1;
@State protected LinkType currentLinkType;
@State protected int selectedRadioPosition = -1;
protected int selectedPreviously = -1;
protected String currentUrl;
@@ -91,8 +92,7 @@ public class RouterActivity extends AppCompatActivity {
}
setTheme(ThemeHelper.isLightThemeSelected(this)
? R.style.RouterActivityThemeLight
: R.style.RouterActivityThemeDark);
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
}
@Override
@@ -122,7 +122,7 @@ public class RouterActivity extends AppCompatActivity {
currentService = NewPipe.getServiceByUrl(url);
currentServiceId = currentService.getServiceId();
currentLinkType = currentService.getLinkTypeByUrl(url);
currentUrl = NavigationHelper.getCleanUrl(currentService, url, currentLinkType);
currentUrl = url;
} else {
currentService = NewPipe.getService(currentServiceId);
}
@@ -159,57 +159,69 @@ public class RouterActivity extends AppCompatActivity {
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);
final String selectedChoiceKey = preferences.getString(getString(R.string.preferred_open_action_key), getString(R.string.preferred_open_action_default));
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) {
handleChoice(getString(R.string.background_player_key));
return;
}
final String playerChoiceKey = preferences.getString(
getString(R.string.preferred_open_action_key),
getString(R.string.preferred_open_action_default));
final String showInfoKey = getString(R.string.show_info_key);
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 String alwaysAskKey = getString(R.string.always_ask_open_action_key);
if (playerChoiceKey.equals(alwaysAskKey)) {
showDialog();
if (selectedChoiceKey.equals(alwaysAskKey)) {
final List<AdapterChoiceItem> choices = getChoicesForService(currentService, currentLinkType);
if (choices.size() == 1) {
handleChoice(choices.get(0).key);
} else if (choices.size() == 0) {
handleChoice(showInfoKey);
} else {
showDialog(choices);
}
} else if (selectedChoiceKey.equals(showInfoKey)) {
handleChoice(showInfoKey);
} else {
handleChoice(playerChoiceKey);
final boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
final boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
final boolean isVideoPlayerSelected = selectedChoiceKey.equals(videoPlayerKey) || selectedChoiceKey.equals(popupPlayerKey);
final boolean isAudioPlayerSelected = selectedChoiceKey.equals(backgroundPlayerKey);
if (currentLinkType != LinkType.STREAM) {
if (isExtAudioEnabled && isAudioPlayerSelected || isExtVideoEnabled && isVideoPlayerSelected) {
Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show();
handleChoice(showInfoKey);
return;
}
}
final List<StreamingService.ServiceInfo.MediaCapability> capabilities = currentService.getServiceInfo().getMediaCapabilities();
boolean serviceSupportsChoice = false;
if (isVideoPlayerSelected) {
serviceSupportsChoice = capabilities.contains(VIDEO);
} else if (selectedChoiceKey.equals(backgroundPlayerKey)) {
serviceSupportsChoice = capabilities.contains(AUDIO);
}
if (serviceSupportsChoice) {
handleChoice(selectedChoiceKey);
} else {
handleChoice(showInfoKey);
}
}
}
private void showDialog() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(this,
ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme);
private void showDialog(final List<AdapterChoiceItem> choices) {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
final Context themeWrapperContext = getThemeWrapperContext();
LayoutInflater inflater = LayoutInflater.from(themeWrapper);
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
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.show_info_key), getString(R.string.show_info),
resolveResourceIdFromAttr(themeWrapper, R.attr.info)),
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];
final AdapterChoiceItem choice = choices.get(indexOfChild);
handleChoice(choice.key);
@@ -218,8 +230,8 @@ public class RouterActivity extends AppCompatActivity {
}
};
final AlertDialog alertDialog = new AlertDialog.Builder(themeWrapper)
.setTitle(R.string.preferred_player_share_menu_title)
final AlertDialog alertDialog = new AlertDialog.Builder(themeWrapperContext)
.setTitle(R.string.preferred_open_action_share_menu_title)
.setView(radioGroup)
.setCancelable(true)
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
@@ -227,6 +239,7 @@ public class RouterActivity extends AppCompatActivity {
.setOnDismissListener((dialog) -> finish())
.create();
//noinspection CodeBlock2Expr
alertDialog.setOnShowListener(dialog -> {
setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1);
});
@@ -240,7 +253,7 @@ public class RouterActivity extends AppCompatActivity {
selectedRadioPosition = indexOfChild;
if (selectedPreviously == selectedRadioPosition) {
handleChoice(choices[selectedRadioPosition].key);
handleChoice(choices.get(selectedRadioPosition).key);
}
};
@@ -259,8 +272,8 @@ public class RouterActivity extends AppCompatActivity {
if (selectedRadioPosition == -1) {
final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_open_action_last_selected_key), null);
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
for (int i = 0; i < choices.length; i++) {
AdapterChoiceItem c = choices[i];
for (int i = 0; i < choices.size(); i++) {
AdapterChoiceItem c = choices.get(i);
if (lastSelectedPlayer.equals(c.key)) {
selectedRadioPosition = i;
break;
@@ -269,7 +282,7 @@ public class RouterActivity extends AppCompatActivity {
}
}
selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.length - 1);
selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.size() - 1);
if (selectedRadioPosition != -1) {
((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true);
}
@@ -278,6 +291,39 @@ public class RouterActivity extends AppCompatActivity {
alertDialog.show();
}
private List<AdapterChoiceItem> getChoicesForService(StreamingService service, LinkType linkType) {
final Context context = getThemeWrapperContext();
final List<AdapterChoiceItem> returnList = new ArrayList<>();
final List<StreamingService.ServiceInfo.MediaCapability> capabilities = service.getServiceInfo().getMediaCapabilities();
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);
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key), getString(R.string.show_info),
resolveResourceIdFromAttr(context, R.attr.info)));
if (capabilities.contains(VIDEO) && !(isExtVideoEnabled && linkType != LinkType.STREAM)) {
returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player),
resolveResourceIdFromAttr(context, R.attr.play)));
returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player),
resolveResourceIdFromAttr(context, R.attr.popup)));
}
if (capabilities.contains(AUDIO) && !(isExtAudioEnabled && linkType != LinkType.STREAM)) {
returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player),
resolveResourceIdFromAttr(context, R.attr.audio)));
}
return returnList;
}
private Context getThemeWrapperContext() {
return new ContextThemeWrapper(this,
ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme);
}
private void setDialogButtonsState(AlertDialog dialog, boolean state) {
final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
@@ -287,17 +333,15 @@ public class RouterActivity extends AppCompatActivity {
positiveButton.setEnabled(state);
}
private void handleChoice(final String playerChoiceKey) {
if (Arrays.asList(getResources()
.getStringArray(R.array.preferred_open_action_values_list))
.contains(playerChoiceKey)) {
private void handleChoice(final String selectedChoiceKey) {
final List<String> validChoicesList = Arrays.asList(getResources().getStringArray(R.array.preferred_open_action_values_list));
if (validChoicesList.contains(selectedChoiceKey)) {
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putString(getString(R.string.preferred_open_action_last_selected_key),
playerChoiceKey).apply();
.putString(getString(R.string.preferred_open_action_last_selected_key), selectedChoiceKey)
.apply();
}
if (playerChoiceKey.equals(getString(R.string.popup_player_key))
&& !PermissionHelper.isPopupEnabled(this)) {
if (selectedChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) {
PermissionHelper.showPopupEnablementToast(this);
finish();
return;
@@ -305,7 +349,7 @@ public class RouterActivity extends AppCompatActivity {
// stop and bypass FetcherService if InfoScreen was selected since
// StreamDetailFragment can fetch data itself
if(playerChoiceKey.equals(getString(R.string.show_info_key))) {
if (selectedChoiceKey.equals(getString(R.string.show_info_key))) {
disposables.add(Observable
.fromCallable(() -> NavigationHelper.getIntentByLink(this, currentUrl))
.subscribeOn(Schedulers.io())
@@ -322,11 +366,8 @@ public class RouterActivity extends AppCompatActivity {
}
final Intent intent = new Intent(this, FetcherService.class);
intent.putExtra(FetcherService.KEY_CHOICE,
new Choice(currentService.getServiceId(),
currentLinkType,
currentUrl,
playerChoiceKey));
final Choice choice = new Choice(currentService.getServiceId(), currentLinkType, currentUrl, selectedChoiceKey);
intent.putExtra(FetcherService.KEY_CHOICE, choice);
startService(intent);
finish();
@@ -334,8 +375,7 @@ public class RouterActivity extends AppCompatActivity {
private static class AdapterChoiceItem {
final String description, key;
@DrawableRes
final int icon;
@DrawableRes final int icon;
AdapterChoiceItem(String key, String description, int icon) {
this.description = description;
@@ -554,7 +594,7 @@ public class RouterActivity extends AppCompatActivity {
* @param sharedText text to scan for URLs.
* @return potential URLs
*/
protected String[] getUris(final String sharedText) {
protected String[] getUris(final String sharedText) {
final Collection<String> result = new HashSet<>();
if (sharedText != null) {
final String[] array = sharedText.split("\\p{Space}");

View File

@@ -32,7 +32,6 @@ public class AboutActivity extends AppCompatActivity {
new SoftwareComponent("Giga Get", "2014", "Peter Cai", "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
new SoftwareComponent("NewPipe Extractor", "2017", "Christian Schabesberger", "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley", "https://github.com/jhy/jsoup", StandardLicenses.MIT),
new SoftwareComponent("Google Gson", "2008", "Google Inc", "https://github.com/google/gson", StandardLicenses.APACHE2),
new SoftwareComponent("Rhino", "2015", "Mozilla", "https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin", "http://www.acra.ch", StandardLicenses.APACHE2),
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich", "https://github.com/nostra13/Android-Universal-Image-Loader", StandardLicenses.APACHE2),
@@ -129,47 +128,31 @@ public class AboutActivity extends AppCompatActivity {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
Context context = this.getContext();
TextView version = rootView.findViewById(R.id.app_version);
version.setText(BuildConfig.VERSION_NAME);
View githubLink = rootView.findViewById(R.id.github_link);
githubLink.setOnClickListener(new OnGithubLinkClickListener());
githubLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.github_url), context));
View donationLink = rootView.findViewById(R.id.donation_link);
donationLink.setOnClickListener(new OnDonationLinkClickListener());
donationLink.setOnClickListener(v -> openWebsite(context.getString(R.string.donation_url), context));
View websiteLink = rootView.findViewById(R.id.website_link);
websiteLink.setOnClickListener(new OnWebsiteLinkClickListener());
websiteLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.website_url), context));
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
privacyPolicyLink.setOnClickListener(v -> openWebsite(context.getString(R.string.privacy_policy_url), context));
return rootView;
}
private static class OnGithubLinkClickListener 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.github_url)));
context.startActivity(intent);
}
private void openWebsite(String url, Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(intent);
}
private static class OnDonationLinkClickListener 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.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

@@ -1,5 +1,6 @@
package org.schabi.newpipe.about;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -39,7 +40,7 @@ public class LicenseFragment extends Fragment {
* @param license the license to show
*/
public static void showLicense(Context context, License license) {
new LicenseFragmentHelper().execute(context, license);
new LicenseFragmentHelper((Activity) context).execute(license);
}
@Override

View File

@@ -1,8 +1,11 @@
package org.schabi.newpipe.about;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.webkit.WebView;
import org.schabi.newpipe.R;
@@ -10,26 +13,46 @@ import org.schabi.newpipe.util.ThemeHelper;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
private Context context;
WeakReference<Activity> weakReference;
private License license;
public LicenseFragmentHelper(@Nullable Activity activity) {
weakReference = new WeakReference<>(activity);
}
@Nullable
private Activity getActivity() {
Activity activity = weakReference.get();
if (activity != null && activity.isFinishing()) {
return null;
} else {
return activity;
}
}
@Override
protected Integer doInBackground(Object... objects) {
context = (Context) objects[0];
license = (License) objects[1];
license = (License) objects[0];
return 1;
}
@Override
protected void onPostExecute(Integer result){
String webViewData = getFormattedLicense(context, license);
AlertDialog.Builder alert = new AlertDialog.Builder(context);
protected void onPostExecute(Integer result) {
Activity activity = getActivity();
if (activity == null) {
return;
}
String webViewData = getFormattedLicense(activity, license);
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
alert.setTitle(license.getName());
WebView wv = new WebView(context);
WebView wv = new WebView(activity);
wv.loadData(webViewData, "text/html; charset=UTF-8", null);
alert.setView(wv);

View File

@@ -9,7 +9,7 @@ import android.arch.persistence.room.PrimaryKey;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.Constants;
import java.io.Serializable;

View File

@@ -1,56 +1,61 @@
package org.schabi.newpipe.download;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
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.detail.SpinnerToolbarAdapter;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.FilenameUtils;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import icepick.Icepick;
import icepick.State;
import io.reactivex.disposables.CompositeDisposable;
import us.shandian.giga.service.DownloadManagerService;
public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
private static final String TAG = "DialogFragment";
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String INFO_KEY = "info_key";
private static final String SORTED_VIDEOS_LIST_KEY = "sorted_videos_list_key";
private static final String SELECTED_VIDEO_KEY = "selected_video_key";
private static final String SELECTED_AUDIO_KEY = "selected_audio_key";
@State protected StreamInfo currentInfo;
@State protected StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty();
@State protected StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty();
@State protected int selectedVideoIndex = 0;
@State protected int selectedAudioIndex = 0;
private StreamInfo currentInfo;
private ArrayList<VideoStream> sortedStreamVideosList;
private int selectedVideoIndex;
private int selectedAudioIndex;
private StreamItemAdapter<AudioStream> audioStreamsAdapter;
private StreamItemAdapter<VideoStream> videoStreamsAdapter;
private CompositeDisposable disposables = new CompositeDisposable();
private EditText nameEditText;
private Spinner streamsSpinner;
@@ -58,17 +63,50 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
private TextView threadsCountTextView;
private SeekBar threadsSeekBar;
public static DownloadDialog newInstance(StreamInfo info, ArrayList<VideoStream> sortedStreamVideosList, int selectedVideoIndex) {
public static DownloadDialog newInstance(StreamInfo info) {
DownloadDialog dialog = new DownloadDialog();
dialog.setInfo(info, sortedStreamVideosList, selectedVideoIndex);
dialog.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
dialog.setInfo(info);
return dialog;
}
private void setInfo(StreamInfo info, ArrayList<VideoStream> sortedStreamVideosList, int selectedVideoIndex) {
public static DownloadDialog newInstance(Context context, StreamInfo info) {
final ArrayList<VideoStream> streamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context,
info.getVideoStreams(), info.getVideoOnlyStreams(), false));
final int selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, streamsList);
final DownloadDialog instance = newInstance(info);
instance.setVideoStreams(streamsList);
instance.setSelectedVideoStream(selectedStreamIndex);
instance.setAudioStreams(info.getAudioStreams());
return instance;
}
private void setInfo(StreamInfo info) {
this.currentInfo = info;
}
public void setAudioStreams(List<AudioStream> audioStreams) {
setAudioStreams(new StreamSizeWrapper<>(audioStreams));
}
public void setAudioStreams(StreamSizeWrapper<AudioStream> wrappedAudioStreams) {
this.wrappedAudioStreams = wrappedAudioStreams;
}
public void setVideoStreams(List<VideoStream> videoStreams) {
setVideoStreams(new StreamSizeWrapper<>(videoStreams));
}
public void setVideoStreams(StreamSizeWrapper<VideoStream> wrappedVideoStreams) {
this.wrappedVideoStreams = wrappedVideoStreams;
}
public void setSelectedVideoStream(int selectedVideoIndex) {
this.selectedVideoIndex = selectedVideoIndex;
this.sortedStreamVideosList = sortedStreamVideosList;
}
public void setSelectedAudioStream(int selectedAudioIndex) {
this.selectedAudioIndex = selectedAudioIndex;
}
/*//////////////////////////////////////////////////////////////////////////
@@ -79,33 +117,26 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
if (!PermissionHelper.checkStoragePermissions(getActivity())) {
if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
getDialog().dismiss();
return;
}
if (savedInstanceState != null) {
Serializable serial = savedInstanceState.getSerializable(INFO_KEY);
if (serial instanceof StreamInfo) currentInfo = (StreamInfo) serial;
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(getContext()));
Icepick.restoreInstanceState(this, savedInstanceState);
serial = savedInstanceState.getSerializable(SORTED_VIDEOS_LIST_KEY);
if (serial instanceof ArrayList) { //noinspection unchecked
sortedStreamVideosList = (ArrayList<VideoStream>) serial;
}
selectedVideoIndex = savedInstanceState.getInt(SELECTED_VIDEO_KEY, 0);
selectedAudioIndex = savedInstanceState.getInt(SELECTED_AUDIO_KEY, 0);
}
this.videoStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedVideoStreams, true);
this.audioStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedAudioStreams);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
return inflater.inflate(R.layout.dialog_url, container);
return inflater.inflate(R.layout.download_dialog, container);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
nameEditText = view.findViewById(R.id.file_name);
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName()));
@@ -116,12 +147,12 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
threadsCountTextView = view.findViewById(R.id.threads_count);
threadsSeekBar = view.findViewById(R.id.threads);
radioVideoAudioGroup = view.findViewById(R.id.video_audio_group);
radioVideoAudioGroup.setOnCheckedChangeListener(this);
initToolbar(view.<Toolbar>findViewById(R.id.toolbar));
checkDownloadOptions(view);
setupVideoSpinner(sortedStreamVideosList, streamsSpinner);
initToolbar(view.findViewById(R.id.toolbar));
setupDownloadOptions();
int def = 3;
threadsCountTextView.setText(String.valueOf(def));
@@ -141,15 +172,35 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
public void onStopTrackingTouch(SeekBar p1) {
}
});
fetchStreamsSize();
}
private void fetchStreamsSize() {
disposables.clear();
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams).subscribe(result -> {
if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.video_button) {
setupVideoSpinner();
}
}));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams).subscribe(result -> {
if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) {
setupAudioSpinner();
}
}));
}
@Override
public void onDestroy() {
super.onDestroy();
disposables.clear();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, currentInfo);
outState.putSerializable(SORTED_VIDEOS_LIST_KEY, sortedStreamVideosList);
outState.putInt(SELECTED_VIDEO_KEY, selectedVideoIndex);
outState.putInt(SELECTED_AUDIO_KEY, selectedAudioIndex);
Icepick.saveInstanceState(this, outState);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -161,39 +212,31 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
toolbar.setTitle(R.string.download_dialog_title);
toolbar.setNavigationIcon(ThemeHelper.isLightThemeSelected(getActivity()) ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp);
toolbar.inflateMenu(R.menu.dialog_url);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getDialog().dismiss();
}
});
toolbar.setNavigationOnClickListener(v -> getDialog().dismiss());
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.okay) {
downloadSelected();
return true;
} else return false;
toolbar.setOnMenuItemClickListener(item -> {
if (item.getItemId() == R.id.okay) {
downloadSelected();
return true;
}
return false;
});
}
public void setupAudioSpinner(final List<AudioStream> audioStreams, Spinner spinner) {
String[] items = new String[audioStreams.size()];
for (int i = 0; i < audioStreams.size(); i++) {
AudioStream audioStream = audioStreams.get(i);
items[i] = audioStream.getFormat().getName() + " " + audioStream.getAverageBitrate() + "kbps";
}
private void setupAudioSpinner() {
if (getContext() == null) return;
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, items);
spinner.setAdapter(itemAdapter);
spinner.setSelection(selectedAudioIndex);
streamsSpinner.setAdapter(audioStreamsAdapter);
streamsSpinner.setSelection(selectedAudioIndex);
setRadioButtonsState(true);
}
public void setupVideoSpinner(final List<VideoStream> videoStreams, Spinner spinner) {
spinner.setAdapter(new SpinnerToolbarAdapter(getContext(), videoStreams, true));
spinner.setSelection(selectedVideoIndex);
private void setupVideoSpinner() {
if (getContext() == null) return;
streamsSpinner.setAdapter(videoStreamsAdapter);
streamsSpinner.setSelection(selectedVideoIndex);
setRadioButtonsState(true);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -205,10 +248,10 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
if (DEBUG) Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
switch (checkedId) {
case R.id.audio_button:
setupAudioSpinner(currentInfo.getAudioStreams(), streamsSpinner);
setupAudioSpinner();
break;
case R.id.video_button:
setupVideoSpinner(sortedStreamVideosList, streamsSpinner);
setupVideoSpinner();
break;
}
}
@@ -238,37 +281,53 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected void checkDownloadOptions(View view) {
RadioButton audioButton = view.findViewById(R.id.audio_button);
RadioButton videoButton = view.findViewById(R.id.video_button);
protected void setupDownloadOptions() {
setRadioButtonsState(false);
if (currentInfo.getAudioStreams() == null || currentInfo.getAudioStreams().size() == 0) {
audioButton.setVisibility(View.GONE);
final RadioButton audioButton = radioVideoAudioGroup.findViewById(R.id.audio_button);
final RadioButton videoButton = radioVideoAudioGroup.findViewById(R.id.video_button);
final boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0;
final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0;
audioButton.setVisibility(isAudioStreamsAvailable ? View.VISIBLE : View.GONE);
videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE);
if (isVideoStreamsAvailable) {
videoButton.setChecked(true);
} else if (sortedStreamVideosList == null || sortedStreamVideosList.size() == 0) {
videoButton.setVisibility(View.GONE);
setupVideoSpinner();
} else if (isAudioStreamsAvailable) {
audioButton.setChecked(true);
setupAudioSpinner();
} else {
Toast.makeText(getContext(), R.string.no_streams_available_download, Toast.LENGTH_SHORT).show();
getDialog().dismiss();
}
}
private void setRadioButtonsState(boolean enabled) {
radioVideoAudioGroup.findViewById(R.id.audio_button).setEnabled(enabled);
radioVideoAudioGroup.findViewById(R.id.video_button).setEnabled(enabled);
}
private void downloadSelected() {
String url, location;
Stream stream;
String location;
String fileName = nameEditText.getText().toString().trim();
if (fileName.isEmpty()) fileName = FilenameUtils.createFilename(getContext(), currentInfo.getName());
boolean isAudio = radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button;
if (isAudio) {
url = currentInfo.getAudioStreams().get(selectedAudioIndex).getUrl();
stream = audioStreamsAdapter.getItem(selectedAudioIndex);
location = NewPipeSettings.getAudioDownloadPath(getContext());
fileName += "." + currentInfo.getAudioStreams().get(selectedAudioIndex).getFormat().getSuffix();
} else {
url = sortedStreamVideosList.get(selectedVideoIndex).getUrl();
stream = videoStreamsAdapter.getItem(selectedVideoIndex);
location = NewPipeSettings.getVideoDownloadPath(getContext());
fileName += "." + sortedStreamVideosList.get(selectedVideoIndex).getFormat().getSuffix();
}
String url = stream.getUrl();
fileName += "." + stream.getFormat().getSuffix();
DownloadManagerService.startMission(getContext(), url, location, fileName, isAudio, threadsSeekBar.getProgress() + 1);
getDialog().dismiss();
}

View File

@@ -239,7 +239,8 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
if (rootView == null && getView() != null) rootView = getView();
if (rootView == null) return;
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView, ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
}
/*//////////////////////////////////////////////////////////////////////////

View File

@@ -27,10 +27,10 @@ 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;
import org.schabi.newpipe.fragments.list.feed.FeedFragment;
import org.schabi.newpipe.local.feed.FeedFragment;
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
import org.schabi.newpipe.fragments.local.bookmark.BookmarkFragment;
import org.schabi.newpipe.fragments.subscription.SubscriptionFragment;
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.KioskTranslator;

View File

@@ -1,74 +0,0 @@
package org.schabi.newpipe.fragments.detail;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.VideoStream;
import java.util.List;
public class SpinnerToolbarAdapter extends BaseAdapter {
private final List<VideoStream> videoStreams;
private final boolean showIconNoAudio;
private final Context context;
public SpinnerToolbarAdapter(Context context, List<VideoStream> videoStreams, boolean showIconNoAudio) {
this.context = context;
this.videoStreams = videoStreams;
this.showIconNoAudio = showIconNoAudio;
}
@Override
public int getCount() {
return videoStreams.size();
}
@Override
public Object getItem(int position) {
return videoStreams.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getCustomView(position, convertView, parent, true);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return getCustomView(((Spinner) parent).getSelectedItemPosition(), convertView, parent, false);
}
private View getCustomView(int position, View convertView, ViewGroup parent, boolean isDropdownItem) {
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.resolutions_spinner_item, parent, false);
}
ImageView woSoundIcon = convertView.findViewById(R.id.wo_sound_icon);
TextView text = convertView.findViewById(android.R.id.text1);
VideoStream item = (VideoStream) getItem(position);
text.setText(item.getFormat().getName() + " " + item.getResolution());
int visibility = !showIconNoAudio ? View.GONE
: item.isVideoOnly ? View.VISIBLE
: isDropdownItem ? View.INVISIBLE
: View.GONE;
woSoundIcon.setVisibility(visibility);
return convertView;
}
}

View File

@@ -12,6 +12,7 @@ import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v7.app.ActionBar;
@@ -61,16 +62,17 @@ import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.info_list.InfoItemBuilder;
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;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
@@ -84,9 +86,10 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import icepick.State;
import io.reactivex.Single;
@@ -108,8 +111,6 @@ public class VideoDetailFragment
// Amount of videos to show on start
private static final int INITIAL_RELATED_VIDEOS = 8;
private ArrayList<VideoStream> sortedStreamVideosList;
private InfoItemBuilder infoItemBuilder = null;
private int updateFlags = 0;
@@ -121,18 +122,16 @@ public class VideoDetailFragment
private boolean showRelatedStreams;
private boolean wasRelatedStreamsExpanded = false;
@State
protected int serviceId = Constants.NO_SERVICE_ID;
@State
protected String name;
@State
protected String url;
@State protected int serviceId = Constants.NO_SERVICE_ID;
@State protected String name;
@State protected String url;
private StreamInfo currentInfo;
private Disposable currentWorker;
private CompositeDisposable disposables = new CompositeDisposable();
private int selectedVideoStream = -1;
private List<VideoStream> sortedVideoStreams;
private int selectedVideoStreamIndex = -1;
/*//////////////////////////////////////////////////////////////////////////
// Views
@@ -205,7 +204,7 @@ public class VideoDetailFragment
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_video_detail, container, false);
}
@@ -356,21 +355,8 @@ public class VideoDetailFragment
}
break;
case R.id.detail_controls_download:
if (!PermissionHelper.checkStoragePermissions(activity)) {
return;
}
try {
DownloadDialog downloadDialog =
DownloadDialog.newInstance(currentInfo,
sortedStreamVideosList,
selectedVideoStream);
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (Exception e) {
Toast.makeText(activity,
R.string.could_not_setup_download_menu,
Toast.LENGTH_LONG).show();
e.printStackTrace();
if (PermissionHelper.checkStoragePermissions(activity, PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
this.openDownloadDialog();
}
break;
case R.id.detail_uploader_root_layout:
@@ -412,6 +398,9 @@ public class VideoDetailFragment
case R.id.detail_controls_popup:
openPopupPlayer(true);
break;
case R.id.detail_controls_download:
NavigationHelper.openDownloads(getActivity());
break;
}
return true;
@@ -533,6 +522,7 @@ public class VideoDetailFragment
detailControlsPopup.setOnClickListener(this);
detailControlsAddToPlaylist.setOnClickListener(this);
detailControlsDownload.setOnClickListener(this);
detailControlsDownload.setOnLongClickListener(this);
relatedStreamExpandButton.setOnClickListener(this);
detailControlsBackground.setLongClickable(true);
@@ -549,7 +539,8 @@ public class VideoDetailFragment
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.enqueue_on_popup),
context.getResources().getString(R.string.append_playlist)
};
final DialogInterface.OnClickListener actions = (DialogInterface dialogInterface, int i) -> {
@@ -560,6 +551,12 @@ public class VideoDetailFragment
case 1:
NavigationHelper.enqueueOnPopupPlayer(getActivity(), new SinglePlayQueue(item));
break;
case 2:
if (getFragmentManager() != null) {
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
.show(getFragmentManager(), TAG);
}
break;
default:
break;
}
@@ -681,15 +678,15 @@ public class VideoDetailFragment
int id = item.getItemId();
switch (id) {
case R.id.menu_item_share: {
if(currentInfo != null) {
shareUrl(currentInfo.getName(), url);
} else {
shareUrl(url, url);
if (currentInfo != null) {
shareUrl(currentInfo.getName(), currentInfo.getUrl());
}
return true;
}
case R.id.menu_item_openInBrowser: {
openUrlInBrowser(url);
if (currentInfo != null) {
openUrlInBrowser(currentInfo.getUrl());
}
return true;
}
case R.id.action_play_with_kodi:
@@ -722,27 +719,25 @@ public class VideoDetailFragment
private void setupActionBar(final StreamInfo info) {
if (DEBUG) Log.d(TAG, "setupActionBarHandler() called with: info = [" + info + "]");
sortedStreamVideosList = new ArrayList<>(ListHelper.getSortedStreamVideosList(
activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false));
selectedVideoStream = ListHelper.getDefaultResolutionIndex(activity, sortedStreamVideosList);
boolean isExternalPlayerEnabled = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.use_external_video_player_key), false);
spinnerToolbar.setAdapter(new SpinnerToolbarAdapter(activity, sortedStreamVideosList,
isExternalPlayerEnabled));
spinnerToolbar.setSelection(selectedVideoStream);
sortedVideoStreams = ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false);
selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(activity, sortedVideoStreams);
final StreamItemAdapter<VideoStream> streamsAdapter = new StreamItemAdapter<>(activity, new StreamSizeWrapper<>(sortedVideoStreams), isExternalPlayerEnabled);
spinnerToolbar.setAdapter(streamsAdapter);
spinnerToolbar.setSelection(selectedVideoStreamIndex);
spinnerToolbar.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedVideoStream = position;
selectedVideoStreamIndex = position;
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
/*//////////////////////////////////////////////////////////////////////////
@@ -818,7 +813,7 @@ public class VideoDetailFragment
public void prepareAndHandleInfo(final StreamInfo info, boolean scrollToTop) {
if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = [" + info + "], scrollToTop = [" + scrollToTop + "]");
setInitialData(info.getServiceId(), info.getUrl(), info.getName());
setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName());
pushToStack(serviceId, url, name);
showLoading();
@@ -954,8 +949,9 @@ public class VideoDetailFragment
this.autoPlayEnabled = autoplay;
}
@Nullable
private VideoStream getSelectedVideoStream() {
return sortedStreamVideosList.get(selectedVideoStream);
return sortedVideoStreams != null ? sortedVideoStreams.get(selectedVideoStreamIndex) : null;
}
private void prepareDescription(final String descriptionHtml) {
@@ -1112,7 +1108,7 @@ public class VideoDetailFragment
public void handleResult(@NonNull StreamInfo info) {
super.handleResult(info);
setInitialData(info.getServiceId(), info.getUrl(), info.getName());
setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName());
pushToStack(serviceId, url, name);
animateView(thumbnailPlayButton, true, 200);
@@ -1192,7 +1188,9 @@ public class VideoDetailFragment
toggleExpandRelatedVideos(currentInfo);
wasRelatedStreamsExpanded = false;
}
setTitleToUrl(info.getServiceId(), info.getUrl(), info.getName());
setTitleToUrl(info.getServiceId(), info.getOriginalUrl(), info.getName());
if (!info.getErrors().isEmpty()) {
showSnackBarError(info.getErrors(),
@@ -1226,6 +1224,23 @@ public class VideoDetailFragment
}
}
public void openDownloadDialog() {
try {
DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
downloadDialog.setVideoStreams(sortedVideoStreams);
downloadDialog.setAudioStreams(currentInfo.getAudioStreams());
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (Exception e) {
Toast.makeText(activity,
R.string.could_not_setup_download_menu,
Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Stream Results
//////////////////////////////////////////////////////////////////////////*/

View File

@@ -20,10 +20,10 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.fragments.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.StateSaver;
@@ -140,9 +140,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
infoListAdapter.setOnStreamSelectedListener(new OnClickGesture<StreamInfoItem>() {
@Override
public void selected(StreamInfoItem selectedItem) {
onItemSelected(selectedItem);
NavigationHelper.openVideoDetailFragment(useAsFrontPage ? getParentFragment().getFragmentManager() : getFragmentManager(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
onStreamSelected(selectedItem);
}
@Override
@@ -178,6 +176,12 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
});
}
private void onStreamSelected(StreamInfoItem selectedItem) {
onItemSelected(selectedItem);
NavigationHelper.openVideoDetailFragment(useAsFrontPage ? getParentFragment().getFragmentManager() : getFragmentManager(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
}
protected void onScrollToBottom() {
if (hasMoreItems() && !isLoading.get()) {
loadMoreItems();
@@ -216,6 +220,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
new InfoItemDialog(getActivity(), item, commands, actions).show();
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/

View File

@@ -32,16 +32,15 @@ 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.Stream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
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.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.subscription.SubscriptionService;
import org.schabi.newpipe.local.subscription.SubscriptionService;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
@@ -49,6 +48,7 @@ import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -161,6 +161,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
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),
context.getResources().getString(R.string.append_playlist)
};
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
@@ -183,6 +184,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
case 4:
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
break;
case 5:
if (getFragmentManager() != null) {
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
.show(getFragmentManager(), TAG);
}
break;
default:
break;
}

View File

@@ -77,8 +77,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
UrlIdHandler kioskTypeUrlIdHandler = service.getKioskList()
.getUrlIdHandlerByType(kioskId);
instance.setInitialData(serviceId,
kioskTypeUrlIdHandler.getUrl(kioskId),
kioskId);
kioskTypeUrlIdHandler.getUrl(kioskId), kioskId);
instance.kioskId = kioskId;
return instance;
}
@@ -168,7 +167,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(),
UserAction.REQUESTED_PLAYLIST,
UserAction.REQUESTED_KIOSK,
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
}
}

View File

@@ -27,14 +27,13 @@ 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.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.fragments.local.RemotePlaylistManager;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
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.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;

View File

@@ -41,7 +41,7 @@ import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.history.HistoryRecordManager;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.AnimationUtils;
@@ -544,7 +544,7 @@ public class SearchFragment
howManyDeleted -> suggestionPublisher
.onNext(searchEditText.getText().toString()),
throwable -> showSnackBarError(throwable,
UserAction.SOMETHING_ELSE, "none",
UserAction.DELETE_FROM_HISTORY, "none",
"Deleting item failed", R.string.general_error)
);
disposables.add(onDelete);

View File

@@ -63,24 +63,15 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
public void onBindViewHolder(SuggestionItemHolder holder, int position) {
final SuggestionItem currentItem = getItem(position);
holder.updateFrom(currentItem);
holder.queryView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) listener.onSuggestionItemSelected(currentItem);
}
holder.queryView.setOnClickListener(v -> {
if (listener != null) listener.onSuggestionItemSelected(currentItem);
});
holder.queryView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
holder.queryView.setOnLongClickListener(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);
}
holder.insertView.setOnClickListener(v -> {
if (listener != null) listener.onSuggestionItemInserted(currentItem);
});
}

View File

@@ -1,21 +0,0 @@
package org.schabi.newpipe.fragments.local.bookmark;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import java.util.Collections;
import java.util.List;
public final class LastPlayedFragment extends StatisticsPlaylistFragment {
@Override
protected String getName() {
return getString(R.string.title_last_played);
}
@Override
protected List<StreamStatisticsEntry> processResult(List<StreamStatisticsEntry> results) {
Collections.sort(results, (left, right) ->
right.latestAccessDate.compareTo(left.latestAccessDate));
return results;
}
}

View File

@@ -1,22 +0,0 @@
package org.schabi.newpipe.fragments.local.bookmark;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import java.util.Collections;
import java.util.List;
public final class MostPlayedFragment extends StatisticsPlaylistFragment {
@Override
protected String getName() {
return getString(R.string.title_most_played);
}
@Override
protected List<StreamStatisticsEntry> processResult(List<StreamStatisticsEntry> results) {
Collections.sort(results, (left, right) ->
((Long) right.watchCount).compareTo(left.watchCount));
return results;
}
}

View File

@@ -1,141 +0,0 @@
package org.schabi.newpipe.history;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import com.jakewharton.rxbinding2.view.RxView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.ThemeHelper;
import io.reactivex.android.schedulers.AndroidSchedulers;
public class HistoryActivity extends AppCompatActivity {
private static final String TAG = "HistoryActivity";
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a
* {@link FragmentPagerAdapter} derivative, which will keep every
* loaded fragment in memory. If this becomes too memory intensive, it
* may be best to switch to a
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
*/
private SectionsPagerAdapter mSectionsPagerAdapter;
/**
* The {@link ViewPager} that will host the section contents.
*/
private ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this);
setContentView(R.layout.activity_history);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.title_activity_history);
}
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
TabLayout tabLayout = findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
final FloatingActionButton fab = findViewById(R.id.fab);
RxView.clicks(fab)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> {
int currentItem = mViewPager.getCurrentItem();
HistoryFragment fragment = (HistoryFragment) mSectionsPagerAdapter
.instantiateItem(mViewPager, currentItem);
fragment.onHistoryCleared();
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_history, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.action_settings:
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
Fragment fragment;
switch (position) {
case 0:
fragment = SearchHistoryFragment.newInstance();
break;
case 1:
fragment = WatchHistoryFragment.newInstance();
break;
default:
throw new IllegalArgumentException("position: " + position);
}
return fragment;
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.title_history_search);
case 1:
return getString(R.string.title_history_view);
}
throw new IllegalArgumentException("position: " + position);
}
@Override
public int getCount() {
// Show 3 total pages.
return 2;
}
}
}

View File

@@ -1,286 +0,0 @@
package org.schabi.newpipe.history;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.annotation.CallSuper;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import icepick.State;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public abstract class HistoryFragment<E> extends BaseFragment
implements HistoryEntryAdapter.OnHistoryItemClickListener<E> {
private SharedPreferences mSharedPreferences;
private String mHistoryIsEnabledKey;
private boolean mHistoryIsEnabled;
private HistoryIsEnabledChangeListener mHistoryIsEnabledChangeListener;
private View mDisabledView;
private View mEmptyHistoryView;
@State
Parcelable mRecyclerViewState;
private RecyclerView mRecyclerView;
private HistoryEntryAdapter<E, ? extends RecyclerView.ViewHolder> mHistoryAdapter;
private Subscription historySubscription;
protected HistoryRecordManager historyRecordManager;
protected CompositeDisposable disposables;
@StringRes
abstract int getEnabledConfigKey();
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHistoryIsEnabledKey = getString(getEnabledConfigKey());
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
// Read history enabled from preferences
mHistoryIsEnabled = isHistoryEnabled();
// Register history enabled listener
mSharedPreferences.registerOnSharedPreferenceChangeListener(mHistoryIsEnabledChangeListener);
historyRecordManager = new HistoryRecordManager(getContext());
disposables = new CompositeDisposable();
}
@NonNull
protected abstract HistoryEntryAdapter<E, ? extends RecyclerView.ViewHolder> createAdapter();
protected abstract Single<List<Long>> insert(final Collection<E> entries);
protected abstract Single<Integer> delete(final Collection<E> entries);
@NonNull
protected abstract Flowable<List<E>> getAll();
@Override
public void onResume() {
super.onResume();
getAll().observeOn(AndroidSchedulers.mainThread()).subscribe(getHistorySubscriber());
final boolean newEnabled = isHistoryEnabled();
if (newEnabled != mHistoryIsEnabled) {
onHistoryIsEnabledChanged(newEnabled);
}
}
@NonNull
private Subscriber<List<E>> getHistorySubscriber() {
return new Subscriber<List<E>>() {
@Override
public void onSubscribe(Subscription s) {
if (historySubscription != null) historySubscription.cancel();
historySubscription = s;
historySubscription.request(1);
}
@Override
public void onNext(List<E> entries) {
if (!entries.isEmpty()) {
mHistoryAdapter.setEntries(entries);
animateView(mEmptyHistoryView, false, 200);
if (mRecyclerViewState != null) {
mRecyclerView.getLayoutManager().onRestoreInstanceState(mRecyclerViewState);
mRecyclerViewState = null;
}
} else {
mHistoryAdapter.clear();
showEmptyHistory();
}
if (historySubscription != null) historySubscription.request(1);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
};
}
private boolean isHistoryEnabled() {
return mSharedPreferences.getBoolean(mHistoryIsEnabledKey, false);
}
/**
* Called when the history is cleared to update the views
*/
@MainThread
public void onHistoryCleared() {
if (getContext() == null) return;
new AlertDialog.Builder(getContext())
.setTitle(R.string.delete_all)
.setMessage(R.string.delete_all_history_prompt)
.setCancelable(true)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.delete_all, (dialog, i) -> clearHistory())
.show();
}
protected void makeSnackbar(@StringRes final int text) {
if (getActivity() == null) return;
View view = getActivity().findViewById(R.id.main_content);
if (view == null) view = mRecyclerView.getRootView();
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
}
private void clearHistory() {
final Collection<E> itemsToDelete = new ArrayList<>(mHistoryAdapter.getItems());
final Disposable deletion = delete(itemsToDelete)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> Log.d(TAG, "Clear history deleted [" +
itemsToDelete.size() + "] items."),
error -> Log.e(TAG, "Clear history delete step failed", error)
);
final Disposable cleanUp = historyRecordManager.removeOrphanedRecords()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> Log.d(TAG, "Clear history deleted orphaned stream records"),
error -> Log.e(TAG, "Clear history remove orphaned records failed", error)
);
disposables.addAll(deletion, cleanUp);
makeSnackbar(R.string.history_cleared);
mHistoryAdapter.clear();
showEmptyHistory();
}
private void showEmptyHistory() {
if (mHistoryIsEnabled) {
animateView(mEmptyHistoryView, true, 200);
}
}
@Nullable
@CallSuper
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_history, container, false);
mRecyclerView = rootView.findViewById(R.id.history_view);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext(),
LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(layoutManager);
mHistoryAdapter = createAdapter();
mHistoryAdapter.setOnHistoryItemClickListener(this);
mRecyclerView.setAdapter(mHistoryAdapter);
mDisabledView = rootView.findViewById(R.id.history_disabled_view);
mEmptyHistoryView = rootView.findViewById(R.id.history_empty);
if (mHistoryIsEnabled) {
mRecyclerView.setVisibility(View.VISIBLE);
} else {
mRecyclerView.setVisibility(View.GONE);
mDisabledView.setVisibility(View.VISIBLE);
}
return rootView;
}
@CallSuper
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.dispose();
if (historySubscription != null) historySubscription.cancel();
mSharedPreferences.unregisterOnSharedPreferenceChangeListener(mHistoryIsEnabledChangeListener);
mSharedPreferences = null;
mHistoryIsEnabledChangeListener = null;
mHistoryIsEnabledKey = null;
historySubscription = null;
disposables = null;
}
@Override
public void onPause() {
super.onPause();
mRecyclerViewState = mRecyclerView.getLayoutManager().onSaveInstanceState();
}
/**
* Called when history enabled flag is changed.
*
* @param historyIsEnabled the new value
*/
@CallSuper
public void onHistoryIsEnabledChanged(boolean historyIsEnabled) {
mHistoryIsEnabled = historyIsEnabled;
if (historyIsEnabled) {
animateView(mRecyclerView, true, 300);
animateView(mDisabledView, false, 300);
if (mHistoryAdapter.isEmpty()) {
animateView(mEmptyHistoryView, true, 300);
}
} else {
animateView(mRecyclerView, false, 300);
animateView(mDisabledView, true, 300);
animateView(mEmptyHistoryView, false, 300);
}
}
private class HistoryIsEnabledChangeListener
implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(mHistoryIsEnabledKey)) {
boolean enabled = sharedPreferences.getBoolean(key, false);
if (mHistoryIsEnabled != enabled) {
onHistoryIsEnabledChanged(enabled);
}
}
}
}
}

View File

@@ -1,145 +0,0 @@
package org.schabi.newpipe.history;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
public class SearchHistoryFragment extends HistoryFragment<SearchHistoryEntry> {
@NonNull
public static SearchHistoryFragment newInstance() {
return new SearchHistoryFragment();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@NonNull
@Override
protected SearchHistoryAdapter createAdapter() {
return new SearchHistoryAdapter(getContext());
}
@Override
protected Single<List<Long>> insert(Collection<SearchHistoryEntry> entries) {
return historyRecordManager.insertSearches(entries);
}
@Override
protected Single<Integer> delete(Collection<SearchHistoryEntry> entries) {
return historyRecordManager.deleteSearches(entries);
}
@NonNull
@Override
protected Flowable<List<SearchHistoryEntry>> getAll() {
return historyRecordManager.getSearchHistory();
}
@StringRes
@Override
int getEnabledConfigKey() {
return R.string.enable_search_history_key;
}
@Override
public void onHistoryItemClick(final SearchHistoryEntry historyItem) {
NavigationHelper.openSearch(getContext(), historyItem.getServiceId(),
historyItem.getSearch());
}
@Override
public void onHistoryItemLongClick(final SearchHistoryEntry item) {
if (activity == null) return;
new AlertDialog.Builder(activity)
.setTitle(item.getSearch())
.setMessage(R.string.delete_item_search_history)
.setCancelable(true)
.setNeutralButton(R.string.cancel, null)
.setPositiveButton(R.string.delete_one, (dialog, i) -> {
final Disposable onDelete = historyRecordManager
.deleteSearches(Collections.singleton(item))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> {/*successful*/},
error -> Log.e(TAG, "Search history Delete One failed:", error)
);
disposables.add(onDelete);
makeSnackbar(R.string.item_deleted);
})
.setNegativeButton(R.string.delete_all, (dialog, i) -> {
final Disposable onDeleteAll = historyRecordManager
.deleteSearchHistory(item.getSearch())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> {/*successful*/},
error -> Log.e(TAG, "Search history Delete All failed:", error)
);
disposables.add(onDeleteAll);
makeSnackbar(R.string.item_deleted);
})
.show();
}
private static class ViewHolder extends RecyclerView.ViewHolder {
private final TextView search;
private final TextView info;
public ViewHolder(View itemView) {
super(itemView);
search = itemView.findViewById(R.id.search);
info = itemView.findViewById(R.id.info);
}
}
protected class SearchHistoryAdapter extends HistoryEntryAdapter<SearchHistoryEntry, ViewHolder> {
SearchHistoryAdapter(Context context) {
super(context);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View rootView = inflater.inflate(R.layout.item_search_history, parent, false);
return new ViewHolder(rootView);
}
@Override
void onBindViewHolder(ViewHolder holder, SearchHistoryEntry entry, int position) {
holder.search.setText(entry.getSearch());
final String info = Localization.concatenateStrings(
getFormattedDate(entry.getCreationDate()),
NewPipe.getNameOfService(entry.getServiceId()));
holder.info.setText(info);
}
}
}

View File

@@ -1,171 +0,0 @@
package org.schabi.newpipe.history;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
public class WatchHistoryFragment extends HistoryFragment<StreamHistoryEntry> {
@NonNull
public static WatchHistoryFragment newInstance() {
return new WatchHistoryFragment();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@StringRes
@Override
int getEnabledConfigKey() {
return R.string.enable_watch_history_key;
}
@NonNull
@Override
protected StreamHistoryAdapter createAdapter() {
return new StreamHistoryAdapter(getContext());
}
@Override
protected Single<List<Long>> insert(Collection<StreamHistoryEntry> entries) {
return historyRecordManager.insertStreamHistory(entries);
}
@Override
protected Single<Integer> delete(Collection<StreamHistoryEntry> entries) {
return historyRecordManager.deleteStreamHistory(entries);
}
@NonNull
@Override
protected Flowable<List<StreamHistoryEntry>> getAll() {
return historyRecordManager.getStreamHistory();
}
@Override
public void onHistoryItemClick(StreamHistoryEntry historyItem) {
NavigationHelper.openVideoDetail(getContext(), historyItem.serviceId, historyItem.url,
historyItem.title);
}
@Override
public void onHistoryItemLongClick(StreamHistoryEntry item) {
new AlertDialog.Builder(activity)
.setTitle(item.title)
.setMessage(R.string.delete_stream_history_prompt)
.setCancelable(true)
.setNeutralButton(R.string.cancel, null)
.setPositiveButton(R.string.delete_one, (dialog, i) -> {
final Disposable onDelete = historyRecordManager
.deleteStreamHistory(Collections.singleton(item))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> {/*successful*/},
error -> Log.e(TAG, "Watch history Delete One failed:", error)
);
disposables.add(onDelete);
makeSnackbar(R.string.item_deleted);
})
.setNegativeButton(R.string.delete_all, (dialog, i) -> {
final Disposable onDeleteAll = historyRecordManager
.deleteStreamHistory(item.streamId)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> {/*successful*/},
error -> Log.e(TAG, "Watch history Delete All failed:", error)
);
disposables.add(onDeleteAll);
makeSnackbar(R.string.item_deleted);
})
.show();
}
private static class StreamHistoryAdapter extends HistoryEntryAdapter<StreamHistoryEntry, ViewHolder> {
StreamHistoryAdapter(Context context) {
super(context);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.list_stream_item, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onViewRecycled(ViewHolder holder) {
holder.itemView.setOnClickListener(null);
ImageLoader.getInstance()
.cancelDisplayTask(holder.thumbnailView);
}
@Override
void onBindViewHolder(ViewHolder holder, StreamHistoryEntry entry, int position) {
final String formattedDate = getFormattedDate(entry.accessDate);
final String info;
if (entry.repeatCount > 1) {
info = Localization.concatenateStrings(formattedDate,
getFormattedViewString(entry.repeatCount));
} else {
info = formattedDate;
}
holder.info.setText(info);
holder.streamTitle.setText(entry.title);
holder.uploader.setText(entry.uploader);
holder.duration.setText(Localization.getDurationString(entry.duration));
ImageLoader.getInstance().displayImage(entry.thumbnailUrl, holder.thumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
}
}
private static class ViewHolder extends RecyclerView.ViewHolder {
private final TextView info;
private final TextView streamTitle;
private final ImageView thumbnailView;
private final TextView uploader;
private final TextView duration;
public ViewHolder(View itemView) {
super(itemView);
thumbnailView = itemView.findViewById(R.id.itemThumbnailView);
info = itemView.findViewById(R.id.itemAdditionalDetails);
streamTitle = itemView.findViewById(R.id.itemVideoTitleView);
uploader = itemView.findViewById(R.id.itemUploaderView);
duration = itemView.findViewById(R.id.itemDurationView);
}
}
}

View File

@@ -17,6 +17,7 @@ import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder;
import org.schabi.newpipe.util.FallbackViewHolder;
import org.schabi.newpipe.util.OnClickGesture;
import java.util.ArrayList;
@@ -238,7 +239,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
return new PlaylistInfoItemHolder(infoItemBuilder, parent);
default:
Log.e(TAG, "Trollolo");
return null;
return new FallbackViewHolder(new View(parent.getContext()));
}
}

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local.bookmark;
package org.schabi.newpipe.local;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@@ -13,7 +13,6 @@ import android.view.View;
import org.schabi.newpipe.R;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.list.ListViewContract;
import org.schabi.newpipe.fragments.local.LocalItemListAdapter;
import static org.schabi.newpipe.util.AnimationUtils.animateView;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local;
package org.schabi.newpipe.local;
import android.support.v7.widget.RecyclerView;
import android.view.View;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local;
package org.schabi.newpipe.local;
import android.content.Context;
import android.widget.ImageView;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local;
package org.schabi.newpipe.local;
import android.app.Activity;
import android.support.v7.widget.RecyclerView;
@@ -7,11 +7,14 @@ import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.fragments.local.holder.LocalItemHolder;
import org.schabi.newpipe.fragments.local.holder.LocalPlaylistItemHolder;
import org.schabi.newpipe.fragments.local.holder.LocalPlaylistStreamItemHolder;
import org.schabi.newpipe.fragments.local.holder.LocalStatisticStreamItemHolder;
import org.schabi.newpipe.fragments.local.holder.RemotePlaylistItemHolder;
import org.schabi.newpipe.local.HeaderFooterHolder;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.holder.LocalItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistStreamItemHolder;
import org.schabi.newpipe.local.holder.LocalStatisticStreamItemHolder;
import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder;
import org.schabi.newpipe.util.FallbackViewHolder;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.OnClickGesture;
@@ -223,7 +226,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
return new LocalStatisticStreamItemHolder(localItemBuilder, parent);
default:
Log.e(TAG, "No view type has been considered for holder: [" + type + "]");
return null;
return new FallbackViewHolder(new View(parent.getContext()));
}
}

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local.bookmark;
package org.schabi.newpipe.local.bookmark;
import android.app.AlertDialog;
import android.os.Bundle;
@@ -19,8 +19,9 @@ import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.fragments.local.LocalPlaylistManager;
import org.schabi.newpipe.fragments.local.RemotePlaylistManager;
import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
@@ -38,9 +39,6 @@ import io.reactivex.disposables.CompositeDisposable;
public final class BookmarkFragment
extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
private View lastPlayedButton;
private View mostPlayedButton;
@State
protected Parcelable itemsListState;
@@ -56,7 +54,8 @@ public final class BookmarkFragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final AppDatabase database = NewPipeDatabase.getInstance(getContext());
if (activity == null) return;
final AppDatabase database = NewPipeDatabase.getInstance(activity);
localPlaylistManager = new LocalPlaylistManager(database);
remotePlaylistManager = new RemotePlaylistManager(database);
disposables = new CompositeDisposable();
@@ -79,7 +78,9 @@ public final class BookmarkFragment
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) setTitle(getString(R.string.tab_bookmarks));
if (activity != null && isVisibleToUser) {
setTitle(activity.getString(R.string.tab_bookmarks));
}
}
///////////////////////////////////////////////////////////////////////////
@@ -91,15 +92,6 @@ public final class BookmarkFragment
super.initViews(rootView, savedInstanceState);
}
@Override
protected View getListHeader() {
final View headerRootLayout = activity.getLayoutInflater()
.inflate(R.layout.bookmark_header, itemsList, false);
lastPlayedButton = headerRootLayout.findViewById(R.id.lastPlayed);
mostPlayedButton = headerRootLayout.findViewById(R.id.mostPlayed);
return headerRootLayout;
}
@Override
protected void initListeners() {
super.initListeners();
@@ -133,18 +125,6 @@ public final class BookmarkFragment
}
}
});
lastPlayedButton.setOnClickListener(view -> {
if (getParentFragment() != null) {
NavigationHelper.openLastPlayedFragment(getParentFragment().getFragmentManager());
}
});
mostPlayedButton.setOnClickListener(view -> {
if (getParentFragment() != null) {
NavigationHelper.openMostPlayedFragment(getParentFragment().getFragmentManager());
}
});
}
///////////////////////////////////////////////////////////////////////////
@@ -177,8 +157,6 @@ public final class BookmarkFragment
@Override
public void onDestroyView() {
super.onDestroyView();
if (mostPlayedButton != null) mostPlayedButton.setOnClickListener(null);
if (lastPlayedButton != null) lastPlayedButton.setOnClickListener(null);
if (disposables != null) disposables.clear();
if (databaseSubscription != null) databaseSubscription.cancel();

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local.dialog;
package org.schabi.newpipe.local.dialog;
import android.annotation.SuppressLint;
import android.os.Bundle;
@@ -18,9 +18,9 @@ import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.local.LocalItemListAdapter;
import org.schabi.newpipe.fragments.local.LocalPlaylistManager;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.local.LocalItemListAdapter;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.OnClickGesture;
import java.util.ArrayList;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local.dialog;
package org.schabi.newpipe.local.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -12,7 +12,7 @@ import android.widget.Toast;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.fragments.local.LocalPlaylistManager;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import java.util.List;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local.dialog;
package org.schabi.newpipe.local.dialog;
import android.os.Bundle;
import android.support.annotation.NonNull;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.list.feed;
package org.schabi.newpipe.local.feed;
import android.os.Bundle;
import android.os.Handler;
@@ -22,7 +22,7 @@ import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.subscription.SubscriptionService;
import org.schabi.newpipe.local.subscription.SubscriptionService;
import java.util.Collections;
import java.util.HashSet;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.history;
package org.schabi.newpipe.local.history;
import android.content.Context;
import android.content.res.Resources;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.history;
package org.schabi.newpipe.local.history;
import android.support.annotation.Nullable;

View File

@@ -1,4 +1,22 @@
package org.schabi.newpipe.history;
package org.schabi.newpipe.local.history;
/*
* Copyright (C) Mauricio Colli 2018
* HistoryRecordManager.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/>.
*/
import android.content.Context;
import android.content.SharedPreferences;
@@ -27,6 +45,7 @@ import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Scheduler;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
@@ -80,6 +99,11 @@ public class HistoryRecordManager {
.subscribeOn(Schedulers.io());
}
public Single<Integer> deleteWholeStreamHistory() {
return Single.fromCallable(() -> streamHistoryTable.deleteAll())
.subscribeOn(Schedulers.io());
}
public Flowable<List<StreamHistoryEntry>> getStreamHistory() {
return streamHistoryTable.getHistory().subscribeOn(Schedulers.io());
}
@@ -114,20 +138,6 @@ public class HistoryRecordManager {
// Search History
///////////////////////////////////////////////////////
public Single<List<Long>> insertSearches(final Collection<SearchHistoryEntry> entries) {
return Single.fromCallable(() -> searchHistoryTable.insertAll(entries))
.subscribeOn(Schedulers.io());
}
public Single<Integer> deleteSearches(final Collection<SearchHistoryEntry> entries) {
return Single.fromCallable(() -> searchHistoryTable.delete(entries))
.subscribeOn(Schedulers.io());
}
public Flowable<List<SearchHistoryEntry>> getSearchHistory() {
return searchHistoryTable.getAll();
}
public Maybe<Long> onSearched(final int serviceId, final String search) {
if (!isSearchHistoryEnabled()) return Maybe.empty();
@@ -150,6 +160,11 @@ public class HistoryRecordManager {
.subscribeOn(Schedulers.io());
}
public Single<Integer> deleteWholeSearchHistory() {
return Single.fromCallable(() -> searchHistoryTable.deleteAll())
.subscribeOn(Schedulers.io());
}
public Flowable<List<SearchHistoryEntry>> getRelatedSearches(final String query,
final int similarQueryLimit,
final int uniqueQueryLimit) {

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local.bookmark;
package org.schabi.newpipe.local.history;
import android.app.Activity;
import android.content.Context;
@@ -7,9 +7,13 @@ import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
@@ -17,13 +21,14 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.history.HistoryRecordManager;
import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
import java.util.Collections;
@@ -31,13 +36,19 @@ import java.util.List;
import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
public abstract class StatisticsPlaylistFragment
public class StatisticsPlaylistFragment
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {
private View headerPlayAllButton;
private View headerPopupButton;
private View headerBackgroundButton;
private View playlistCtrl;
private View sortButton;
private ImageView sortButtonIcon;
private TextView sortButtonText;
@State
protected Parcelable itemsListState;
@@ -45,14 +56,28 @@ public abstract class StatisticsPlaylistFragment
/* Used for independent events */
private Subscription databaseSubscription;
private HistoryRecordManager recordManager;
private CompositeDisposable disposables = new CompositeDisposable();
///////////////////////////////////////////////////////////////////////////
// Abstracts
///////////////////////////////////////////////////////////////////////////
private enum StatisticSortMode {
LAST_PLAYED,
MOST_PLAYED,
}
protected abstract String getName();
StatisticSortMode sortMode = StatisticSortMode.LAST_PLAYED;
protected abstract List<StreamStatisticsEntry> processResult(final List<StreamStatisticsEntry> results);
protected List<StreamStatisticsEntry> processResult(final List<StreamStatisticsEntry> results) {
switch (sortMode) {
case LAST_PLAYED:
Collections.sort(results, (left, right) ->
right.latestAccessDate.compareTo(left.latestAccessDate));
return results;
case MOST_PLAYED:
Collections.sort(results, (left, right) ->
((Long) right.watchCount).compareTo(left.watchCount));
return results;
default: return null;
}
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Creation
@@ -78,16 +103,20 @@ public abstract class StatisticsPlaylistFragment
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
setTitle(getName());
setTitle(getString(R.string.title_last_played));
}
@Override
protected View getListHeader() {
final View headerRootLayout = activity.getLayoutInflater().inflate(R.layout.playlist_control,
final View headerRootLayout = activity.getLayoutInflater().inflate(R.layout.statistic_playlist_control,
itemsList, false);
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);
sortButton = headerRootLayout.findViewById(R.id.sortButton);
sortButtonIcon = headerRootLayout.findViewById(R.id.sortButtonIcon);
sortButtonText = headerRootLayout.findViewById(R.id.sortButtonText);
return headerRootLayout;
}
@@ -193,6 +222,8 @@ public abstract class StatisticsPlaylistFragment
super.handleResult(result);
if (itemListAdapter == null) return;
playlistCtrl.setVisibility(View.VISIBLE);
itemListAdapter.clearStreamItemList();
if (result.isEmpty()) {
@@ -212,6 +243,7 @@ public abstract class StatisticsPlaylistFragment
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
headerBackgroundButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
sortButton.setOnClickListener(view -> toggleSortMode());
hideLoading();
}
@@ -238,6 +270,21 @@ public abstract class StatisticsPlaylistFragment
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void toggleSortMode() {
if(sortMode == StatisticSortMode.LAST_PLAYED) {
sortMode = StatisticSortMode.MOST_PLAYED;
setTitle(getString(R.string.title_most_played));
sortButtonIcon.setImageResource(ThemeHelper.getIconByAttr(R.attr.history, getContext()));
sortButtonText.setText(R.string.title_last_played);
} else {
sortMode = StatisticSortMode.LAST_PLAYED;
setTitle(getString(R.string.title_last_played));
sortButtonIcon.setImageResource(ThemeHelper.getIconByAttr(R.attr.filter, getContext()));
sortButtonText.setText(R.string.title_most_played);
}
startLoading(true);
}
private void showStreamDialog(final StreamStatisticsEntry item) {
final Context context = getContext();
final Activity activity = getActivity();
@@ -250,6 +297,7 @@ public abstract class StatisticsPlaylistFragment
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),
context.getResources().getString(R.string.delete),
};
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
@@ -270,6 +318,9 @@ public abstract class StatisticsPlaylistFragment
case 4:
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
break;
case 5:
deleteEntry(index);
break;
default:
break;
}
@@ -278,6 +329,32 @@ public abstract class StatisticsPlaylistFragment
new InfoItemDialog(getActivity(), infoItem, commands, actions).show();
}
private void deleteEntry(final int index) {
final LocalItem infoItem = itemListAdapter.getItemsList()
.get(index);
if(infoItem instanceof StreamStatisticsEntry) {
final StreamStatisticsEntry entry = (StreamStatisticsEntry) infoItem;
final Disposable onDelete = recordManager.deleteStreamHistory(entry.streamId)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDelted -> {
if(getView() != null) {
Snackbar.make(getView(), R.string.one_item_deleted,
Snackbar.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(),
R.string.one_item_deleted,
Toast.LENGTH_SHORT).show();
}
},
throwable -> showSnackBarError(throwable,
UserAction.DELETE_FROM_HISTORY, "none",
"Deleting item failed", R.string.general_error));
disposables.add(onDelete);
}
}
private PlayQueue getPlayQueue() {
return getPlayQueue(0);
}

View File

@@ -1,11 +1,11 @@
package org.schabi.newpipe.fragments.local.holder;
package org.schabi.newpipe.local.holder;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.fragments.local.LocalItemBuilder;
import org.schabi.newpipe.local.LocalItemBuilder;
import java.text.DateFormat;

View File

@@ -1,11 +1,11 @@
package org.schabi.newpipe.fragments.local.holder;
package org.schabi.newpipe.local.holder;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.fragments.local.LocalItemBuilder;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.util.ImageDisplayConstants;
import java.text.DateFormat;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local.holder;
package org.schabi.newpipe.local.holder;
import android.support.v4.content.ContextCompat;
import android.view.MotionEvent;
@@ -11,7 +11,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.fragments.local.LocalItemBuilder;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local.holder;
package org.schabi.newpipe.local.holder;
import android.support.v4.content.ContextCompat;
import android.view.View;
@@ -10,7 +10,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.fragments.local.LocalItemBuilder;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local.holder;
package org.schabi.newpipe.local.holder;
import android.view.ViewGroup;
import android.widget.ImageView;
@@ -6,7 +6,7 @@ import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.fragments.local.LocalItemBuilder;
import org.schabi.newpipe.local.LocalItemBuilder;
import java.text.DateFormat;

View File

@@ -1,11 +1,11 @@
package org.schabi.newpipe.fragments.local.holder;
package org.schabi.newpipe.local.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.fragments.local.LocalItemBuilder;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local.bookmark;
package org.schabi.newpipe.local.playlist;
import android.app.Activity;
import android.content.Context;
@@ -26,10 +26,10 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.local.LocalPlaylistManager;
import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
@@ -173,7 +173,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
@Override
public void held(LocalItem selectedItem) {
if (selectedItem instanceof PlaylistStreamEntry) {
showStreamDialog((PlaylistStreamEntry) selectedItem);
showStreamItemDialog((PlaylistStreamEntry) selectedItem);
}
}
@@ -506,7 +506,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected void showStreamDialog(final PlaylistStreamEntry item) {
protected void showStreamItemDialog(final PlaylistStreamEntry item) {
final Context context = getContext();
final Activity activity = getActivity();
if (context == null || context.getResources() == null || getActivity() == null) return;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local;
package org.schabi.newpipe.local.playlist;
import android.support.annotation.Nullable;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.local;
package org.schabi.newpipe.local.playlist;
import org.schabi.newpipe.database.AppDatabase;
import org.schabi.newpipe.database.playlist.dao.PlaylistRemoteDAO;
@@ -13,11 +13,9 @@ import io.reactivex.schedulers.Schedulers;
public class RemotePlaylistManager {
private final AppDatabase database;
private final PlaylistRemoteDAO playlistRemoteTable;
public RemotePlaylistManager(final AppDatabase db) {
database = db;
playlistRemoteTable = db.playlistRemoteDAO();
}

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.subscription;
package org.schabi.newpipe.local.subscription;
import android.app.AlertDialog;
import android.app.Dialog;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.subscription;
package org.schabi.newpipe.local.subscription;
public interface ImportExportEventListener {
/**

View File

@@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.schabi.newpipe.subscription;
package org.schabi.newpipe.local.subscription;
import android.support.annotation.Nullable;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.subscription;
package org.schabi.newpipe.local.subscription;
import android.app.Activity;
import android.content.BroadcastReceiver;
@@ -39,9 +39,8 @@ import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.subscription.SubscriptionService;
import org.schabi.newpipe.subscription.services.SubscriptionsExportService;
import org.schabi.newpipe.subscription.services.SubscriptionsImportService;
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService;
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
@@ -53,7 +52,6 @@ import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -65,9 +63,9 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.subscription.services.SubscriptionsImportService.KEY_MODE;
import static org.schabi.newpipe.subscription.services.SubscriptionsImportService.KEY_VALUE;
import static org.schabi.newpipe.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE;
import static org.schabi.newpipe.util.AnimationUtils.animateRotation;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -104,8 +102,8 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
setTitle(getString(R.string.tab_subscriptions));
if (activity != null && isVisibleToUser) {
setTitle(activity.getString(R.string.tab_subscriptions));
}
}

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.subscription;
package org.schabi.newpipe.local.subscription;
import android.content.Context;
import android.support.annotation.NonNull;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.fragments.subscription;
package org.schabi.newpipe.local.subscription;
import android.app.Activity;
import android.content.Intent;
@@ -26,7 +26,7 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.subscription.services.SubscriptionsImportService;
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.ServiceHelper;
@@ -37,10 +37,10 @@ import java.util.List;
import icepick.State;
import static org.schabi.newpipe.extractor.subscription.SubscriptionExtractor.ContentSource.CHANNEL_URL;
import static org.schabi.newpipe.subscription.services.SubscriptionsImportService.CHANNEL_URL_MODE;
import static org.schabi.newpipe.subscription.services.SubscriptionsImportService.INPUT_STREAM_MODE;
import static org.schabi.newpipe.subscription.services.SubscriptionsImportService.KEY_MODE;
import static org.schabi.newpipe.subscription.services.SubscriptionsImportService.KEY_VALUE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.CHANNEL_URL_MODE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.INPUT_STREAM_MODE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE;
public class SubscriptionsImportFragment extends BaseFragment {
private static final int REQUEST_IMPORT_FILE_CODE = 666;

View File

@@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.schabi.newpipe.subscription.services;
package org.schabi.newpipe.local.subscription.services;
import android.app.Service;
import android.content.Intent;
@@ -36,8 +36,8 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.subscription.ImportExportEventListener;
import org.schabi.newpipe.subscription.SubscriptionService;
import org.schabi.newpipe.local.subscription.ImportExportEventListener;
import org.schabi.newpipe.local.subscription.SubscriptionService;
import java.io.FileNotFoundException;
import java.io.IOException;

View File

@@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.schabi.newpipe.subscription.services;
package org.schabi.newpipe.local.subscription.services;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
@@ -29,7 +29,7 @@ import org.reactivestreams.Subscription;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.extractor.subscription.SubscriptionItem;
import org.schabi.newpipe.subscription.ImportExportJsonHelper;
import org.schabi.newpipe.local.subscription.ImportExportJsonHelper;
import java.io.File;
import java.io.FileNotFoundException;
@@ -49,7 +49,7 @@ public class SubscriptionsExportService extends BaseImportExportService {
/**
* A {@link LocalBroadcastManager local broadcast} will be made with this action when the export is successfully completed.
*/
public static final String EXPORT_COMPLETE_ACTION = "org.schabi.newpipe.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE";
public static final String EXPORT_COMPLETE_ACTION = "org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE";
private Subscription subscription;
private File outFile;

View File

@@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.schabi.newpipe.subscription.services;
package org.schabi.newpipe.local.subscription.services;
import android.content.Intent;
import android.support.annotation.NonNull;
@@ -33,7 +33,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.subscription.SubscriptionItem;
import org.schabi.newpipe.subscription.ImportExportJsonHelper;
import org.schabi.newpipe.local.subscription.ImportExportJsonHelper;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
@@ -64,7 +64,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
/**
* A {@link LocalBroadcastManager local broadcast} will be made with this action when the import is successfully completed.
*/
public static final String IMPORT_COMPLETE_ACTION = "org.schabi.newpipe.subscription.services.SubscriptionsImportService.IMPORT_COMPLETE";
public static final String IMPORT_COMPLETE_ACTION = "org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.IMPORT_COMPLETE";
private Subscription subscription;
private int currentMode;

View File

@@ -48,7 +48,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -118,8 +118,12 @@ public final class BackgroundPlayer extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent +
"], flags = [" + flags + "], startId = [" + startId + "]");
basePlayerImpl.handleIntent(intent);
if (basePlayerImpl.mediaSessionManager != null) {
basePlayerImpl.mediaSessionManager.handleMediaButtonIntent(intent);
}
return START_NOT_STICKY;
}
@@ -160,6 +164,11 @@ public final class BackgroundPlayer extends Service {
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
shouldUpdateOnProgress = on;
basePlayerImpl.triggerProgressUpdate();
if (on) {
basePlayerImpl.startProgressLoop();
} else {
basePlayerImpl.stopProgressLoop();
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -545,7 +554,6 @@ public final class BackgroundPlayer extends Service {
super.onPaused();
updateNotification(R.drawable.ic_play_arrow_white);
if (isProgressLoopRunning()) stopProgressLoop();
lockManager.releaseWifiAndCpu();
}

View File

@@ -46,7 +46,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.Util;
@@ -58,19 +58,20 @@ import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.history.HistoryRecordManager;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.helper.AudioReactor;
import org.schabi.newpipe.player.helper.LoadController;
import org.schabi.newpipe.player.helper.MediaSessionManager;
import org.schabi.newpipe.player.helper.PlayerDataSource;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.mediasource.FailedMediaSource;
import org.schabi.newpipe.player.playback.BasePlayerMediaSession;
import org.schabi.newpipe.player.playback.CustomTrackSelector;
import org.schabi.newpipe.player.playback.MediaSourceManager;
import org.schabi.newpipe.player.playback.PlaybackListener;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueAdapter;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.SerializedCache;
import java.io.IOException;
@@ -124,7 +125,6 @@ public abstract class BasePlayer implements
//////////////////////////////////////////////////////////////////////////*/
protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f};
protected static final float[] PLAYBACK_PITCHES = {0.8f, 0.9f, 0.95f, 1f, 1.05f, 1.1f, 1.2f};
protected PlayQueue playQueue;
protected PlayQueueAdapter playQueueAdapter;
@@ -140,10 +140,10 @@ public abstract class BasePlayer implements
// Player
//////////////////////////////////////////////////////////////////////////*/
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 final static int FAST_FORWARD_REWIND_AMOUNT_MILLIS = 10000; // 10 Seconds
protected final static int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds
protected final static int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
protected final static int RECOVERY_SKIP_THRESHOLD_MILLIS = 3000; // 3 seconds
protected CustomTrackSelector trackSelector;
protected PlayerDataSource dataSource;
@@ -177,11 +177,11 @@ public abstract class BasePlayer implements
}
public void setup() {
if (simpleExoPlayer == null) initPlayer();
if (simpleExoPlayer == null) initPlayer(/*playOnInit=*/true);
initListeners();
}
public void initPlayer() {
public void initPlayer(final boolean playOnReady) {
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
@@ -191,15 +191,15 @@ public abstract class BasePlayer implements
final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter);
final AdaptiveTrackSelection.Factory trackSelectionFactory =
new AdaptiveTrackSelection.Factory(bandwidthMeter);
final TrackSelection.Factory trackSelectionFactory =
PlayerHelper.getQualitySelector(context, bandwidthMeter);
trackSelector = new CustomTrackSelector(trackSelectionFactory);
final LoadControl loadControl = new LoadController(context);
final RenderersFactory renderFactory = new DefaultRenderersFactory(context);
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderFactory, trackSelector, loadControl);
simpleExoPlayer.addListener(this);
simpleExoPlayer.setPlayWhenReady(true);
simpleExoPlayer.setPlayWhenReady(playOnReady);
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
audioReactor = new AudioReactor(context, simpleExoPlayer);
@@ -237,15 +237,16 @@ public abstract class BasePlayer implements
final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch());
// Good to go...
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch);
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, /*playOnInit=*/true);
}
protected void initPlayback(@NonNull final PlayQueue queue,
@Player.RepeatMode final int repeatMode,
final float playbackSpeed,
final float playbackPitch) {
final float playbackPitch,
final boolean playOnReady) {
destroyPlayer();
initPlayer();
initPlayer(playOnReady);
setRepeatMode(repeatMode);
setPlaybackParameters(playbackSpeed, playbackPitch);
@@ -518,15 +519,16 @@ public abstract class BasePlayer implements
}
public void triggerProgressUpdate() {
if (simpleExoPlayer == null) return;
onUpdateProgress(
(int) simpleExoPlayer.getCurrentPosition(),
Math.max((int) simpleExoPlayer.getCurrentPosition(), 0),
(int) simpleExoPlayer.getDuration(),
simpleExoPlayer.getBufferedPercentage()
);
}
private Disposable getProgressReactor() {
return Observable.interval(PROGRESS_LOOP_INTERVAL, TimeUnit.MILLISECONDS)
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> triggerProgressUpdate());
}
@@ -553,8 +555,8 @@ public abstract class BasePlayer implements
// Ensure dynamic/livestream timeline changes does not cause negative position
if (isPlaylistStable && !isCurrentWindowValid() && !isSynchronizing) {
if (DEBUG) Log.d(TAG, "Playback - negative time position reached, " +
"clamping position to 0ms.");
seekTo(/*clampToTime=*/0);
"clamping to default position.");
seekToDefault();
}
break;
}
@@ -640,12 +642,12 @@ public abstract class BasePlayer implements
seekTo(recoveryPositionMillis);
playQueue.unsetRecovery(currentSourceIndex);
} else if (isSynchronizing && simpleExoPlayer.isCurrentWindowDynamic()) {
} else if (isSynchronizing && isLive()) {
if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time");
// Is still synchronizing?
seekToDefault();
} else if (isSynchronizing && presetStartPositionMillis != 0L) {
} else if (isSynchronizing && presetStartPositionMillis > 0L) {
if (DEBUG) Log.d(TAG, "Playback - Seeking to preset start " +
"position=[" + presetStartPositionMillis + "]");
// Has another start position?
@@ -700,41 +702,23 @@ public abstract class BasePlayer implements
}
}
/**
* Processes {@link ExoPlaybackException} tagged with {@link ExoPlaybackException#TYPE_SOURCE}.
* <br><br>
* If the current {@link com.google.android.exoplayer2.Timeline.Window window} is valid,
* then we know the error is produced by transitioning into a bad window, therefore we report
* an error to the play queue based on if the current error can be skipped.
* <br><br>
* This is done because ExoPlayer reports the source exceptions before window is
* 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.
* <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>
* In the event of livestreaming being lagged behind for any reason, most notably pausing for
* too long, a {@link BehindLiveWindowException} will be produced. This will trigger a reload
* instead of skipping or removal.
* */
private void processSourceError(final IOException error) {
if (simpleExoPlayer == null || playQueue == null) return;
if (simpleExoPlayer.getCurrentPosition() <
simpleExoPlayer.getDuration() - RECOVERY_SKIP_THRESHOLD) {
setRecovery();
}
setRecovery();
final Throwable cause = error.getCause();
if (cause instanceof BehindLiveWindowException) {
reload();
} else if (cause instanceof UnknownHostException) {
playQueue.error(/*isNetworkProblem=*/true);
} else if (isCurrentWindowValid()) {
playQueue.error(/*isTransitioningToBadStream=*/true);
} else if (cause instanceof FailedMediaSource.MediaSourceResolutionException) {
playQueue.error(/*recoverableWithNoAvailableStream=*/false);
} else if (cause instanceof FailedMediaSource.StreamInfoLoadException) {
playQueue.error(/*recoverableIfLoadFailsWhenNetworkIsFine=*/false);
} else {
playQueue.error(isCurrentWindowValid());
playQueue.error(/*noIdeaWhatHappenedAndLetUserChooseWhatToDo=*/true);
}
}
@@ -787,9 +771,10 @@ public abstract class BasePlayer implements
//////////////////////////////////////////////////////////////////////////*/
@Override
public boolean isNearPlaybackEdge(final long timeToEndMillis) {
public boolean isApproachingPlaybackEdge(final long timeToEndMillis) {
// If live, then not near playback edge
if (simpleExoPlayer == null || simpleExoPlayer.isCurrentWindowDynamic()) return false;
// If not playing, then not approaching playback edge
if (simpleExoPlayer == null || isLive() || !isPlaying()) return false;
final long currentPositionMillis = simpleExoPlayer.getCurrentPosition();
final long currentDurationMillis = simpleExoPlayer.getDuration();
@@ -985,22 +970,22 @@ public abstract class BasePlayer implements
public void onFastRewind() {
if (DEBUG) Log.d(TAG, "onFastRewind() called");
seekBy(-FAST_FORWARD_REWIND_AMOUNT);
seekBy(-FAST_FORWARD_REWIND_AMOUNT_MILLIS);
}
public void onFastForward() {
if (DEBUG) Log.d(TAG, "onFastForward() called");
seekBy(FAST_FORWARD_REWIND_AMOUNT);
seekBy(FAST_FORWARD_REWIND_AMOUNT_MILLIS);
}
public void onPlayPrevious() {
if (simpleExoPlayer == null || playQueue == null) return;
if (DEBUG) Log.d(TAG, "onPlayPrevious() called");
/* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT milliseconds,
/* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT_MILLIS milliseconds,
* restart current track. Also restart the track if the current track
* is the first in a queue.*/
if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT ||
if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT_MILLIS ||
playQueue.getIndex() == 0) {
seekToDefault();
playQueue.offsetIndex(0);
@@ -1050,7 +1035,9 @@ public abstract class BasePlayer implements
}
public void seekToDefault() {
if (simpleExoPlayer != null) simpleExoPlayer.seekToDefaultPosition();
if (simpleExoPlayer != null) {
simpleExoPlayer.seekToDefaultPosition();
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -1091,9 +1078,9 @@ public abstract class BasePlayer implements
private void savePlaybackState() {
if (simpleExoPlayer == null || currentInfo == null) return;
if (simpleExoPlayer.getCurrentPosition() > RECOVERY_SKIP_THRESHOLD &&
if (simpleExoPlayer.getCurrentPosition() > RECOVERY_SKIP_THRESHOLD_MILLIS &&
simpleExoPlayer.getCurrentPosition() <
simpleExoPlayer.getDuration() - RECOVERY_SKIP_THRESHOLD) {
simpleExoPlayer.getDuration() - RECOVERY_SKIP_THRESHOLD_MILLIS) {
savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
}
}
@@ -1127,9 +1114,7 @@ public abstract class BasePlayer implements
/** Checks if the current playback is a livestream AND is playing at or beyond the live edge */
public boolean isLiveEdge() {
if (simpleExoPlayer == null) return false;
final boolean isLive = simpleExoPlayer.isCurrentWindowDynamic();
if (!isLive) return false;
if (simpleExoPlayer == null || !isLive()) return false;
final Timeline currentTimeline = simpleExoPlayer.getCurrentTimeline();
final int currentWindowIndex = simpleExoPlayer.getCurrentWindowIndex();
@@ -1143,6 +1128,16 @@ public abstract class BasePlayer implements
return timelineWindow.getDefaultPositionMs() <= simpleExoPlayer.getCurrentPosition();
}
public boolean isLive() {
if (simpleExoPlayer == null) return false;
try {
return simpleExoPlayer.isCurrentWindowDynamic();
} catch (@NonNull IndexOutOfBoundsException ignored) {
// Why would this even happen =(
return false;
}
}
public boolean isPlaying() {
final int state = simpleExoPlayer.getPlaybackState();
return (state == Player.STATE_READY || state == Player.STATE_BUFFERING)
@@ -1170,10 +1165,6 @@ public abstract class BasePlayer implements
setPlaybackParameters(speed, getPlaybackPitch());
}
public void setPlaybackPitch(float pitch) {
setPlaybackParameters(getPlaybackSpeed(), pitch);
}
public PlaybackParameters getPlaybackParameters() {
final PlaybackParameters defaultParameters = new PlaybackParameters(1f, 1f);
if (simpleExoPlayer == null) return defaultParameters;

View File

@@ -25,13 +25,16 @@ import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
@@ -50,6 +53,7 @@ import android.widget.TextView;
import android.widget.Toast;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.SubtitleView;
@@ -59,11 +63,10 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
import org.schabi.newpipe.playlist.PlayQueueItemHolder;
import org.schabi.newpipe.playlist.PlayQueueItemTouchCallback;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
@@ -95,12 +98,12 @@ public final class MainVideoPlayer extends AppCompatActivity
private GestureDetector gestureDetector;
private boolean activityPaused;
private VideoPlayerImpl playerImpl;
private SharedPreferences defaultPreferences;
@Nullable private StateSaver.SavedState savedState;
@Nullable private PlayerState playerState;
private boolean isInMultiWindow;
/*//////////////////////////////////////////////////////////////////////////
// Activity LifeCycle
@@ -112,6 +115,7 @@ public final class MainVideoPlayer extends AppCompatActivity
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(this);
ThemeHelper.setTheme(this);
getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) getWindow().setStatusBarColor(Color.BLACK);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
@@ -135,8 +139,9 @@ public final class MainVideoPlayer extends AppCompatActivity
@Override
protected void onRestoreInstanceState(@NonNull Bundle bundle) {
if (DEBUG) Log.d(TAG, "onRestoreInstanceState() called");
super.onRestoreInstanceState(bundle);
savedState = StateSaver.tryToRestore(bundle, this);
StateSaver.tryToRestore(bundle, this);
}
@Override
@@ -148,26 +153,28 @@ public final class MainVideoPlayer extends AppCompatActivity
@Override
protected void onResume() {
super.onResume();
if (DEBUG) Log.d(TAG, "onResume() called");
if (playerImpl.getPlayer() != null && activityPaused && playerImpl.wasPlaying()
&& !playerImpl.isPlaying()) {
playerImpl.onPlay();
}
activityPaused = false;
super.onResume();
if(globalScreenOrientationLocked()) {
boolean lastOrientationWasLandscape
= defaultPreferences.getBoolean(getString(R.string.last_orientation_landscape_key), false);
if (globalScreenOrientationLocked()) {
boolean lastOrientationWasLandscape = defaultPreferences.getBoolean(
getString(R.string.last_orientation_landscape_key), false);
setLandscape(lastOrientationWasLandscape);
}
}
@Override
public void onBackPressed() {
if (DEBUG) Log.d(TAG, "onBackPressed() called");
super.onBackPressed();
if (playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(false);
// Upon going in or out of multiwindow mode, isInMultiWindow will always be false,
// since the first onResume needs to restore the player.
// Subsequent onResume calls while multiwindow mode remains the same and the player is
// prepared should be ignored.
if (isInMultiWindow) return;
isInMultiWindow = isInMultiWindow();
if (playerState != null) {
playerImpl.setPlaybackQuality(playerState.getPlaybackQuality());
playerImpl.initPlayback(playerState.getPlayQueue(), playerState.getRepeatMode(),
playerState.getPlaybackSpeed(), playerState.getPlaybackPitch(),
playerState.wasPlaying());
}
}
@Override
@@ -180,33 +187,24 @@ public final class MainVideoPlayer extends AppCompatActivity
}
}
@Override
protected void onPause() {
super.onPause();
if (DEBUG) Log.d(TAG, "onPause() called");
if (playerImpl != null && playerImpl.getPlayer() != null && !activityPaused) {
playerImpl.wasPlaying = playerImpl.isPlaying();
playerImpl.onPause();
}
activityPaused = true;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
if (DEBUG) Log.d(TAG, "onSaveInstanceState() called");
super.onSaveInstanceState(outState);
if (playerImpl == null) return;
playerImpl.setRecovery();
savedState = StateSaver.tryToSave(isChangingConfigurations(), savedState,
outState, this);
playerState = new PlayerState(playerImpl.getPlayQueue(), playerImpl.getRepeatMode(),
playerImpl.getPlaybackSpeed(), playerImpl.getPlaybackPitch(),
playerImpl.getPlaybackQuality(), playerImpl.isPlaying());
StateSaver.tryToSave(isChangingConfigurations(), null, outState, this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (DEBUG) Log.d(TAG, "onDestroy() called");
if (playerImpl != null) playerImpl.destroy();
protected void onStop() {
if (DEBUG) Log.d(TAG, "onStop() called");
super.onStop();
playerImpl.destroy();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -221,48 +219,19 @@ public final class MainVideoPlayer extends AppCompatActivity
@Override
public void writeTo(Queue<Object> objectsToSave) {
if (objectsToSave == null) return;
objectsToSave.add(playerImpl.getPlayQueue());
objectsToSave.add(playerImpl.getRepeatMode());
objectsToSave.add(playerImpl.getPlaybackSpeed());
objectsToSave.add(playerImpl.getPlaybackPitch());
objectsToSave.add(playerImpl.getPlaybackQuality());
objectsToSave.add(playerState);
}
@Override
@SuppressWarnings("unchecked")
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
@NonNull final PlayQueue queue = (PlayQueue) savedObjects.poll();
final int repeatMode = (int) savedObjects.poll();
final float playbackSpeed = (float) savedObjects.poll();
final float playbackPitch = (float) savedObjects.poll();
@NonNull final String playbackQuality = (String) savedObjects.poll();
playerImpl.setPlaybackQuality(playbackQuality);
playerImpl.initPlayback(queue, repeatMode, playbackSpeed, playbackPitch);
StateSaver.onDestroy(savedState);
public void readFrom(@NonNull Queue<Object> savedObjects) {
playerState = (PlayerState) savedObjects.poll();
}
/*//////////////////////////////////////////////////////////////////////////
// View
//////////////////////////////////////////////////////////////////////////*/
/**
* Prior to Kitkat, hiding system ui causes the player view to be overlaid and require two
* clicks to get rid of that invisible overlay. By showing the system UI on actions/events,
* that overlay is removed and the player view is put to the foreground.
*
* Post Kitkat, navbar and status bar can be pulled out by swiping the edge of
* screen, therefore, we can do nothing or hide the UI on actions/events.
* */
private void changeSystemUi() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
showSystemUi();
} else {
hideSystemUi();
}
}
private void showSystemUi() {
if (DEBUG) Log.d(TAG, "showSystemUi() called");
if (playerImpl != null && playerImpl.queueVisible) return;
@@ -275,6 +244,14 @@ public final class MainVideoPlayer extends AppCompatActivity
} else {
visibility = View.STATUS_BAR_VISIBLE;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@ColorInt final int systenUiColor =
ActivityCompat.getColor(getApplicationContext(), R.color.video_overlay_color);
getWindow().setStatusBarColor(systenUiColor);
getWindow().setNavigationBarColor(systenUiColor);
}
getWindow().getDecorView().setSystemUiVisibility(visibility);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
@@ -342,6 +319,10 @@ public final class MainVideoPlayer extends AppCompatActivity
}
}
private boolean isInMultiWindow() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInMultiWindowMode();
}
////////////////////////////////////////////////////////////////////////////
// Playback Parameters Listener
////////////////////////////////////////////////////////////////////////////
@@ -411,15 +392,6 @@ public final class MainVideoPlayer extends AppCompatActivity
this.itemsListCloseButton = findViewById(R.id.playQueueClose);
this.itemsList = findViewById(R.id.playQueue);
this.windowRootLayout = rootView.findViewById(R.id.playbackWindowRoot);
// Prior to Kitkat, there is no way of setting translucent navbar programmatically.
// Thus, fit system windows is opted instead.
// See https://stackoverflow.com/questions/29069070/completely-transparent-status-bar-and-navigation-bar-on-lollipop
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
windowRootLayout.setFitsSystemWindows(false);
windowRootLayout.invalidate();
}
titleTextView.setSelected(true);
channelTextView.setSelected(true);
@@ -428,20 +400,15 @@ public final class MainVideoPlayer extends AppCompatActivity
@Override
protected void setupSubtitleView(@NonNull SubtitleView view,
@NonNull String captionSizeKey) {
final float captionRatioInverse;
if (captionSizeKey.equals(getString(R.string.smaller_caption_size_key))) {
captionRatioInverse = 22f;
} else if (captionSizeKey.equals(getString(R.string.larger_caption_size_key))) {
captionRatioInverse = 18f;
} else {
captionRatioInverse = 20f;
}
final float captionScale,
@NonNull final CaptionStyleCompat captionStyle) {
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels);
final float captionRatioInverse = 20f + 4f * (1f - captionScale);
view.setFixedTextSize(TypedValue.COMPLEX_UNIT_PX,
(float) minimumLength / captionRatioInverse);
view.setApplyEmbeddedStyles(captionStyle.equals(CaptionStyleCompat.DEFAULT));
view.setStyle(captionStyle);
}
@Override
@@ -727,7 +694,7 @@ public final class MainVideoPlayer extends AppCompatActivity
animatePlayButtons(true, 200);
});
changeSystemUi();
showSystemUi();
getRootView().setKeepScreenOn(false);
}
@@ -882,10 +849,12 @@ public final class MainVideoPlayer extends AppCompatActivity
if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
if (!playerImpl.isPlaying()) return false;
if (e.getX() > playerImpl.getRootView().getWidth() / 2) {
if (e.getX() > playerImpl.getRootView().getWidth() * 2 / 3) {
playerImpl.onFastForward();
} else {
} else if (e.getX() < playerImpl.getRootView().getWidth() / 3) {
playerImpl.onFastRewind();
} else {
playerImpl.getPlayPauseButton().performClick();
}
return true;
@@ -900,7 +869,7 @@ public final class MainVideoPlayer extends AppCompatActivity
playerImpl.hideControls(150, 0);
} else {
playerImpl.showControlsThenHide();
changeSystemUi();
showSystemUi();
}
return true;
}

View File

@@ -0,0 +1,68 @@
package org.schabi.newpipe.player;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import java.io.Serializable;
public class PlayerState implements Serializable {
@NonNull private final PlayQueue playQueue;
private final int repeatMode;
private final float playbackSpeed;
private final float playbackPitch;
@Nullable private final String playbackQuality;
private final boolean wasPlaying;
PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode,
final float playbackSpeed, final float playbackPitch, final boolean wasPlaying) {
this(playQueue, repeatMode, playbackSpeed, playbackPitch, null, wasPlaying);
}
PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode,
final float playbackSpeed, final float playbackPitch,
@Nullable final String playbackQuality, final boolean wasPlaying) {
this.playQueue = playQueue;
this.repeatMode = repeatMode;
this.playbackSpeed = playbackSpeed;
this.playbackPitch = playbackPitch;
this.playbackQuality = playbackQuality;
this.wasPlaying = wasPlaying;
}
/*//////////////////////////////////////////////////////////////////////////
// Serdes
//////////////////////////////////////////////////////////////////////////*/
/*//////////////////////////////////////////////////////////////////////////
// Getters
//////////////////////////////////////////////////////////////////////////*/
@NonNull
public PlayQueue getPlayQueue() {
return playQueue;
}
public int getRepeatMode() {
return repeatMode;
}
public float getPlaybackSpeed() {
return playbackSpeed;
}
public float getPlaybackPitch() {
return playbackPitch;
}
@Nullable
public String getPlaybackQuality() {
return playbackQuality;
}
public boolean wasPlaying() {
return wasPlaying;
}
}

View File

@@ -44,6 +44,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.RemoteViews;
import android.widget.SeekBar;
@@ -52,6 +53,7 @@ import android.widget.TextView;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.SubtitleView;
@@ -63,7 +65,7 @@ import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.old.PlayVideoActivity;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -369,6 +371,7 @@ public final class PopupVideoPlayer extends Service {
protected class VideoPlayerImpl extends VideoPlayer implements View.OnLayoutChangeListener {
private TextView resizingIndicator;
private ImageButton fullScreenButton;
private ImageView videoPlayPause;
private View extraOptionsView;
@@ -390,6 +393,8 @@ public final class PopupVideoPlayer extends Service {
resizingIndicator = rootView.findViewById(R.id.resizing_indicator);
fullScreenButton = rootView.findViewById(R.id.fullScreenButton);
fullScreenButton.setOnClickListener(v -> onFullScreenButtonClicked());
videoPlayPause = rootView.findViewById(R.id.videoPlayPause);
videoPlayPause.setOnClickListener(this::onPlayPauseButtonPressed);
extraOptionsView = rootView.findViewById(R.id.extraOptionsView);
rootView.addOnLayoutChangeListener(this);
@@ -397,14 +402,16 @@ public final class PopupVideoPlayer extends Service {
@Override
protected void setupSubtitleView(@NonNull SubtitleView view,
@NonNull String captionSizeKey) {
float captionRatio = SubtitleView.DEFAULT_TEXT_SIZE_FRACTION;
if (captionSizeKey.equals(getString(R.string.smaller_caption_size_key))) {
captionRatio *= 0.9;
} else if (captionSizeKey.equals(getString(R.string.larger_caption_size_key))) {
captionRatio *= 1.1;
}
view.setFractionalTextSize(captionRatio);
final float captionScale,
@NonNull final CaptionStyleCompat captionStyle) {
float captionRatio = (captionScale - 1f) / 5f + 1f;
view.setFractionalTextSize(SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio);
view.setApplyEmbeddedStyles(captionStyle.equals(CaptionStyleCompat.DEFAULT));
view.setStyle(captionStyle);
}
private void onPlayPauseButtonPressed(View ib) {
onPlayPause();
}
@Override
@@ -652,6 +659,7 @@ public final class PopupVideoPlayer extends Service {
public void onPlaying() {
super.onPlaying();
updateNotification(R.drawable.ic_pause_white);
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
lockManager.acquireWifiAndCpu();
hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
@@ -667,13 +675,14 @@ public final class PopupVideoPlayer extends Service {
public void onPaused() {
super.onPaused();
updateNotification(R.drawable.ic_play_arrow_white);
showAndAnimateControl(R.drawable.ic_play_arrow_white, false);
videoPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_white);
lockManager.releaseWifiAndCpu();
}
@Override
public void onPausedSeek() {
super.onPausedSeek();
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
updateNotification(R.drawable.ic_play_arrow_white);
}
@@ -681,10 +690,27 @@ public final class PopupVideoPlayer extends Service {
public void onCompleted() {
super.onCompleted();
updateNotification(R.drawable.ic_replay_white);
showAndAnimateControl(R.drawable.ic_replay_white, false);
videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white);
lockManager.releaseWifiAndCpu();
}
@Override
public void showControlsThenHide() {
videoPlayPause.setVisibility(View.VISIBLE);
super.showControlsThenHide();
}
public void showControls(long duration) {
videoPlayPause.setVisibility(View.VISIBLE);
super.showControls(duration);
}
public void hideControls(final long duration, long delay) {
super.hideControlsAndButton(duration, delay, videoPlayPause);
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@@ -718,6 +744,8 @@ public final class PopupVideoPlayer extends Service {
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
if (playerImpl == null || !playerImpl.isPlaying()) return false;
playerImpl.hideControls(0, 0);
if (e.getX() > popupWidth / 2) {
playerImpl.onFastForward();
} else {
@@ -731,7 +759,12 @@ public final class PopupVideoPlayer extends Service {
public boolean onSingleTapConfirmed(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
if (playerImpl == null || playerImpl.getPlayer() == null) return false;
playerImpl.onPlayPause();
if (playerImpl.isControlsVisible()) {
playerImpl.hideControls(100, 100);
} else {
playerImpl.showControlsThenHide();
}
return true;
}

View File

@@ -29,17 +29,21 @@ 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.fragments.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
import org.schabi.newpipe.playlist.PlayQueueItemHolder;
import org.schabi.newpipe.playlist.PlayQueueItemTouchCallback;
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.Collections;
import java.util.List;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatPitch;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
@@ -151,7 +155,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
finish();
return true;
case R.id.action_append_playlist:
appendToPlaylist();
appendAllToPlaylist();
return true;
case R.id.action_settings:
NavigationHelper.openSettings(this);
@@ -187,13 +191,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
private void appendToPlaylist() {
if (this.player != null && this.player.getPlayQueue() != null) {
PlaylistAppendDialog.fromPlayQueueItems(this.player.getPlayQueue().getStreams())
.show(getSupportFragmentManager(), getTag());
}
}
////////////////////////////////////////////////////////////////////////////
// Service Connection
////////////////////////////////////////////////////////////////////////////
@@ -319,7 +316,8 @@ 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);
final MenuItem remove = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/0,
Menu.NONE, R.string.play_queue_remove);
remove.setOnMenuItemClickListener(menuItem -> {
if (player == null) return false;
@@ -328,12 +326,20 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return true;
});
final MenuItem detail = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 1, Menu.NONE, R.string.play_queue_stream_detail);
final MenuItem detail = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/1,
Menu.NONE, R.string.play_queue_stream_detail);
detail.setOnMenuItemClickListener(menuItem -> {
onOpenDetail(item.getServiceId(), item.getUrl(), item.getTitle());
return true;
});
final MenuItem append = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/2,
Menu.NONE, R.string.append_playlist);
append.setOnMenuItemClickListener(menuItem -> {
openPlaylistAppendDialog(Collections.singletonList(item));
return true;
});
menu.show();
}
@@ -488,6 +494,21 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
seeking = false;
}
////////////////////////////////////////////////////////////////////////////
// Playlist append
////////////////////////////////////////////////////////////////////////////
private void appendAllToPlaylist() {
if (player != null && player.getPlayQueue() != null) {
openPlaylistAppendDialog(player.getPlayQueue().getStreams());
}
}
private void openPlaylistAppendDialog(final List<PlayQueueItem> playlist) {
PlaylistAppendDialog.fromPlayQueueItems(playlist)
.show(getSupportFragmentManager(), getTag());
}
////////////////////////////////////////////////////////////////////////////
// Binding Service Listener
////////////////////////////////////////////////////////////////////////////
@@ -497,6 +518,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
onStateChanged(state);
onPlayModeChanged(repeatMode, shuffled);
onPlaybackParameterChanged(parameters);
onMaybePlaybackAdapterChanged();
}
@Override
@@ -609,4 +631,12 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
playbackPitchButton.setText(formatPitch(parameters.pitch));
}
}
private void onMaybePlaybackAdapterChanged() {
if (itemsList == null || player == null) return;
final PlayQueueAdapter maybeNewAdapter = player.getPlayQueueAdapter();
if (maybeNewAdapter != null && itemsList.getAdapter() != maybeNewAdapter) {
itemsList.setAdapter(maybeNewAdapter);
}
}
}

View File

@@ -32,7 +32,6 @@ import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
@@ -55,6 +54,7 @@ import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.SubtitleView;
@@ -68,7 +68,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ListHelper;
@@ -189,10 +189,10 @@ public abstract class VideoPlayer extends BasePlayer
this.qualityTextView = rootView.findViewById(R.id.qualityTextView);
this.subtitleView = rootView.findViewById(R.id.subtitleView);
final String captionSizeKey = PreferenceManager.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.caption_size_key),
context.getString(R.string.caption_size_default));
setupSubtitleView(subtitleView, captionSizeKey);
final float captionScale = PlayerHelper.getCaptionScale(context);
final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context);
setupSubtitleView(subtitleView, captionScale, captionStyle);
this.resizeView = rootView.findViewById(R.id.resizeTextView);
resizeView.setText(PlayerHelper.resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode()));
@@ -214,7 +214,8 @@ public abstract class VideoPlayer extends BasePlayer
}
protected abstract void setupSubtitleView(@NonNull SubtitleView view,
@NonNull String captionSizeKey);
final float captionScale,
@NonNull final CaptionStyleCompat captionStyle);
@Override
public void initListeners() {
@@ -228,8 +229,8 @@ public abstract class VideoPlayer extends BasePlayer
}
@Override
public void initPlayer() {
super.initPlayer();
public void initPlayer(final boolean playOnReady) {
super.initPlayer(playOnReady);
// Setup video view
simpleExoPlayer.setVideoSurfaceView(surfaceView);
@@ -886,6 +887,19 @@ public abstract class VideoPlayer extends BasePlayer
() -> animateView(controlsRoot, false, duration), delay);
}
public void hideControlsAndButton(final long duration, long delay, View button) {
if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
controlsVisibilityHandler.removeCallbacksAndMessages(null);
controlsVisibilityHandler.postDelayed(hideControlsAndButtonHandler(duration, button), delay);
}
private Runnable hideControlsAndButtonHandler(long duration, View videoPlayPause)
{
return () -> {
videoPlayPause.setVisibility(View.INVISIBLE);
animateView(controlsRoot, false,duration);
};
}
/*//////////////////////////////////////////////////////////////////////////
// Getters and Setters
//////////////////////////////////////////////////////////////////////////*/

View File

@@ -1,8 +1,12 @@
package org.schabi.newpipe.player.helper;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.media.session.MediaButtonReceiver;
import android.support.v4.media.session.MediaSessionCompat;
import android.view.KeyEvent;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
@@ -15,8 +19,8 @@ import org.schabi.newpipe.player.mediasession.PlayQueuePlaybackController;
public class MediaSessionManager {
private static final String TAG = "MediaSessionManager";
private final MediaSessionCompat mediaSession;
private final MediaSessionConnector sessionConnector;
@NonNull private final MediaSessionCompat mediaSession;
@NonNull private final MediaSessionConnector sessionConnector;
public MediaSessionManager(@NonNull final Context context,
@NonNull final Player player,
@@ -28,11 +32,9 @@ public class MediaSessionManager {
this.sessionConnector.setPlayer(player, new DummyPlaybackPreparer());
}
public MediaSessionCompat getMediaSession() {
return mediaSession;
}
public MediaSessionConnector getSessionConnector() {
return sessionConnector;
@Nullable
@SuppressWarnings("UnusedReturnValue")
public KeyEvent handleMediaButtonIntent(final Intent intent) {
return MediaButtonReceiver.handleIntent(mediaSession, intent);
}
}

View File

@@ -2,12 +2,18 @@ package org.schabi.newpipe.player.helper;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.accessibility.CaptioningManager;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.MimeTypes;
import org.schabi.newpipe.R;
@@ -18,9 +24,9 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.SubtitlesFormat;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import java.text.DecimalFormat;
import java.text.NumberFormat;
@@ -203,6 +209,16 @@ public class PlayerHelper {
return 60000;
}
public static TrackSelection.Factory getQualitySelector(@NonNull final Context context,
@NonNull final BandwidthMeter meter) {
return new AdaptiveTrackSelection.Factory(meter,
AdaptiveTrackSelection.DEFAULT_MAX_INITIAL_BITRATE,
/*bufferDurationRequiredForQualityIncrease=*/1000,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION);
}
public static boolean isUsingDSP(@NonNull final Context context) {
return true;
}
@@ -215,6 +231,35 @@ public class PlayerHelper {
return 2500;
}
@NonNull
public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return CaptionStyleCompat.DEFAULT;
final CaptioningManager captioningManager = (CaptioningManager)
context.getSystemService(Context.CAPTIONING_SERVICE);
if (captioningManager == null || !captioningManager.isEnabled()) {
return CaptionStyleCompat.DEFAULT;
}
return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
}
/**
* System font scaling:
* Very small - 0.25f, Small - 0.5f, Normal - 1.0f, Large - 1.5f, Very Large - 2.0f
* */
@NonNull
public static float getCaptionScale(@NonNull final Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return 1f;
final CaptioningManager captioningManager = (CaptioningManager)
context.getSystemService(Context.CAPTIONING_SERVICE);
if (captioningManager == null || !captioningManager.isEnabled()) {
return 1f;
}
return captioningManager.getFontScale();
}
////////////////////////////////////////////////////////////////////////////
// Private helpers
////////////////////////////////////////////////////////////////////////////

View File

@@ -7,20 +7,42 @@ import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.upstream.Allocator;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import java.io.IOException;
public class FailedMediaSource implements ManagedMediaSource {
private final String TAG = "FailedMediaSource@" + Integer.toHexString(hashCode());
public static class FailedMediaSourceException extends Exception {
FailedMediaSourceException(String message) {
super(message);
}
FailedMediaSourceException(Throwable cause) {
super(cause);
}
}
public static final class MediaSourceResolutionException extends FailedMediaSourceException {
public MediaSourceResolutionException(String message) {
super(message);
}
}
public static final class StreamInfoLoadException extends FailedMediaSourceException {
public StreamInfoLoadException(Throwable cause) {
super(cause);
}
}
private final PlayQueueItem playQueueItem;
private final Throwable error;
private final FailedMediaSourceException error;
private final long retryTimestamp;
public FailedMediaSource(@NonNull final PlayQueueItem playQueueItem,
@NonNull final Throwable error,
@NonNull final FailedMediaSourceException error,
final long retryTimestamp) {
this.playQueueItem = playQueueItem;
this.error = error;
@@ -32,7 +54,7 @@ public class FailedMediaSource implements ManagedMediaSource {
* The error will always be propagated to ExoPlayer.
* */
public FailedMediaSource(@NonNull final PlayQueueItem playQueueItem,
@NonNull final Throwable error) {
@NonNull final FailedMediaSourceException error) {
this.playQueueItem = playQueueItem;
this.error = error;
this.retryTimestamp = Long.MAX_VALUE;
@@ -42,7 +64,7 @@ public class FailedMediaSource implements ManagedMediaSource {
return playQueueItem;
}
public Throwable getError() {
public FailedMediaSourceException getError() {
return error;
}

View File

@@ -7,7 +7,7 @@ import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.upstream.Allocator;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import java.io.IOException;

View File

@@ -4,7 +4,7 @@ import android.support.annotation.NonNull;
import com.google.android.exoplayer2.source.MediaSource;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
public interface ManagedMediaSource extends MediaSource {
/**

View File

@@ -0,0 +1,135 @@
package org.schabi.newpipe.player.mediasource;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
import com.google.android.exoplayer2.source.ShuffleOrder;
public class ManagedMediaSourcePlaylist {
@NonNull private final DynamicConcatenatingMediaSource internalSource;
public ManagedMediaSourcePlaylist() {
internalSource = new DynamicConcatenatingMediaSource(/*isPlaylistAtomic=*/false,
new ShuffleOrder.UnshuffledShuffleOrder(0));
}
/*//////////////////////////////////////////////////////////////////////////
// MediaSource Delegations
//////////////////////////////////////////////////////////////////////////*/
public int size() {
return internalSource.getSize();
}
/**
* Returns the {@link ManagedMediaSource} at the given index of the playlist.
* If the index is invalid, then null is returned.
* */
@Nullable
public ManagedMediaSource get(final int index) {
return (index < 0 || index >= size()) ?
null : (ManagedMediaSource) internalSource.getMediaSource(index);
}
public void dispose() {
internalSource.releaseSource();
}
@NonNull
public DynamicConcatenatingMediaSource getParentMediaSource() {
return internalSource;
}
/*//////////////////////////////////////////////////////////////////////////
// Playlist Manipulation
//////////////////////////////////////////////////////////////////////////*/
/**
* Expands the {@link DynamicConcatenatingMediaSource} by appending it with a
* {@link PlaceholderMediaSource}.
*
* @see #append(ManagedMediaSource)
* */
public synchronized void expand() {
append(new PlaceholderMediaSource());
}
/**
* Appends a {@link ManagedMediaSource} to the end of {@link DynamicConcatenatingMediaSource}.
* @see DynamicConcatenatingMediaSource#addMediaSource
* */
public synchronized void append(@NonNull final ManagedMediaSource source) {
internalSource.addMediaSource(source);
}
/**
* Removes a {@link ManagedMediaSource} from {@link DynamicConcatenatingMediaSource}
* at the given index. If this index is out of bound, then the removal is ignored.
* @see DynamicConcatenatingMediaSource#removeMediaSource(int)
* */
public synchronized void remove(final int index) {
if (index < 0 || index > internalSource.getSize()) return;
internalSource.removeMediaSource(index);
}
/**
* Moves a {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
* from the given source index to the target index. If either index is out of bound,
* then the call is ignored.
* @see DynamicConcatenatingMediaSource#moveMediaSource(int, int)
* */
public synchronized void move(final int source, final int target) {
if (source < 0 || target < 0) return;
if (source >= internalSource.getSize() || target >= internalSource.getSize()) return;
internalSource.moveMediaSource(source, target);
}
/**
* Invalidates the {@link ManagedMediaSource} at the given index by replacing it
* with a {@link PlaceholderMediaSource}.
* @see #update(int, ManagedMediaSource, Runnable)
* */
public synchronized void invalidate(final int index,
@Nullable final Runnable finalizingAction) {
if (get(index) instanceof PlaceholderMediaSource) return;
update(index, new PlaceholderMediaSource(), finalizingAction);
}
/**
* Updates the {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
* at the given index with a given {@link ManagedMediaSource}.
* @see #update(int, ManagedMediaSource, Runnable)
* */
public synchronized void update(final int index, @NonNull final ManagedMediaSource source) {
update(index, source, /*doNothing=*/null);
}
/**
* Updates the {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
* at the given index with a given {@link ManagedMediaSource}. If the index is out of bound,
* then the replacement is ignored.
* @see DynamicConcatenatingMediaSource#addMediaSource
* @see DynamicConcatenatingMediaSource#removeMediaSource(int, Runnable)
* */
public synchronized void update(final int index, @NonNull final ManagedMediaSource source,
@Nullable final Runnable finalizingAction) {
if (index < 0 || index >= internalSource.getSize()) return;
// Add and remove are sequential on the same thread, therefore here, the exoplayer
// message queue must receive and process add before remove, effectively treating them
// as atomic.
// Since the finalizing action occurs strictly after the timeline has completed
// all its changes on the playback thread, thus, it is possible, in the meantime,
// other calls that modifies the playlist media source occur in between. This makes
// it unsafe to call remove as the finalizing action of add.
internalSource.addMediaSource(index + 1, source);
// Because of the above race condition, it is thus only safe to synchronize the player
// in the finalizing action AFTER the removal is complete and the timeline has changed.
internalSource.removeMediaSource(index, finalizingAction);
}
}

View File

@@ -6,7 +6,7 @@ import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.upstream.Allocator;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import java.io.IOException;

View File

@@ -5,7 +5,7 @@ import android.support.v4.media.MediaDescriptionCompat;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
public class BasePlayerMediaSession implements MediaSessionCallback {
private BasePlayer player;

View File

@@ -2,11 +2,11 @@ package org.schabi.newpipe.player.playback;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.ArraySet;
import android.util.Log;
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ShuffleOrder;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
@@ -14,17 +14,20 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.mediasource.FailedMediaSource;
import org.schabi.newpipe.player.mediasource.LoadedMediaSource;
import org.schabi.newpipe.player.mediasource.ManagedMediaSource;
import org.schabi.newpipe.player.mediasource.ManagedMediaSourcePlaylist;
import org.schabi.newpipe.player.mediasource.PlaceholderMediaSource;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.events.MoveEvent;
import org.schabi.newpipe.playlist.events.PlayQueueEvent;
import org.schabi.newpipe.playlist.events.RemoveEvent;
import org.schabi.newpipe.playlist.events.ReorderEvent;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.events.MoveEvent;
import org.schabi.newpipe.player.playqueue.events.PlayQueueEvent;
import org.schabi.newpipe.player.playqueue.events.RemoveEvent;
import org.schabi.newpipe.player.playqueue.events.ReorderEvent;
import org.schabi.newpipe.util.ServiceHelper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -37,9 +40,12 @@ import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.SerialDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.internal.subscriptions.EmptySubscription;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import static org.schabi.newpipe.playlist.PlayQueue.DEBUG;
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException;
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfoLoadException;
import static org.schabi.newpipe.player.playqueue.PlayQueue.DEBUG;
public class MediaSourceManager {
@NonNull private final String TAG = "MediaSourceManager@" + hashCode();
@@ -52,7 +58,6 @@ public class MediaSourceManager {
* streams before will only be cached for future usage.
*
* @see #onMediaSourceReceived(PlayQueueItem, ManagedMediaSource)
* @see #update(int, MediaSource, Runnable)
* */
private final static int WINDOW_SIZE = 1;
@@ -103,7 +108,7 @@ public class MediaSourceManager {
@NonNull private final AtomicBoolean isBlocked;
@NonNull private DynamicConcatenatingMediaSource sources;
@NonNull private ManagedMediaSourcePlaylist playlist;
public MediaSourceManager(@NonNull final PlaybackListener listener,
@NonNull final PlayQueue playQueue) {
@@ -143,9 +148,9 @@ public class MediaSourceManager {
this.isBlocked = new AtomicBoolean(false);
this.sources = new DynamicConcatenatingMediaSource();
this.playlist = new ManagedMediaSourcePlaylist();
this.loadingItems = Collections.synchronizedSet(new HashSet<>());
this.loadingItems = Collections.synchronizedSet(new ArraySet<>());
playQueue.getBroadcastReceiver()
.observeOn(AndroidSchedulers.mainThread())
@@ -167,7 +172,7 @@ public class MediaSourceManager {
playQueueReactor.cancel();
loaderReactor.dispose();
syncReactor.dispose();
sources.releaseSource();
playlist.dispose();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -215,17 +220,18 @@ public class MediaSourceManager {
break;
case REMOVE:
final RemoveEvent removeEvent = (RemoveEvent) event;
remove(removeEvent.getRemoveIndex());
playlist.remove(removeEvent.getRemoveIndex());
break;
case MOVE:
final MoveEvent moveEvent = (MoveEvent) event;
move(moveEvent.getFromIndex(), moveEvent.getToIndex());
playlist.move(moveEvent.getFromIndex(), moveEvent.getToIndex());
break;
case REORDER:
// Need to move to ensure the playing index from play queue matches that of
// the source timeline, and then window correction can take care of the rest
final ReorderEvent reorderEvent = (ReorderEvent) event;
move(reorderEvent.getFromSelectedIndex(), reorderEvent.getToSelectedIndex());
playlist.move(reorderEvent.getFromSelectedIndex(),
reorderEvent.getToSelectedIndex());
break;
case RECOVERY:
default:
@@ -266,10 +272,11 @@ public class MediaSourceManager {
}
private boolean isPlaybackReady() {
if (sources.getSize() != playQueue.size()) return false;
if (playlist.size() != playQueue.size()) return false;
final ManagedMediaSource mediaSource = playlist.get(playQueue.getIndex());
if (mediaSource == null) return false;
final ManagedMediaSource mediaSource =
(ManagedMediaSource) sources.getMediaSource(playQueue.getIndex());
final PlayQueueItem playQueueItem = playQueue.getItem();
return mediaSource.isStreamEqual(playQueueItem);
}
@@ -288,9 +295,9 @@ public class MediaSourceManager {
private void maybeUnblock() {
if (DEBUG) Log.d(TAG, "maybeUnblock() called.");
if (isPlayQueueReady() && isPlaybackReady() && isBlocked.get()) {
if (isBlocked.get()) {
isBlocked.set(false);
playbackListener.onPlaybackUnblock(sources);
playbackListener.onPlaybackUnblock(playlist.getParentMediaSource());
}
}
@@ -299,10 +306,10 @@ public class MediaSourceManager {
//////////////////////////////////////////////////////////////////////////*/
private void maybeSync() {
if (DEBUG) Log.d(TAG, "onPlaybackSynchronize() called.");
if (DEBUG) Log.d(TAG, "maybeSync() called.");
final PlayQueueItem currentItem = playQueue.getItem();
if (isBlocked.get() || !isPlaybackReady() || currentItem == null) return;
if (isBlocked.get() || currentItem == null) return;
final Consumer<StreamInfo> onSuccess = info -> syncInternal(currentItem, info);
final Consumer<Throwable> onError = throwable -> syncInternal(currentItem, null);
@@ -321,9 +328,11 @@ public class MediaSourceManager {
}
}
private void maybeSynchronizePlayer() {
maybeUnblock();
maybeSync();
private synchronized void maybeSynchronizePlayer() {
if (isPlayQueueReady() && isPlaybackReady()) {
maybeUnblock();
maybeSync();
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -332,12 +341,14 @@ public class MediaSourceManager {
private Observable<Long> getEdgeIntervalSignal() {
return Observable.interval(progressUpdateIntervalMillis, TimeUnit.MILLISECONDS)
.filter(ignored -> playbackListener.isNearPlaybackEdge(playbackNearEndGapMillis));
.filter(ignored ->
playbackListener.isApproachingPlaybackEdge(playbackNearEndGapMillis));
}
private Disposable getDebouncedLoader() {
return debouncedSignal.mergeWith(nearEndIntervalSignal)
.debounce(loadDebounceMillis, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.single())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(timestamp -> loadImmediate());
}
@@ -348,42 +359,21 @@ public class MediaSourceManager {
private void loadImmediate() {
if (DEBUG) Log.d(TAG, "MediaSource - loadImmediate() called");
// The current item has higher priority
final int currentIndex = playQueue.getIndex();
final PlayQueueItem currentItem = playQueue.getItem(currentIndex);
if (currentItem == null) return;
final ItemsToLoad itemsToLoad = getItemsToLoad(playQueue, WINDOW_SIZE);
if (itemsToLoad == null) return;
// Evict the items being loaded to free up memory
if (loaderReactor.size() > MAXIMUM_LOADER_SIZE) {
loaderReactor.clear();
loadingItems.clear();
}
maybeLoadItem(currentItem);
// Evict the previous items being loaded to free up memory, before start loading new ones
maybeClearLoaders();
// The rest are just for seamless playback
// Although timeline is not updated prior to the current index, these sources are still
// loaded into the cache for faster retrieval at a potentially later time.
final int leftBound = Math.max(0, currentIndex - WINDOW_SIZE);
final int rightLimit = currentIndex + WINDOW_SIZE + 1;
final int rightBound = Math.min(playQueue.size(), rightLimit);
final Set<PlayQueueItem> items = new HashSet<>(
playQueue.getStreams().subList(leftBound,rightBound));
// Do a round robin
final int excess = rightLimit - playQueue.size();
if (excess >= 0) {
items.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess)));
}
items.remove(currentItem);
for (final PlayQueueItem item : items) {
maybeLoadItem(itemsToLoad.center);
for (final PlayQueueItem item : itemsToLoad.neighbors) {
maybeLoadItem(item);
}
}
private void maybeLoadItem(@NonNull final PlayQueueItem item) {
if (DEBUG) Log.d(TAG, "maybeLoadItem() called.");
if (playQueue.indexOf(item) >= sources.getSize()) return;
if (playQueue.indexOf(item) >= playlist.size()) return;
if (!loadingItems.contains(item) && isCorrectionNeeded(item)) {
if (DEBUG) Log.d(TAG, "MediaSource - Loading=[" + item.getTitle() +
@@ -402,19 +392,19 @@ public class MediaSourceManager {
return stream.getStream().map(streamInfo -> {
final MediaSource source = playbackListener.sourceOf(stream, streamInfo);
if (source == null) {
final Exception exception = new IllegalStateException(
"Unable to resolve source from stream info." +
" URL: " + stream.getUrl() +
", audio count: " + streamInfo.getAudioStreams().size() +
", video count: " + streamInfo.getVideoOnlyStreams().size() +
streamInfo.getVideoStreams().size());
return new FailedMediaSource(stream, exception);
final String message = "Unable to resolve source from stream info." +
" URL: " + stream.getUrl() +
", audio count: " + streamInfo.getAudioStreams().size() +
", video count: " + streamInfo.getVideoOnlyStreams().size() +
streamInfo.getVideoStreams().size();
return new FailedMediaSource(stream, new MediaSourceResolutionException(message));
}
final long expiration = System.currentTimeMillis() +
ServiceHelper.getCacheExpirationMillis(streamInfo.getServiceId());
return new LoadedMediaSource(source, stream, expiration);
}).onErrorReturn(throwable -> new FailedMediaSource(stream, throwable));
}).onErrorReturn(throwable -> new FailedMediaSource(stream,
new StreamInfoLoadException(throwable)));
}
private void onMediaSourceReceived(@NonNull final PlayQueueItem item,
@@ -426,10 +416,10 @@ public class MediaSourceManager {
final int itemIndex = playQueue.indexOf(item);
// Only update the playlist timeline for items at the current index or after.
if (itemIndex >= playQueue.getIndex() && isCorrectionNeeded(item)) {
if (isCorrectionNeeded(item)) {
if (DEBUG) Log.d(TAG, "MediaSource - Updating index=[" + itemIndex + "] with " +
"title=[" + item.getTitle() + "] at url=[" + item.getUrl() + "]");
update(itemIndex, mediaSource, this::maybeSynchronizePlayer);
playlist.update(itemIndex, mediaSource, this::maybeSynchronizePlayer);
}
}
@@ -445,10 +435,8 @@ public class MediaSourceManager {
* */
private boolean isCorrectionNeeded(@NonNull final PlayQueueItem item) {
final int index = playQueue.indexOf(item);
if (index == -1 || index >= sources.getSize()) return false;
final ManagedMediaSource mediaSource = (ManagedMediaSource) sources.getMediaSource(index);
return mediaSource.shouldBeReplacedWith(item,
final ManagedMediaSource mediaSource = playlist.get(index);
return mediaSource != null && mediaSource.shouldBeReplacedWith(item,
/*mightBeInProgress=*/index != playQueue.getIndex());
}
@@ -465,10 +453,9 @@ public class MediaSourceManager {
* */
private void maybeRenewCurrentIndex() {
final int currentIndex = playQueue.getIndex();
if (sources.getSize() <= currentIndex) return;
final ManagedMediaSource currentSource = playlist.get(currentIndex);
if (currentSource == null) return;
final ManagedMediaSource currentSource =
(ManagedMediaSource) sources.getMediaSource(currentIndex);
final PlayQueueItem currentItem = playQueue.getItem();
if (!currentSource.shouldBeReplacedWith(currentItem, /*canInterruptOnRenew=*/true)) {
maybeSynchronizePlayer();
@@ -477,7 +464,16 @@ public class MediaSourceManager {
if (DEBUG) Log.d(TAG, "MediaSource - Reloading currently playing, " +
"index=[" + currentIndex + "], item=[" + currentItem.getTitle() + "]");
update(currentIndex, new PlaceholderMediaSource(), this::loadImmediate);
playlist.invalidate(currentIndex, this::loadImmediate);
}
private void maybeClearLoaders() {
if (DEBUG) Log.d(TAG, "MediaSource - maybeClearLoaders() called.");
if (!loadingItems.contains(playQueue.getItem()) &&
loaderReactor.size() > MAXIMUM_LOADER_SIZE) {
loaderReactor.clear();
loadingItems.clear();
}
}
/*//////////////////////////////////////////////////////////////////////////
// MediaSource Playlist Helpers
@@ -486,72 +482,55 @@ public class MediaSourceManager {
private void resetSources() {
if (DEBUG) Log.d(TAG, "resetSources() called.");
this.sources.releaseSource();
this.sources = new DynamicConcatenatingMediaSource(false,
// Shuffling is done on PlayQueue, thus no need to use ExoPlayer's shuffle order
new ShuffleOrder.UnshuffledShuffleOrder(0));
playlist.dispose();
playlist = new ManagedMediaSourcePlaylist();
}
private void populateSources() {
if (DEBUG) Log.d(TAG, "populateSources() called.");
if (sources.getSize() >= playQueue.size()) return;
for (int index = sources.getSize() - 1; index < playQueue.size(); index++) {
emplace(index, new PlaceholderMediaSource());
while (playlist.size() < playQueue.size()) {
playlist.expand();
}
}
/*//////////////////////////////////////////////////////////////////////////
// MediaSource Playlist Manipulation
// Manager Helpers
//////////////////////////////////////////////////////////////////////////*/
@Nullable
private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue,
final int windowSize) {
// The current item has higher priority
final int currentIndex = playQueue.getIndex();
final PlayQueueItem currentItem = playQueue.getItem(currentIndex);
if (currentItem == null) return null;
/**
* Places a {@link MediaSource} into the {@link DynamicConcatenatingMediaSource}
* with position in respect to the play queue only if no {@link MediaSource}
* already exists at the given index.
* */
private synchronized void emplace(final int index, @NonNull final MediaSource source) {
if (index < sources.getSize()) return;
// The rest are just for seamless playback
// Although timeline is not updated prior to the current index, these sources are still
// loaded into the cache for faster retrieval at a potentially later time.
final int leftBound = Math.max(0, currentIndex - windowSize);
final int rightLimit = currentIndex + windowSize + 1;
final int rightBound = Math.min(playQueue.size(), rightLimit);
final Set<PlayQueueItem> neighbors = new ArraySet<>(
playQueue.getStreams().subList(leftBound,rightBound));
sources.addMediaSource(index, source);
// Do a round robin
final int excess = rightLimit - playQueue.size();
if (excess >= 0) {
neighbors.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess)));
}
neighbors.remove(currentItem);
return new ItemsToLoad(currentItem, neighbors);
}
/**
* Removes a {@link MediaSource} from {@link DynamicConcatenatingMediaSource}
* at the given index. If this index is out of bound, then the removal is ignored.
* */
private synchronized void remove(final int index) {
if (index < 0 || index > sources.getSize()) return;
private static class ItemsToLoad {
@NonNull final private PlayQueueItem center;
@NonNull final private Collection<PlayQueueItem> neighbors;
sources.removeMediaSource(index);
}
/**
* Moves a {@link MediaSource} in {@link DynamicConcatenatingMediaSource}
* from the given source index to the target index. If either index is out of bound,
* then the call is ignored.
* */
private synchronized void move(final int source, final int target) {
if (source < 0 || target < 0) return;
if (source >= sources.getSize() || target >= sources.getSize()) return;
sources.moveMediaSource(source, target);
}
/**
* Updates the {@link MediaSource} in {@link DynamicConcatenatingMediaSource}
* at the given index with a given {@link MediaSource}. If the index is out of bound,
* then the replacement is ignored.
* <br><br>
* Not recommended to use on indices LESS THAN the currently playing index, since
* this will modify the playback timeline prior to the index and may cause desynchronization
* on the playing item between {@link PlayQueue} and {@link DynamicConcatenatingMediaSource}.
* */
private synchronized void update(final int index, @NonNull final MediaSource source,
@Nullable final Runnable finalizingAction) {
if (index < 0 || index >= sources.getSize()) return;
sources.addMediaSource(index + 1, source, () ->
sources.removeMediaSource(index, finalizingAction));
ItemsToLoad(@NonNull final PlayQueueItem center,
@NonNull final Collection<PlayQueueItem> neighbors) {
this.center = center;
this.neighbors = neighbors;
}
}
}

View File

@@ -6,20 +6,20 @@ import android.support.annotation.Nullable;
import com.google.android.exoplayer2.source.MediaSource;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import java.util.List;
public interface PlaybackListener {
/**
* Called to check if the currently playing stream is close to the end of its playback.
* Implementation should return true when the current playback position is within
* timeToEndMillis or less until its playback completes or transitions.
* Called to check if the currently playing stream is approaching the end of its playback.
* Implementation should return true when the current playback position is progressing within
* timeToEndMillis or less to its playback during.
*
* May be called at any time.
* */
boolean isNearPlaybackEdge(final long timeToEndMillis);
boolean isApproachingPlaybackEdge(final long timeToEndMillis);
/**
* Called when the stream at the current queue index is not ready yet.

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist;
package org.schabi.newpipe.player.playqueue;
import android.util.Log;

View File

@@ -1,6 +1,6 @@
package org.schabi.newpipe.playlist;
package org.schabi.newpipe.player.playqueue;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist;
package org.schabi.newpipe.player.playqueue;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -6,15 +6,15 @@ import android.util.Log;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.schabi.newpipe.playlist.events.AppendEvent;
import org.schabi.newpipe.playlist.events.ErrorEvent;
import org.schabi.newpipe.playlist.events.InitEvent;
import org.schabi.newpipe.playlist.events.MoveEvent;
import org.schabi.newpipe.playlist.events.PlayQueueEvent;
import org.schabi.newpipe.playlist.events.RecoveryEvent;
import org.schabi.newpipe.playlist.events.RemoveEvent;
import org.schabi.newpipe.playlist.events.ReorderEvent;
import org.schabi.newpipe.playlist.events.SelectEvent;
import org.schabi.newpipe.player.playqueue.events.AppendEvent;
import org.schabi.newpipe.player.playqueue.events.ErrorEvent;
import org.schabi.newpipe.player.playqueue.events.InitEvent;
import org.schabi.newpipe.player.playqueue.events.MoveEvent;
import org.schabi.newpipe.player.playqueue.events.PlayQueueEvent;
import org.schabi.newpipe.player.playqueue.events.RecoveryEvent;
import org.schabi.newpipe.player.playqueue.events.RemoveEvent;
import org.schabi.newpipe.player.playqueue.events.ReorderEvent;
import org.schabi.newpipe.player.playqueue.events.SelectEvent;
import java.io.Serializable;
import java.util.ArrayList;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist;
package org.schabi.newpipe.player.playqueue;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
@@ -8,12 +8,13 @@ import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.playlist.events.AppendEvent;
import org.schabi.newpipe.playlist.events.ErrorEvent;
import org.schabi.newpipe.playlist.events.MoveEvent;
import org.schabi.newpipe.playlist.events.PlayQueueEvent;
import org.schabi.newpipe.playlist.events.RemoveEvent;
import org.schabi.newpipe.playlist.events.SelectEvent;
import org.schabi.newpipe.player.playqueue.events.AppendEvent;
import org.schabi.newpipe.player.playqueue.events.ErrorEvent;
import org.schabi.newpipe.player.playqueue.events.MoveEvent;
import org.schabi.newpipe.player.playqueue.events.PlayQueueEvent;
import org.schabi.newpipe.player.playqueue.events.RemoveEvent;
import org.schabi.newpipe.player.playqueue.events.SelectEvent;
import org.schabi.newpipe.util.FallbackViewHolder;
import java.util.List;
@@ -188,7 +189,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
return new PlayQueueItemHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.play_queue_item, parent, false));
default:
Log.e(TAG, "Attempting to create view holder with undefined type: " + type);
return null;
return new FallbackViewHolder(new View(parent.getContext()));
}
}

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist;
package org.schabi.newpipe.player.playqueue;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -28,12 +28,12 @@ public class PlayQueueItem implements Serializable {
private long recoveryPosition;
private Throwable error;
private transient Single<StreamInfo> stream;
PlayQueueItem(@NonNull final StreamInfo info) {
this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(),
info.getThumbnailUrl(), info.getUploaderName(), info.getStreamType());
this.stream = Single.just(info);
if (info.getStartPosition() > 0)
setRecoveryPosition(info.getStartPosition() * 1000);
}
PlayQueueItem(@NonNull final StreamInfoItem item) {
@@ -100,11 +100,6 @@ public class PlayQueueItem implements Serializable {
@NonNull
public Single<StreamInfo> getStream() {
return stream == null ? stream = getInfo() : stream;
}
@NonNull
private Single<StreamInfo> getInfo() {
return ExtractorHelper.getStreamInfo(this.serviceId, this.url, false)
.subscribeOn(Schedulers.io())
.doOnError(throwable -> error = throwable);

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist;
package org.schabi.newpipe.player.playqueue;
import android.content.Context;
import android.text.TextUtils;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist;
package org.schabi.newpipe.player.playqueue;
import android.support.v7.widget.RecyclerView;
import android.view.View;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist;
package org.schabi.newpipe.player.playqueue;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;

View File

@@ -1,6 +1,5 @@
package org.schabi.newpipe.playlist;
package org.schabi.newpipe.player.playqueue;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist;
package org.schabi.newpipe.player.playqueue;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist.events;
package org.schabi.newpipe.player.playqueue.events;
public class AppendEvent implements PlayQueueEvent {

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist.events;
package org.schabi.newpipe.player.playqueue.events;
public class ErrorEvent implements PlayQueueEvent {

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist.events;
package org.schabi.newpipe.player.playqueue.events;
public class InitEvent implements PlayQueueEvent {
@Override

View File

@@ -1,4 +1,4 @@
package org.schabi.newpipe.playlist.events;
package org.schabi.newpipe.player.playqueue.events;
public class MoveEvent implements PlayQueueEvent {
final private int fromIndex;

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