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

Compare commits

..

652 Commits

Author SHA1 Message Date
TobiGr
175652f23b Release 0.20.1 (955) 2020-10-16 22:20:23 +02:00
TobiGr
3329e0c4a1 Update extractor version
[YouTube] Fix search for some users
[YouTube] Fix random decryption exceptions
[SoundCloud] URLs that end with a slash are now parsed correctly
2020-10-16 22:20:16 +02:00
Tobias Groza
ea0a0c7c5a Merge pull request #4333 from TeamNewPipe/release/0.20.0
Release 0.20.0
2020-10-04 14:53:37 +02:00
Hosted Weblate
535a0504d8 Merge branch 'origin/dev' into Weblate. 2020-10-04 12:12:17 +02:00
Eric
365c49d6d2 Translated using Weblate (Chinese (Simplified))
Currently translated at 42.8% (15 of 35 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
2020-10-04 12:11:08 +02:00
chr56
b70bea48f2 Translated using Weblate (Chinese (Traditional))
Currently translated at 8.5% (3 of 35 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
2020-10-04 12:11:06 +02:00
Ajeje Brazorf
996f8644c4 Translated using Weblate (Sardinian)
Currently translated at 5.7% (2 of 35 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sc/
2020-10-04 12:11:06 +02:00
ssantos
b3882ec6e3 Translated using Weblate (Portuguese (Portugal))
Currently translated at 48.5% (17 of 35 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
2020-10-04 12:11:05 +02:00
Yaron Shahrabani
f87d447397 Translated using Weblate (Hebrew)
Currently translated at 2.8% (1 of 35 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/
2020-10-04 12:11:05 +02:00
ssantos
9d8570d0d2 Translated using Weblate (Portuguese)
Currently translated at 48.5% (17 of 35 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
2020-10-04 12:11:04 +02:00
AioiLight
796755dad8 Translated using Weblate (Japanese)
Currently translated at 8.5% (3 of 35 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ja/
2020-10-04 12:11:03 +02:00
Hakim Oubouali
9387753995 Translated using Weblate (Berber)
Currently translated at 23.5% (141 of 600 strings)
2020-10-04 12:11:03 +02:00
Allan Nordhøy
618d36dc07 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:11:02 +02:00
ssantos
f11b0be483 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:11:01 +02:00
Allan Nordhøy
1b8b15b136 Translated using Weblate (Sardinian)
Currently translated at 99.8% (599 of 600 strings)
2020-10-04 12:11:01 +02:00
Allan Nordhøy
bb63673cce Translated using Weblate (Central Kurdish)
Currently translated at 99.6% (598 of 600 strings)
2020-10-04 12:11:00 +02:00
Allan Nordhøy
6770ad68d5 Translated using Weblate (Malayalam)
Currently translated at 94.6% (568 of 600 strings)
2020-10-04 12:11:00 +02:00
Allan Nordhøy
bfe90c58d1 Translated using Weblate (Nepali)
Currently translated at 93.1% (559 of 600 strings)
2020-10-04 12:11:00 +02:00
Allan Nordhøy
f1cbeb3c29 Translated using Weblate (Danish)
Currently translated at 67.5% (405 of 600 strings)
2020-10-04 12:10:59 +02:00
Allan Nordhøy
554ab4ea16 Translated using Weblate (Galician)
Currently translated at 94.8% (569 of 600 strings)
2020-10-04 12:10:58 +02:00
Allan Nordhøy
a2becac2e6 Translated using Weblate (Punjabi)
Currently translated at 75.6% (454 of 600 strings)
2020-10-04 12:10:58 +02:00
gamerboy
67a651f5e9 Translated using Weblate (Punjabi)
Currently translated at 75.6% (454 of 600 strings)
2020-10-04 12:10:58 +02:00
Allan Nordhøy
ac8efe19d8 Translated using Weblate (Albanian)
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:57 +02:00
Allan Nordhøy
ffd65d5afa Translated using Weblate (Urdu)
Currently translated at 79.0% (474 of 600 strings)
2020-10-04 12:10:57 +02:00
Allan Nordhøy
e86677178f Translated using Weblate (Catalan)
Currently translated at 81.5% (489 of 600 strings)
2020-10-04 12:10:57 +02:00
Allan Nordhøy
3c49a3341a Translated using Weblate (Kurdish)
Currently translated at 94.1% (565 of 600 strings)
2020-10-04 12:10:56 +02:00
Allan Nordhøy
3433b2a73e Translated using Weblate (Finnish)
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:56 +02:00
Allan Nordhøy
730988e7b7 Translated using Weblate (Swedish)
Currently translated at 95.1% (571 of 600 strings)
2020-10-04 12:10:56 +02:00
Yaron Shahrabani
2a3b89e596 Translated using Weblate (Hebrew)
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:55 +02:00
Allan Nordhøy
f1a31bf58c Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:54 +02:00
Allan Nordhøy
f1b62a9056 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:54 +02:00
Allan Nordhøy
dd943d24c8 Translated using Weblate (Polish)
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:54 +02:00
Oğuz Ersen
801320a3f3 Translated using Weblate (Turkish)
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:54 +02:00
Allan Nordhøy
222ed2debd Translated using Weblate (Indonesian)
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:53 +02:00
Allan Nordhøy
7aab782c5f Translated using Weblate (Arabic)
Currently translated at 99.5% (597 of 600 strings)
2020-10-04 12:10:53 +02:00
Allan Nordhøy
3836f2f353 Translated using Weblate (Czech)
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:53 +02:00
Allan Nordhøy
5bfaa9a5db Translated using Weblate (Esperanto)
Currently translated at 91.1% (547 of 600 strings)
2020-10-04 12:10:53 +02:00
Allan Nordhøy
95581771d6 Translated using Weblate (Slovak)
Currently translated at 98.3% (590 of 600 strings)
2020-10-04 12:10:53 +02:00
ssantos
db5e3f2479 Translated using Weblate (Portuguese)
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:52 +02:00
Allan Nordhøy
b5a9631bcc Translated using Weblate (Korean)
Currently translated at 83.1% (499 of 600 strings)
2020-10-04 12:10:51 +02:00
Allan Nordhøy
4a2d62ece0 Translated using Weblate (Japanese)
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:51 +02:00
Allan Nordhøy
c3836decee Translated using Weblate (Russian)
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:51 +02:00
Allan Nordhøy
f7a030c895 Translated using Weblate (English)
Currently translated at 99.8% (599 of 600 strings)
2020-10-04 12:10:51 +02:00
Éfrit
f171a692d3 Translated using Weblate (English)
Currently translated at 99.8% (599 of 600 strings)
2020-10-04 12:10:51 +02:00
Allan Nordhøy
df5e73192b Translated using Weblate (Dutch)
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 12:10:50 +02:00
Allan Nordhøy
fc1447d614 Translated using Weblate (Hungarian)
Currently translated at 69.6% (418 of 600 strings)
2020-10-04 12:10:50 +02:00
Éfrit
77fd206b06 Translated using Weblate (French)
Currently translated at 99.8% (599 of 600 strings)
2020-10-04 12:10:50 +02:00
Allan Nordhøy
23bdc03490 Translated using Weblate (Spanish)
Currently translated at 99.8% (599 of 600 strings)
2020-10-04 12:10:49 +02:00
TobiGr
9fe4de5709 Added Portuguese (Portugal), Neapolitan and Sardinian 2020-10-04 12:01:16 +02:00
chr56
54fd601809 Translated using Weblate (Chinese (Simplified))
Currently translated at 40.0% (14 of 35 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
2020-10-04 06:20:00 +02:00
Allan Nordhøy
71d027a966 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (600 of 600 strings)
2020-10-04 00:51:07 +02:00
Anxhelo Lushka
8b63aa2fe6 Translated using Weblate (Albanian)
Currently translated at 99.8% (599 of 600 strings)
2020-10-03 22:48:45 +02:00
TobiGr
5a35842c28 fix fastlane locale 2020-10-03 21:24:26 +02:00
Allan Nordhøy
d6a1ae3b3a Norwegian Bokmål fastlane support (#4374) 2020-10-03 21:17:56 +02:00
bopol
be5f4cb562 Rename full_description.fr.txt to full_description.txt 2020-10-03 18:29:52 +02:00
bopol
d8b5464833 Rename short_description.fr.txt to short_description.txt 2020-10-03 18:29:52 +02:00
Stypox
5e7bbcd3bc Add italian translation for fastlane 2020-10-03 18:29:52 +02:00
bopol
5383e53c4d fix typo in 0.20.0 french changelog 2020-10-03 18:29:52 +02:00
bopol
5b6fc713d6 translate fastlane metadata in french 2020-10-03 18:29:52 +02:00
TobiGr
272be025e1 Release 0.20.0 2020-10-03 18:29:52 +02:00
bopol
e4ab250729 add berber, bengali languages 2020-10-03 18:29:52 +02:00
TobiGr
4dcca9d5af Merge remote-tracking branch 'Weblate/dev' into dev 2020-10-02 21:23:33 +02:00
TobiGr
1988a08631 Translated using Weblate (Bengali)
Currently translated at 64.1% (385 of 600 strings)
2020-10-02 21:22:20 +02:00
Digiwizkid
34de0e569f Translated using Weblate (Bengali)
Currently translated at 64.1% (385 of 600 strings)
2020-10-02 21:22:20 +02:00
Kurd As
343d0fa09d Translated using Weblate (Central Kurdish)
Currently translated at 99.8% (599 of 600 strings)
2020-10-02 21:22:19 +02:00
TobiGr
8eb6686103 Translated using Weblate (Malay)
Currently translated at 68.6% (412 of 600 strings)
2020-10-02 21:22:19 +02:00
TobiGr
9e9687b5b8 Translated using Weblate (Belarusian)
Currently translated at 75.3% (452 of 600 strings)
2020-10-02 21:22:19 +02:00
TobiGr
ef888d1afe Translated using Weblate (Estonian)
Currently translated at 67.0% (402 of 600 strings)
2020-10-02 21:22:18 +02:00
TobiGr
0e70e1a37a Translated using Weblate (Punjabi)
Currently translated at 73.1% (439 of 600 strings)
2020-10-02 21:22:17 +02:00
TobiGr
06aaceb673 Translated using Weblate (Macedonian)
Currently translated at 64.3% (386 of 600 strings)
2020-10-02 21:22:16 +02:00
TobiGr
703a4b7858 Translated using Weblate (Bulgarian)
Currently translated at 62.0% (372 of 600 strings)
2020-10-02 21:22:16 +02:00
TobiGr
32ba2ba83d Translated using Weblate (Tamil)
Currently translated at 33.6% (202 of 600 strings)
2020-10-02 21:22:16 +02:00
TobiGr
272b03ed92 Translated using Weblate (Telugu)
Currently translated at 23.1% (139 of 600 strings)
2020-10-02 21:22:15 +02:00
TobiGr
ecf19214ee Translated using Weblate (Hindi)
Currently translated at 77.5% (465 of 600 strings)
2020-10-02 21:22:15 +02:00
TobiGr
f3eb0c497f Translated using Weblate (Croatian)
Currently translated at 82.6% (496 of 600 strings)
2020-10-02 21:22:15 +02:00
TobiGr
c1f29a7565 Translated using Weblate (Swedish)
Currently translated at 94.5% (567 of 600 strings)
2020-10-02 21:22:15 +02:00
TobiGr
fb745b9108 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 62.5% (375 of 600 strings)
2020-10-02 21:22:14 +02:00
TobiGr
9410bf40d3 Translated using Weblate (Persian)
Currently translated at 100.0% (600 of 600 strings)
2020-10-02 21:22:14 +02:00
Mostafa Ahangarha
c7a695cb04 Translated using Weblate (Persian)
Currently translated at 100.0% (600 of 600 strings)
2020-10-02 21:22:14 +02:00
TobiGr
b991d5cab6 Translated using Weblate (Romanian)
Currently translated at 65.6% (394 of 600 strings)
2020-10-02 21:22:12 +02:00
TobiGr
42fd318321 Translated using Weblate (Chinese (Traditional, Hong Kong))
Currently translated at 27.3% (164 of 600 strings)
2020-10-02 21:22:12 +02:00
TobiGr
903aeec383 Translated using Weblate (Basque)
Currently translated at 91.5% (549 of 600 strings)
2020-10-02 21:22:12 +02:00
ssantos
8768fe4dcf Translated using Weblate (Portuguese)
Currently translated at 96.0% (576 of 600 strings)
2020-10-02 21:22:11 +02:00
TobiGr
d8ba2ceed4 Translated using Weblate (Hungarian)
Currently translated at 69.6% (418 of 600 strings)
2020-10-02 21:22:11 +02:00
Tobias Groza
ef8a1bcf47 Merge pull request #4365 from B0pol/prettytime
Update to PrettyTime 4.0.6
2020-10-02 20:40:11 +02:00
bopol
2b1469e02e update to PrettyTime 4.0.6
fixes #4324
2020-10-03 19:04:44 +02:00
Tobias Groza
83ea91586b Merge pull request #4362 from Stypox/fix-queue
Random fixes and improvements
2020-10-02 16:48:04 +02:00
Stypox
dbb86d25e1 Fix video detail controls visibility set inconsistently 2020-10-02 16:03:43 +02:00
Tobias Groza
794c74e514 Merge pull request #4360 from avently/player-overlays
Player overlays now centered
2020-10-02 15:37:28 +02:00
Stypox
fbcdaa77e3 Initialize player notification asap
Otherwise Android's foreground services implementation would complain
2020-10-02 15:17:04 +02:00
Stypox
dbdc04c45e Make player close button always white 2020-10-02 14:53:16 +02:00
Stypox
a4bb22280f Prevent touches behind minimized player 2020-10-02 14:53:16 +02:00
Stypox
c0e1bbbfb6 Fix queue close image not following theme 2020-10-02 14:52:49 +02:00
TobiGr
196b9dc771 Remove unused string resource "enable_lock_screen_video_thumbnail_title" 2020-10-01 16:43:04 +02:00
TobiGr
09578b4e46 Remove unused string resource "enable_lock_screen_video_thumbnail_summary" 2020-10-01 16:43:04 +02:00
TobiGr
4d88dadf8c Remove unused string resource "play_btn_text" 2020-10-01 16:43:04 +02:00
TobiGr
d4fda5847d Remove unused string resource "next_video_title" 2020-10-01 16:43:04 +02:00
Tobias Groza
b1ea7d6cbc Merge pull request #4350 from 4D17Y4/commenter
Disabled commenter image view on LoadThumbnail set to false
2020-10-01 15:07:45 +02:00
Tobias Groza
4e7632949d Merge pull request #4347 from avently/player-rebind
Player rebind
2020-10-01 15:03:43 +02:00
Avently
26a8bd147b Now player's overlays are aware of insets 2020-10-01 03:10:51 +03:00
Avently
dd726fac02 Skipped interception of buttons in the player in some cases and made image view from playQueue visible 2020-10-01 03:10:42 +03:00
Tobias Groza
3a3ecc7775 Merge pull request #4353 from opusforlife2/tap_behind_queue
Prevent tapping behind queue in main player
2020-09-30 15:04:35 +02:00
Avently
6665d630ec Added comments and improved the code 2020-09-30 00:49:34 +03:00
opusforlife2
7706d7471a Do the same for tablet layout.
Signed-off-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com>
2020-09-30 02:17:04 +05:30
TobiGr
ed87d6b268 Deleted translation using Weblate (Neapolitan) 2020-09-29 21:57:04 +02:00
Tobias Groza
ed51c8b318 Merge pull request #4340 from Stypox/notification-settings-fix
Small adjustments to notification settings layout
2020-09-29 21:55:29 +02:00
TobiGr
6ffbb7b1ed Remove linebreaks from localizations of notification_actions_summary 2020-09-29 21:46:47 +02:00
Stypox
06764db118 Small adjustments to notification settings layout 2020-09-29 21:46:00 +02:00
TobiGr
4864fa3f2d Merge remote-tracking branch 'Weblate/dev' into dev 2020-09-29 21:08:17 +02:00
ssantos
2d25b6a1f4 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 21:07:53 +02:00
Ajeje Brazorf
be76b3d105 Translated using Weblate (Sardinian)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 21:07:52 +02:00
thami simo
81cbeb4b24 Translated using Weblate (Arabic)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 21:07:52 +02:00
Gontzal Manuel Pujana Onaindia
f0b658ba14 Translated using Weblate (Basque)
Currently translated at 91.5% (549 of 600 strings)
2020-09-29 21:07:52 +02:00
ssantos
295836fc7e Translated using Weblate (Portuguese)
Currently translated at 96.0% (576 of 600 strings)
2020-09-29 21:07:52 +02:00
opusforlife2
54e9858148 Prevent tapping behind queue in main player
(I have no idea what I just did. Shhh! Don't tell anyone. 🤫 )
2020-09-29 18:57:49 +00:00
TobiGr
b68f015825 Update extractor version 2020-09-29 20:25:28 +02:00
Aditya Srivastava
87ae26ede3 Disabled commenter image view on LoadThumbnail set to false 2020-09-29 21:17:39 +05:30
zmni
49615f81b4 Translated using Weblate (Indonesian)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 12:34:37 +02:00
Terry Louwers
87ce5140fa Translated using Weblate (Dutch)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 12:34:37 +02:00
Tobias Groza
3ba9fb375c Merge pull request #4349 from wb9688/fix-queue-with-no-next-videos
Fix auto-queue with no next videos
2020-09-29 12:18:57 +02:00
wb9688
f4bd20361a Fix auto-queue with no next videos 2020-09-29 10:43:17 +02:00
Oymate
54f8a17aac Translated using Weblate (Bengali)
Currently translated at 63.1% (379 of 600 strings)
2020-09-29 06:58:42 +02:00
Юрий Иванович Шмаровский
7a1e5026c4 Translated using Weblate (Belarusian)
Currently translated at 75.3% (452 of 600 strings)
2020-09-29 06:58:35 +02:00
Allan Nordhøy
323161c6de Translated using Weblate (Norwegian Bokmål)
Currently translated at 93.8% (563 of 600 strings)
2020-09-29 06:58:34 +02:00
Ville Rantanen
1ac4890893 Translated using Weblate (Finnish)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:34 +02:00
Isak Holmström
e9c88fecc5 Translated using Weblate (Swedish)
Currently translated at 94.5% (567 of 600 strings)
2020-09-29 06:58:32 +02:00
Yaron Shahrabani
33deaaefac Translated using Weblate (Hebrew)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:32 +02:00
Jeff Huang
bb6438ebe4 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:32 +02:00
chr56
d42af74afa Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:31 +02:00
Eric
ea1f2f4ad4 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:31 +02:00
Samuel Carvalho de Araújo
95b45651bb Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:30 +02:00
WaldiS
0d5730d33e Translated using Weblate (Polish)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:30 +02:00
Emin Tufan Çetin
0625a35ddf Translated using Weblate (Turkish)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:30 +02:00
Rex_sa
2d3271ee13 Translated using Weblate (Arabic)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:29 +02:00
Krysa Czech
6da2e80027 Translated using Weblate (Czech)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:28 +02:00
Marian Hanzel
439edbf85c Translated using Weblate (Slovak)
Currently translated at 98.5% (591 of 600 strings)
2020-09-29 06:58:26 +02:00
THANOS SIOURDAKIS
e0237a0b86 Translated using Weblate (Greek)
Currently translated at 93.5% (561 of 600 strings)
2020-09-29 06:58:24 +02:00
ssantos
e1845ba603 Translated using Weblate (Portuguese)
Currently translated at 94.8% (569 of 600 strings)
2020-09-29 06:58:22 +02:00
pjammo
1cf757d401 Translated using Weblate (Italian)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:20 +02:00
AioiLight
14985b1727 Translated using Weblate (Japanese)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:20 +02:00
Nikita Epifanov
6b2788be57 Translated using Weblate (Russian)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:19 +02:00
Ben De Meester
ac888f4cb2 Translated using Weblate (Dutch)
Currently translated at 99.5% (597 of 600 strings)
2020-09-29 06:58:18 +02:00
Bopol
df06cfc4c5 Translated using Weblate (French)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:17 +02:00
Laura Arjona Reina
dcba3a681c Translated using Weblate (Spanish)
Currently translated at 99.8% (599 of 600 strings)
2020-09-29 06:58:16 +02:00
Bruno Tendler
7fd49c22a8 Translated using Weblate (Spanish)
Currently translated at 99.8% (599 of 600 strings)
2020-09-29 06:58:16 +02:00
TobiGr
314287a6d9 Translated using Weblate (German)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:15 +02:00
BurningKarl
f5e7b8f229 Translated using Weblate (German)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:15 +02:00
C. Rüdinger
de84db070e Translated using Weblate (German)
Currently translated at 100.0% (600 of 600 strings)
2020-09-29 06:58:15 +02:00
Avently
c1d5a5cd98 Player will be rebound when needed, prev/next/queue buttons, preserving paused state
- each time something starts to play in any player VideoDetailFragment will be started (if not yet started) and mini player will show up. It makes possible to see a playing stream in mini player even if the stream was started without using fragment or after player service was closed somehow
- play/next/queue buttons will be updated in realtime when stream was added/removed from queue instead of waiting for a onPlay/onPause action to happen
- when popup or background players start the stream will start playing only if paused state wasn't requested. Which means, for example, if a user opens popup it will be started when START_PAUSED is false. If, for example, the stream was played in main player and then popup was started the stream will still be playing, but if it was paused it still be paused in popup (or background) in APPEND_ONLY mode (but will be playing on new queue initialization)
2020-09-29 06:22:53 +03:00
opusforlife2
160a04c3c7 Merge pull request #4288 from avently/performance-increase
Performance increase
2020-09-28 17:45:25 +00:00
Oymate
23bfc30c57 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 62.5% (375 of 600 strings)
2020-09-28 10:13:01 +02:00
Oymate
d9329bffd1 Added translation using Weblate (Bengali) 2020-09-28 10:10:43 +02:00
Oğuz Ersen
bafc1df988 Translated using Weblate (Turkish)
Currently translated at 100.0% (600 of 600 strings)
2020-09-27 23:28:04 +02:00
David Braz
30b8835919 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (600 of 600 strings)
2020-09-27 19:08:02 +02:00
RachelB
44b19e75f6 Translated using Weblate (Spanish)
Currently translated at 95.1% (571 of 600 strings)
2020-09-27 18:55:56 +02:00
Laura Arjona Reina
2a558ad11d Translated using Weblate (Spanish)
Currently translated at 95.1% (571 of 600 strings)
2020-09-27 18:55:56 +02:00
Hosted Weblate
123d8972e1 Merge branch 'origin/dev' into Weblate. 2020-09-27 17:19:59 +02:00
Ajeje Brazorf
e550a8ea27 Translated using Weblate (Sardinian)
Currently translated at 99.5% (597 of 600 strings)
2020-09-27 17:19:44 +02:00
Kurd As
8b29460fed Translated using Weblate (Central Kurdish)
Currently translated at 99.6% (598 of 600 strings)
2020-09-27 17:19:44 +02:00
Allan Nordhøy
e380d63c57 Translated using Weblate (Norwegian Bokmål)
Currently translated at 90.5% (543 of 600 strings)
2020-09-27 17:19:43 +02:00
Yaron Shahrabani
89b4f2c4d4 Translated using Weblate (Hebrew)
Currently translated at 100.0% (600 of 600 strings)
2020-09-27 17:19:42 +02:00
Oğuz Ersen
6c2f63f738 Translated using Weblate (Turkish)
Currently translated at 100.0% (600 of 600 strings)
2020-09-27 17:19:41 +02:00
zmni
77c612f0f5 Translated using Weblate (Indonesian)
Currently translated at 100.0% (600 of 600 strings)
2020-09-27 17:19:41 +02:00
THANOS SIOURDAKIS
2d06c01192 Translated using Weblate (Greek)
Currently translated at 85.3% (512 of 600 strings)
2020-09-27 17:19:40 +02:00
AioiLight
d13c19f05f Translated using Weblate (Japanese)
Currently translated at 100.0% (600 of 600 strings)
2020-09-27 17:19:40 +02:00
Bopol
3d2ba05c77 Translated using Weblate (French)
Currently translated at 100.0% (600 of 600 strings)
2020-09-27 17:19:39 +02:00
Florian
6898b9d9a4 Translated using Weblate (French)
Currently translated at 100.0% (600 of 600 strings)
2020-09-27 17:19:39 +02:00
TobiGr
a65aaa6b83 Translated using Weblate (German)
Currently translated at 99.1% (595 of 600 strings)
2020-09-27 17:19:39 +02:00
nautilusx
b3136c20c4 Translated using Weblate (German)
Currently translated at 99.1% (595 of 600 strings)
2020-09-27 17:19:38 +02:00
Tobias Groza
0ae3dfd9cc Merge pull request #4315 from comradekingu/patch-12
String improvements
2020-09-27 17:12:20 +02:00
Avently
0370fa6c00 Merged 'dev' branch 2020-09-27 18:02:31 +03:00
Allan Nordhøy
7ab323b00c Spelling: Back to browser 2020-09-27 14:53:24 +00:00
Tobias Groza
541eb70b9c Merge pull request #4272 from avently/small-fixes2
Small fixes of issues with old devices support, brightness, etc
2020-09-27 15:31:02 +02:00
Avently
e53e5ca20e Disabled nested scrolling of queue 2020-09-27 15:50:21 +03:00
Avently
609bf64856 Merged 'dev' branch 2020-09-27 15:04:20 +03:00
bopol
a9fafe91a5 Merge pull request #4326 from Stypox/appid-numbers
Allow numbers and uppercase letters in app package id
2020-09-27 12:00:55 +02:00
Hosted Weblate
0466b320dd Merge branch 'origin/dev' into Weblate. 2020-09-27 11:24:38 +02:00
MohammedSR Vevo
5b74d22d0a Translated using Weblate (Kurdish)
Currently translated at 97.9% (578 of 590 strings)
2020-09-27 11:24:29 +02:00
THANOS SIOURDAKIS
4152c7f956 Translated using Weblate (Greek)
Currently translated at 86.6% (511 of 590 strings)
2020-09-27 11:24:29 +02:00
Tobias Groza
d5f603303d Merge pull request #4259 from TeamNewPipe/pref_migration
Add settings migration, remove "Detail page" option from share dialog and minimize to background by default
2020-09-27 11:20:39 +02:00
Tobias Groza
fc9c073a60 Merge pull request #3178 from cool-student/notificationImprovements
Notification Improvements
2020-09-27 10:43:11 +02:00
Avently
9a0c2c40bd Refactoring and made the player returning from landscape & fullscreen on vertical video to portrait after clicking on fullscreen button 2020-09-27 06:39:42 +03:00
Avently
d0fc9fda71 Fixed player's ZOOM mode for KitKat devices 2020-09-27 04:25:06 +03:00
Avently
df9823988e Changes for tablets and device's orientation behavior
- the app will not rotate the screen to portrait after video completes, it will just exit from fullscreen mode
- ability to rotate the orientation via fullscreen button from landscape to portrait when device has locked orientation in landscape
- ability to enter/exit to/from fullscreen on tablets with unlocked global orientation in portrait mode
2020-09-27 04:11:38 +03:00
Ajeje Brazorf
eeb09c074c Translated using Weblate (Sardinian)
Currently translated at 100.0% (590 of 590 strings)
2020-09-26 22:42:32 +02:00
Allan Nordhøy
af0928e2bd Translated using Weblate (Norwegian Bokmål)
Currently translated at 87.9% (519 of 590 strings)
2020-09-26 22:42:31 +02:00
WaldiS
d9cf4de3f7 Translated using Weblate (Polish)
Currently translated at 100.0% (590 of 590 strings)
2020-09-26 22:42:31 +02:00
zeritti
2d6dd4b3be Translated using Weblate (Czech)
Currently translated at 100.0% (590 of 590 strings)
2020-09-26 22:42:30 +02:00
pjammo
160312393a Translated using Weblate (Italian)
Currently translated at 100.0% (590 of 590 strings)
2020-09-26 22:42:30 +02:00
AioiLight
e3ff9f9c86 Translated using Weblate (Japanese)
Currently translated at 100.0% (590 of 590 strings)
2020-09-26 22:42:30 +02:00
Nikita Epifanov
95570d796d Translated using Weblate (Russian)
Currently translated at 100.0% (590 of 590 strings)
2020-09-26 22:42:29 +02:00
Bopol
cd0d58a915 Translated using Weblate (French)
Currently translated at 100.0% (590 of 590 strings)
2020-09-26 22:42:29 +02:00
TobiGr
2f1007c725 Translated using Weblate (German)
Currently translated at 98.9% (584 of 590 strings)
2020-09-26 22:42:28 +02:00
Stypox
b53d5d8c00 Allow numbers and uppercase letters in app package id 2020-09-26 22:32:11 +02:00
TobiGr
3c4a4e5384 Set default value for "minimize_on_exit" to background for better UX. 2020-09-26 21:58:38 +02:00
TobiGr
0e5f85db95 Remove "Detail Page" open action from share dialog under certain circumstances
With the new application workflow and unified player, video detail page and video player are the same activity. So show only one of these options based on whether autoplay is enabled or not, and show both if using external player
2020-09-26 21:58:34 +02:00
TobiGr
ad3364671d Add migration concept for shared preferences 2020-09-26 21:43:58 +02:00
Avently
3add24b8aa Merged 'dev' branch 2020-09-26 02:42:26 +03:00
Tobias Groza
e0f02d4080 Merge pull request #4246 from avently/preloading
Disabled preloading when switching streams
2020-09-25 21:22:31 +02:00
Allan Nordhøy
00c4c10472 String improvements 2020-09-25 08:06:18 +00:00
Yaron Shahrabani
a2b8cc9dc2 Translated using Weblate (Hebrew)
Currently translated at 100.0% (590 of 590 strings)
2020-09-25 09:14:16 +02:00
Jeff Huang
3465002cbb Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (590 of 590 strings)
2020-09-25 09:14:15 +02:00
Eric
631cb73305 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (590 of 590 strings)
2020-09-25 09:14:15 +02:00
Samuel Carvalho de Araújo
b3b6384bef Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (590 of 590 strings)
2020-09-25 09:14:15 +02:00
Oğuz Ersen
941ca575fb Translated using Weblate (Turkish)
Currently translated at 100.0% (590 of 590 strings)
2020-09-25 09:14:14 +02:00
zmni
2d65c3595d Translated using Weblate (Indonesian)
Currently translated at 100.0% (590 of 590 strings)
2020-09-25 09:14:14 +02:00
Bopol
b3812d913a Translated using Weblate (French)
Currently translated at 100.0% (590 of 590 strings)
2020-09-25 09:14:13 +02:00
Tobias Groza
adcc420c81 Merge pull request #4310 from B0pol/duplicate
Translations improvements
2020-09-24 20:24:00 +02:00
bopol
b97ad99bb4 lint translations 2020-09-24 18:27:24 +02:00
bopol
e93a2850d6 Translations improvements :
- remove duplicated string (name) and avoid potential duplicate (autoplay_never_description -> Never, autoplay_always_description -> Always because they are just "Always" or "Never"). Fixes #4268
- leakCanary string removed (fixes #4233)
2020-09-24 15:24:59 +02:00
TobiGr
411d0691fa Merge remote-tracking branch 'Weblate/dev' into dev 2020-09-24 15:13:04 +02:00
Avently
c843e77183 Made notification thumbnail smaller 2020-09-23 15:20:25 +03:00
Kornelijus Tvarijanavičius
093d6e5336 Translated using Weblate (Lithuanian)
Currently translated at 44.6% (261 of 584 strings)
2020-09-23 01:40:55 +02:00
bopol
8a3c752d42 Merge pull request #4275 from B0pol/quality
change default quality
2020-09-22 20:53:30 +02:00
Stypox
b4e073cde7 Show replay icon in notification when player state is completed 2020-09-22 18:17:16 +02:00
Stypox
814efbf8df Remove ACTION_BUFFERING, update buffering only if needed
- ACTION_BUFFERING was just wrong: why should the user be able to trigger the internal onBuffering() state by pressing on the buffering button? So that was replaced by a null intent, doing nothing.
- Now updating notification in onBuffering() only when buffering actions are not already buffering, to prevent useless updates
2020-09-22 18:17:16 +02:00
Stypox
11e048abb1 Remove hardcoded and duplicate strings, use exoplayer ones 2020-09-22 18:17:09 +02:00
wrghig
34e7855af6 Translated using Weblate (Hungarian)
Currently translated at 73.1% (427 of 584 strings)
2020-09-21 06:28:23 +02:00
Parsa Akhbar
de54dc27ad Translated using Weblate (Persian)
Currently translated at 96.5% (564 of 584 strings)
2020-09-20 11:39:52 +02:00
Mostafa Ahangarha
790133978d Translated using Weblate (Persian)
Currently translated at 96.5% (564 of 584 strings)
2020-09-20 11:39:52 +02:00
zeritti
b914d67d9d Translated using Weblate (Czech)
Currently translated at 100.0% (584 of 584 strings)
2020-09-20 11:39:51 +02:00
Avently
518eb97e3a Variable width for caption button and hiding system UI after popup close 2020-09-19 22:52:59 +03:00
Avently
f41549ccf1 Added a comment and excluded automatic switch of orientation on tablets and Android TVs 2020-09-19 17:21:01 +03:00
Tobias Groza
b69e477ecd Merge pull request #4029 from Stypox/search-suggestions
Improve search suggestion experience when remote ones can't be fetched
2020-09-19 16:01:30 +02:00
Stypox
0062ff9cfa Fix deprecations, warnings and useless null checks in SearchFragment 2020-09-19 15:25:04 +02:00
Stypox
f8de72f59f Improve search suggestion experience when remote ones can't be fetched
Do not show anything in case of network error (it can simply be ignored).
Show a snackbar otherwise, which still allows writing things into the search box.
2020-09-19 15:22:54 +02:00
Stypox
7317737e90 Fix invisible queue close button 2020-09-19 13:44:07 +02:00
Avently
5b8eda4805 Increased performance of the UI. main thread is not as busy as before 2020-09-17 23:42:35 +03:00
Avently
886a949a00 Enable/disable video after screen on/off regardless of background playback setting 2020-09-17 22:30:03 +03:00
Avently
92e13dafe5 Correct exit from fullscreen in case of error or close from notification, 2020-09-17 19:01:20 +03:00
Avently
c9be812330 Fix for untouchable area 2020-09-16 23:41:49 +03:00
Stypox
59e7ebabfa Random adjustements to notification 2020-09-16 14:00:22 +02:00
thami simo
1afc48fce3 Translated using Weblate (Arabic)
Currently translated at 100.0% (584 of 584 strings)
2020-09-16 10:36:11 +02:00
Avently
a1e4ef9e8e Fix for multiple listeners of insets 2020-09-16 04:49:26 +03:00
Avently
5ada0ae2c7 Hiding controls when orientation changes to landscape 2020-09-15 22:10:38 +03:00
Avently
a5312c1341 Perfect shadow 2020-09-15 19:50:46 +03:00
Avently
150e156d26 Reimagined player positioning 2020-09-15 14:43:43 +03:00
Avently
6d38615ea8 Android 11: transparent navigation and status bars 2020-09-14 11:30:41 +03:00
Avently
011cc7d337 Android 11 initial support 2020-09-14 02:46:00 +03:00
bopol
b747d09836 change default quality 2020-09-13 14:01:01 +02:00
bopol
4b7311bafd Merge pull request #3826 from Stypox/unsupported-url-dialog
Show dialog with open options in case of an unsupported url
2020-09-12 23:54:51 +02:00
bopol
eeba9c0a5f Merge remote-tracking branch 'upstream/dev' into unsupported-url-dialog 2020-09-12 23:19:18 +02:00
Stypox
11d9a037f7 Merge pull request #4252 from opusforlife2/autoplay_to_autoqueue2
Change Autoplay to Autoqueue to reduce ambiguity
2020-09-11 22:11:13 +02:00
Avently
883e4fcd7c Small fixes of issues with old devices support, brightness, etc 2020-09-11 20:52:38 +03:00
Stypox
2017e6a3e3 Refactor MediaSessionManager 2020-09-10 20:36:52 +02:00
Stypox
bccfe500b3 Fix seekbar invisible or not updating
Have the notification recreate only when strictly necessary, and recreate it if there was a timeline change, fixing the seekbar not updating at all sometimes
2020-09-10 20:22:22 +02:00
Stypox
5846fbabce Change "image" to "thumbnail" 2020-09-10 19:47:07 +02:00
Stypox
52e89c1d1c Prevent seeking out of video duration in player 2020-09-10 19:47:02 +02:00
Stypox
1605e50cef Update notification when play queue is edited 2020-09-10 13:36:34 +02:00
Stypox
2215ce58a4 Merge pull request #3794 from budde25/download-same-file-crash
Fixes crash when a file is deleted then redownloaded
2020-09-09 15:41:06 +02:00
Stypox
a13e6b69e3 Merge branch 'dev' into pr3178 2020-09-08 23:58:10 +02:00
Stjepan
1d6370e11c Translated using Weblate (Croatian)
Currently translated at 86.9% (508 of 584 strings)
2020-09-08 23:19:45 +02:00
Stypox
bd34c7ede3 Make player foreground playback-specific in manifest 2020-09-08 22:00:28 +02:00
Stypox
bc8954fbba Fix notification content intent not being updated when needed 2020-09-08 22:00:28 +02:00
Stypox
9cf0bc6c82 Make notification creation and cancelling more consistent 2020-09-08 22:00:28 +02:00
Stypox
71b32fe641 Add notification costumization settings menu 2020-09-08 22:00:24 +02:00
Stypox
530f745e44 Merge pull request #4154 from avently/video-placement
Prevent jumping of the player and wrong padding on devices with cutout
2020-09-08 19:47:09 +02:00
opusforlife2
a5a2313851 Use new string for auto-queue toggle 2020-09-08 10:44:58 +00:00
opusforlife2
4e98c2e7f6 Add string for auto queue toggle 2020-09-08 10:43:11 +00:00
Stypox
14486782dc Merge pull request #3909 from TacoTheDank/deprecations-and-cleanup
Deprecations and cleanup
2020-09-07 20:51:24 +02:00
Avently
31814b70da Disabled preloading when switching streams 2020-09-07 19:34:10 +03:00
Stypox
408e819d32 Extract duplicate setActivityTitle code to own function 2020-09-07 15:28:38 +02:00
Antony
622676f9bc Translated using Weblate (Greek)
Currently translated at 85.2% (498 of 584 strings)
2020-09-07 11:36:12 +02:00
Stypox
5b631e0387 Revert to deprecated BEHAVIOR_SET_USER_VISIBLE_HINT in TabAdapter
Also added comment explaining why
2020-09-06 14:02:25 +02:00
TacoTheDank
06d54ef77e Clean up SDK version checks 2020-09-06 12:55:30 +02:00
TacoTheDank
6c5ef567ed Replace deprecated Html#fromHtml with HtmlCompat#fromHtml 2020-09-06 12:55:30 +02:00
TacoTheDank
f86b40302d Some general-purpose lint cleanup 2020-09-06 12:55:26 +02:00
TacoTheDank
273c287fbf Fix some lambdas 2020-09-06 12:52:43 +02:00
TacoTheDank
6cb16be5df Use enhanced 'for' loops 2020-09-06 12:52:43 +02:00
TacoTheDank
a4feb3fc09 Fix some deprecations 2020-09-06 12:52:43 +02:00
TacoTheDank
ba6c7de35a Use AndroidX preference 2020-09-06 12:52:42 +02:00
TacoTheDank
79e2bb382f Update ExoPlayer, OkHttp, use Kotlin JDK8 2020-09-06 12:48:35 +02:00
pitachips
0090256ded Translated using Weblate (Korean)
Currently translated at 87.3% (510 of 584 strings)
2020-09-05 22:36:11 +02:00
TobiGr
00ce077758 Translated using Weblate (Korean)
Currently translated at 87.3% (510 of 584 strings)
2020-09-05 22:36:11 +02:00
Stypox
a801d0994f Merge pull request #4223 from avently/small-fixes
Small fixes of issues with brightness, background playback, gestures
2020-09-05 20:51:37 +02:00
Stypox
628575dc5f Clean up MediaSessionManager 2020-09-04 18:44:09 +02:00
Avently
0a22f21410 Small fixes of issues with brightness, background playback, gestures 2020-09-04 05:39:55 +03:00
Stypox
97ff9e9c5b Merge branch 'dev' into pr3178 2020-09-03 21:56:48 +02:00
Stypox
8b3a09306b Various notification code improvements
Improve builder parameters
Reorder code and extract large icon function
service.startForeground() now is also provided with service type in android versions >= Q
2020-09-03 21:54:31 +02:00
Stypox
7766fd13fd Extract hardcoded strings into strings.xml and improve them 2020-09-03 21:54:31 +02:00
Stypox
c79997ebe3 Show hourglass icon when buffering 2020-09-03 21:54:28 +02:00
Tobias Groza
0fd1e2fcd9 Merge pull request #4038 from vmazoyer/remember_dl_pref
Remember last selected media type for downloads.
2020-09-03 20:11:36 +02:00
vmazoyer
99442b6e04 Remember last selected media type for downloads. 2020-09-03 19:47:34 +02:00
Avently
b8a35e9e4a Moved device-specific code into DeviceUtils 2020-09-03 15:48:17 +03:00
Avently
e833d415e3 Fixed wrong padding on devices with cutout on vertical videos 2020-09-03 15:48:17 +03:00
Avently
8030312924 Prevent jumping of the player on devices with cutout 2020-09-03 15:48:17 +03:00
Stypox
a84b54f940 Merge pull request #4127 from nmurali94/bugfix-keep-license-on-rotate
Restore license pop-up when orientation changes
2020-09-02 16:28:43 +02:00
TobiGr
c66c81294e Remove unused and redundant code. 2020-09-01 17:39:06 +02:00
Tobias Groza
bfdc215c65 Merge pull request #4155 from avently/gestures-interception
Skipping interception of some gestures
2020-09-01 16:51:34 +02:00
Kahina Messaoudi
e10c7beedb Translated using Weblate (Kabyle)
Currently translated at 31.8% (186 of 584 strings)
2020-08-29 00:15:27 +02:00
zeritti
18c3286364 Translated using Weblate (Czech)
Currently translated at 100.0% (584 of 584 strings)
2020-08-29 00:15:27 +02:00
Tobias Groza
8d2ec30818 Merge pull request #4120 from mhmdanas/replace-SubtitlesStream-getURL-with-getUrl
Use SubtitlesStream#getUrl instead of getURL
2020-08-26 22:15:44 +02:00
mhmdanas
e5ffddfc6b Use SubtitlesStream#getUrl instead of getURL 2020-08-26 23:04:18 +03:00
Hakim Oubouali
59fc1e4b5f Translated using Weblate (Berber)
Currently translated at 24.3% (142 of 584 strings)
2020-08-26 18:13:49 +02:00
ssantos
7ead581953 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (584 of 584 strings)
2020-08-22 10:36:37 +02:00
Ivo Andonov
b860980df4 Translated using Weblate (Bulgarian)
Currently translated at 65.2% (381 of 584 strings)
2020-08-22 10:36:37 +02:00
Kim Nyberg
97a366d62e Translated using Weblate (Swedish)
Currently translated at 99.3% (580 of 584 strings)
2020-08-22 10:36:36 +02:00
ssantos
9ad68097d0 Translated using Weblate (Portuguese)
Currently translated at 100.0% (584 of 584 strings)
2020-08-22 10:36:36 +02:00
wb9688
331999fb95 Merge pull request #4158 from TobiGr/code-improvements
Remove duplicate code
2020-08-21 16:41:11 +02:00
Luna Jernberg
6519d7051d Translated using Weblate (Swedish)
Currently translated at 99.1% (579 of 584 strings)
2020-08-19 14:33:14 +02:00
TobiGr
552d585fca Extract common part from if 2020-08-18 13:28:13 +02:00
Avently
24c24d6c72 Skipping interception of some gestures 2020-08-17 20:42:05 +03:00
wb9688
b7f50c3e12 Merge pull request #4080 from avently/cpu-usage-improvement
Reduced CPU usage when playing a video by 7-10%
2020-08-17 09:46:07 +02:00
Avently
aed1687a45 Improved an animation logic 2020-08-16 22:44:27 +03:00
nmurali94
daa427dc15 Restore license pop up after orientation change 2020-08-16 12:23:40 +02:00
Tobias Groza
e9d4303fdb Merge pull request #4134 from avently/bottom-space
Set bottom padding of the main fragment when the mini player is visible
2020-08-16 12:15:08 +02:00
Tobias Groza
5485e994ee Merge pull request #4138 from XiangRongLin/checkstyle_final
Add checkstyle rule to show final local variable violations as warning
2020-08-16 11:55:46 +02:00
wb9688
87228673b4 Use final where possible 2020-08-16 10:25:09 +02:00
Xiang Rong Lin
d306513319 Add checkstyle rule to show final local variable violations as warning 2020-08-16 10:25:09 +02:00
Stypox
e08480f345 Completely remove old player notification 2020-08-15 22:03:32 +02:00
Tobias Groza
13c9096417 Merge pull request #4130 from Stypox/swipe-queueitem-fix
[regression] Close player in onPlaybackShutdown()
2020-08-15 20:25:54 +02:00
Avently
d3d65c8e3a Set bottom padding of the main fragment when the mini player is visible 2020-08-15 20:51:52 +03:00
Stypox
12ac5ef781 [regression] Close player in onPlaybackShutdown() 2020-08-15 15:58:25 +02:00
Stypox
adef9a8acf Rename notification functions: they are not background player only 2020-08-15 15:16:17 +02:00
Tobias Groza
5ef407d15f Merge pull request #4126 from gkeegan/contributor-discussions
Add need for contributors to discuss possible changes
2020-08-15 11:44:52 +02:00
Hakim Oubouali
970b636eb4 Translated using Weblate (Berber)
Currently translated at 17.9% (105 of 584 strings)
2020-08-15 00:56:30 +02:00
ssantos
fcf9131aae Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (584 of 584 strings)
2020-08-15 00:56:28 +02:00
sivemortenfan
7fd27fac45 Translated using Weblate (Malayalam)
Currently translated at 100.0% (584 of 584 strings)
2020-08-15 00:56:19 +02:00
Juraj Liso
6e17af91fb Translated using Weblate (Czech)
Currently translated at 98.4% (575 of 584 strings)
2020-08-15 00:56:18 +02:00
Juraj Liso
68555573ad Translated using Weblate (Slovak)
Currently translated at 97.9% (572 of 584 strings)
2020-08-15 00:56:18 +02:00
Keegan
fb9905a89e Add need for contributors to discuss possible changes
This was discussed in IRC, and should help prevent the occurrence of problems where people spend hours on a feature only for it to be rejected for miscellaneous reasons.
2020-08-14 11:27:06 -05:00
Hakim Oubouali
1e7504dc5a Added translation using Weblate (Berber) 2020-08-14 10:14:05 +02:00
Tobias Groza
d7af019511 Merge pull request #4115 from nmurali94/bugfix-remove-timestamp-from-livestream
Remove timestamp from url when sharing a live stream
2020-08-12 17:57:18 +02:00
nmurali94
04bb070afa Remove timestamp when sharing a live stream 2020-08-12 09:54:35 -04:00
Sérgio Marques
e693d80857 Added translation using Weblate (Portuguese (Portugal)) 2020-08-12 01:30:16 +02:00
Allan Nordhøy
45ae05f1b5 Translated using Weblate (Norwegian Bokmål)
Currently translated at 88.3% (516 of 584 strings)
2020-08-11 13:32:55 +02:00
Allan Nordhøy
05a83beb44 Translated using Weblate (Norwegian Bokmål)
Currently translated at 88.1% (515 of 584 strings)
2020-08-10 10:32:51 +02:00
Tobias Groza
8a1a42e83b Merge pull request #3948 from TobiGr/basic_resize
Add basic resize functionality
2020-08-08 21:34:12 +02:00
BenjaminForster
1b3f3cedb3 Translated using Weblate (Afrikaans)
Currently translated at 4.6% (27 of 584 strings)
2020-08-07 14:32:58 +02:00
TobiGr
f815ae5973 Translated using Weblate (Filipino)
Currently translated at 11.1% (65 of 584 strings)
2020-08-07 14:32:58 +02:00
David Rebolo Magariños
fb7035bf22 Translated using Weblate (Galician)
Currently translated at 100.0% (584 of 584 strings)
2020-08-07 14:32:57 +02:00
Deleted User
02bcbc3221 Translated using Weblate (Malay)
Currently translated at 72.0% (421 of 584 strings)
2020-08-07 14:32:54 +02:00
TobiGr
f0a51d4ab4 Translated using Weblate (Belarusian)
Currently translated at 77.7% (454 of 584 strings)
2020-08-07 14:32:53 +02:00
TobiGr
24fe8fe9a0 Translated using Weblate (Azerbaijani)
Currently translated at 10.2% (60 of 584 strings)
2020-08-07 14:32:53 +02:00
TobiGr
244e95d959 Translated using Weblate (Telugu)
Currently translated at 23.9% (140 of 584 strings)
2020-08-07 14:32:53 +02:00
Kim Nyberg
ad72c64e32 Translated using Weblate (Swedish)
Currently translated at 98.6% (576 of 584 strings)
2020-08-07 14:32:53 +02:00
Yaron Shahrabani
767ac6a51b Translated using Weblate (Hebrew)
Currently translated at 100.0% (584 of 584 strings)
2020-08-07 14:32:52 +02:00
TobiGr
3a6f87659a Translated using Weblate (Armenian)
Currently translated at 13.0% (76 of 584 strings)
2020-08-07 14:32:52 +02:00
Samuel Carvalho de Araújo
b7ac16c7d9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (584 of 584 strings)
2020-08-07 14:32:52 +02:00
Emin Tufan Çetin
f9f84cbd89 Translated using Weblate (Turkish)
Currently translated at 100.0% (584 of 584 strings)
2020-08-07 14:32:52 +02:00
TobiGr
701c87eefa Translated using Weblate (Ukrainian)
Currently translated at 98.9% (578 of 584 strings)
2020-08-07 14:32:52 +02:00
karnak2016
5379cf0544 Translated using Weblate (Ukrainian)
Currently translated at 98.9% (578 of 584 strings)
2020-08-07 14:32:51 +02:00
TobiGr
6b5c37f17f Translated using Weblate (Greek)
Currently translated at 83.2% (486 of 584 strings)
2020-08-07 14:32:51 +02:00
Terry Louwers
50b2fad180 Translated using Weblate (Dutch)
Currently translated at 100.0% (584 of 584 strings)
2020-08-07 14:32:51 +02:00
ButterflyOfFire
b9cb65b24e Translated using Weblate (Kabyle)
Currently translated at 31.5% (184 of 584 strings)
2020-08-06 15:51:51 +02:00
Avently
d7574973e9 Reduced CPU usage when playing a video by 7-10% 2020-08-05 12:46:25 +03:00
Tobias Groza
ff48c93d59 Merge pull request #4050 from TeamNewPipe/popup-setting-text
Reduce redundancy in "Remeber popup size and position" setting
2020-08-04 23:23:03 +02:00
Tobias Groza
d2e6700dd1 Merge pull request #4066 from BoFFire/dev
Fixing name for Taqbaylit language
2020-08-03 19:17:11 +02:00
Selyan Sliman Amiri
05b8c3f35f Translated using Weblate (Kabyle)
Currently translated at 31.1% (182 of 584 strings)
2020-08-03 19:16:19 +02:00
ButterflyOfFire
70b643e7ba Fixing name for Taqbaylit language
Fixing name for Taqbaylit language
2020-08-03 19:01:51 +02:00
TobiGr
dce973a519 Add basic resize functionality
Addresses #3947
See 9e352df1ed
See https://developer.samsung.com/samsung-dex/modify-optimizing.html#Enabling-Multi-Window-support
2020-08-03 18:40:32 +02:00
Tobias Groza
eb2f75579a Merge pull request #3892 from wb9688/fix-local-playlist-tab
Fix crash when opening video in local playlist tab
2020-08-03 15:49:06 +02:00
Tobias Groza
773316ce4f Merge pull request #4061 from avently/unsupported_api
Removed java.util.Objects calls
2020-08-03 15:32:56 +02:00
wb9688
5fd7ae33b4 Replace getFragmentManager() with getFM() 2020-08-03 14:47:10 +02:00
wb9688
13a065f2dc Fix crash when opening video in local playlist tab 2020-08-03 14:47:10 +02:00
wb9688
1a8ff81087 Use AppCompatImageButton to not crash on 4.4 2020-08-03 14:17:12 +02:00
Tobias Groza
65637fce40 Merge pull request #3944 from Stypox/playlist-layout
Improve playlist header layout: align with info items
2020-08-03 14:15:30 +02:00
ButterflyOfFire
b11fa7a28e Translated using Weblate (Kabyle)
Currently translated at 6.5% (38 of 584 strings)
2020-08-03 13:04:58 +02:00
Avently
45408caf33 Removed java.util.Objects calls 2020-08-03 03:33:51 +03:00
Stypox
963ee4dbab Merge branch 'dev' into pr3178 2020-08-02 22:59:43 +02:00
TobiGr
3b46d5a440 Translated using Weblate (Catalan)
Currently translated at 85.2% (498 of 584 strings)
2020-08-02 13:20:01 +02:00
Kim Nyberg
212fddd8e1 Translated using Weblate (Swedish)
Currently translated at 98.2% (574 of 584 strings)
2020-08-02 13:20:01 +02:00
ktln
433485470e Translated using Weblate (Armenian)
Currently translated at 12.8% (75 of 584 strings)
2020-08-02 13:20:00 +02:00
TobiGr
e160a1f794 Translated using Weblate (Armenian)
Currently translated at 12.8% (75 of 584 strings)
2020-08-02 13:20:00 +02:00
WaldiS
7911b7e637 Translated using Weblate (Polish)
Currently translated at 100.0% (584 of 584 strings)
2020-08-02 13:19:59 +02:00
Oğuz Ersen
7fc5a77e7e Translated using Weblate (Turkish)
Currently translated at 99.6% (582 of 584 strings)
2020-08-02 13:19:59 +02:00
TobiGr
f0fb55640e Translated using Weblate (Romanian)
Currently translated at 69.5% (406 of 584 strings)
2020-08-02 13:19:59 +02:00
TobiGr
d5685f2255 Translated using Weblate (Slovenian)
Currently translated at 55.9% (327 of 584 strings)
2020-08-02 13:19:58 +02:00
Igor Nedoboy
a732233db6 Translated using Weblate (Russian)
Currently translated at 100.0% (584 of 584 strings)
2020-08-02 13:19:55 +02:00
Laszlo Almasi
a5918c29ee Translated using Weblate (Hungarian)
Currently translated at 72.9% (426 of 584 strings)
2020-08-02 13:19:55 +02:00
TobiGr
3b719803bb Translated using Weblate (Hungarian)
Currently translated at 72.9% (426 of 584 strings)
2020-08-02 13:19:54 +02:00
TobiGr
d77463c9f1 Translated using Weblate (German)
Currently translated at 100.0% (584 of 584 strings)
2020-08-02 13:19:53 +02:00
TobiGr
2d8fd9bedf Translated using Weblate (Hungarian)
Currently translated at 62.3% (364 of 584 strings)
2020-08-02 12:07:46 +02:00
Laszlo Almasi
b17a667a9d Translated using Weblate (Hungarian)
Currently translated at 62.3% (364 of 584 strings)
2020-08-02 12:07:46 +02:00
Tobias Groza
b7287a070b Make title and summary of "Remeber popup size and position" setting less redundant 2020-08-02 11:07:48 +02:00
Tobias Groza
fbb5c8cdd6 Update bug_report.md 2020-08-02 09:37:52 +02:00
Artyom
baaf2815e4 Translated using Weblate (Russian)
Currently translated at 100.0% (584 of 584 strings)
2020-08-01 19:03:30 +02:00
Tobias Groza
d8b5549fd9 Merge pull request #2907 from avently/unifiedplayer
Expandable player with unified UI
2020-08-01 12:53:19 +02:00
wb9688
6de03f2bf0 Fix crash when playing stream in background with shuffle in notification 2020-07-31 09:25:32 +02:00
wb9688
caf7c55069 Log only in debug build 2020-07-31 09:10:28 +02:00
wb9688
7d499ffba1 Use vector drawables for close and replay 2020-07-31 09:10:28 +02:00
cool-student
4abf6b2f5c Notification Improvements
- add MediaStyle notifications for Background and Popup playback
- reduce excessive notification updating ( / recreating of Notification.Builder object) when playing background / popup media
- add new buffering state indicator (can be disabled)
- upscale close icon / downscale replay icon
- add notification slot settings
- move notification settings to appearance
- fix Metadata (song title, artist and album art) sometimes not being set correctly
- other misc notification fixes

Co-authored-by: wb9688 <wb9688@users.noreply.github.com>
2020-07-31 09:10:28 +02:00
Mateo Coltura
a842b06301 Translated using Weblate (Flemish)
Currently translated at 73.6% (430 of 584 strings)
2020-07-30 23:12:37 +02:00
Oymate
0bca4925d7 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 32.7% (191 of 584 strings)
2020-07-30 23:12:37 +02:00
Éfrit
ae3953cbec Translated using Weblate (French)
Currently translated at 100.0% (584 of 584 strings)
2020-07-30 23:12:36 +02:00
Raymundo
a99667c54c Translated using Weblate (Spanish)
Currently translated at 100.0% (584 of 584 strings)
2020-07-30 23:12:35 +02:00
Ajeje Brazorf
465963a8c2 Translated using Weblate (Sardinian)
Currently translated at 99.4% (581 of 584 strings)
2020-07-30 01:08:02 +02:00
Digiwizkid
b9b4762faf Translated using Weblate (Bengali (India))
Currently translated at 61.6% (360 of 584 strings)
2020-07-30 01:07:50 +02:00
MohammedSR Vevo
a56b128a4b Translated using Weblate (Kurdish)
Currently translated at 99.3% (580 of 584 strings)
2020-07-30 01:07:49 +02:00
sudoLife
d43cc089fd Translated using Weblate (Lithuanian)
Currently translated at 42.9% (251 of 584 strings)
2020-07-30 01:07:49 +02:00
Jeff Huang
771513d287 Translated using Weblate (Chinese (Traditional))
Currently translated at 99.8% (583 of 584 strings)
2020-07-30 01:07:48 +02:00
Samuel Carvalho de Araújo
c387678217 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.8% (583 of 584 strings)
2020-07-30 01:07:48 +02:00
thami simo
847368718b Translated using Weblate (Arabic)
Currently translated at 100.0% (584 of 584 strings)
2020-07-30 01:07:48 +02:00
pjammo
458b3daac3 Translated using Weblate (Italian)
Currently translated at 99.8% (583 of 584 strings)
2020-07-30 01:07:47 +02:00
AioiLight
3ea5278b12 Translated using Weblate (Japanese)
Currently translated at 99.8% (583 of 584 strings)
2020-07-30 01:07:47 +02:00
sudoLife
20b9748a8c Translated using Weblate (Russian)
Currently translated at 100.0% (584 of 584 strings)
2020-07-30 01:07:46 +02:00
Artyom
79487adbec Translated using Weblate (Russian)
Currently translated at 100.0% (584 of 584 strings)
2020-07-30 01:07:46 +02:00
sudoLife
89f3fca6b1 Translated using Weblate (English)
Currently translated at 99.8% (583 of 584 strings)
2020-07-30 01:07:46 +02:00
Tobias Groza
f290b2bf5a Merge pull request #3982 from Stypox/fix-acra
Fix ACRA bug reports not containing stack trace
2020-07-29 17:58:52 +02:00
Stypox
04e7d13043 Remove deprecated calls to set Sender class to ACRA
setReportSenderFactoryClasses() is deprecated, now extensions (ReportSenderFactory is an extension) should be registered using AutoService: https://github.com/ACRA/acra/wiki/Custom-Extensions#by-annotation
2020-07-29 10:56:33 +02:00
Avently
e41218c46b Disable starting player service via media button when there is nothing to play (no active play queue) 2020-07-28 21:36:06 +03:00
Avently
8562fbdbbe Merge branch 'dev' 2020-07-28 21:35:01 +03:00
Anxhelo Lushka
c841d7a32b Translated using Weblate (Albanian)
Currently translated at 100.0% (584 of 584 strings)
2020-07-28 17:20:25 +02:00
Ville Rantanen
8827ae4d2c Translated using Weblate (Finnish)
Currently translated at 100.0% (584 of 584 strings)
2020-07-28 17:20:24 +02:00
chr56
2e1029e157 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (584 of 584 strings)
2020-07-28 17:20:24 +02:00
Rafael Gonçalves
a7dc0c2d55 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.8% (577 of 584 strings)
2020-07-28 17:20:24 +02:00
WaldiS
7f1749d853 Translated using Weblate (Polish)
Currently translated at 100.0% (584 of 584 strings)
2020-07-28 17:20:23 +02:00
zmni
b4a34d58db Translated using Weblate (Indonesian)
Currently translated at 100.0% (584 of 584 strings)
2020-07-28 17:20:23 +02:00
ssantos
4a50fcab2c Translated using Weblate (Portuguese)
Currently translated at 100.0% (584 of 584 strings)
2020-07-28 17:20:23 +02:00
Bernd N
c9ef089199 Translated using Weblate (German)
Currently translated at 100.0% (584 of 584 strings)
2020-07-28 17:20:14 +02:00
TobiGr
94ecf9a081 Merge remote-tracking branch 'Weblate/dev' into dev 2020-07-28 13:46:36 +02:00
Stypox
67aaa9a655 Make local playlist layout consistent with remote one 2020-07-28 13:10:28 +02:00
Tobias Groza
823f5640f7 Merge pull request #3984 from TobiGr/search_color
Fix color of correct / suggested search term
2020-07-28 13:03:51 +02:00
TobiGr
45d1c63895 Fix color of correct / suggested search term
Fixes TeamNewPipe/NewPipe#3973
2020-07-28 11:41:23 +02:00
Tobias Groza
d3b6781bb8 Update bug report template
DO NOT REPORT THAT CRASH ANY MORE. WE ARE GETTING SPAMMED11!!!1
2020-07-28 10:56:06 +02:00
Stypox
21d1f69d6d Do not init ACRA if inside its own process
https://github.com/ACRA/acra/wiki/Troubleshooting-Guide#applicationoncreate
2020-07-28 10:48:54 +02:00
Stypox
1b9f5989ef Fix empty stacktrace in bug report
ACRA has to be initialized after MultiDex
https://github.com/ACRA/acra/issues/619
https://github.com/ACRA/acra/wiki/Troubleshooting-Guide#legacy-multidex
2020-07-28 10:48:25 +02:00
Hosted Weblate
348e46ff3b Merge branch 'origin/dev' into Weblate. 2020-07-28 09:55:58 +02:00
TobiGr
7918e3a1aa Update version to 0.19.8 (953)
Update extractot version.
Fix extraction of YouTube's decryption function.
2020-07-28 01:07:13 +02:00
TobiGr
d6d8c7830c Merge branch 'dev' 2020-07-26 14:40:57 +02:00
TobiGr
f53a0f0d07 Update version to 0.19.7 (952) 2020-07-26 14:39:18 +02:00
TobiGr
17edd1c3d4 Add changelog 2020-07-26 14:38:49 +02:00
TobiGr
b0685c153a Update extractor version 2020-07-26 14:26:09 +02:00
Hosted Weblate
a5ca20ee4c Merge branch 'origin/dev' into Weblate. 2020-07-26 13:22:12 +02:00
Stypox
03685db2fc Improve playlist header layout: align with info items 2020-07-26 12:04:40 +02:00
Avently
68ed738dcd Renamed files 2020-07-25 09:45:33 +03:00
Avently
5293d17e32 Removed unused files, translations, styles, settings key 2020-07-25 09:39:42 +03:00
Avently
f2e4b69466 Another part of UI improvements for Android TV
- focus will be hidden right after start of a video; fullscreen works like this too
- back button will not needed to be pressed one more time like before
- prev & next buttons for playqueue will be hidden with play/pause button before video be ready to play
2020-07-25 07:00:53 +03:00
Avently
ec8b00042b Merged the latest code 2020-07-25 04:18:41 +03:00
Avently
08db1d59e5 Android TV: ability to select all buttons in the main player, as well as in the main fragment 2020-07-25 04:14:29 +03:00
Avently
7c79d421e8 Quality selector for external playback and better fullscreen mode for old devices 2020-07-24 00:43:09 +03:00
TobiGr
ade5e38fa5 Disable shrinkResources to fix F-Droid's reproducible build
For more information see https://f-droid.org/docs/Reproducible_Builds/#resource-shrinker
2020-07-23 21:19:47 +02:00
Tobias Groza
7385aa09a8 Merge pull request #3928 from wb9688/disable-shrink-resources
Disable shrinkResources
2020-07-23 18:02:44 +02:00
wb9688
185a5fad88 Disable shrinkResources 2020-07-23 13:24:48 +02:00
Hosted Weblate
a1e2477d14 Merge branch 'origin/dev' into Weblate. 2020-07-22 19:15:38 +02:00
Ajeje Brazorf
a1200a5fff Added translation using Weblate (Sardinian) 2020-07-22 19:15:35 +02:00
Avently
91a0257c8f Fixes for Android API <21 2020-07-22 17:19:32 +03:00
Tobias Groza
53ffc82fe2 Merge pull request #3920 from chdir/invisible_focus_fix
Fix lingering focus highlight overlay
2020-07-22 14:28:47 +02:00
Alexander--
801267df18 Add @NonNull annotation to method argument
Co-authored-by: Tobias Groza <TobiGr@users.noreply.github.com>
2020-07-22 07:57:04 -04:00
Alexander--
6e73e0b395 Use View.isShown() to avoid focus overlay glitches
A View can become focused while being invisible, if it's
parent is invisible. Use global draw listener and
View.isShown() to catch such cases.
2020-07-22 06:21:25 +06:59
Avently
7aa8a5c368 Fixed a situation when background playback could use a video stream instead of an audio stream 2020-07-22 02:20:58 +03:00
Avently
3ecbbea7cb Better TV support, icons, activity_main refactoring
- on Android TV you'll be able to navigate with D-pad in main fragment and in the player. But not between them for now
- play/pause/next/previous buttons are smaller now
- replaced ic_list with previous version of it
- activity_main looks better which helps with Android TV support
2020-07-22 01:20:30 +03:00
Avently
77cd3182f1 Removed unused line 2020-07-21 01:53:59 +03:00
Avently
c7ccf9bab8 AndroidTvUtils -> DeviceUtils 2020-07-21 01:43:49 +03:00
Avently
06e70abb86 Merged the latest changes 2020-07-21 01:37:36 +03:00
TobiGr
88c86e88b0 Update extractor version 2020-07-20 20:34:02 +02:00
Hosted Weblate
2d6cf48532 Merge branch 'origin/dev' into Weblate. 2020-07-18 15:48:16 +02:00
Tobias Groza
19e152a54b Merge pull request #3689 from wb9688/next-stream
Remove calls to getNextStream()
2020-07-18 11:59:04 +02:00
Tobias Groza
2898bead66 Merge pull request #3902 from wb9688/null-description
Check for description == null
2020-07-18 11:54:10 +02:00
Tobias Groza
381c329441 Merge pull request #3884 from wb9688/use-androidx-annotation
Use androidx.annotation.NonNull instead of io.reactivex.annotations.NonNull
2020-07-18 11:51:08 +02:00
Hosted Weblate
a3de3705f7 Merge branch 'origin/dev' into Weblate. 2020-07-18 10:16:37 +02:00
Mario Rossi
dc3dc6b77f Added translation using Weblate (Neapolitan) 2020-07-18 10:16:35 +02:00
wb9688
e028a63f30 Check for description == null 2020-07-18 10:01:44 +02:00
Avently
d196f8b4b2 New icons 2020-07-16 01:15:24 +03:00
wb9688
4274827dbe Use relatedItems instead of info.getRelatedStreams() 2020-07-15 18:52:36 +02:00
wb9688
7a30f4a7d2 Remove calls to getNextStream() 2020-07-14 21:27:59 +02:00
wb9688
d0c03a0211 Use androidx.annotation.NonNull instead of io.reactivex.annotations.NonNull 2020-07-14 21:15:29 +02:00
Avently
787b136d13 NonNull instead of NotNull annotations 2020-07-14 22:08:12 +03:00
Avently
08412d6108 Mini player slide to botom fix, buttons size fix 2020-07-14 21:52:55 +03:00
Avently
d8f7db4715 Made checkStyle happy 2020-07-14 20:21:32 +03:00
Avently
bff238774e Small fixes of issues 2020-07-13 23:28:39 +03:00
Avently
d2aaa6f691 Merged the latest changes 2020-07-13 04:17:21 +03:00
Tobias Groza
c900ef036c Merge pull request #3863 from TeamNewPipe/release_0.19.6
Release 0.19.6
2020-07-12 20:25:05 +02:00
Avently
b2164ce5fc Marked many (too many) variables as final 2020-07-12 03:59:47 +03:00
TobiGr
d088d432c5 Release NewPipe 0.19.6 (951) 2020-07-11 21:47:19 +02:00
TobiGr
c9fafbe198 Add changelog 2020-07-11 21:47:19 +02:00
TobiGr
2d909b0514 Remove untranslatable translations 2020-07-11 21:47:19 +02:00
TobiGr
c2a012553d Use %1$d instead of %1$s in deleted_downloads string
We don't pass a String, but an Integer
2020-07-11 21:47:19 +02:00
TobiGr
085f0266ac Fix Linter (translations) 2020-07-11 20:52:20 +02:00
Tobias Groza
7ede2daa3c Merge pull request #3861 from TeamNewPipe/languages
add the new languages translated in NewPipe
2020-07-08 22:45:16 +02:00
bopol
713c53d170 add bengali (india) and arabic (lybia) 2020-07-08 21:34:34 +02:00
TobiGr
110b3a6a8f Add some new languages to the language selector
ckb - Central Kurdish
jv - Javanese
kab - Kabyle
2020-07-07 22:32:14 +02:00
Tobias Groza
e12e6dd7a7 Merge pull request #3441 from wb9688/nextpage
Next page stuff
2020-07-07 21:19:27 +02:00
wb9688
e183fc6118 Bump NPE version 2020-07-07 21:04:08 +02:00
wb9688
dd57e246b8 Use getNextPage() instead of getNextPageUrl() 2020-07-07 21:03:24 +02:00
Tobias Groza
f4a4172369 Merge pull request #3471 from Royosef/DisplaySearchSuggestion
Display search suggestion: did you mean & showing result for
2020-07-07 20:57:02 +02:00
TobiGr
b96d1714b5 Highlight search suggestion 2020-07-07 20:23:41 +02:00
TobiGr
dbd809b040 Merge remote-tracking branch 'Weblate/dev' into dev 2020-07-07 18:41:55 +02:00
Tobias Groza
ff4e6b139d Merge pull request #3579 from TobiGr/error_md_export
Add Markdown export of crash logs
2020-07-07 00:01:12 +02:00
Tobias Groza
af098aaac8 Merge pull request #3843 from kapodamy/drop-writting-application-metadata
Drop writting application metadata in muxed files
2020-07-06 23:55:06 +02:00
Digiwizkid
5d7e62c736 Translated using Weblate (Bengali (India))
Currently translated at 61.0% (352 of 577 strings)
2020-07-06 12:56:56 +02:00
Prajwol Pradhan
e2ead011f5 Translated using Weblate (Nepali)
Currently translated at 99.8% (576 of 577 strings)
2020-07-06 12:56:54 +02:00
Allan Nordhøy
a067c950e1 Translated using Weblate (Norwegian Bokmål)
Currently translated at 89.0% (514 of 577 strings)
2020-07-06 12:56:54 +02:00
Yaron Shahrabani
3369618cfa Translated using Weblate (Hebrew)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:54 +02:00
Jeff Huang
4e9b6520e5 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:53 +02:00
Eric
a074203fae Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:53 +02:00
Samuel Carvalho de Araújo
42092e3f28 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:53 +02:00
WaldiS
c07b34e298 Translated using Weblate (Polish)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:53 +02:00
Emin Tufan Çetin
ef5f181328 Translated using Weblate (Turkish)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:52 +02:00
ssantos
a7ea2fcf92 Translated using Weblate (Portuguese)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:52 +02:00
AioiLight
e81730715c Translated using Weblate (Japanese)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:52 +02:00
Lesya Novaselskaya
c09f2ad482 Translated using Weblate (Russian)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:52 +02:00
B0pol
84aef8b5b5 Translated using Weblate (French)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:51 +02:00
nautilusx
8fac3e8221 Translated using Weblate (German)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:51 +02:00
Prajwol Pradhan
5874ed781d Translated using Weblate (Nepali)
Currently translated at 99.8% (576 of 577 strings)
2020-07-06 12:56:49 +02:00
Ishwor Ghimire
d8f29bd7a7 Translated using Weblate (Nepali)
Currently translated at 99.8% (576 of 577 strings)
2020-07-06 12:56:48 +02:00
kapodamy
8120b6aaaa checkstyle's amend 2020-07-05 23:55:40 -03:00
Stypox
13a0d1de70 Replace search query without searching on suggestion panel long click 2020-07-05 22:01:35 +02:00
Stypox
20e828be51 Improve suggestion panel 2020-07-05 22:01:35 +02:00
wb9688
ccd82fb8b8 Improve search suggestion code 2020-07-05 22:01:35 +02:00
Roy Yosef
0711650ff8 Fix search suggestions not working on resume
* add searchSuggestionString, isCorrectedSearch fields to state and load onResume
2020-07-05 22:01:35 +02:00
Roy Yosef
4194ac2226 Display search suggestion: did you mean & showing result for 2020-07-05 22:01:35 +02:00
Stypox
248e2d7ee0 Merge pull request #3506 from Royosef/PlaylistTab
Add playlist tab to main page
2020-07-05 21:06:23 +02:00
Tobias Groza
1daa654051 Merge pull request #3845 from B0pol/peertube_instance
add privacytools.io peertube instance to manifest
2020-07-05 14:37:11 +02:00
bopol
d751434979 add privacytools.io peertube instance to manifest 2020-07-04 11:39:00 +02:00
Stypox
8cc21920b7 Move local/remote playlist merge() to PlaylistLocalItem class
In order not to have a utils class just for one function
2020-07-04 11:38:22 +02:00
Stypox
248212588d Fix style issues 2020-07-04 11:38:22 +02:00
Roy Yosef
13c0fdef08 Final declarations, naming & redundant code
* add final declarations where missing
* fix typo "onSelectedLisener" to "onSelectedListener"
* rename "baseEqual" to "baseEquals"
* replace getPlaylistsObserver code with functions pointers
* remove duplicate code in constructors
* remove useless null checks
2020-07-04 11:37:38 +02:00
Roy Yosef
dfc27b2480 Add playlist tab to main page
Add bookmarked playlist as tab in the main page (by Settings > Content > Content of main page)
2020-07-04 11:35:45 +02:00
kapodamy
b2d78d380b update WebMWriter.java 2020-07-03 20:51:45 -03:00
Tobias Groza
faa6cb5c7d Merge pull request #3841 from B0pol/update_inv_instances
update invidious instances
2020-07-03 15:38:10 +02:00
kapodamy
452977abdf Drop "writing/muxed by" metadata
* All muxers (mp4, webm and ogg) are affected
* solve some checkstyle's errors (building was blocked)

Mp4FromDashWriter:
* drop "writing application"
* drop "handler name"

OggFromWebMWriter:
* drop "writing application" for OPUS and VORBIS header

WebMWriter:
* Drop "Muxing application"
* Drop "Writing application"
2020-07-03 02:07:42 -03:00
Hosted Weblate
93570b2f59 Merge branch 'origin/dev' into Weblate. 2020-07-03 00:18:10 +02:00
Ville Rantanen
073f5c2c8c Translated using Weblate (Finnish)
Currently translated at 100.0% (576 of 576 strings)
2020-07-03 00:18:06 +02:00
bopol
1b4313f847 update extractor version 2020-07-02 23:17:38 +02:00
Tobias Groza
07cead7e99 Merge pull request #3404 from mauriciocolli/feed-add-filter-sub-list
Add filter to the feed group dialog to show only ungrouped subscriptions
2020-07-02 22:53:11 +02:00
bopol
f128751aba update invidious instances 2020-07-02 21:41:23 +02:00
Tobias Groza
9516d9da17 Merge pull request #3837 from Stypox/audio-sync
Fix audio/video desync caused by floating point cumulative errors
2020-07-02 21:29:18 +02:00
Hosted Weblate
e9aafc2a56 Merge branch 'origin/dev' into Weblate. 2020-07-02 17:34:57 +02:00
Digiwizkid
a91a6575e0 Translated using Weblate (Bengali (India))
Currently translated at 50.3% (290 of 576 strings)
2020-07-02 17:34:56 +02:00
Milo Ivir
5bd4093dfb Translated using Weblate (Croatian)
Currently translated at 86.6% (499 of 576 strings)
2020-07-02 17:34:54 +02:00
WaldiS
f9890e2016 Translated using Weblate (Polish)
Currently translated at 100.0% (576 of 576 strings)
2020-07-02 17:34:54 +02:00
BenjaminForster
00529fe134 Added translation using Weblate (Afrikaans) 2020-07-02 17:34:51 +02:00
Stypox
5e9dce7d39 Merge pull request #3774 from eames-palmer/status-bar-color
Update status color to match toolbar color
2020-07-01 08:39:32 +02:00
Tobias Groza
734680b9f0 Merge pull request #3373 from mauriciocolli/feed-add-search-sub-list
Add search for subscription picker in the feed group dialog
2020-07-01 00:16:01 +02:00
Tobias Groza
d0b5345252 Merge pull request #3822 from Stypox/fix-queue-colors
Fix queue channel name color for some devices
2020-07-01 00:13:29 +02:00
Tobias Groza
c2b4a44a59 Merge pull request #3828 from wb9688/checkstyle-fix
Do not include Checkstyle in any APK
2020-06-30 23:19:33 +02:00
Stypox
38c79bbc11 Fix audio/video desync caused by floating point cumulative errors 2020-06-30 22:41:09 +02:00
Digiwizkid
0d7028a36c Translated using Weblate (Bengali (India))
Currently translated at 36.1% (208 of 576 strings)
2020-06-30 19:32:06 +02:00
Digiwizkid
b63f687491 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 30.9% (178 of 576 strings)
2020-06-30 19:32:02 +02:00
Digiwizkid
952b636569 Added translation using Weblate (Bengali (India)) 2020-06-30 12:20:19 +02:00
Jasper Eames Palmer
e14ba48244 Update non-service status bar colors to match toolbar colors 2020-06-29 22:30:09 -07:00
Hosted Weblate
720c8c31ec Merge branch 'origin/dev' into Weblate. 2020-06-29 13:30:45 +02:00
Allan Nordhøy
4e8407ed8f Translated using Weblate (Norwegian Bokmål)
Currently translated at 89.2% (514 of 576 strings)
2020-06-29 13:30:43 +02:00
Enol P
26c5f69161 Translated using Weblate (Asturian)
Currently translated at 51.0% (294 of 576 strings)
2020-06-29 13:30:43 +02:00
Anonim Qwerty
078ae15794 Translated using Weblate (Ukrainian)
Currently translated at 98.9% (570 of 576 strings)
2020-06-29 13:30:42 +02:00
ssantos
16ae90dc9f Translated using Weblate (German)
Currently translated at 100.0% (576 of 576 strings)
2020-06-29 13:30:42 +02:00
TobiGr
3de5afc68e Add Markdown export of crash logs
Add app language as additional debug information to reports
2020-06-28 16:07:22 +02:00
wb9688
a7e8f5087e Do not include Checkstyle in any APK 2020-06-28 14:59:44 +02:00
Stypox
5cc60ed760 Show dialog with open options in case of an unsupported url 2020-06-28 13:33:08 +02:00
Mauricio Colli
2e6e75cd4e Add filter to the feed group dialog to show only ungrouped subscriptions 2020-06-27 11:58:40 -03:00
Mauricio Colli
9f3b35634a Fix subscription picker items flickering in the feed group dialog
The adapter could not tell the items were the same because the
subscription class was missing some methods (i.e. equals and hashcode),
so a full rebind was being done.
2020-06-27 11:25:31 -03:00
Mauricio Colli
c24dfc63dc Add search for subscription picker in the feed group dialog 2020-06-27 11:25:25 -03:00
Stypox
c029929850 Fix queue channel name color for some devices 2020-06-27 16:11:25 +02:00
Tobias Groza
d9100913d5 Merge pull request #3787 from budde25/fix-popup-queue
Fixes enqueue resuming paused videos
2020-06-27 11:56:08 +02:00
Avently
a7fbe05a73 Changes for review 2020-06-27 06:25:50 +03:00
Tobias Groza
e03d970bc2 Merge pull request #3406 from B0pol/playlist_peertube
Add possibility to open PeerTube links in NP for some instances (without needing to share to NP)
2020-06-26 18:53:49 +02:00
Tobias Groza
fe4516ea23 Merge pull request #3752 from Redirion/exoupdate
Update to ExoPlayer 2.11.6
2020-06-26 18:33:53 +02:00
Jwtiyar Nariman
039b47b872 Translated using Weblate (Central Kurdish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-25 14:41:53 +02:00
Robin
0a57a8a7f3 2.11.6 2020-06-25 09:26:59 +02:00
Jwtiyar Nariman
8c823f3a2d Added translation using Weblate (Central Kurdish) 2020-06-24 11:12:52 +02:00
Éfrit
33f3a4f455 Translated using Weblate (French)
Currently translated at 100.0% (576 of 576 strings)
2020-06-24 04:41:44 +02:00
Zackz
2ecf8044d7 Translated using Weblate (Javanese)
Currently translated at 10.9% (63 of 576 strings)
2020-06-22 19:41:47 +02:00
Zackz
c9a1eb55b5 Added translation using Weblate (Javanese) 2020-06-21 18:02:36 +02:00
Ayoub Rejal
76bb1fd61e Translated using Weblate (Arabic (Libya))
Currently translated at 6.7% (39 of 576 strings)
2020-06-19 17:41:46 +02:00
ButterflyOfFire
1275b26ba0 Translated using Weblate (Kabyle)
Currently translated at 4.3% (25 of 576 strings)
2020-06-19 17:41:45 +02:00
Andreas Westrell
a470a4af9b Translated using Weblate (Swedish)
Currently translated at 99.1% (571 of 576 strings)
2020-06-19 17:41:45 +02:00
Marian Hanzel
487c9ebbd4 Translated using Weblate (Slovak)
Currently translated at 96.1% (554 of 576 strings)
2020-06-19 17:41:43 +02:00
Ethan Budd
c796e2ae3c Fixes crash when a file is deleted then redownloaded 2020-06-18 22:43:06 -05:00
Ayoub Rejal
746cab92f0 Added translation using Weblate (Arabic (Libya)) 2020-06-18 17:17:41 +02:00
Ethan Budd
33266a96ff fixes enqueue resuming paused videos 2020-06-17 22:15:50 -05:00
Hosted Weblate
2816889d8d Merge branch 'origin/dev' into Weblate. 2020-06-17 14:25:36 +02:00
ButterflyOfFire
58e177b3e4 Added translation using Weblate (Kabyle) 2020-06-17 14:25:34 +02:00
wb9688
5cfd8bbb56 Merge pull request #3704 from Stypox/keep-failed-streams
Do not remove items generating errors form queue
2020-06-15 15:16:26 +02:00
wb9688
e6fe6fd645 Merge pull request #3437 from TheLastGimbus/fast-rewind-forward-in-background-activity
Fast rewind forward in background activity
2020-06-15 15:06:58 +02:00
TheLastGimbus
63e167b38e Add buttons also in landscape mode 2020-06-14 23:22:31 +02:00
TheLastGimbus
abe77c4783 Change to final 2020-06-14 19:52:58 +02:00
TheLastGimbus
72af51fe9d Add speed button to top bar 2020-06-14 15:16:04 +02:00
TheLastGimbus
36b4134838 Remove speed buttons from bottom menu 2020-06-14 15:15:38 +02:00
Hosted Weblate
cf6ee26fdb Merge branch 'origin/dev' into Weblate. 2020-06-14 12:41:48 +02:00
Oymate
858111e623 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 30.5% (176 of 576 strings)
2020-06-14 12:41:42 +02:00
Oğuz Ersen
367e625804 Translated using Weblate (Turkish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-14 12:41:42 +02:00
Freddy Morán Jr
ae4d9c7f80 Translated using Weblate (Spanish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-14 12:41:42 +02:00
nautilusx
6c6ee41346 Translated using Weblate (German)
Currently translated at 99.8% (575 of 576 strings)
2020-06-14 12:41:41 +02:00
Tobias Groza
9ef7688f9e Merge pull request #3772 from adinilfeld/copy-video-title
Copy video title
2020-06-13 16:38:14 +02:00
Jasper Eames Palmer
7d6e226c2b Update status color to match toolbar color 2020-06-11 19:29:15 -07:00
adinilfeld
17d1346a8a made ClipboardManager final 2020-06-11 09:36:57 -07:00
adinilfeld
59e0c10c42 inverted if-else statement 2020-06-11 09:36:05 -07:00
adinilfeld
0d29e66092 removed unnecessary setLongClickable 2020-06-11 09:33:05 -07:00
wb9688
caa000f447 Merge pull request #3759 from Stypox/fix-search
Fix search crash: adapter array index out of bounds
2020-06-11 13:06:30 +02:00
adinilfeld
267e114354 added a copyToClipboard method to ShareUtils, and modified CommentsMiniInfoItemHolder and VideoDetailFragment to use the new method. 2020-06-10 15:14:08 -07:00
adinilfeld
b5375396d2 allowed user to copy video title to clipboard (from detail screen) 2020-06-10 14:17:43 -07:00
adinilfeld
e34f666b70 set an OnLongClickListener 2020-06-10 14:11:06 -07:00
Theophine Savio Theodore
19334b4f96 Translated using Weblate (Malayalam)
Currently translated at 100.0% (576 of 576 strings)
2020-06-10 21:41:40 +02:00
Theophine Savio Theodore
1fa609e539 Translated using Weblate (Malayalam)
Currently translated at 100.0% (576 of 576 strings)
2020-06-09 21:03:02 +02:00
Ville Rantanen
d9ce25a721 Translated using Weblate (Finnish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-09 16:22:19 +02:00
thami simo
5b8fc25da6 Translated using Weblate (Arabic)
Currently translated at 100.0% (576 of 576 strings)
2020-06-09 16:22:16 +02:00
Hosted Weblate
c70968dcf1 Merge branch 'origin/dev' into Weblate. 2020-06-08 14:14:35 +02:00
Ville Rantanen
77147510fb Translated using Weblate (Finnish)
Currently translated at 83.5% (481 of 576 strings)
2020-06-08 14:14:33 +02:00
Freddy Morán Jr
7092577482 Translated using Weblate (Spanish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-08 14:14:29 +02:00
Stypox
3e70050056 Fix search crash: adapter array index out of bounds 2020-06-07 21:28:54 +02:00
Tobias Groza
1f23c814e5 Merge pull request #3698 from B0pol/openWebsite
avoid duplicate: use openUrlInBrowser instead of openWebsite
2020-06-06 18:44:24 +02:00
Robin
145e0a0b7b Update to ExoPlayer 2.11.5 2020-06-06 15:29:52 +02:00
pitachips
e68e787e7a Translated using Weblate (Korean)
Currently translated at 84.3% (486 of 576 strings)
2020-06-05 19:41:39 +02:00
WaldiS
903308d285 Translated using Weblate (Polish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-04 02:41:47 +02:00
zmni
a4d8388b2e Translated using Weblate (Indonesian)
Currently translated at 100.0% (576 of 576 strings)
2020-06-04 02:41:46 +02:00
Hosted Weblate
3e83f9f956 Merge branch 'origin/dev' into Weblate. 2020-06-01 19:41:43 +02:00
Anxhelo Lushka
4063221313 Translated using Weblate (Albanian)
Currently translated at 100.0% (577 of 577 strings)
2020-06-01 19:41:39 +02:00
zeritti
c3c8d80919 Translated using Weblate (Czech)
Currently translated at 100.0% (577 of 577 strings)
2020-06-01 19:41:39 +02:00
notramo
3ae71c73c4 Translated using Weblate (Hungarian)
Currently translated at 60.6% (350 of 577 strings)
2020-06-01 19:41:38 +02:00
Stypox
b3db8c9549 Do not remove items generating errors form queue 2020-05-31 14:06:22 +02:00
bopol
596eb4a0f9 Merge branch 'dev' into playlist_peertube 2020-05-31 12:26:46 +02:00
bopol
6b0381b903 avoid duplicate: use openUrlInBrowser instead of openWebsite 2020-05-31 12:17:54 +02:00
TobiGr
049c8f70cd Release version 0.19.5 (950) 2020-05-30 10:43:10 +02:00
TobiGr
353bf69550 Add changelog for 0.19.5 (950) 2020-05-30 10:42:39 +02:00
Tobias Groza
cdb989ede3 Merge pull request #3679 from wb9688/fix-android-10
Enable requestLegacyExternalStorage
2020-05-30 10:32:46 +02:00
Tobias Groza
74079e4238 Merge pull request #3680 from wb9688/fix-kiosks
Open the correct kiosk
2020-05-30 10:28:24 +02:00
wb9688
c89746214c Open the correct kiosk 2020-05-30 08:14:54 +02:00
wb9688
1d97db3ef9 Enable requestLegacyExternalStorage 2020-05-30 08:05:40 +02:00
Hosted Weblate
e43fdf5ef9 Merge branch 'origin/dev' into Weblate. 2020-05-29 07:29:53 +02:00
Ishwor Ghimire
3984fc075f Translated using Weblate (Nepali)
Currently translated at 99.8% (576 of 577 strings)
2020-05-29 07:29:53 +02:00
Allan Nordhøy
ee2a159374 Translated using Weblate (Norwegian Bokmål)
Currently translated at 89.0% (514 of 577 strings)
2020-05-29 07:29:52 +02:00
Eric
57c9b29ba3 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:52 +02:00
Samuel Carvalho de Araújo
927ea72337 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:51 +02:00
Mostafa Ahangarha
123bdbd13b Translated using Weblate (Persian)
Currently translated at 96.1% (555 of 577 strings)
2020-05-29 07:29:51 +02:00
WaldiS
0baa5a2f04 Translated using Weblate (Polish)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:46 +02:00
Emin Tufan Çetin
7d98a70028 Translated using Weblate (Turkish)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:45 +02:00
Rex_sa
73b72ab01c Translated using Weblate (Arabic)
Currently translated at 99.8% (576 of 577 strings)
2020-05-29 07:29:45 +02:00
B0pol
e8cf71f41c Translated using Weblate (Esperanto)
Currently translated at 97.5% (563 of 577 strings)
2020-05-29 07:29:44 +02:00
ssantos
5f2d2a64d2 Translated using Weblate (Portuguese)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:44 +02:00
AioiLight
27bf3901de Translated using Weblate (Japanese)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:44 +02:00
TotalCaesar659
16d4fa03a5 Translated using Weblate (English)
Currently translated at 99.8% (576 of 577 strings)
2020-05-29 07:29:43 +02:00
Terry Louwers
bef579ec26 Translated using Weblate (Dutch)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:43 +02:00
Edoardo Regni
0cff10e02e Translated using Weblate (Dutch)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:41 +02:00
Terry Louwers
4c6f7238dd Translated using Weblate (Dutch)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:41 +02:00
bopol
55027a9b2b bump extractor version 2020-05-11 21:41:56 +02:00
bopol
0a984ca8c8 remove duplicate host in manifest 2020-05-11 21:41:56 +02:00
bopol
929d13bfea add support for some peertube instances
The one used for tests, popular instances and user wanted (e.g. la quadrature du net, video.lqdn.fr, or @TheAssassin instance, media-assassinate-you.net)
2020-05-11 21:41:56 +02:00
TheLastGimbus
1975973ff2 Update progress bar on fast forward/rewind 2020-04-25 15:46:56 +02:00
TheLastGimbus
63afacc067 Add listeners in activity 2020-04-14 22:06:32 +02:00
TheLastGimbus
3175199787 Add fast-rewind/forward buttons in layout 2020-04-14 22:05:51 +02:00
Avently
398cbe9284 Better backstack, better tablet support, switching players confirmation, fix for background playback 2020-03-10 12:06:38 +03:00
Avently
d87e488c23 Fix for a ripple effect on a button 2020-02-29 22:13:07 +03:00
Avently
5c2ff9b777 Better implementation of old code 2020-02-29 02:57:54 +03:00
Avently
6d7e37610c Vertical videos in portrait & fullscreen, UI enhancements for tablets and phones, fixes
- vertical videos now work ok in portrait and fullscreen mode at the same time
- auto pause on back press is disabled for large tablets
- large dragable area for swipe to bottom in fullscreen mode in place of top controls
- appbar will be scrolled to top when entering in fullscreen mode
2020-02-25 02:15:22 +03:00
Avently
a47e6dd8c5 AppBarLayout scrolling awesomeness, PlayQueue layout touches interception, player's controls' margin
- made scrolling in appBarLayout awesome
- PlayQueue layout was intercepting touches while it was in GONE visibility state. Now it's not gonna happen
- removed margin between two lines of player's controls
- when a user leaves the app with two back presses the app will not stop MainPlayer service if popup or background players play
2020-02-12 22:33:23 +03:00
Avently
f334a2740f Mini player, ExpandableSurfaceView with ZOOM support, popup
- mini player's title, image and author information will be updated in many situations but the main idea is that the info will be the same as currently playing stream. If nothing played then you'll see the info about currently opened stream in fragment. When MainPlayer service stops the info updates too
- made ExpandableSurfaceView to replace AspectRatioFrameLayout. The reason for that is to make possible to use aspect ratio mode ZOOM. It's impossible to show a stream inside AspectRatioFrameLayout with ZOOM mode and to fit the video view to a screen space at the same time. Now the new view able to do that and to show vertical videos in a slightly wide space for them
- refactored some methods to make the code more understandable
- made fixes for player view for landscape-to-landscape orientation change
- added Java docs
- adapted swipe tracking inside bottom sheet
- fixed PlayQueue crashes on clearing
- paddings for popup player now as small as possible
2020-02-05 08:59:30 +03:00
Avently
26e487c01a Hotfix 2020-01-26 07:33:52 +03:00
Avently
cc438fdb7b Player's elements positioning is better for tablet and in multiWindow mode
- status bar got a fix for situation when a phone vendor did not provide status bar height for landscape orientation
- popup will not be init'd twice
- also fixed some non-reproduceable bugs
2020-01-17 17:37:53 +03:00
Avently
92ff98d99a New logic for handling global orientation
- added a button to manually change an orientation of a video
- adapted UI for an automatic global orientation too
2020-01-16 14:20:22 +03:00
Avently
d1609cba90 Enhancements to background playback and media button handling 2020-01-15 21:32:29 +03:00
Avently
0c394b123c Another fix of VideoDetailFragment 2020-01-13 19:24:28 +03:00
Avently
421b8214cb Fixes of VideoDetailFragment 2020-01-10 17:32:05 +03:00
Avently
6fc91312d2 Changed default autoplay type to "Only on WiFi" 2020-01-09 19:27:10 +03:00
Avently
22bb129bd9 Autoplay enhancement and new button at the top left corner
- added a video close button to the top left corner
- autoplay will not work if stream plays in background or popup players
2020-01-09 18:28:06 +03:00
Avently
4c57893312 New features and fixes
- added autoplay options inside settings: always, only on wifi, never
- now statusbar will be shown in fullscreen mode
- playlists, channels can be autoplayed too (if enabled)
- changed title of background activity to Play queue
- fixed a crash
2020-01-08 19:16:50 +03:00
Avently
a2d5314cf7 Fourth block of fixes for review
- wrote more methods to PlayQueue. Now it supports internal history of played items with ability to play previous() item. Also it has equals() to check whether queues has the same content or not
- backstack in fragment is more powerful now with help of PlayQueue's history and able to work great with playlists' PlayQueue and SinglePlayQueue at the same time
- simplified logic inside fragment. Easy to understand. New PlayQueue will be added in backstack from only one place; less number of setInitialData() calls
- BasePlayer now able to check PlayQueue and compare it with currently playing. And if it is the same queue it tries to not init() it twice. It gives possibility to have a great backstack in fragment since the same queue will not be played from two different instances and will not be added to backstack twice  with duplicated history inside
- better support of Player.STATE_IDLE
- worked with layouts of player and made them better and more universal
- service will be stopped when activity finishes by a user decision
- fixed a problem related to ChannelPlayQueue and PlaylistPlayQueue in initial start of fragment
- fixed crash in popup
2020-01-06 13:39:01 +03:00
Avently
e063967734 Third block of fixes for review
- audio-only streams plays the same way as video streams
- fullscreen mode for tablet with controls on the right place
- hidden controls while swiping mini player down
- mini player works better
2020-01-03 19:19:14 +03:00
Avently
4519dd010d Second block of fixes for review
- hide/show controls with respect of SystemUI. In fullscreen mode controls will stay away from NavigationBar
- notification from running service will be hidden if a user disabled background playback
- fixed incorrect handling of a system method in API 19
- better MultiWindow support
2020-01-03 08:05:31 +03:00
Avently
bc2dc8d933 First block of fixes for review
- popup player click event changed to show/hide buttons
- queue panel WORKS. Finally
- removed theme overriding in fragment
- added scroll to top after stream selection
- adjusted padding/margin of buttons in player
- player will itself in fullscreen after user hides it in fullscreen mode and then expands it again while video still playing
2019-12-31 19:06:39 +03:00
Avently
fc9b63298c Optimizations and fixes of rare situations
- popup after orientation change had incorrect allowed bounds for swiping
- popup could cause a crash after many quick switches to main player and back
- better method of setting fullscreen/non-fullscreen layout using thumbnail view. Also fixed thumbnail height in fullscreen layout
- global settings observer didn't work when a user closed a service manually via notification because it checked for service existing
- app will now exits from fullscreen mode when the user switches players
- playQueuePanel has visibility "gone" by default (not "invisible") because "invisible" can cause problems
2019-12-31 05:07:07 +03:00
Avently
c45514b989 All players in one place
- main, background, popup players now connected via one service, one view, one fragment, one activity and one gesture listener
- playback position is synchronized between players. Easy to switch from one to another
- expandable player at the bottom of the screen with cool animation and additional features like long click to open channel of a video, play/pause/close buttons and swipe down to dismiss
- in-player integrated buttons for opening in browser, playing with Kodi, sharing a video
- better background playback that can be activated in settings. Allows to automatically switch to audio-only mode when going to background and then switching to video-mode when returning to the app.
2019-12-30 00:15:01 +03:00
423 changed files with 18499 additions and 10041 deletions

View File

@@ -33,6 +33,7 @@ with your GitHub account.
## Code contribution
* If you want to add a feature or change one, please open an issue describing your change. This gives the team and community a chance to give feedback before you spend any time on something that could be done differently or not done at all. It also prevents two contributors from working on the same thing and one being disappointed when only one user's code can be added.
* Stick to NewPipe's style conventions: follow [checkStyle](https://github.com/checkstyle/checkstyle). It will run each time you build the project.
* Do not bring non-free software (e.g. binary blobs) into the project. Also, make sure you do not introduce Google
libraries.

View File

@@ -13,8 +13,8 @@ android {
resValue "string", "app_name", "NewPipe"
minSdkVersion 19
targetSdkVersion 29
versionCode 940
versionName "0.19.4"
versionCode 955
versionName "0.20.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
@@ -33,7 +33,7 @@ android {
// suffix the app id and the app name with git branch name
def workingBranch = getGitWorkingBranch()
def normalizedWorkingBranch = workingBranch.replaceAll("[^A-Za-z]+", "").toLowerCase()
def normalizedWorkingBranch = workingBranch.replaceFirst("^[^A-Za-z]+", "").replaceAll("[^0-9A-Za-z]+", "")
if (normalizedWorkingBranch.isEmpty() || workingBranch == "master" || workingBranch == "dev") {
// default values when branch name could not be determined or is master or dev
applicationIdSuffix ".debug"
@@ -50,7 +50,7 @@ android {
// TODO: update Gradle version
release {
minifyEnabled true
shrinkResources true
shrinkResources false // disabled to fix F-Droid's reproducible build
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
archivesBaseName = 'app'
}
@@ -84,11 +84,17 @@ ext {
checkstyleVersion = '8.32'
stethoVersion = '1.5.1'
leakCanaryVersion = '2.2'
exoPlayerVersion = '2.11.4'
exoPlayerVersion = '2.11.8'
androidxLifecycleVersion = '2.2.0'
androidxRoomVersion = '2.2.5'
groupieVersion = '2.8.0'
markwonVersion = '4.3.1'
googleAutoServiceVersion = '1.0-rc7'
}
configurations {
checkstyle
ktlint
}
checkstyle {
@@ -106,8 +112,7 @@ task runCheckstyle(type: Checkstyle) {
exclude '**/BuildConfig.java'
exclude 'main/java/us/shandian/giga/**'
// empty classpath
classpath = files()
classpath = configurations.checkstyle
showViolations true
@@ -117,10 +122,6 @@ task runCheckstyle(type: Checkstyle) {
}
}
configurations {
ktlint
}
task runKtlint(type: JavaExec) {
main = "com.pinterest.ktlint.Main"
classpath = configurations.ktlint
@@ -138,12 +139,12 @@ afterEvaluate {
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "frankiesardo:icepick:${icepickVersion}"
kapt "frankiesardo:icepick-processor:${icepickVersion}"
debugImplementation "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
ktlint "com.pinterest:ktlint:0.35.0"
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
@@ -163,18 +164,21 @@ dependencies {
exclude module: 'support-annotations'
}
implementation 'com.github.TeamNewPipe:NewPipeExtractor:98055a3c3c17f2543a63d375a44c1d1f557fa76e'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:350eed6214b93255d788dfa208b1e9a5e5da91e6'
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
implementation "org.jsoup:jsoup:1.13.1"
implementation "com.squareup.okhttp3:okhttp:3.12.11"
implementation "com.squareup.okhttp3:okhttp:3.12.12"
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
implementation "com.google.android.material:material:1.1.0"
compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}"
kapt "com.google.auto.service:auto-service:${googleAutoServiceVersion}"
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.1.0"
@@ -183,7 +187,6 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-extensions:${androidxLifecycleVersion}"
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
@@ -206,7 +209,7 @@ dependencies {
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "com.jakewharton.rxbinding2:rxbinding:2.2.0"
implementation "org.ocpsoft.prettytime:prettytime:4.0.5.Final"
implementation "org.ocpsoft.prettytime:prettytime:4.0.6.Final"
}
static String getGitWorkingBranch() {

View File

@@ -21,13 +21,13 @@ public class ErrorInfoTest {
@Test
public void errorInfoTestParcelable() {
ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request",
final ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request",
R.string.general_error);
// Obtain a Parcel object and write the parcelable object to it:
Parcel parcel = Parcel.obtain();
final Parcel parcel = Parcel.obtain();
info.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
final ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
assertEquals(UserAction.USER_REPORT, infoFromParcel.userAction);
assertEquals("youtube", infoFromParcel.serviceName);

View File

@@ -1,6 +1,5 @@
package org.schabi.newpipe
import android.content.Context
import androidx.multidex.MultiDex
import androidx.preference.PreferenceManager
import com.facebook.stetho.Stetho
@@ -11,11 +10,6 @@ import okhttp3.OkHttpClient
import org.schabi.newpipe.extractor.downloader.Downloader
class DebugApp : App() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun onCreate() {
super.onCreate()
initStetho()
@@ -34,6 +28,12 @@ class DebugApp : App() {
return downloader
}
override fun initACRA() {
// install MultiDex before initializing ACRA
MultiDex.install(this)
super.initACRA()
}
private fun initStetho() {
// Create an InitializerBuilder
val initializerBuilder = Stetho.newInitializerBuilder(this)

View File

@@ -1,47 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.schabi.newpipe">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<application
android:banner="@mipmap/newpipe_tv_banner"
android:name=".App"
android:allowBackup="true"
android:banner="@mipmap/newpipe_tv_banner"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
android:theme="@style/OpeningTheme"
android:resizeableActivity="true"
tools:ignore="AllowBackup">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
<receiver android:name="androidx.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<service
android:name=".player.BackgroundPlayer"
android:exported="false">
android:name=".player.MainPlayer"
android:exported="false"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
@@ -49,36 +54,20 @@
<activity
android:name=".player.BackgroundPlayerActivity"
android:launchMode="singleTask"
android:label="@string/title_activity_background_player"/>
<activity
android:name=".player.PopupVideoPlayerActivity"
android:launchMode="singleTask"
android:label="@string/title_activity_popup_player"/>
<service
android:name=".player.PopupVideoPlayer"
android:exported="false"/>
<activity
android:name=".player.MainVideoPlayer"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/VideoPlayerTheme"/>
android:label="@string/title_activity_play_queue"
android:launchMode="singleTask" />
<activity
android:name=".settings.SettingsActivity"
android:label="@string/settings"/>
android:label="@string/settings" />
<activity
android:name=".about.AboutActivity"
android:label="@string/title_activity_about"/>
android:label="@string/title_activity_about" />
<service android:name=".local.subscription.services.SubscriptionsImportService"/>
<service android:name=".local.subscription.services.SubscriptionsExportService"/>
<service android:name=".local.feed.service.FeedLoadService"/>
<service android:name=".local.subscription.services.SubscriptionsImportService" />
<service android:name=".local.subscription.services.SubscriptionsExportService" />
<service android:name=".local.feed.service.FeedLoadService" />
<activity
android:name=".PanicResponderActivity"
@@ -86,25 +75,25 @@
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".ExitActivity"
android:label="@string/general_error"
android:theme="@android:style/Theme.NoDisplay"/>
<activity android:name=".report.ErrorActivity"/>
android:theme="@android:style/Theme.NoDisplay" />
<activity android:name=".report.ErrorActivity" />
<!-- giga get related -->
<activity
android:name=".download.DownloadActivity"
android:label="@string/app_name"
android:launchMode="singleTask"/>
android:launchMode="singleTask" />
<service android:name="us.shandian.giga.service.DownloadManagerService"/>
<service android:name="us.shandian.giga.service.DownloadManagerService" />
<activity
android:name=".util.FilePickerActivityHelper"
@@ -118,7 +107,7 @@
<activity
android:name=".ReCaptchaActivity"
android:label="@string/recaptcha"/>
android:label="@string/recaptcha" />
<provider
android:name="androidx.core.content.FileProvider"
@@ -127,7 +116,7 @@
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/nnf_provider_paths"/>
android:resource="@xml/nnf_provider_paths" />
</provider>
<activity
@@ -139,165 +128,203 @@
<!-- Youtube filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="youtube.com"/>
<data android:host="m.youtube.com"/>
<data android:host="www.youtube.com"/>
<data android:host="music.youtube.com"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="youtube.com" />
<data android:host="m.youtube.com" />
<data android:host="www.youtube.com" />
<data android:host="music.youtube.com" />
<!-- video prefix -->
<data android:pathPrefix="/v/"/>
<data android:pathPrefix="/embed/"/>
<data android:pathPrefix="/watch"/>
<data android:pathPrefix="/attribution_link"/>
<data android:pathPrefix="/v/" />
<data android:pathPrefix="/embed/" />
<data android:pathPrefix="/watch" />
<data android:pathPrefix="/attribution_link" />
<!-- channel prefix -->
<data android:pathPrefix="/channel/"/>
<data android:pathPrefix="/user/"/>
<data android:pathPrefix="/c/"/>
<data android:pathPrefix="/channel/" />
<data android:pathPrefix="/user/" />
<data android:pathPrefix="/c/" />
<!-- playlist prefix -->
<data android:pathPrefix="/playlist"/>
<data android:pathPrefix="/playlist" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="youtu.be"/>
<data android:pathPrefix="/"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="youtu.be" />
<data android:pathPrefix="/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="www.youtube-nocookie.com"/>
<data android:pathPrefix="/embed/"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="www.youtube-nocookie.com" />
<data android:pathPrefix="/embed/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="vnd.youtube"/>
<data android:scheme="vnd.youtube.launch"/>
<data android:scheme="vnd.youtube" />
<data android:scheme="vnd.youtube.launch" />
</intent-filter>
<!-- Hooktube filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="hooktube.com"/>
<data android:host="*.hooktube.com"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="hooktube.com" />
<data android:host="*.hooktube.com" />
<!-- video prefix -->
<data android:pathPrefix="/v/"/>
<data android:pathPrefix="/embed/"/>
<data android:pathPrefix="/watch"/>
<data android:pathPrefix="/v/" />
<data android:pathPrefix="/embed/" />
<data android:pathPrefix="/watch" />
<!-- channel prefix -->
<data android:pathPrefix="/channel/"/>
<data android:pathPrefix="/user/"/>
<data android:pathPrefix="/channel/" />
<data android:pathPrefix="/user/" />
</intent-filter>
<!-- Invidious filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="invidio.us"/>
<data android:host="dev.invidio.us"/>
<data android:host="www.invidio.us"/>
<data android:host="invidious.snopyta.org"/>
<data android:host="de.invidious.snopyta.org"/>
<data android:host="fi.invidious.snopyta.org"/>
<data android:host="vid.wxzm.sx"/>
<data android:host="invidious.kabi.tk"/>
<data android:host="invidiou.sh"/>
<data android:host="www.invidiou.sh"/>
<data android:host="no.invidiou.sh"/>
<data android:host="invidious.enkirton.net"/>
<data android:host="tube.poal.co"/>
<data android:host="invidious.13ad.de"/>
<data android:host="yt.elukerio.org"/>
<data android:pathPrefix="/"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="invidio.us" />
<data android:host="dev.invidio.us" />
<data android:host="www.invidio.us" />
<data android:host="invidious.snopyta.org" />
<data android:host="fi.invidious.snopyta.org" />
<data android:host="yewtu.be" />
<data android:host="invidious.ggc-project.de" />
<data android:host="yt.maisputain.ovh" />
<data android:host="invidious.13ad.de" />
<data android:host="invidious.toot.koeln" />
<data android:host="invidious.fdn.fr" />
<data android:host="watch.nettohikari.com" />
<data android:host="invidious.snwmds.net" />
<data android:host="invidious.snwmds.org" />
<data android:host="invidious.snwmds.com" />
<data android:host="invidious.sunsetravens.com" />
<data android:host="invidious.gachirangers.com" />
<data android:pathPrefix="/" />
</intent-filter>
<!-- Soundcloud filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="soundcloud.com"/>
<data android:host="m.soundcloud.com"/>
<data android:host="www.soundcloud.com"/>
<data android:pathPrefix="/"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="soundcloud.com" />
<data android:host="m.soundcloud.com" />
<data android:host="www.soundcloud.com" />
<data android:pathPrefix="/" />
</intent-filter>
<!-- Share filter -->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<!-- MediaCCC filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="media.ccc.de"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="media.ccc.de" />
<!-- video prefix -->
<data android:pathPrefix="/v/"/>
<data android:pathPrefix="/v/" />
<!-- channel prefix-->
<data android:pathPrefix="/c/"/>
<data android:pathPrefix="/b/"/>
<data android:pathPrefix="/c/" />
<data android:pathPrefix="/b/" />
</intent-filter>
<!-- PeerTube filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="framatube.org" />
<data android:host="media.assassinate-you.net" />
<data android:host="peertube.co.uk" />
<data android:host="peertube.cpy.re" />
<data android:host="peertube.mastodon.host" />
<data android:host="peertube.fr" />
<data android:host="peertube.live" />
<data android:host="peertube.video" />
<data android:host="tube.privacytools.io" />
<data android:host="video.ploud.fr" />
<data android:host="video.lqdn.fr" />
<data android:host="skeptikon.fr" />
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
<data android:pathPrefix="/accounts/" />
<data android:pathPrefix="/video-channels/" />
</intent-filter>
</activity>
<service
android:name=".RouterActivity$FetcherService"
android:exported="false"/>
android:exported="false" />
<!-- see https://github.com/TeamNewPipe/NewPipe/issues/3947 -->
<!-- Version < 3.0. DeX Mode and Screen Mirroring support -->
<meta-data android:name="com.samsung.android.keepalive.density" android:value="true"/>
<!-- Version >= 3.0. DeX Dual Mode support -->
<meta-data android:name="com.samsung.android.multidisplay.keep_process_alive" android:value="true"/>
</application>
</manifest>

View File

@@ -150,7 +150,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
final Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
@@ -160,12 +160,12 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
final Fragment fragment = getItem(position);
if (DEBUG) {
Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
}
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
final Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
@@ -191,7 +191,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
@Override
public void destroyItem(@NonNull final ViewGroup container, final int position,
@NonNull final Object object) {
Fragment fragment = (Fragment) object;
final Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
@@ -217,7 +217,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
@SuppressWarnings({"ReferenceEquality", "deprecation"})
public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
@NonNull final Object object) {
Fragment fragment = (Fragment) object;
final Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
@@ -267,17 +267,17 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
Bundle state = null;
if (mSavedState.size() > 0) {
state = new Bundle();
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
final Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
for (int i = 0; i < mFragments.size(); i++) {
Fragment f = mFragments.get(i);
final Fragment f = mFragments.get(i);
if (f != null && f.isAdded()) {
if (state == null) {
state = new Bundle();
}
String key = "f" + i;
final String key = "f" + i;
mFragmentManager.putFragment(state, key, f);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@@ -294,21 +294,21 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
@Override
public void restoreState(@Nullable final Parcelable state, @Nullable final ClassLoader loader) {
if (state != null) {
Bundle bundle = (Bundle) state;
final Bundle bundle = (Bundle) state;
bundle.setClassLoader(loader);
Parcelable[] fss = bundle.getParcelableArray("states");
final Parcelable[] fss = bundle.getParcelableArray("states");
mSavedState.clear();
mFragments.clear();
if (fss != null) {
for (int i = 0; i < fss.length; i++) {
mSavedState.add((Fragment.SavedState) fss[i]);
for (final Parcelable parcelable : fss) {
mSavedState.add((Fragment.SavedState) parcelable);
}
}
Iterable<String> keys = bundle.keySet();
for (String key: keys) {
final Iterable<String> keys = bundle.keySet();
for (final String key : keys) {
if (key.startsWith("f")) {
int index = Integer.parseInt(key.substring(1));
Fragment f = mFragmentManager.getFragment(bundle, key);
final int index = Integer.parseInt(key.substring(1));
final Fragment f = mFragmentManager.getFragment(bundle, key);
if (f != null) {
while (mFragments.size() <= index) {
mFragments.add(null);

View File

@@ -4,13 +4,17 @@ import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.OverScroller;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import org.schabi.newpipe.R;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
// See https://stackoverflow.com/questions/56849221#57997489
public final class FlingBehavior extends AppBarLayout.Behavior {
@@ -20,23 +24,28 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
super(context, attrs);
}
private boolean allowScroll = true;
private final Rect globalRect = new Rect();
private final List<Integer> skipInterceptionOfElements = Arrays.asList(
R.id.playQueuePanel, R.id.playbackSeekBar,
R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton);
@Override
public boolean onRequestChildRectangleOnScreen(
@NonNull final CoordinatorLayout coordinatorLayout, @NonNull final AppBarLayout child,
@NonNull final Rect rectangle, final boolean immediate) {
focusScrollRect.set(rectangle);
coordinatorLayout.offsetDescendantRectToMyCoords(child, focusScrollRect);
int height = coordinatorLayout.getHeight();
final int height = coordinatorLayout.getHeight();
if (focusScrollRect.top <= 0 && focusScrollRect.bottom >= height) {
// the child is too big to fit inside ourselves completely, ignore request
return false;
}
int dy;
final int dy;
if (focusScrollRect.bottom > height) {
dy = focusScrollRect.top;
@@ -48,13 +57,24 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
return false;
}
int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0);
final int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0);
return consumed == dy;
}
public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child,
final MotionEvent ev) {
for (final Integer element : skipInterceptionOfElements) {
final View view = child.findViewById(element);
if (view != null) {
final boolean visible = view.getGlobalVisibleRect(globalRect);
if (visible && globalRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
allowScroll = false;
return false;
}
}
}
allowScroll = true;
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// remove reference to old nested scrolling child
@@ -68,17 +88,37 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
return super.onInterceptTouchEvent(parent, child, ev);
}
@Override
public boolean onStartNestedScroll(@NonNull final CoordinatorLayout parent,
@NonNull final AppBarLayout child,
@NonNull final View directTargetChild,
final View target,
final int nestedScrollAxes,
final int type) {
return allowScroll && super.onStartNestedScroll(
parent, child, directTargetChild, target, nestedScrollAxes, type);
}
@Override
public boolean onNestedFling(@NonNull final CoordinatorLayout coordinatorLayout,
@NonNull final AppBarLayout child,
@NonNull final View target, final float velocityX,
final float velocityY, final boolean consumed) {
return allowScroll && super.onNestedFling(
coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
@Nullable
private OverScroller getScrollerField() {
try {
Class<?> headerBehaviorType = this.getClass()
final Class<?> headerBehaviorType = this.getClass()
.getSuperclass().getSuperclass().getSuperclass();
if (headerBehaviorType != null) {
Field field = headerBehaviorType.getDeclaredField("scroller");
final Field field = headerBehaviorType.getDeclaredField("scroller");
field.setAccessible(true);
return ((OverScroller) field.get(this));
}
} catch (NoSuchFieldException | IllegalAccessException e) {
} catch (final NoSuchFieldException | IllegalAccessException e) {
// ?
}
return null;
@@ -87,34 +127,35 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
@Nullable
private Field getLastNestedScrollingChildRefField() {
try {
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass();
final Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass();
if (headerBehaviorType != null) {
Field field = headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef");
final Field field
= headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef");
field.setAccessible(true);
return field;
}
} catch (NoSuchFieldException e) {
} catch (final NoSuchFieldException e) {
// ?
}
return null;
}
private void resetNestedScrollingChild() {
Field field = getLastNestedScrollingChildRefField();
final Field field = getLastNestedScrollingChildRefField();
if (field != null) {
try {
Object value = field.get(this);
final Object value = field.get(this);
if (value != null) {
field.set(this, null);
}
} catch (IllegalAccessException e) {
} catch (final IllegalAccessException e) {
// ?
}
}
}
private void stopAppBarLayoutFling() {
OverScroller scroller = getScrollerField();
final OverScroller scroller = getScrollerField();
if (scroller != null) {
scroller.forceFinished(true);
}

View File

@@ -9,6 +9,7 @@ import android.content.SharedPreferences;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
@@ -19,10 +20,8 @@ import org.acra.ACRA;
import org.acra.config.ACRAConfigurationException;
import org.acra.config.CoreConfiguration;
import org.acra.config.CoreConfigurationBuilder;
import org.acra.sender.ReportSenderFactory;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.report.AcraReportSenderFactory;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
@@ -37,7 +36,6 @@ import java.net.SocketException;
import java.util.Collections;
import java.util.List;
import io.reactivex.annotations.NonNull;
import io.reactivex.exceptions.CompositeException;
import io.reactivex.exceptions.MissingBackpressureException;
import io.reactivex.exceptions.OnErrorNotImplementedException;
@@ -65,9 +63,6 @@ import io.reactivex.plugins.RxJavaPlugins;
public class App extends Application {
protected static final String TAG = App.class.toString();
@SuppressWarnings("unchecked")
private static final Class<? extends ReportSenderFactory>[]
REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
private static App app;
public static App getApp() {
@@ -77,7 +72,6 @@ public class App extends Application {
@Override
protected void attachBaseContext(final Context base) {
super.attachBaseContext(base);
initACRA();
}
@@ -110,7 +104,7 @@ public class App extends Application {
}
protected Downloader getDownloader() {
DownloaderImpl downloader = DownloaderImpl.init(null);
final DownloaderImpl downloader = DownloaderImpl.init(null);
setCookiesToDownloader(downloader);
return downloader;
}
@@ -200,14 +194,21 @@ public class App extends Application {
.build();
}
private void initACRA() {
/**
* Called in {@link #attachBaseContext(Context)} after calling the {@code super} method.
* Should be overridden if MultiDex is enabled, since it has to be initialized before ACRA.
*/
protected void initACRA() {
if (ACRA.isACRASenderServiceProcess()) {
return;
}
try {
final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
.setBuildConfigClass(BuildConfig.class)
.build();
ACRA.init(this, acraConfig);
} catch (ACRAConfigurationException ace) {
} catch (final ACRAConfigurationException ace) {
ace.printStackTrace();
ErrorActivity.reportError(this,
ace,
@@ -219,7 +220,7 @@ public class App extends Application {
}
public void initNotificationChannel() {
if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
@@ -230,10 +231,10 @@ public class App extends Application {
// Keep this below DEFAULT to avoid making noise on every notification update
final int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = new NotificationChannel(id, name, importance);
final NotificationChannel mChannel = new NotificationChannel(id, name, importance);
mChannel.setDescription(description);
NotificationManager mNotificationManager =
final NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannel(mChannel);
@@ -254,11 +255,11 @@ public class App extends Application {
final String appUpdateDescription
= getString(R.string.app_update_notification_channel_description);
NotificationChannel appUpdateChannel
final NotificationChannel appUpdateChannel
= new NotificationChannel(appUpdateId, appUpdateName, importance);
appUpdateChannel.setDescription(appUpdateDescription);
NotificationManager appUpdateNotificationManager
final NotificationManager appUpdateNotificationManager
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
}

View File

@@ -11,7 +11,7 @@ import android.content.pm.Signature;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.util.Log;
import androidx.core.app.NotificationCompat;
@@ -62,7 +62,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
try {
packageInfo = pm.getPackageInfo(packageName, flags);
} catch (PackageManager.NameNotFoundException e) {
} catch (final PackageManager.NameNotFoundException e) {
ErrorActivity.reportError(APP, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not find package info", R.string.app_ui_crash));
@@ -77,7 +77,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
try {
final CertificateFactory cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (CertificateException e) {
} catch (final CertificateException e) {
ErrorActivity.reportError(APP, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Certificate error", R.string.app_ui_crash));
@@ -86,7 +86,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
String hexString = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
final MessageDigest md = MessageDigest.getInstance("SHA1");
final byte[] publicKey = md.digest(c.getEncoded());
hexString = byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
@@ -167,7 +167,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
} catch (JsonParserException e) {
} catch (final JsonParserException e) {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, Log.getStackTraceString(e));
@@ -187,7 +187,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
private void compareAppVersionAndShowNotification(final String versionName,
final String apkLocationUrl,
final int versionCode) {
int notificationId = 2000;
final int notificationId = 2000;
if (BuildConfig.VERSION_CODE < versionCode) {

View File

@@ -2,7 +2,7 @@ package org.schabi.newpipe;
import android.content.Context;
import android.os.Build;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -94,18 +94,18 @@ public final class DownloaderImpl extends Downloader {
private static void enableModernTLS(final OkHttpClient.Builder builder) {
try {
// get the default TrustManager
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
final X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
// insert our own TLSSocketFactory
SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
final SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
builder.sslSocketFactory(sslSocketFactory, trustManager);
@@ -114,16 +114,16 @@ public final class DownloaderImpl extends Downloader {
// Necessary because some servers (e.g. Framatube.org)
// don't support the old cipher suites.
// https://github.com/square/okhttp/issues/4053#issuecomment-402579554
List<CipherSuite> cipherSuites = new ArrayList<>();
cipherSuites.addAll(ConnectionSpec.MODERN_TLS.cipherSuites());
final List<CipherSuite> cipherSuites =
new ArrayList<>(ConnectionSpec.MODERN_TLS.cipherSuites());
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
final ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
.build();
builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT));
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
} catch (final KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
if (DEBUG) {
e.printStackTrace();
}
@@ -131,15 +131,15 @@ public final class DownloaderImpl extends Downloader {
}
public String getCookies(final String url) {
List<String> resultCookies = new ArrayList<>();
final List<String> resultCookies = new ArrayList<>();
if (url.contains(YOUTUBE_DOMAIN)) {
String youtubeCookie = getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
final String youtubeCookie = getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
if (youtubeCookie != null) {
resultCookies.add(youtubeCookie);
}
}
// Recaptcha cookie is always added TODO: not sure if this is necessary
String recaptchaCookie = getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY);
final String recaptchaCookie = getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY);
if (recaptchaCookie != null) {
resultCookies.add(recaptchaCookie);
}
@@ -159,9 +159,9 @@ public final class DownloaderImpl extends Downloader {
}
public void updateYoutubeRestrictedModeCookies(final Context context) {
String restrictedModeEnabledKey =
final String restrictedModeEnabledKey =
context.getString(R.string.youtube_restricted_mode_enabled);
boolean restrictedModeEnabled = PreferenceManager.getDefaultSharedPreferences(context)
final boolean restrictedModeEnabled = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(restrictedModeEnabledKey, false);
updateYoutubeRestrictedModeCookies(restrictedModeEnabled);
}
@@ -186,9 +186,9 @@ public final class DownloaderImpl extends Downloader {
try {
final Response response = head(url);
return Long.parseLong(response.getHeader("Content-Length"));
} catch (NumberFormatException e) {
} catch (final NumberFormatException e) {
throw new IOException("Invalid content length", e);
} catch (ReCaptchaException e) {
} catch (final ReCaptchaException e) {
throw new IOException(e);
}
}
@@ -199,7 +199,7 @@ public final class DownloaderImpl extends Downloader {
.method("GET", null).url(siteUrl)
.addHeader("User-Agent", USER_AGENT);
String cookies = getCookies(siteUrl);
final String cookies = getCookies(siteUrl);
if (!cookies.isEmpty()) {
requestBuilder.addHeader("Cookie", cookies);
}
@@ -218,7 +218,7 @@ public final class DownloaderImpl extends Downloader {
}
return body.byteStream();
} catch (ReCaptchaException e) {
} catch (final ReCaptchaException e) {
throw new IOException(e.getMessage(), e.getCause());
}
}
@@ -240,18 +240,18 @@ public final class DownloaderImpl extends Downloader {
.method(httpMethod, requestBody).url(url)
.addHeader("User-Agent", USER_AGENT);
String cookies = getCookies(url);
final String cookies = getCookies(url);
if (!cookies.isEmpty()) {
requestBuilder.addHeader("Cookie", cookies);
}
for (Map.Entry<String, List<String>> pair : headers.entrySet()) {
for (final Map.Entry<String, List<String>> pair : headers.entrySet()) {
final String headerName = pair.getKey();
final List<String> headerValueList = pair.getValue();
if (headerValueList.size() > 1) {
requestBuilder.removeHeader(headerName);
for (String headerValue : headerValueList) {
for (final String headerValue : headerValueList) {
requestBuilder.addHeader(headerName, headerValue);
}
} else if (headerValueList.size() == 1) {

View File

@@ -27,7 +27,7 @@ import android.os.Bundle;
public class ExitActivity extends Activity {
public static void exitAndRemoveFromRecentApps(final Activity activity) {
Intent intent = new Intent(activity, ExitActivity.class);
final Intent intent = new Intent(activity, ExitActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
@@ -42,7 +42,7 @@ public class ExitActivity extends Activity {
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 21) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
finishAndRemoveTask();
} else {
finish();

View File

@@ -4,7 +4,7 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;

View File

@@ -20,29 +20,32 @@
package org.schabi.newpipe;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
@@ -53,6 +56,7 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.navigation.NavigationView;
import org.schabi.newpipe.extractor.NewPipe;
@@ -63,14 +67,18 @@ import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.player.VideoPlayer;
import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PeertubeHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.SerializedCache;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
@@ -96,6 +104,8 @@ public class MainActivity extends AppCompatActivity {
private boolean servicesShown = false;
private ImageView serviceArrow;
private BroadcastReceiver broadcastReceiver;
private static final int ITEM_ID_SUBSCRIPTIONS = -1;
private static final int ITEM_ID_FEED = -2;
private static final int ITEM_ID_BOOKMARKS = -3;
@@ -127,12 +137,6 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window w = getWindow();
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
if (getSupportFragmentManager() != null
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments();
@@ -141,13 +145,14 @@ public class MainActivity extends AppCompatActivity {
setSupportActionBar(findViewById(R.id.toolbar));
try {
setupDrawer();
} catch (Exception e) {
} catch (final Exception e) {
ErrorActivity.reportUiError(this, e);
}
if (AndroidTvUtils.isTv(this)) {
if (DeviceUtils.isTv(this)) {
FocusOverlayView.setupFocusObserver(this);
}
setupBroadcastReceiver();
}
private void setupDrawer() throws Exception {
@@ -156,8 +161,8 @@ public class MainActivity extends AppCompatActivity {
drawerItems = findViewById(R.id.navigation);
//Tabs
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
StreamingService service = NewPipe.getService(currentServiceId);
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
final StreamingService service = NewPipe.getService(currentServiceId);
int kioskId = 0;
@@ -166,6 +171,7 @@ public class MainActivity extends AppCompatActivity {
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcon(ks, this));
kioskId++;
}
drawerItems.getMenu()
@@ -228,7 +234,7 @@ public class MainActivity extends AppCompatActivity {
case R.id.menu_tabs_group:
try {
tabSelected(item);
} catch (Exception e) {
} catch (final Exception e) {
ErrorActivity.reportUiError(this, e);
}
break;
@@ -269,8 +275,8 @@ public class MainActivity extends AppCompatActivity {
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
break;
default:
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
StreamingService service = NewPipe.getService(currentServiceId);
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
final StreamingService service = NewPipe.getService(currentServiceId);
String serviceName = "";
int kioskId = 0;
@@ -299,8 +305,8 @@ public class MainActivity extends AppCompatActivity {
}
private void setupDrawerHeader() {
NavigationView navigationView = findViewById(R.id.navigation);
View hView = navigationView.getHeaderView(0);
final NavigationView navigationView = findViewById(R.id.navigation);
final View hView = navigationView.getHeaderView(0);
serviceArrow = hView.findViewById(R.id.drawer_arrow);
headerServiceIcon = hView.findViewById(R.id.drawer_header_service_icon);
@@ -335,7 +341,7 @@ public class MainActivity extends AppCompatActivity {
} else {
try {
showTabs();
} catch (Exception e) {
} catch (final Exception e) {
ErrorActivity.reportUiError(this, e);
}
}
@@ -344,11 +350,11 @@ public class MainActivity extends AppCompatActivity {
private void showServices() {
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
for (StreamingService s : NewPipe.getServices()) {
for (final StreamingService s : NewPipe.getServices()) {
final String title = s.getServiceInfo().getName()
+ (ServiceHelper.isBeta(s) ? " (beta)" : "");
MenuItem menuItem = drawerItems.getMenu()
final MenuItem menuItem = drawerItems.getMenu()
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
@@ -362,20 +368,20 @@ public class MainActivity extends AppCompatActivity {
}
private void enhancePeertubeMenu(final StreamingService s, final MenuItem menuItem) {
PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance();
final PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance();
menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : ""));
Spinner spinner = (Spinner) LayoutInflater.from(this)
final Spinner spinner = (Spinner) LayoutInflater.from(this)
.inflate(R.layout.instance_spinner_layout, null);
List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this);
List<String> items = new ArrayList<>();
final List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this);
final List<String> items = new ArrayList<>();
int defaultSelect = 0;
for (PeertubeInstance instance : instances) {
for (final PeertubeInstance instance : instances) {
items.add(instance.getName());
if (instance.getUrl().equals(currentInstace.getUrl())) {
defaultSelect = items.size() - 1;
}
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
final ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
R.layout.instance_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
@@ -384,7 +390,7 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onItemSelected(final AdapterView<?> parent, final View view,
final int position, final long id) {
PeertubeInstance newInstance = instances.get(position);
final PeertubeInstance newInstance = instances.get(position);
if (newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) {
return;
}
@@ -410,8 +416,8 @@ public class MainActivity extends AppCompatActivity {
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp);
//Tabs
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
StreamingService service = NewPipe.getService(currentServiceId);
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
final StreamingService service = NewPipe.getService(currentServiceId);
int kioskId = 0;
@@ -454,6 +460,9 @@ public class MainActivity extends AppCompatActivity {
if (!isChangingConfigurations()) {
StateSaver.clearStateFiles();
}
if (broadcastReceiver != null) {
unregisterReceiver(broadcastReceiver);
}
}
@Override
@@ -476,11 +485,12 @@ public class MainActivity extends AppCompatActivity {
headerServiceView.post(() -> headerServiceView.setSelected(true));
toggleServiceButton.setContentDescription(
getString(R.string.drawer_header_description) + selectedServiceName);
} catch (Exception e) {
} catch (final Exception e) {
ErrorActivity.reportUiError(this, e);
}
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
final SharedPreferences sharedPreferences
= PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
if (DEBUG) {
Log.d(TAG, "Theme has changed, recreating activity...");
@@ -513,7 +523,7 @@ public class MainActivity extends AppCompatActivity {
if (intent != null) {
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
// to not destroy the already created backstack
String action = intent.getAction();
final String action = intent.getAction();
if ((action != null && action.equals(Intent.ACTION_MAIN))
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
return;
@@ -525,25 +535,60 @@ public class MainActivity extends AppCompatActivity {
handleIntent(intent);
}
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent event) {
final Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_player_holder);
if (fragment instanceof OnKeyDownListener
&& !bottomSheetHiddenOrCollapsed()) {
// Provide keyDown event to fragment which then sends this event
// to the main player service
return ((OnKeyDownListener) fragment).onKeyDown(keyCode)
|| super.onKeyDown(keyCode, event);
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onBackPressed() {
if (DEBUG) {
Log.d(TAG, "onBackPressed() called");
}
if (AndroidTvUtils.isTv(this)) {
View drawerPanel = findViewById(R.id.navigation);
if (DeviceUtils.isTv(this)) {
final View drawerPanel = findViewById(R.id.navigation);
if (drawer.isDrawerOpen(drawerPanel)) {
drawer.closeDrawers();
return;
}
}
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
if (fragment instanceof BackPressable) {
if (((BackPressable) fragment).onBackPressed()) {
// In case bottomSheet is not visible on the screen or collapsed we can assume that the user
// interacts with a fragment inside fragment_holder so all back presses should be
// handled by it
if (bottomSheetHiddenOrCollapsed()) {
final Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
if (fragment instanceof BackPressable) {
if (((BackPressable) fragment).onBackPressed()) {
return;
}
}
} else {
final Fragment fragmentPlayer = getSupportFragmentManager()
.findFragmentById(R.id.fragment_player_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
if (fragmentPlayer instanceof BackPressable) {
if (!((BackPressable) fragmentPlayer).onBackPressed()) {
final FrameLayout bottomSheetLayout =
findViewById(R.id.fragment_player_holder);
BottomSheetBehavior.from(bottomSheetLayout)
.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
return;
}
}
@@ -559,7 +604,7 @@ public class MainActivity extends AppCompatActivity {
public void onRequestPermissionsResult(final int requestCode,
@NonNull final String[] permissions,
@NonNull final int[] grantResults) {
for (int i : grantResults) {
for (final int i : grantResults) {
if (i == PackageManager.PERMISSION_DENIED) {
return;
}
@@ -569,8 +614,8 @@ public class MainActivity extends AppCompatActivity {
NavigationHelper.openDownloads(this);
break;
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_holder);
final Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_player_holder);
if (fragment instanceof VideoDetailFragment) {
((VideoDetailFragment) fragment).openDownloadDialog();
}
@@ -621,17 +666,14 @@ public class MainActivity extends AppCompatActivity {
}
super.onCreateOptionsMenu(menu);
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (!(fragment instanceof VideoDetailFragment)) {
findViewById(R.id.toolbar).findViewById(R.id.toolbar_spinner).setVisibility(View.GONE);
}
final Fragment fragment
= getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (!(fragment instanceof SearchFragment)) {
findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container)
.setVisibility(View.GONE);
}
ActionBar actionBar = getSupportActionBar();
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(false);
}
@@ -646,7 +688,7 @@ public class MainActivity extends AppCompatActivity {
if (DEBUG) {
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
}
int id = item.getItemId();
final int id = item.getItemId();
switch (id) {
case android.R.id.home:
@@ -667,6 +709,13 @@ public class MainActivity extends AppCompatActivity {
}
StateSaver.clearStateFiles();
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
// When user watch a video inside popup and then tries to open the video in main player
// while the app is closed he will see a blank fragment on place of kiosk.
// Let's open it first
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
NavigationHelper.openMainFragment(getSupportFragmentManager());
}
handleIntent(getIntent());
} else {
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
@@ -707,16 +756,22 @@ public class MainActivity extends AppCompatActivity {
}
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
String url = intent.getStringExtra(Constants.KEY_URL);
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
String title = intent.getStringExtra(Constants.KEY_TITLE);
final String url = intent.getStringExtra(Constants.KEY_URL);
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
final String title = intent.getStringExtra(Constants.KEY_TITLE);
switch (((StreamingService.LinkType) intent
.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
case STREAM:
boolean autoPlay = intent
final boolean autoPlay = intent
.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
final String intentCacheKey = intent
.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY);
final PlayQueue playQueue = intentCacheKey != null
? SerializedCache.getInstance()
.take(intentCacheKey, PlayQueue.class)
: null;
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(),
serviceId, url, title, autoPlay);
serviceId, url, title, autoPlay, playQueue);
break;
case CHANNEL:
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
@@ -736,7 +791,7 @@ public class MainActivity extends AppCompatActivity {
if (searchString == null) {
searchString = "";
}
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
NavigationHelper.openSearchFragment(
getSupportFragmentManager(),
serviceId,
@@ -745,8 +800,48 @@ public class MainActivity extends AppCompatActivity {
} else {
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
}
} catch (Exception e) {
} catch (final Exception e) {
ErrorActivity.reportUiError(this, e);
}
}
private void setupBroadcastReceiver() {
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
if (intent.getAction().equals(VideoDetailFragment.ACTION_PLAYER_STARTED)) {
final Fragment fragmentPlayer = getSupportFragmentManager()
.findFragmentById(R.id.fragment_player_holder);
if (fragmentPlayer == null) {
/*
* We still don't have a fragment attached to the activity.
* It can happen when a user started popup or background players
* without opening a stream inside the fragment.
* Adding it in a collapsed state (only mini player will be visible)
* */
NavigationHelper.showMiniPlayer(getSupportFragmentManager());
}
/*
* At this point the player is added 100%, we can unregister.
* Other actions are useless since the fragment will not be removed after that
* */
unregisterReceiver(broadcastReceiver);
broadcastReceiver = null;
}
}
};
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED);
registerReceiver(broadcastReceiver, intentFilter);
}
private boolean bottomSheetHiddenOrCollapsed() {
final FrameLayout bottomSheetLayout = findViewById(R.id.fragment_player_holder);
final BottomSheetBehavior<FrameLayout> bottomSheetBehavior =
BottomSheetBehavior.from(bottomSheetLayout);
final int sheetState = bottomSheetBehavior.getState();
return sheetState == BottomSheetBehavior.STATE_HIDDEN
|| sheetState == BottomSheetBehavior.STATE_COLLAPSED;
}
}

View File

@@ -46,7 +46,7 @@ public final class NewPipeDatabase {
if (databaseInstance == null) {
throw new IllegalStateException("database is not initialized");
}
Cursor c = databaseInstance.query("pragma wal_checkpoint(full)", null);
final Cursor c = databaseInstance.query("pragma wal_checkpoint(full)", null);
if (c.moveToFirst() && c.getInt(0) == 1) {
throw new RuntimeException("Checkpoint was blocked from completing");
}

View File

@@ -31,7 +31,7 @@ public class PanicResponderActivity extends Activity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
final Intent intent = getIntent();
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
// TODO: Explicitly clear the search results
// once they are restored when the app restarts
@@ -40,7 +40,7 @@ public class PanicResponderActivity extends Activity {
ExitActivity.exitAndRemoveFromRecentApps(this);
}
if (Build.VERSION.SDK_INT >= 21) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
finishAndRemoveTask();
} else {
finish();

View File

@@ -61,7 +61,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
ThemeHelper.setTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recaptcha);
Toolbar toolbar = findViewById(R.id.toolbar);
final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
String url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
@@ -76,7 +76,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
webView = findViewById(R.id.reCaptchaWebView);
// enable Javascript
WebSettings webSettings = webView.getSettings();
final WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient() {
@@ -84,7 +84,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
@Override
public boolean shouldOverrideUrlLoading(final WebView view,
final WebResourceRequest request) {
String url = request.getUrl().toString();
final String url = request.getUrl().toString();
if (MainActivity.DEBUG) {
Log.d(TAG, "shouldOverrideUrlLoading: request.url=" + url);
}
@@ -113,7 +113,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
// cleaning cache, history and cookies from webView
webView.clearCache(true);
webView.clearHistory();
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
final android.webkit.CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.removeAllCookies(aBoolean -> {
});
@@ -128,7 +128,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
ActionBar actionBar = getSupportActionBar();
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(false);
actionBar.setTitle(R.string.title_activity_recaptcha);
@@ -145,7 +145,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
int id = item.getItemId();
final int id = item.getItemId();
switch (id) {
case R.id.menu_item_done:
saveCookiesAndFinish();
@@ -173,7 +173,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
setResult(RESULT_OK);
}
Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
final Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
}
@@ -188,13 +188,13 @@ public class ReCaptchaActivity extends AppCompatActivity {
return;
}
String cookies = CookieManager.getInstance().getCookie(url);
final String cookies = CookieManager.getInstance().getCookie(url);
handleCookies(cookies);
// sometimes cookies are inside the url
int abuseStart = url.indexOf("google_abuse=");
final int abuseStart = url.indexOf("google_abuse=");
if (abuseStart != -1) {
int abuseEnd = url.indexOf("+path");
final int abuseEnd = url.indexOf("+path");
try {
String abuseCookie = url.substring(abuseStart + 13, abuseEnd);

View File

@@ -8,7 +8,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -39,17 +39,19 @@ 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.VideoStream;
import org.schabi.newpipe.player.helper.PlayerHelper;
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.AndroidTvUtils;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.util.urlfinder.UrlFinder;
import org.schabi.newpipe.views.FocusOverlayView;
@@ -159,27 +161,36 @@ public class RouterActivity extends AppCompatActivity {
if (result) {
onSuccess();
} else {
onError();
showUnsupportedUrlDialog(url);
}
}, this::handleError));
}, throwable -> handleError(throwable, url)));
}
private void handleError(final Throwable error) {
error.printStackTrace();
private void handleError(final Throwable throwable, final String url) {
throwable.printStackTrace();
if (error instanceof ExtractionException) {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
if (throwable instanceof ExtractionException) {
showUnsupportedUrlDialog(url);
} else {
ExtractorHelper.handleGeneralException(this, -1, null, error,
ExtractorHelper.handleGeneralException(this, -1, url, throwable,
UserAction.SOMETHING_ELSE, null);
finish();
}
finish();
}
private void onError() {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
finish();
private void showUnsupportedUrlDialog(final String url) {
final Context context = getThemeWrapperContext();
new AlertDialog.Builder(context)
.setTitle(R.string.unsupported_url)
.setMessage(R.string.unsupported_url_dialog_message)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_share))
.setPositiveButton(R.string.open_in_browser,
(dialog, which) -> ShareUtils.openUrlInBrowser(this, url))
.setNegativeButton(R.string.share,
(dialog, which) -> ShareUtils.shareUrl(this, "", url)) // no subject
.setNeutralButton(R.string.cancel, null)
.setOnDismissListener(dialog -> finish())
.show();
}
protected void onSuccess() {
@@ -258,7 +269,7 @@ public class RouterActivity extends AppCompatActivity {
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
final LinearLayout rootLayout = (LinearLayout) inflater.inflate(
R.layout.preferred_player_dialog_view, null, false);
R.layout.single_choice_dialog_view, null, false);
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
@@ -268,6 +279,7 @@ public class RouterActivity extends AppCompatActivity {
handleChoice(choice.key);
// open future streams always like this one, because "always" button was used by user
if (which == DialogInterface.BUTTON_POSITIVE) {
preferences.edit()
.putString(getString(R.string.preferred_open_action_key), choice.key)
@@ -310,7 +322,7 @@ public class RouterActivity extends AppCompatActivity {
};
int id = 12345;
for (AdapterChoiceItem item : choices) {
for (final AdapterChoiceItem item : choices) {
final RadioButton radioButton
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
radioButton.setText(item.description);
@@ -330,7 +342,7 @@ public class RouterActivity extends AppCompatActivity {
getString(R.string.preferred_open_action_last_selected_key), null);
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
for (int i = 0; i < choices.size(); i++) {
AdapterChoiceItem c = choices.get(i);
final AdapterChoiceItem c = choices.get(i);
if (lastSelectedPlayer.equals(c.key)) {
selectedRadioPosition = i;
break;
@@ -347,7 +359,7 @@ public class RouterActivity extends AppCompatActivity {
alertDialog.show();
if (AndroidTvUtils.isTv(this)) {
if (DeviceUtils.isTv(this)) {
FocusOverlayView.setupFocusObserver(alertDialog);
}
}
@@ -362,28 +374,55 @@ public class RouterActivity extends AppCompatActivity {
final SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(this);
boolean isExtVideoEnabled = preferences.getBoolean(
final boolean isExtVideoEnabled = preferences.getBoolean(
getString(R.string.use_external_video_player_key), false);
boolean isExtAudioEnabled = preferences.getBoolean(
final 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.ic_info_outline)));
final AdapterChoiceItem videoPlayer = new AdapterChoiceItem(
getString(R.string.video_player_key), getString(R.string.video_player),
resolveResourceIdFromAttr(context, R.attr.ic_play_arrow));
final AdapterChoiceItem showInfo = new AdapterChoiceItem(
getString(R.string.show_info_key), getString(R.string.show_info),
resolveResourceIdFromAttr(context, R.attr.ic_info_outline));
final AdapterChoiceItem popupPlayer = new AdapterChoiceItem(
getString(R.string.popup_player_key), getString(R.string.popup_player),
resolveResourceIdFromAttr(context, R.attr.ic_popup));
final AdapterChoiceItem backgroundPlayer = new AdapterChoiceItem(
getString(R.string.background_player_key), getString(R.string.background_player),
resolveResourceIdFromAttr(context, R.attr.ic_headset));
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.ic_play_arrow)));
returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key),
getString(R.string.popup_player),
resolveResourceIdFromAttr(context, R.attr.ic_popup)));
}
if (linkType == LinkType.STREAM) {
if (isExtVideoEnabled) {
// show both "show info" and "video player", they are two different activities
returnList.add(showInfo);
returnList.add(videoPlayer);
} else if (capabilities.contains(VIDEO)
&& PlayerHelper.isAutoplayAllowedByUser(context)) {
// show only "video player" since the details activity will be opened and the video
// will be autoplayed there and "show info" would do the exact same thing
returnList.add(videoPlayer);
} else {
// show only "show info" if video player is not applicable or autoplay is disabled
returnList.add(showInfo);
}
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.ic_headset)));
if (capabilities.contains(VIDEO)) {
returnList.add(popupPlayer);
}
if (capabilities.contains(AUDIO)) {
returnList.add(backgroundPlayer);
}
} else {
returnList.add(showInfo);
if (capabilities.contains(VIDEO) && !isExtVideoEnabled) {
returnList.add(videoPlayer);
returnList.add(popupPlayer);
}
if (capabilities.contains(AUDIO) && !isExtAudioEnabled) {
returnList.add(backgroundPlayer);
}
}
returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
@@ -410,9 +449,9 @@ public class RouterActivity extends AppCompatActivity {
}
private void handleText() {
String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
final String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
final int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
final Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
NavigationHelper.openSearch(getThemeWrapperContext(), serviceId, searchString);
@@ -459,7 +498,7 @@ public class RouterActivity extends AppCompatActivity {
startActivity(intent);
finish();
}, this::handleError)
}, throwable -> handleError(throwable, currentUrl))
);
return;
}
@@ -479,24 +518,22 @@ public class RouterActivity extends AppCompatActivity {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull StreamInfo result) -> {
List<VideoStream> sortedVideoStreams = ListHelper
final List<VideoStream> sortedVideoStreams = ListHelper
.getSortedStreamVideosList(this, result.getVideoStreams(),
result.getVideoOnlyStreams(), false);
int selectedVideoStreamIndex = ListHelper
final int selectedVideoStreamIndex = ListHelper
.getDefaultResolutionIndex(this, sortedVideoStreams);
FragmentManager fm = getSupportFragmentManager();
DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
final FragmentManager fm = getSupportFragmentManager();
final DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
downloadDialog.setVideoStreams(sortedVideoStreams);
downloadDialog.setAudioStreams(result.getAudioStreams());
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.show(fm, "downloadDialog");
fm.executePendingTransactions();
downloadDialog.getDialog().setOnDismissListener(dialog -> {
finish();
});
downloadDialog.getDialog().setOnDismissListener(dialog -> finish());
}, (@NonNull Throwable throwable) -> {
onError();
showUnsupportedUrlDialog(currentUrl);
});
}
@@ -504,7 +541,7 @@ public class RouterActivity extends AppCompatActivity {
public void onRequestPermissionsResult(final int requestCode,
@NonNull final String[] permissions,
@NonNull final int[] grantResults) {
for (int i : grantResults) {
for (final int i : grantResults) {
if (i == PackageManager.PERMISSION_DENIED) {
finish();
return;
@@ -572,7 +609,7 @@ public class RouterActivity extends AppCompatActivity {
}
}
}
return result.toArray(new String[result.size()]);
return result.toArray(new String[0]);
}
private static class AdapterChoiceItem {
@@ -634,7 +671,7 @@ public class RouterActivity extends AppCompatActivity {
if (!(serializable instanceof Choice)) {
return;
}
Choice playerChoice = (Choice) serializable;
final Choice playerChoice = (Choice) serializable;
handleChoice(playerChoice);
}
@@ -682,13 +719,13 @@ public class RouterActivity extends AppCompatActivity {
final SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(this);
boolean isExtVideoEnabled = preferences.getBoolean(
final boolean isExtVideoEnabled = preferences.getBoolean(
getString(R.string.use_external_video_player_key), false);
boolean isExtAudioEnabled = preferences.getBoolean(
final boolean isExtAudioEnabled = preferences.getBoolean(
getString(R.string.use_external_audio_player_key), false);
PlayQueue playQueue;
String playerChoice = choice.playerChoice;
final String playerChoice = choice.playerChoice;
if (info instanceof StreamInfo) {
if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) {
@@ -701,7 +738,7 @@ public class RouterActivity extends AppCompatActivity {
playQueue = new SinglePlayQueue((StreamInfo) info);
if (playerChoice.equals(videoPlayerKey)) {
NavigationHelper.playOnMainPlayer(this, playQueue, true);
openMainPlayer(playQueue, choice);
} else if (playerChoice.equals(backgroundPlayerKey)) {
NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
} else if (playerChoice.equals(popupPlayerKey)) {
@@ -716,7 +753,7 @@ public class RouterActivity extends AppCompatActivity {
: new PlaylistPlayQueue((PlaylistInfo) info);
if (playerChoice.equals(videoPlayerKey)) {
NavigationHelper.playOnMainPlayer(this, playQueue, true);
openMainPlayer(playQueue, choice);
} else if (playerChoice.equals(backgroundPlayerKey)) {
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
} else if (playerChoice.equals(popupPlayerKey)) {
@@ -726,6 +763,11 @@ public class RouterActivity extends AppCompatActivity {
};
}
private void openMainPlayer(final PlayQueue playQueue, final Choice choice) {
NavigationHelper.playOnMainPlayer(this, playQueue, choice.linkType,
choice.url, "", true, true);
}
@Override
public void onDestroy() {
super.onDestroy();

View File

@@ -1,8 +1,6 @@
package org.schabi.newpipe.about;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
@@ -26,6 +24,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import static org.schabi.newpipe.util.ShareUtils.openUrlInBrowser;
public class AboutActivity extends AppCompatActivity {
/**
@@ -89,7 +88,7 @@ public class AboutActivity extends AppCompatActivity {
setContentView(R.layout.activity_about);
Toolbar toolbar = findViewById(R.id.toolbar);
final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Create the adapter that will return a fragment for each of the three
@@ -100,13 +99,13 @@ public class AboutActivity extends AppCompatActivity {
mViewPager = findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
TabLayout tabLayout = findViewById(R.id.tabs);
final TabLayout tabLayout = findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
int id = item.getItemId();
final int id = item.getItemId();
switch (id) {
case android.R.id.home:
@@ -135,36 +134,31 @@ public class AboutActivity extends AppCompatActivity {
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
Context context = this.getContext();
final View rootView = inflater.inflate(R.layout.fragment_about, container, false);
final Context context = this.getContext();
TextView version = rootView.findViewById(R.id.app_version);
final TextView version = rootView.findViewById(R.id.app_version);
version.setText(BuildConfig.VERSION_NAME);
View githubLink = rootView.findViewById(R.id.github_link);
final View githubLink = rootView.findViewById(R.id.github_link);
githubLink.setOnClickListener(nv ->
openWebsite(context.getString(R.string.github_url), context));
openUrlInBrowser(context, context.getString(R.string.github_url)));
View donationLink = rootView.findViewById(R.id.donation_link);
final View donationLink = rootView.findViewById(R.id.donation_link);
donationLink.setOnClickListener(v ->
openWebsite(context.getString(R.string.donation_url), context));
openUrlInBrowser(context, context.getString(R.string.donation_url)));
View websiteLink = rootView.findViewById(R.id.website_link);
final View websiteLink = rootView.findViewById(R.id.website_link);
websiteLink.setOnClickListener(nv ->
openWebsite(context.getString(R.string.website_url), context));
openUrlInBrowser(context, context.getString(R.string.website_url)));
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
final View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
privacyPolicyLink.setOnClickListener(v ->
openWebsite(context.getString(R.string.privacy_policy_url), context));
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url)));
return rootView;
}
private void openWebsite(final String url, final Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(intent);
}
}
/**
@@ -173,7 +167,7 @@ public class AboutActivity extends AppCompatActivity {
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(final FragmentManager fm) {
super(fm);
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
@Override

View File

@@ -4,10 +4,12 @@ import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
/**
* Class for storing information about a software license.
*/
public class License implements Parcelable {
public class License implements Parcelable, Serializable {
public static final Creator<License> CREATOR = new Creator<License>() {
@Override
public License createFromParcel(final Parcel source) {

View File

@@ -1,7 +1,6 @@
package org.schabi.newpipe.about;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.LayoutInflater;
@@ -11,12 +10,14 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ShareUtils;
import java.io.Serializable;
import java.util.Arrays;
/**
@@ -26,13 +27,15 @@ public class LicenseFragment extends Fragment {
private static final String ARG_COMPONENTS = "components";
private SoftwareComponent[] softwareComponents;
private SoftwareComponent componentForContextMenu;
private License activeLicense;
private static final String LICENSE_KEY = "ACTIVE_LICENSE";
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
if (softwareComponents == null) {
throw new NullPointerException("softwareComponents is null");
}
LicenseFragment fragment = new LicenseFragment();
Bundle bundle = new Bundle();
final LicenseFragment fragment = new LicenseFragment();
final Bundle bundle = new Bundle();
bundle.putParcelableArray(ARG_COMPONENTS, softwareComponents);
fragment.setArguments(bundle);
return fragment;
@@ -44,8 +47,8 @@ public class LicenseFragment extends Fragment {
* @param context the context to use
* @param license the license to show
*/
private static void showLicense(final Context context, final License license) {
new LicenseFragmentHelper((Activity) context).execute(license);
private static void showLicense(final Activity context, final License license) {
new LicenseFragmentHelper(context).execute(license);
}
@Override
@@ -54,6 +57,12 @@ public class LicenseFragment extends Fragment {
softwareComponents = (SoftwareComponent[]) getArguments()
.getParcelableArray(ARG_COMPONENTS);
if (savedInstanceState != null) {
final Serializable license = savedInstanceState.getSerializable(LICENSE_KEY);
if (license != null) {
activeLicense = (License) license;
}
}
// Sort components by name
Arrays.sort(softwareComponents, (o1, o2) -> o1.getName().compareTo(o2.getName()));
}
@@ -66,8 +75,10 @@ public class LicenseFragment extends Fragment {
final ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
final View licenseLink = rootView.findViewById(R.id.app_read_license);
licenseLink.setOnClickListener(v ->
showLicense(getActivity(), StandardLicenses.GPL3));
licenseLink.setOnClickListener(v -> {
activeLicense = StandardLicenses.GPL3;
showLicense(getActivity(), StandardLicenses.GPL3);
});
for (final SoftwareComponent component : softwareComponents) {
final View componentView = inflater
@@ -81,11 +92,16 @@ public class LicenseFragment extends Fragment {
component.getLicense().getAbbreviation()));
componentView.setTag(component);
componentView.setOnClickListener(v ->
showLicense(getActivity(), component.getLicense()));
componentView.setOnClickListener(v -> {
activeLicense = component.getLicense();
showLicense(getActivity(), component.getLicense());
});
softwareComponentsView.addView(componentView);
registerForContextMenu(componentView);
}
if (activeLicense != null) {
showLicense(getActivity(), activeLicense);
}
return rootView;
}
@@ -101,7 +117,7 @@ public class LicenseFragment extends Fragment {
}
@Override
public boolean onContextItemSelected(final MenuItem item) {
public boolean onContextItemSelected(@NonNull final MenuItem item) {
// item.getMenuInfo() is null so we use the tag of the view
final SoftwareComponent component = componentForContextMenu;
if (component == null) {
@@ -116,4 +132,12 @@ public class LicenseFragment extends Fragment {
}
return false;
}
@Override
public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
if (activeLicense != null) {
savedInstanceState.putSerializable(LICENSE_KEY, activeLicense);
}
}
}

View File

@@ -51,7 +51,7 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
// split the HTML file and insert the stylesheet into the HEAD of the file
webViewData = licenseContent.toString().replace("</head>",
"<style>" + getLicenseStylesheet(context) + "</style></head>");
} catch (IOException e) {
} catch (final IOException e) {
throw new IllegalArgumentException(
"Could not get license file: " + license.getFilename(), e);
}

View File

@@ -14,13 +14,13 @@ import io.reactivex.Flowable;
@Dao
public interface BasicDAO<Entity> {
/* Inserts */
@Insert(onConflict = OnConflictStrategy.FAIL)
@Insert(onConflict = OnConflictStrategy.ABORT)
long insert(Entity entity);
@Insert(onConflict = OnConflictStrategy.FAIL)
@Insert(onConflict = OnConflictStrategy.ABORT)
List<Long> insertAll(Entity... entities);
@Insert(onConflict = OnConflictStrategy.FAIL)
@Insert(onConflict = OnConflictStrategy.ABORT)
List<Long> insertAll(Collection<Entity> entities);
/* Searches */

View File

@@ -49,7 +49,7 @@ public final class Converters {
@TypeConverter
public static FeedGroupIcon feedGroupIconOf(final Integer id) {
for (FeedGroupIcon icon : FeedGroupIcon.values()) {
for (final FeedGroupIcon icon : FeedGroupIcon.values()) {
if (icon.getId() == id) {
return icon;
}

View File

@@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity;
public final class Migrations {
public static final int DB_VER_1 = 1;
@@ -14,7 +14,7 @@ public final class Migrations {
public static final int DB_VER_3 = 3;
private static final String TAG = Migrations.class.getName();
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
public static final boolean DEBUG = MainActivity.DEBUG;
public static final Migration MIGRATION_1_2 = new Migration(DB_VER_1, DB_VER_2) {
@Override

View File

@@ -1,7 +1,33 @@
package org.schabi.newpipe.database.playlist;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public interface PlaylistLocalItem extends LocalItem {
String getOrderingName();
static List<PlaylistLocalItem> merge(
final List<PlaylistMetadataEntry> localPlaylists,
final List<PlaylistRemoteEntity> remotePlaylists) {
final List<PlaylistLocalItem> items = new ArrayList<>(
localPlaylists.size() + remotePlaylists.size());
items.addAll(localPlaylists);
items.addAll(remotePlaylists);
Collections.sort(items, (left, right) -> {
final String on1 = left.getOrderingName();
final String on2 = right.getOrderingName();
if (on1 == null) {
return on2 == null ? 0 : 1;
} else {
return on2 == null ? -1 : on1.compareToIgnoreCase(on2);
}
});
return items;
}
}

View File

@@ -20,6 +20,45 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
@Query("""
SELECT * FROM subscriptions
WHERE name LIKE '%' || :filter || '%'
ORDER BY name COLLATE NOCASE ASC
""")
abstract fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
@Query("""
SELECT * FROM subscriptions s
LEFT JOIN feed_group_subscription_join fgs
ON s.uid = fgs.subscription_id
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
ORDER BY name COLLATE NOCASE ASC
""")
abstract fun getSubscriptionsOnlyUngrouped(
currentGroupId: Long
): Flowable<List<SubscriptionEntity>>
@Query("""
SELECT * FROM subscriptions s
LEFT JOIN feed_group_subscription_join fgs
ON s.uid = fgs.subscription_id
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
AND s.name LIKE '%' || :filter || '%'
ORDER BY name COLLATE NOCASE ASC
""")
abstract fun getSubscriptionsOnlyUngroupedFiltered(
currentGroupId: Long,
filter: String
): Flowable<List<SubscriptionEntity>>
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
abstract fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>
@@ -52,7 +91,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
entity.uid = uidFromInsert
} else {
val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url)
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
entity.uid = subscriptionIdFromDb
update(entity)

View File

@@ -50,7 +50,7 @@ public class SubscriptionEntity {
@Ignore
public static SubscriptionEntity from(@NonNull final ChannelInfo info) {
SubscriptionEntity result = new SubscriptionEntity();
final SubscriptionEntity result = new SubscriptionEntity();
result.setServiceId(info.getServiceId());
result.setUrl(info.getUrl());
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(),
@@ -124,10 +124,61 @@ public class SubscriptionEntity {
@Ignore
public ChannelInfoItem toChannelInfoItem() {
ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
final ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
item.setThumbnailUrl(getAvatarUrl());
item.setSubscriberCount(getSubscriberCount());
item.setDescription(getDescription());
return item;
}
// TODO: Remove these generated methods by migrating this class to a data class from Kotlin.
@Override
@SuppressWarnings("EqualsReplaceableByObjectsCall")
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final SubscriptionEntity that = (SubscriptionEntity) o;
if (uid != that.uid) {
return false;
}
if (serviceId != that.serviceId) {
return false;
}
if (!url.equals(that.url)) {
return false;
}
if (name != null ? !name.equals(that.name) : that.name != null) {
return false;
}
if (avatarUrl != null ? !avatarUrl.equals(that.avatarUrl) : that.avatarUrl != null) {
return false;
}
if (subscriberCount != null
? !subscriberCount.equals(that.subscriberCount)
: that.subscriberCount != null) {
return false;
}
return description != null
? description.equals(that.description)
: that.description == null;
}
@Override
public int hashCode() {
int result = (int) (uid ^ (uid >>> 32));
result = 31 * result + serviceId;
result = 31 * result + url.hashCode();
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0);
result = 31 * result + (subscriberCount != null ? subscriberCount.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
return result;
}
}

View File

@@ -1,6 +1,5 @@
package org.schabi.newpipe.download;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
@@ -11,9 +10,10 @@ import android.view.ViewTreeObserver;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentTransaction;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
@@ -29,7 +29,7 @@ public class DownloadActivity extends AppCompatActivity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
// Service
Intent i = new Intent();
final Intent i = new Intent();
i.setClass(this, DownloadManagerService.class);
startService(i);
@@ -38,10 +38,10 @@ public class DownloadActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_downloader);
Toolbar toolbar = findViewById(R.id.toolbar);
final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.downloads_title);
@@ -57,13 +57,13 @@ public class DownloadActivity extends AppCompatActivity {
}
});
if (AndroidTvUtils.isTv(this)) {
if (DeviceUtils.isTv(this)) {
FocusOverlayView.setupFocusObserver(this);
}
}
private void updateFragments() {
MissionsFragment fragment = new MissionsFragment();
final MissionsFragment fragment = new MissionsFragment();
getSupportFragmentManager().beginTransaction()
.replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG)
@@ -74,7 +74,7 @@ public class DownloadActivity extends AppCompatActivity {
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.download_menu, menu);

View File

@@ -10,7 +10,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
@@ -124,7 +124,7 @@ public class DownloadDialog extends DialogFragment
private SharedPreferences prefs;
public static DownloadDialog newInstance(final StreamInfo info) {
DownloadDialog dialog = new DownloadDialog();
final DownloadDialog dialog = new DownloadDialog();
dialog.setInfo(info);
return dialog;
}
@@ -208,14 +208,15 @@ public class DownloadDialog extends DialogFragment
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
Icepick.restoreInstanceState(this, savedInstanceState);
SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams = new SparseArray<>(4);
List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
final SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams
= new SparseArray<>(4);
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
for (int i = 0; i < videoStreams.size(); i++) {
if (!videoStreams.get(i).isVideoOnly()) {
continue;
}
AudioStream audioStream = SecondaryStreamHelper
final AudioStream audioStream = SecondaryStreamHelper
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
if (audioStream != null) {
@@ -232,13 +233,13 @@ public class DownloadDialog extends DialogFragment
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
Intent intent = new Intent(context, DownloadManagerService.class);
final Intent intent = new Intent(context, DownloadManagerService.class);
context.startService(intent);
context.bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(final ComponentName cname, final IBinder service) {
DownloadManagerBinder mgr = (DownloadManagerBinder) service;
final DownloadManagerBinder mgr = (DownloadManagerBinder) service;
mainStorageAudio = mgr.getMainStorageAudio();
mainStorageVideo = mgr.getMainStorageVideo();
@@ -294,9 +295,9 @@ public class DownloadDialog extends DialogFragment
initToolbar(view.findViewById(R.id.toolbar));
setupDownloadOptions();
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
int threads = prefs.getInt(getString(R.string.default_download_threads), 3);
final int threads = prefs.getInt(getString(R.string.default_download_threads), 3);
threadsCountTextView.setText(String.valueOf(threads));
threadsSeekBar.setProgress(threads - 1);
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@@ -373,13 +374,13 @@ public class DownloadDialog extends DialogFragment
}
if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) {
File file = Utils.getFileForUri(data.getData());
final File file = Utils.getFileForUri(data.getData());
checkSelectedDownload(null, Uri.fromFile(file), file.getName(),
StoredFileHelper.DEFAULT_MIME);
return;
}
DocumentFile docFile = DocumentFile.fromSingleUri(context, data.getData());
final DocumentFile docFile = DocumentFile.fromSingleUri(context, data.getData());
if (docFile == null) {
showFailedDialog(R.string.general_error);
return;
@@ -515,7 +516,23 @@ public class DownloadDialog extends DialogFragment
videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE);
subtitleButton.setVisibility(isSubtitleStreamsAvailable ? View.VISIBLE : View.GONE);
if (isVideoStreamsAvailable) {
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
final String defaultMedia = prefs.getString(getString(R.string.last_used_download_type),
getString(R.string.last_download_type_video_key));
if (isVideoStreamsAvailable
&& (defaultMedia.equals(getString(R.string.last_download_type_video_key)))) {
videoButton.setChecked(true);
setupVideoSpinner();
} else if (isAudioStreamsAvailable
&& (defaultMedia.equals(getString(R.string.last_download_type_audio_key)))) {
audioButton.setChecked(true);
setupAudioSpinner();
} else if (isSubtitleStreamsAvailable
&& (defaultMedia.equals(getString(R.string.last_download_type_subtitle_key)))) {
subtitleButton.setChecked(true);
setupSubtitleSpinner();
} else if (isVideoStreamsAvailable) {
videoButton.setChecked(true);
setupVideoSpinner();
} else if (isAudioStreamsAvailable) {
@@ -564,7 +581,7 @@ public class DownloadDialog extends DialogFragment
}
private String getNameEditText() {
String str = nameEditText.getText().toString().trim();
final String str = nameEditText.getText().toString().trim();
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
}
@@ -591,9 +608,10 @@ public class DownloadDialog extends DialogFragment
}
private void prepareSelectedDownload() {
StoredDirectoryHelper mainStorage;
MediaFormat format;
String mime;
final StoredDirectoryHelper mainStorage;
final MediaFormat format;
final String mime;
final String selectedMediaType;
// first, build the filename and get the output folder (if possible)
// later, run a very very very large file checking logic
@@ -602,6 +620,7 @@ public class DownloadDialog extends DialogFragment
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
selectedMediaType = getString(R.string.last_download_type_audio_key);
mainStorage = mainStorageAudio;
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
switch (format) {
@@ -616,12 +635,14 @@ public class DownloadDialog extends DialogFragment
}
break;
case R.id.video_button:
selectedMediaType = getString(R.string.last_download_type_video_key);
mainStorage = mainStorageVideo;
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
mime = format.mimeType;
filename += format.suffix;
break;
case R.id.subtitle_button:
selectedMediaType = getString(R.string.last_download_type_subtitle_key);
mainStorage = mainStorageVideo; // subtitle & video files go together
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
mime = format.mimeType;
@@ -663,6 +684,11 @@ public class DownloadDialog extends DialogFragment
// check for existing file with the same name
checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime);
// remember the last media type downloaded by the user
prefs.edit()
.putString(getString(R.string.last_used_download_type), selectedMediaType)
.apply();
}
private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
@@ -683,15 +709,17 @@ public class DownloadDialog extends DialogFragment
storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile,
mainStorage.getTag());
}
} catch (Exception e) {
} catch (final Exception e) {
showErrorActivity(e);
return;
}
// check if is our file
MissionState state = downloadManager.checkForExistingMission(storage);
@StringRes int msgBtn;
@StringRes int msgBody;
final MissionState state = downloadManager.checkForExistingMission(storage);
@StringRes
final int msgBtn;
@StringRes
final int msgBody;
switch (state) {
case Finished:
@@ -744,8 +772,7 @@ public class DownloadDialog extends DialogFragment
return;
}
AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
final AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
.setTitle(R.string.download_dialog_title)
.setMessage(msgBody)
.setNegativeButton(android.R.string.cancel, null);
@@ -787,7 +814,7 @@ public class DownloadDialog extends DialogFragment
// try take (or steal) the file
storageNew = new StoredFileHelper(context, mainStorage.getUri(),
targetFile, mainStorage.getTag());
} catch (IOException e) {
} catch (final IOException e) {
Log.e(TAG, "Failed to take (or steal) the file in "
+ targetFile.toString());
storageNew = null;
@@ -825,18 +852,18 @@ public class DownloadDialog extends DialogFragment
if (storage.length() > 0) {
storage.truncate();
}
} catch (IOException e) {
} catch (final IOException e) {
Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e);
showFailedDialog(R.string.overwrite_failed);
return;
}
Stream selectedStream;
final Stream selectedStream;
Stream secondaryStream = null;
char kind;
final char kind;
int threads = threadsSeekBar.getProgress() + 1;
String[] urls;
MissionRecoveryInfo[] recoveryInfo;
final String[] urls;
final MissionRecoveryInfo[] recoveryInfo;
String psName = null;
String[] psArgs = null;
long nearLength = 0;
@@ -857,7 +884,7 @@ public class DownloadDialog extends DialogFragment
kind = 'v';
selectedStream = videoStreamsAdapter.getItem(selectedVideoIndex);
SecondaryStreamHelper<AudioStream> secondary = videoStreamsAdapter
final SecondaryStreamHelper<AudioStream> secondary = videoStreamsAdapter
.getAllSecondary()
.get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream));
@@ -871,7 +898,7 @@ public class DownloadDialog extends DialogFragment
}
psArgs = null;
long videoSize = wrappedVideoStreams
final long videoSize = wrappedVideoStreams
.getSizeInBytes((VideoStream) selectedStream);
// set nearLength, only, if both sizes are fetched or known. This probably

View File

@@ -230,7 +230,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
}
Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
Intent intent = new Intent(activity, ReCaptchaActivity.class);
final Intent intent = new Intent(activity, ReCaptchaActivity.class);
intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, exception.getUrl());
startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST);

View File

@@ -3,7 +3,7 @@ package org.schabi.newpipe.fragments;
import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -74,7 +74,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
previousYoutubeRestrictedModeEnabled =
PreferenceManager.getDefaultSharedPreferences(getContext())
PreferenceManager.getDefaultSharedPreferences(requireContext())
.getBoolean(youtubeRestrictedModeEnabledKey, false);
}
@@ -104,8 +104,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public void onResume() {
super.onResume();
boolean youtubeRestrictedModeEnabled =
PreferenceManager.getDefaultSharedPreferences(getContext())
final boolean youtubeRestrictedModeEnabled =
PreferenceManager.getDefaultSharedPreferences(requireContext())
.getBoolean(youtubeRestrictedModeEnabledKey, false);
if (previousYoutubeRestrictedModeEnabled != youtubeRestrictedModeEnabled) {
previousYoutubeRestrictedModeEnabled = youtubeRestrictedModeEnabled;
@@ -137,7 +137,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
inflater.inflate(R.menu.main_fragment_menu, menu);
ActionBar supportActionBar = activity.getSupportActionBar();
final ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayHomeAsUpEnabled(false);
}
@@ -148,11 +148,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
switch (item.getItemId()) {
case R.id.action_search:
try {
NavigationHelper.openSearchFragment(
getFragmentManager(),
ServiceHelper.getSelectedServiceId(activity),
"");
} catch (Exception e) {
NavigationHelper.openSearchFragment(getFM(),
ServiceHelper.getSelectedServiceId(activity), "");
} catch (final Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
return true;
@@ -239,7 +237,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
Fragment fragment = null;
try {
fragment = tab.getFragment(context);
} catch (ExtractionException e) {
} catch (final ExtractionException e) {
throwable = e;
}

View File

@@ -14,9 +14,9 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
int pastVisibleItems = 0;
int visibleItemCount;
int totalItemCount;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
final int visibleItemCount;
final int totalItemCount;
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
visibleItemCount = layoutManager.getChildCount();
totalItemCount = layoutManager.getItemCount();
@@ -26,7 +26,7 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
pastVisibleItems = ((LinearLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int[] positions = ((StaggeredGridLayoutManager) layoutManager)
final int[] positions = ((StaggeredGridLayoutManager) layoutManager)
.findFirstVisibleItemPositions(null);
if (positions != null && positions.length > 0) {
pastVisibleItems = positions[0];

View File

@@ -1,16 +1,29 @@
package org.schabi.newpipe.fragments.detail;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import java.io.Serializable;
class StackItem implements Serializable {
private final int serviceId;
private final String url;
private String url;
private String title;
private PlayQueue playQueue;
StackItem(final int serviceId, final String url, final String title) {
StackItem(final int serviceId, final String url,
final String title, final PlayQueue playQueue) {
this.serviceId = serviceId;
this.url = url;
this.title = title;
this.playQueue = playQueue;
}
public void setUrl(final String url) {
this.url = url;
}
public void setPlayQueue(final PlayQueue queue) {
this.playQueue = queue;
}
public int getServiceId() {
@@ -29,6 +42,10 @@ class StackItem implements Serializable {
return url;
}
public PlayQueue getPlayQueue() {
return playQueue;
}
@Override
public String toString() {
return getServiceId() + ":" + getUrl() + " > " + getTitle();

View File

@@ -2,6 +2,7 @@ package org.schabi.newpipe.fragments.detail;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@@ -10,16 +11,20 @@ import androidx.fragment.app.FragmentPagerAdapter;
import java.util.ArrayList;
import java.util.List;
public class TabAdaptor extends FragmentPagerAdapter {
public class TabAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
private final FragmentManager fragmentManager;
public TabAdaptor(final FragmentManager fm) {
super(fm);
public TabAdapter(final FragmentManager fm) {
// if changed to BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT => crash if enqueueing stream in
// the background and then clicking on it to open VideoDetailFragment:
// "Cannot setMaxLifecycle for Fragment not attached to FragmentManager"
super(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
this.fragmentManager = fm;
}
@NonNull
@Override
public Fragment getItem(final int position) {
return mFragmentList.get(position);
@@ -50,14 +55,14 @@ public class TabAdaptor extends FragmentPagerAdapter {
}
public void updateItem(final String title, final Fragment fragment) {
int index = mFragmentTitleList.indexOf(title);
final int index = mFragmentTitleList.indexOf(title);
if (index != -1) {
updateItem(index, fragment);
}
}
@Override
public int getItemPosition(final Object object) {
public int getItemPosition(@NonNull final Object object) {
if (mFragmentList.contains(object)) {
return mFragmentList.indexOf(object);
} else {
@@ -82,7 +87,9 @@ public class TabAdaptor extends FragmentPagerAdapter {
}
@Override
public void destroyItem(final ViewGroup container, final int position, final Object object) {
public void destroyItem(@NonNull final ViewGroup container,
final int position,
@NonNull final Object object) {
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
}

View File

@@ -6,7 +6,7 @@ import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
@@ -136,7 +136,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
final RecyclerView.ViewHolder itemHolder =
itemsList.findContainingViewHolder(focusedItem);
return itemHolder.getAdapterPosition();
} catch (NullPointerException e) {
} catch (final NullPointerException e) {
return -1;
}
}
@@ -169,7 +169,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
}
itemsList.post(() -> {
RecyclerView.ViewHolder focusedHolder =
final RecyclerView.ViewHolder focusedHolder =
itemsList.findViewHolderForAdapterPosition(position);
if (focusedHolder != null) {
@@ -279,7 +279,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
selectedItem.getServiceId(),
selectedItem.getUrl(),
selectedItem.getName());
} catch (Exception e) {
} catch (final Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
}
@@ -294,7 +294,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
selectedItem.getServiceId(),
selectedItem.getUrl(),
selectedItem.getName());
} catch (Exception e) {
} catch (final Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
}
@@ -367,7 +367,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar();
final ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayShowTitleEnabled(true);
if (useAsFrontPage) {

View File

@@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.views.NewPipeRecyclerView;
@@ -30,7 +31,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
protected String url;
protected I currentInfo;
protected String currentNextPageUrl;
protected Page currentNextPage;
protected Disposable currentWorker;
@Override
@@ -78,7 +79,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
public void writeTo(final Queue<Object> objectsToSave) {
super.writeTo(objectsToSave);
objectsToSave.add(currentInfo);
objectsToSave.add(currentNextPageUrl);
objectsToSave.add(currentNextPage);
}
@Override
@@ -86,7 +87,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects);
currentInfo = (I) savedObjects.poll();
currentNextPageUrl = (String) savedObjects.poll();
currentNextPage = (Page) savedObjects.poll();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -130,9 +131,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
.subscribe((@NonNull I result) -> {
isLoading.set(false);
currentInfo = result;
currentNextPageUrl = result.getNextPageUrl();
currentNextPage = result.getNextPage();
handleResult(result);
}, (@NonNull Throwable throwable) -> onError(throwable));
}, this::onError);
}
/**
@@ -157,11 +158,10 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::allowDownwardFocusScroll)
.subscribe((@io.reactivex.annotations.NonNull
ListExtractor.InfoItemsPage InfoItemsPage) -> {
.subscribe((@NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
isLoading.set(false);
handleNextItems(InfoItemsPage);
}, (@io.reactivex.annotations.NonNull Throwable throwable) -> {
}, (@NonNull Throwable throwable) -> {
isLoading.set(false);
onError(throwable);
});
@@ -182,7 +182,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
@Override
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
currentNextPageUrl = result.getNextPageUrl();
currentNextPage = result.getNextPage();
infoListAdapter.addInfoItemList(result.getItems());
showListFooter(hasMoreItems());
@@ -190,7 +190,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
@Override
protected boolean hasMoreItems() {
return !TextUtils.isEmpty(currentNextPageUrl);
return Page.isValid(currentNextPage);
}
/*//////////////////////////////////////////////////////////////////////////

View File

@@ -98,7 +98,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
public static ChannelFragment getInstance(final int serviceId, final String url,
final String name) {
ChannelFragment instance = new ChannelFragment();
final ChannelFragment instance = new ChannelFragment();
instance.setInitialData(serviceId, url, name);
return instance;
}
@@ -189,7 +189,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar();
final ActionBar supportActionBar = activity.getSupportActionBar();
if (useAsFrontPage && supportActionBar != null) {
supportActionBar.setDisplayHomeAsUpEnabled(false);
} else {
@@ -206,7 +206,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
private void openRssFeed() {
final ChannelInfo info = currentInfo;
if (info != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl()));
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl()));
startActivity(intent);
}
}
@@ -345,7 +345,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
if (DEBUG) {
Log.d(TAG, "No subscription to this channel!");
}
SubscriptionEntity channel = new SubscriptionEntity();
final SubscriptionEntity channel = new SubscriptionEntity();
channel.setServiceId(info.getServiceId());
channel.setUrl(info.getUrl());
channel.setData(info.getName(),
@@ -371,16 +371,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
+ "isSubscribed = [" + isSubscribed + "]");
}
boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
int backgroundDuration = isButtonVisible ? 300 : 0;
int textDuration = isButtonVisible ? 200 : 0;
final boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
final int backgroundDuration = isButtonVisible ? 300 : 0;
final int textDuration = isButtonVisible ? 200 : 0;
int subscribeBackground = ThemeHelper
final int subscribeBackground = ThemeHelper
.resolveColorFromAttr(activity, R.attr.colorPrimary);
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
int subscribedBackground = ContextCompat
final int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
final int subscribedBackground = ContextCompat
.getColor(activity, R.color.subscribed_background_color);
int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
final int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
if (!isSubscribed) {
headerSubscribeButton.setText(R.string.subscribe_button_title);
@@ -403,7 +403,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPageUrl);
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPage);
}
@Override
@@ -426,10 +426,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
case R.id.sub_channel_title_view:
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
try {
NavigationHelper.openChannelFragment(getFragmentManager(),
currentInfo.getServiceId(), currentInfo.getParentChannelUrl(),
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
currentInfo.getParentChannelUrl(),
currentInfo.getParentChannelName());
} catch (Exception e) {
} catch (final Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
} else if (DEBUG) {
@@ -490,13 +490,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
playlistCtrl.setVisibility(View.VISIBLE);
List<Throwable> errors = new ArrayList<>(result.getErrors());
final List<Throwable> errors = new ArrayList<>(result.getErrors());
if (!errors.isEmpty()) {
// handling ContentNotSupportedException not to show the error but an appropriate string
// so that crashes won't be sent uselessly and the user will understand what happened
for (Iterator<Throwable> it = errors.iterator(); it.hasNext();) {
Throwable throwable = it.next();
final Throwable throwable = it.next();
if (throwable instanceof ContentNotSupportedException) {
showContentNotSupported();
it.remove();
@@ -519,7 +519,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
monitorSubscription(result);
headerPlayAllButton.setOnClickListener(view -> NavigationHelper
.playOnMainPlayer(activity, getPlayQueue(), false));
.playOnMainPlayer(activity, getPlayQueue(), true));
headerPopupButton.setOnClickListener(view -> NavigationHelper
.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view -> NavigationHelper
@@ -549,13 +549,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
private PlayQueue getPlayQueue(final int index) {
final List<StreamInfoItem> streamItems = new ArrayList<>();
for (InfoItem i : infoListAdapter.getItemsList()) {
for (final InfoItem i : infoListAdapter.getItemsList()) {
if (i instanceof StreamInfoItem) {
streamItems.add((StreamInfoItem) i);
}
}
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
currentInfo.getNextPageUrl(), streamItems, index);
currentInfo.getNextPage(), streamItems, index);
}
@Override
@@ -581,7 +581,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
return true;
}
int errorId = exception instanceof ExtractionException
final int errorId = exception instanceof ExtractionException
? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL,

View File

@@ -26,11 +26,9 @@ import io.reactivex.disposables.CompositeDisposable;
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
private CompositeDisposable disposables = new CompositeDisposable();
private boolean mIsVisibleToUser = false;
public static CommentsFragment getInstance(final int serviceId, final String url,
final String name) {
CommentsFragment instance = new CommentsFragment();
final CommentsFragment instance = new CommentsFragment();
instance.setInitialData(serviceId, url, name);
return instance;
}
@@ -39,12 +37,6 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
}
@Override
public void onAttach(final Context context) {
super.onAttach(context);
@@ -71,7 +63,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPage);
}
@Override
@@ -92,7 +84,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
public void handleResult(@NonNull final CommentsInfo result) {
super.handleResult(result);
AnimationUtils.slideUp(getView(), 120, 150, 0.06f);
AnimationUtils.slideUp(requireView(), 120, 150, 0.06f);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,

View File

@@ -44,8 +44,8 @@ public class DefaultKioskFragment extends KioskFragment {
name = kioskTranslatedName;
currentInfo = null;
currentNextPageUrl = null;
} catch (ExtractionException e) {
currentNextPage = null;
} catch (final ExtractionException e) {
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
"Loading default kiosk from selected service", 0);
}

View File

@@ -72,9 +72,9 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
public static KioskFragment getInstance(final int serviceId, final String kioskId)
throws ExtractionException {
KioskFragment instance = new KioskFragment();
StreamingService service = NewPipe.getService(serviceId);
ListLinkHandlerFactory kioskLinkHandlerFactory = service.getKioskList()
final KioskFragment instance = new KioskFragment();
final StreamingService service = NewPipe.getService(serviceId);
final ListLinkHandlerFactory kioskLinkHandlerFactory = service.getKioskList()
.getListLinkHandlerFactoryByType(kioskId);
instance.setInitialData(serviceId,
kioskLinkHandlerFactory.fromId(kioskId).getUrl(), kioskId);
@@ -101,7 +101,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
if (useAsFrontPage && isVisibleToUser && activity != null) {
try {
setTitle(kioskTranslatedName);
} catch (Exception e) {
} catch (final Exception e) {
onUnrecoverableError(e, UserAction.UI_ERROR,
"none",
"none", R.string.app_ui_crash);
@@ -132,7 +132,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar();
final ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null && useAsFrontPage) {
supportActionBar.setDisplayHomeAsUpEnabled(false);
}
@@ -150,7 +150,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
@Override
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPageUrl);
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPage);
}
/*//////////////////////////////////////////////////////////////////////////

View File

@@ -85,7 +85,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
public static PlaylistFragment getInstance(final int serviceId, final String url,
final String name) {
PlaylistFragment instance = new PlaylistFragment();
final PlaylistFragment instance = new PlaylistFragment();
instance.setInitialData(serviceId, url, name);
return instance;
}
@@ -229,7 +229,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPageUrl);
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPage);
}
@Override
@@ -286,11 +286,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
headerUploaderLayout.setOnClickListener(v -> {
try {
NavigationHelper.openChannelFragment(getFragmentManager(),
result.getServiceId(),
result.getUploaderUrl(),
result.getUploaderName());
} catch (Exception e) {
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
result.getUploaderUrl(), result.getUploaderName());
} catch (final Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
});
@@ -318,7 +316,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
.subscribe(getPlaylistBookmarkSubscriber());
headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view ->
@@ -341,7 +339,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
private PlayQueue getPlayQueue(final int index) {
final List<StreamInfoItem> infoItems = new ArrayList<>();
for (InfoItem i : infoListAdapter.getItemsList()) {
for (final InfoItem i : infoListAdapter.getItemsList()) {
if (i instanceof StreamInfoItem) {
infoItems.add((StreamInfoItem) i);
}
@@ -349,7 +347,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
return new PlaylistPlayQueue(
currentInfo.getServiceId(),
currentInfo.getUrl(),
currentInfo.getNextPageUrl(),
currentInfo.getNextPage(),
infoItems,
index
);
@@ -375,7 +373,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
return true;
}
int errorId = exception instanceof ExtractionException
final int errorId = exception instanceof ExtractionException
? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST,
NewPipe.getNameOfService(serviceId), url, errorId);

View File

@@ -5,8 +5,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager;
import android.text.Editable;
import android.text.Html;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
@@ -37,6 +39,7 @@ import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.search.SearchExtractor;
@@ -46,9 +49,10 @@ import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
@@ -75,7 +79,7 @@ import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovement
import static java.util.Arrays.asList;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage>
public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage<?>>
implements BackPressable {
/*//////////////////////////////////////////////////////////////////////////
// Search
@@ -118,14 +122,18 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@State
String lastSearchedString;
@State
String searchSuggestion;
@State
boolean isCorrectedSearch;
@State
boolean wasSearchFocused = false;
private Map<Integer, String> menuItemToFilterName;
private StreamingService service;
private String currentPageUrl;
private String nextPageUrl;
private String contentCountry;
private Page nextPage;
private boolean isSuggestionsEnabled = true;
private Disposable searchDisposable;
@@ -143,7 +151,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
private EditText searchEditText;
private View searchClear;
private TextView correctSuggestion;
private View suggestionsPanel;
private boolean suggestionsPanelVisible = false;
private RecyclerView suggestionsRecyclerView;
/*////////////////////////////////////////////////////////////////////////*/
@@ -151,7 +162,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
private TextWatcher textWatcher;
public static SearchFragment getInstance(final int serviceId, final String searchString) {
SearchFragment searchFragment = new SearchFragment();
final SearchFragment searchFragment = new SearchFragment();
searchFragment.setQuery(serviceId, searchString, new String[0], "");
if (!TextUtils.isEmpty(searchString)) {
@@ -177,8 +188,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
super.onAttach(context);
suggestionListAdapter = new SuggestionListAdapter(activity);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
boolean isSearchHistoryEnabled = preferences
final SharedPreferences preferences
= PreferenceManager.getDefaultSharedPreferences(activity);
final boolean isSearchHistoryEnabled = preferences
.getBoolean(getString(R.string.enable_search_history_key), true);
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
@@ -189,11 +201,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
final SharedPreferences preferences
= PreferenceManager.getDefaultSharedPreferences(activity);
isSuggestionsEnabled = preferences
.getBoolean(getString(R.string.show_search_suggestions_key), true);
contentCountry = preferences.getString(getString(R.string.content_country_key),
getString(R.string.default_localization_key));
}
@Override
@@ -221,9 +232,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (suggestionDisposable != null) {
suggestionDisposable.dispose();
}
if (disposables != null) {
disposables.clear();
}
disposables.clear();
hideKeyboardSearch();
}
@@ -236,9 +245,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
try {
service = NewPipe.getService(serviceId);
} catch (Exception e) {
ErrorActivity.reportError(getActivity(), e, getActivity().getClass(),
getActivity().findViewById(android.R.id.content),
} catch (final Exception e) {
ErrorActivity.reportError(getActivity(), e, requireActivity().getClass(),
requireActivity().findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
"",
"", R.string.general_error));
@@ -257,6 +266,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
}
handleSearchSuggestion();
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
initSuggestionObserver();
}
@@ -289,26 +300,20 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (suggestionDisposable != null) {
suggestionDisposable.dispose();
}
if (disposables != null) {
disposables.clear();
}
disposables.clear();
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case ReCaptchaActivity.RECAPTCHA_REQUEST:
if (resultCode == Activity.RESULT_OK
&& !TextUtils.isEmpty(searchString)) {
search(searchString, contentFilter, sortFilter);
} else {
Log.e(TAG, "ReCaptcha failed");
}
break;
default:
Log.e(TAG, "Request code from activity not supported [" + requestCode + "]");
break;
if (requestCode == ReCaptchaActivity.RECAPTCHA_REQUEST) {
if (resultCode == Activity.RESULT_OK
&& !TextUtils.isEmpty(searchString)) {
search(searchString, contentFilter, sortFilter);
} else {
Log.e(TAG, "ReCaptcha failed");
}
} else {
Log.e(TAG, "Request code from activity not supported [" + requestCode + "]");
}
}
@@ -326,7 +331,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public int getMovementFlags(@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder viewHolder) {
return getSuggestionMovementFlags(recyclerView, viewHolder);
return getSuggestionMovementFlags(viewHolder);
}
@Override
@@ -338,13 +343,15 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, final int i) {
onSuggestionItemSwiped(viewHolder, i);
onSuggestionItemSwiped(viewHolder);
}
}).attachToRecyclerView(suggestionsRecyclerView);
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
correctSuggestion = rootView.findViewById(R.id.correct_suggestion);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -354,15 +361,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public void writeTo(final Queue<Object> objectsToSave) {
super.writeTo(objectsToSave);
objectsToSave.add(currentPageUrl);
objectsToSave.add(nextPageUrl);
objectsToSave.add(nextPage);
}
@Override
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects);
currentPageUrl = (String) savedObjects.poll();
nextPageUrl = (String) savedObjects.poll();
nextPage = (Page) savedObjects.poll();
}
@Override
@@ -401,7 +406,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar();
final ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayShowTitleEnabled(false);
supportActionBar.setDisplayHomeAsUpEnabled(true);
@@ -412,16 +417,16 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
int itemId = 0;
boolean isFirstItem = true;
final Context c = getContext();
for (String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
for (final String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
if (filter.equals("music_songs")) {
MenuItem musicItem = menu.add(2,
final MenuItem musicItem = menu.add(2,
itemId++,
0,
"YouTube Music");
musicItem.setEnabled(false);
}
menuItemToFilterName.put(itemId, filter);
MenuItem item = menu.add(1,
final MenuItem item = menu.add(1,
itemId++,
0,
ServiceHelper.getTranslatedFilterString(filter, c));
@@ -437,7 +442,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
List<String> cf = new ArrayList<>(1);
final List<String> cf = new ArrayList<>(1);
cf.add(menuItemToFilterName.get(item.getItemId()));
changeContentFilter(item, cf);
@@ -446,7 +451,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
private void restoreFilterChecked(final Menu menu, final int itemId) {
if (itemId != -1) {
MenuItem item = menu.findItem(itemId);
final MenuItem item = menu.findItem(itemId);
if (item == null) {
return;
}
@@ -470,16 +475,16 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
searchToolbarContainer.setTranslationX(100);
searchToolbarContainer.setAlpha(0f);
searchToolbarContainer.setAlpha(0.0f);
searchToolbarContainer.setVisibility(View.VISIBLE);
searchToolbarContainer.animate()
.translationX(0)
.alpha(1f)
.alpha(1.0f)
.setDuration(200)
.setInterpolator(new DecelerateInterpolator()).start();
} else {
searchToolbarContainer.setTranslationX(0);
searchToolbarContainer.setAlpha(1f);
searchToolbarContainer.setAlpha(1.0f);
searchToolbarContainer.setVisibility(View.VISIBLE);
}
}
@@ -493,10 +498,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
if (TextUtils.isEmpty(searchEditText.getText())) {
NavigationHelper.gotoMainFragment(getFragmentManager());
NavigationHelper.gotoMainFragment(getFM());
return;
}
correctSuggestion.setVisibility(View.GONE);
searchEditText.setText("");
suggestionListAdapter.setItems(new ArrayList<>());
showKeyboardSearch();
@@ -511,7 +518,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel();
}
if (AndroidTvUtils.isTv(getContext())) {
if (DeviceUtils.isTv(getContext())) {
showKeyboardSearch();
}
});
@@ -554,15 +561,17 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(final CharSequence s, final int start,
final int count, final int after) { }
final int count, final int after) {
}
@Override
public void onTextChanged(final CharSequence s, final int start,
final int before, final int count) { }
final int before, final int count) {
}
@Override
public void afterTextChanged(final Editable s) {
String newText = searchEditText.getText().toString();
final String newText = searchEditText.getText().toString();
suggestionPublisher.onNext(newText);
}
};
@@ -609,6 +618,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (DEBUG) {
Log.d(TAG, "showSuggestionsPanel() called");
}
suggestionsPanelVisible = true;
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, true, 200);
}
@@ -616,6 +626,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (DEBUG) {
Log.d(TAG, "hideSuggestionsPanel() called");
}
suggestionsPanelVisible = false;
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, false, 200);
}
@@ -628,7 +639,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
if (searchEditText.requestFocus()) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
final InputMethodManager imm = (InputMethodManager) activity.getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
}
@@ -642,7 +653,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
return;
}
InputMethodManager imm = (InputMethodManager) activity
final InputMethodManager imm = (InputMethodManager) activity
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
InputMethodManager.RESULT_UNCHANGED_SHOWN);
@@ -651,8 +662,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
private void showDeleteSuggestionDialog(final SuggestionItem item) {
if (activity == null || historyRecordManager == null || suggestionPublisher == null
|| searchEditText == null || disposables == null) {
if (activity == null || historyRecordManager == null || searchEditText == null) {
return;
}
final String query = item.query;
@@ -677,7 +687,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public boolean onBackPressed() {
if (suggestionsPanel.getVisibility() == View.VISIBLE
if (suggestionsPanelVisible
&& infoListAdapter.getItemsList().size() > 0
&& !isLoading.get()) {
hideSuggestionsPanel();
@@ -688,10 +698,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
return false;
}
public void giveSearchEditTextFocus() {
showKeyboardSearch();
}
private void initSuggestionObserver() {
if (DEBUG) {
Log.d(TAG, "initSuggestionObserver() called");
@@ -713,8 +719,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
.getRelatedSearches(query, 3, 25);
final Observable<List<SuggestionItem>> local = flowable.toObservable()
.map(searchHistoryEntries -> {
List<SuggestionItem> result = new ArrayList<>();
for (SearchHistoryEntry entry : searchHistoryEntries) {
final List<SuggestionItem> result = new ArrayList<>();
for (final SearchHistoryEntry entry : searchHistoryEntries) {
result.add(new SuggestionItem(true, entry.getSearch()));
}
return result;
@@ -728,17 +734,24 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
final Observable<List<SuggestionItem>> network = ExtractorHelper
.suggestionsFor(serviceId, query)
.onErrorReturn(throwable -> {
if (!ExceptionUtils.isNetworkRelated(throwable)) {
showSnackBarError(throwable, UserAction.GET_SUGGESTIONS,
NewPipe.getNameOfService(serviceId), searchString, 0);
}
return new ArrayList<>();
})
.toObservable()
.map(strings -> {
List<SuggestionItem> result = new ArrayList<>();
for (String entry : strings) {
final List<SuggestionItem> result = new ArrayList<>();
for (final String entry : strings) {
result.add(new SuggestionItem(false, entry));
}
return result;
});
return Observable.zip(local, network, (localResult, networkResult) -> {
List<SuggestionItem> result = new ArrayList<>();
final List<SuggestionItem> result = new ArrayList<>();
if (localResult.size() > 0) {
result.addAll(localResult);
}
@@ -747,7 +760,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
final Iterator<SuggestionItem> iterator = networkResult.iterator();
while (iterator.hasNext() && localResult.size() > 0) {
final SuggestionItem next = iterator.next();
for (SuggestionItem item : localResult) {
for (final SuggestionItem item : localResult) {
if (item.query.equals(next.query)) {
iterator.remove();
break;
@@ -777,58 +790,58 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
// no-op
}
private void search(final String ss, final String[] cf, final String sf) {
private void search(final String theSearchString,
final String[] theContentFilter,
final String theSortFilter) {
if (DEBUG) {
Log.d(TAG, "search() called with: query = [" + ss + "]");
Log.d(TAG, "search() called with: query = [" + theSearchString + "]");
}
if (ss.isEmpty()) {
if (theSearchString.isEmpty()) {
return;
}
try {
final StreamingService streamingService = NewPipe.getServiceByUrl(ss);
final StreamingService streamingService = NewPipe.getServiceByUrl(theSearchString);
if (streamingService != null) {
showLoading();
disposables.add(Observable
.fromCallable(() ->
NavigationHelper.getIntentByLink(activity, streamingService, ss))
.fromCallable(() -> NavigationHelper.getIntentByLink(activity,
streamingService, theSearchString))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
getFragmentManager().popBackStackImmediate();
getFM().popBackStackImmediate();
activity.startActivity(intent);
}, throwable ->
showError(getString(R.string.url_not_supported_toast), false)));
showError(getString(R.string.unsupported_url), false)));
return;
}
} catch (Exception ignored) {
} catch (final Exception ignored) {
// Exception occurred, it's not a url
}
lastSearchedString = this.searchString;
this.searchString = ss;
this.searchString = theSearchString;
infoListAdapter.clearStreamItemList();
hideSuggestionsPanel();
hideKeyboardSearch();
historyRecordManager.onSearched(serviceId, ss)
disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> {
},
error -> showSnackBarError(error, UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId), ss, 0)
);
suggestionPublisher.onNext(ss);
NewPipe.getNameOfService(serviceId), theSearchString, 0)
));
suggestionPublisher.onNext(theSearchString);
startLoading(false);
}
@Override
public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad);
if (disposables != null) {
disposables.clear();
}
disposables.clear();
if (searchDisposable != null) {
searchDisposable.dispose();
}
@@ -845,7 +858,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
protected void loadMoreItems() {
if (nextPageUrl == null || nextPageUrl.isEmpty()) {
if (!Page.isValid(nextPage)) {
return;
}
isLoading.set(true);
@@ -858,7 +871,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
searchString,
asList(contentFilter),
sortFilter,
nextPageUrl)
nextPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
@@ -867,8 +880,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
protected boolean hasMoreItems() {
// TODO: No way to tell if search has more items in the moment
return true;
return Page.isValid(nextPage);
}
@Override
@@ -881,22 +893,25 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void changeContentFilter(final MenuItem item, final List<String> cf) {
this.filterItemCheckedId = item.getItemId();
private void changeContentFilter(final MenuItem item, final List<String> theContentFilter) {
filterItemCheckedId = item.getItemId();
item.setChecked(true);
this.contentFilter = new String[]{cf.get(0)};
contentFilter = new String[]{theContentFilter.get(0)};
if (!TextUtils.isEmpty(searchString)) {
search(searchString, this.contentFilter, sortFilter);
search(searchString, contentFilter, sortFilter);
}
}
private void setQuery(final int sid, final String ss, final String[] cf, final String sf) {
this.serviceId = sid;
this.searchString = searchString;
this.contentFilter = cf;
this.sortFilter = sf;
private void setQuery(final int theServiceId,
final String theSearchString,
final String[] theContentFilter,
final String theSortFilter) {
serviceId = theServiceId;
searchString = theSearchString;
contentFilter = theContentFilter;
sortFilter = theSortFilter;
}
/*//////////////////////////////////////////////////////////////////////////
@@ -910,7 +925,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
suggestionsRecyclerView.smoothScrollToPosition(0);
suggestionsRecyclerView.post(() -> suggestionListAdapter.setItems(suggestions));
if (errorPanelRoot.getVisibility() == View.VISIBLE) {
if (suggestionsPanelVisible && errorPanelRoot.getVisibility() == View.VISIBLE) {
hideLoading();
}
}
@@ -923,7 +938,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
return;
}
int errorId = exception instanceof ParsingException
final int errorId = exception instanceof ParsingException
? R.string.parsing_error
: R.string.general_error;
onUnrecoverableError(exception, UserAction.GET_SUGGESTIONS,
@@ -961,9 +976,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
NewPipe.getNameOfService(serviceId), searchString, 0);
}
searchSuggestion = result.getSearchSuggestion();
isCorrectedSearch = result.isCorrectedSearch();
handleSearchSuggestion();
lastSearchedString = searchString;
nextPageUrl = result.getNextPageUrl();
currentPageUrl = result.getUrl();
nextPage = result.getNextPage();
if (infoListAdapter.getItemsList().size() == 0) {
if (!result.getRelatedItems().isEmpty()) {
@@ -978,17 +997,48 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
super.handleResult(result);
}
private void handleSearchSuggestion() {
if (TextUtils.isEmpty(searchSuggestion)) {
correctSuggestion.setVisibility(View.GONE);
} else {
final String helperText = getString(isCorrectedSearch
? R.string.search_showing_result_for
: R.string.did_you_mean);
final String highlightedSearchSuggestion =
"<b><i>" + Html.escapeHtml(searchSuggestion) + "</i></b>";
final String text = String.format(helperText, highlightedSearchSuggestion);
correctSuggestion.setText(HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_LEGACY));
correctSuggestion.setOnClickListener(v -> {
correctSuggestion.setVisibility(View.GONE);
search(searchSuggestion, contentFilter, sortFilter);
searchEditText.setText(searchSuggestion);
});
correctSuggestion.setOnLongClickListener(v -> {
searchEditText.setText(searchSuggestion);
searchEditText.setSelection(searchSuggestion.length());
showKeyboardSearch();
return true;
});
correctSuggestion.setVisibility(View.VISIBLE);
}
}
@Override
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
public void handleNextItems(final ListExtractor.InfoItemsPage<?> result) {
showListFooter(false);
currentPageUrl = result.getNextPageUrl();
infoListAdapter.addInfoItemList(result.getItems());
nextPageUrl = result.getNextPageUrl();
nextPage = result.getNextPage();
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId),
"\"" + searchString + "\" → page: " + nextPageUrl, 0);
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
+ "pageIds: " + nextPage.getIds() + ", "
+ "pageCookies: " + nextPage.getCookies(), 0);
}
super.handleNextItems(result);
}
@@ -1003,7 +1053,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
infoListAdapter.clearStreamItemList();
showEmptyState();
} else {
int errorId = exception instanceof ParsingException
final int errorId = exception instanceof ParsingException
? R.string.parsing_error
: R.string.general_error;
onUnrecoverableError(exception, UserAction.SEARCHED,
@@ -1017,16 +1067,18 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
// Suggestion item touch helper
//////////////////////////////////////////////////////////////////////////*/
public int getSuggestionMovementFlags(@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder viewHolder) {
public int getSuggestionMovementFlags(@NonNull final RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition();
if (position == RecyclerView.NO_POSITION) {
return 0;
}
final SuggestionItem item = suggestionListAdapter.getItem(position);
return item.fromHistory ? makeMovementFlags(0,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
}
public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
final int i) {
public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition();
final String query = suggestionListAdapter.getItem(position).query;
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)

View File

@@ -33,7 +33,7 @@ public class SuggestionListAdapter
this.items.addAll(items);
} else {
// remove history items if history is disabled
for (SuggestionItem item : items) {
for (final SuggestionItem item : items) {
if (!item.fromHistory) {
this.items.add(item);
}
@@ -123,8 +123,8 @@ public class SuggestionListAdapter
private static int resolveResourceIdFromAttr(final Context context,
@AttrRes final int attr) {
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
int attributeResourceId = a.getResourceId(0, 0);
final TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
final int attributeResourceId = a.getResourceId(0, 0);
a.recycle();
return attributeResourceId;
}

View File

@@ -3,13 +3,12 @@ package org.schabi.newpipe.fragments.list.videos;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Switch;
import androidx.annotation.NonNull;
@@ -40,22 +39,14 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
//////////////////////////////////////////////////////////////////////////*/
private View headerRootLayout;
private Switch aSwitch;
private boolean mIsVisibleToUser = false;
private Switch autoplaySwitch;
public static RelatedVideosFragment getInstance(final StreamInfo info) {
RelatedVideosFragment instance = new RelatedVideosFragment();
final RelatedVideosFragment instance = new RelatedVideosFragment();
instance.setInitialData(info);
return instance;
}
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@@ -81,22 +72,18 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
}
protected View getListHeader() {
if (relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null) {
if (relatedStreamInfo != null && relatedStreamInfo.getRelatedItems() != null) {
headerRootLayout = activity.getLayoutInflater()
.inflate(R.layout.related_streams_header, itemsList, false);
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
autoplaySwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
aSwitch.setChecked(autoplay);
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton compoundButton,
final boolean b) {
PreferenceManager.getDefaultSharedPreferences(getContext()).edit()
.putBoolean(getString(R.string.auto_queue_key), b).apply();
}
});
final SharedPreferences pref = PreferenceManager
.getDefaultSharedPreferences(requireContext());
final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
autoplaySwitch.setChecked(autoplay);
autoplaySwitch.setOnCheckedChangeListener((compoundButton, b) ->
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit()
.putBoolean(getString(R.string.auto_queue_key), b).apply());
return headerRootLayout;
} else {
return null;
@@ -105,7 +92,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
return Single.fromCallable(ListExtractor.InfoItemsPage::emptyPage);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -179,12 +166,10 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
@Override
public void setTitle(final String title) {
return;
}
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
return;
}
private void setInitialData(final StreamInfo info) {
@@ -204,7 +189,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
super.onRestoreInstanceState(savedState);
if (savedState != null) {
Serializable serializable = savedState.getSerializable(INFO_KEY);
final Serializable serializable = savedState.getSerializable(INFO_KEY);
if (serializable instanceof RelatedStreamInfo) {
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
}
@@ -214,10 +199,11 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String s) {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
if (null != aSwitch) {
aSwitch.setChecked(autoplay);
final SharedPreferences pref =
PreferenceManager.getDefaultSharedPreferences(requireContext());
final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
if (autoplaySwitch != null) {
autoplaySwitch.setChecked(autoplay);
}
}

View File

@@ -70,7 +70,8 @@ public class InfoItemBuilder {
public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
final HistoryRecordManager historyRecordManager,
final boolean useMiniVariant) {
InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
final InfoItemHolder holder
= holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
holder.updateFromItem(infoItem, historyRecordManager);
return holder.itemView;
}

View File

@@ -31,10 +31,10 @@ public class InfoItemDialog {
final View bannerView = View.inflate(activity, R.layout.dialog_title, null);
bannerView.setSelected(true);
TextView titleView = bannerView.findViewById(R.id.itemTitleView);
final TextView titleView = bannerView.findViewById(R.id.itemTitleView);
titleView.setText(title);
TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
final TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
if (additionalDetail != null) {
detailsView.setText(additionalDetail);
detailsView.setVisibility(View.VISIBLE);

View File

@@ -123,7 +123,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
+ infoItemList.size() + ", data.size() = " + data.size());
}
int offsetStart = sizeConsideringHeaderOffset();
final int offsetStart = sizeConsideringHeaderOffset();
infoItemList.addAll(data);
if (DEBUG) {
@@ -135,7 +135,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
notifyItemRangeInserted(offsetStart, data.size());
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeaderOffset();
final int footerNow = sizeConsideringHeaderOffset();
notifyItemMoved(offsetStart, footerNow);
if (DEBUG) {
@@ -160,7 +160,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
+ infoItemList.size() + ", thread = " + Thread.currentThread());
}
int positionInserted = sizeConsideringHeaderOffset();
final int positionInserted = sizeConsideringHeaderOffset();
infoItemList.add(data);
if (DEBUG) {
@@ -172,7 +172,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
notifyItemInserted(positionInserted);
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeaderOffset();
final int footerNow = sizeConsideringHeaderOffset();
notifyItemMoved(positionInserted, footerNow);
if (DEBUG) {
@@ -191,7 +191,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
public void setHeader(final View header) {
boolean changed = header != this.header;
final boolean changed = header != this.header;
this.header = header;
if (changed) {
notifyDataSetChanged();
@@ -219,7 +219,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
private int sizeConsideringHeaderOffset() {
int i = infoItemList.size() + (header != null ? 1 : 0);
final int i = infoItemList.size() + (header != null ? 1 : 0);
if (DEBUG) {
Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i);
}
@@ -347,7 +347,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
@NonNull final List<Object> payloads) {
if (!payloads.isEmpty() && holder instanceof InfoItemHolder) {
for (Object payload : payloads) {
for (final Object payload : payloads) {
if (payload instanceof StreamStateEntity) {
((InfoItemHolder) holder).updateState(infoItemList
.get(header == null ? position : position - 1), recordManager);

View File

@@ -56,8 +56,8 @@ public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder {
String details = super.getDetailLine(item);
if (item.getStreamCount() >= 0) {
String formattedVideoAmount = Localization.localizeStreamCount(itemBuilder.getContext(),
item.getStreamCount());
final String formattedVideoAmount = Localization.localizeStreamCount(
itemBuilder.getContext(), item.getStreamCount());
if (!details.isEmpty()) {
details += "" + formattedVideoAmount;

View File

@@ -1,17 +1,18 @@
package org.schabi.newpipe.info_list.holder;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
@@ -19,11 +20,12 @@ import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.CommentTextOnTouchListener;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -34,7 +36,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
private static final int COMMENT_DEFAULT_LINES = 2;
private static final int COMMENT_EXPANDED_LINES = 1000;
private static final Pattern PATTERN = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)");
private final String downloadThumbnailKey;
private final int commentHorizontalPadding;
private final int commentVerticalPadding;
private SharedPreferences preferences = null;
private final RelativeLayout itemRoot;
public final CircleImageView itemThumbnailView;
private final TextView itemContentView;
private final TextView itemLikesCountView;
@@ -48,9 +55,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
@Override
public String transformUrl(final Matcher match, final String url) {
int timestamp = 0;
String hours = match.group(1);
String minutes = match.group(2);
String seconds = match.group(3);
final String hours = match.group(1);
final String minutes = match.group(2);
final String seconds = match.group(3);
if (hours != null) {
timestamp += (Integer.parseInt(hours.replace(":", "")) * 3600);
}
@@ -68,11 +75,20 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
final ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
itemRoot = itemView.findViewById(R.id.itemRoot);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
itemDislikesCountView = itemView.findViewById(R.id.detail_thumbs_down_count_view);
itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime);
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
downloadThumbnailKey = infoItemBuilder.getContext().
getString(R.string.download_thumbnail_key);
commentHorizontalPadding = (int) infoItemBuilder.getContext()
.getResources().getDimension(R.dimen.comments_horizontal_padding);
commentVerticalPadding = (int) infoItemBuilder.getContext()
.getResources().getDimension(R.dimen.comments_vertical_padding);
}
public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
@@ -88,11 +104,24 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
}
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
preferences = PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext());
itemBuilder.getImageLoader()
.displayImage(item.getUploaderAvatarUrl(),
itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
if (preferences.getBoolean(downloadThumbnailKey, true)) {
itemThumbnailView.setVisibility(View.VISIBLE);
itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
commentVerticalPadding, commentVerticalPadding);
} else {
itemThumbnailView.setVisibility(View.GONE);
itemRoot.setPadding(commentHorizontalPadding, commentVerticalPadding,
commentHorizontalPadding, commentVerticalPadding);
}
itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));
streamUrl = item.getUrl();
@@ -129,14 +158,10 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
itemView.setOnLongClickListener(view -> {
if (!AndroidTvUtils.isTv(itemBuilder.getContext())) {
ClipboardManager clipboardManager = (ClipboardManager) itemBuilder.getContext()
.getSystemService(Context.CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, commentText));
Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT)
.show();
} else {
if (DeviceUtils.isTv(itemBuilder.getContext())) {
openCommentAuthor(item);
} else {
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
}
return true;
});
@@ -153,7 +178,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
item.getServiceId(),
item.getUploaderUrl(),
item.getUploaderName());
} catch (Exception e) {
} catch (final Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
}
}
@@ -171,7 +196,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
return false;
}
URLSpan[] urls = itemContentView.getUrls();
final URLSpan[] urls = itemContentView.getUrls();
return urls != null && urls.length != 0;
}
@@ -188,12 +213,13 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
boolean hasEllipsis = false;
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
int endOfLastLine = itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1);
final int endOfLastLine
= itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1);
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
if (end == -1) {
end = Math.max(endOfLastLine - 2, 0);
}
String newVal = itemContentView.getText().subSequence(0, end) + "";
final String newVal = itemContentView.getText().subSequence(0, end) + "";
itemContentView.setText(newVal);
hasEllipsis = true;
}

View File

@@ -1,6 +1,6 @@
package org.schabi.newpipe.info_list.holder;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.text.TextUtils;
import android.view.ViewGroup;
import android.widget.TextView;

View File

@@ -60,7 +60,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
final StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
.blockingGet()[0];
if (state2 != null) {
itemProgressView.setVisibility(View.VISIBLE);
@@ -113,7 +113,8 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
final HistoryRecordManager historyRecordManager) {
final StreamInfoItem item = (StreamInfoItem) infoItem;
StreamStateEntity state = historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
final StreamStateEntity state
= historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
if (state != null && item.getDuration() > 0
&& item.getStreamType() != StreamType.LIVE_STREAM) {
itemProgressView.setMax((int) item.getDuration());

View File

@@ -4,7 +4,7 @@ import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;

View File

@@ -101,7 +101,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
+ localItems.size() + ", data.size() = " + data.size());
}
int offsetStart = sizeConsideringHeader();
final int offsetStart = sizeConsideringHeader();
localItems.addAll(data);
if (DEBUG) {
@@ -113,7 +113,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
notifyItemRangeInserted(offsetStart, data.size());
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeader();
final int footerNow = sizeConsideringHeader();
notifyItemMoved(offsetStart, footerNow);
if (DEBUG) {
@@ -158,7 +158,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
}
public void setHeader(final View header) {
boolean changed = header != this.header;
final boolean changed = header != this.header;
this.header = header;
if (changed) {
notifyDataSetChanged();
@@ -316,7 +316,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
@NonNull final List<Object> payloads) {
if (!payloads.isEmpty() && holder instanceof LocalItemHolder) {
for (Object payload : payloads) {
for (final Object payload : payloads) {
if (payload instanceof StreamStateEntity) {
((LocalItemHolder) holder).updateState(localItems
.get(header == null ? position : position - 1), recordManager);

View File

@@ -30,8 +30,6 @@ import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import icepick.State;
@@ -54,31 +52,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
// Fragment LifeCycle - Creation
///////////////////////////////////////////////////////////////////////////
private static List<PlaylistLocalItem> merge(
final List<PlaylistMetadataEntry> localPlaylists,
final List<PlaylistRemoteEntity> remotePlaylists) {
List<PlaylistLocalItem> items = new ArrayList<>(
localPlaylists.size() + remotePlaylists.size());
items.addAll(localPlaylists);
items.addAll(remotePlaylists);
Collections.sort(items, (left, right) -> {
String on1 = left.getOrderingName();
String on2 = right.getOrderingName();
if (on1 == null && on2 == null) {
return 0;
} else if (on1 != null && on2 == null) {
return -1;
} else if (on1 == null && on2 != null) {
return 1;
} else {
return on1.compareToIgnoreCase(on2);
}
});
return items;
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -164,7 +137,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
super.startLoading(forceLoad);
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
remotePlaylistManager.getPlaylists(), BookmarkFragment::merge)
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getPlaylistsSubscriber());
@@ -292,15 +265,14 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
}
private void showLocalDialog(final PlaylistMetadataEntry selectedItem) {
View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null);
EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
final View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null);
final EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
editText.setText(selectedItem.name);
Builder builder = new AlertDialog.Builder(activity);
final Builder builder = new AlertDialog.Builder(activity);
builder.setView(dialogView)
.setPositiveButton(R.string.rename_playlist, (dialog, which) -> {
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString());
})
.setPositiveButton(R.string.rename_playlist, (dialog, which) ->
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString()))
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.delete, (dialog, which) -> {
showDeleteDialog(selectedItem.name,

View File

@@ -39,14 +39,14 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
private CompositeDisposable playlistDisposables = new CompositeDisposable();
public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) {
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
dialog.setInfo(Collections.singletonList(new StreamEntity(info)));
return dialog;
}
public static PlaylistAppendDialog fromStreamInfoItems(final List<StreamInfoItem> items) {
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
List<StreamEntity> entities = new ArrayList<>(items.size());
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
final List<StreamEntity> entities = new ArrayList<>(items.size());
for (final StreamInfoItem item : items) {
entities.add(new StreamEntity(item));
}
@@ -55,8 +55,8 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
}
public static PlaylistAppendDialog fromPlayQueueItems(final List<PlayQueueItem> items) {
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
List<StreamEntity> entities = new ArrayList<>(items.size());
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
final List<StreamEntity> entities = new ArrayList<>(items.size());
for (final PlayQueueItem item : items) {
entities.add(new StreamEntity(item));
}

View File

@@ -21,7 +21,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
public final class PlaylistCreationDialog extends PlaylistDialog {
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
PlaylistCreationDialog dialog = new PlaylistCreationDialog();
final PlaylistCreationDialog dialog = new PlaylistCreationDialog();
dialog.setInfo(streams);
return dialog;
}
@@ -37,8 +37,8 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
return super.onCreateDialog(savedInstanceState);
}
View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
EditText nameInput = dialogView.findViewById(R.id.playlist_name);
final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
final EditText nameInput = dialogView.findViewById(R.id.playlist_name);
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext())
.setTitle(R.string.create_playlist)

View File

@@ -30,7 +30,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import icepick.State
import java.util.Calendar
@@ -82,7 +82,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
super.onViewCreated(rootView, savedInstanceState)
viewModel = ViewModelProviders.of(this, FeedViewModel.Factory(requireContext(), groupId)).get(FeedViewModel::class.java)
viewModel = ViewModelProvider(this, FeedViewModel.Factory(requireContext(), groupId)).get(FeedViewModel::class.java)
viewModel.stateLiveData.observe(viewLifecycleOwner, Observer { it?.let(::handleResult) })
}

View File

@@ -27,10 +27,10 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.IBinder
import android.preference.PreferenceManager
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.preference.PreferenceManager
import io.reactivex.Flowable
import io.reactivex.Notification
import io.reactivex.Single

View File

@@ -20,7 +20,7 @@ package org.schabi.newpipe.local.history;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import androidx.annotation.NonNull;
@@ -88,7 +88,7 @@ public class HistoryRecordManager {
final Date currentTime = new Date();
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
final long streamId = streamTable.upsert(new StreamEntity(info));
StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
if (latestEntry != null) {
streamHistoryTable.delete(latestEntry);
@@ -129,7 +129,7 @@ public class HistoryRecordManager {
}
public Single<List<Long>> insertStreamHistory(final Collection<StreamHistoryEntry> entries) {
List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
final List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
for (final StreamHistoryEntry entry : entries) {
entities.add(entry.toStreamHistoryEntity());
}
@@ -138,7 +138,7 @@ public class HistoryRecordManager {
}
public Single<Integer> deleteStreamHistory(final Collection<StreamHistoryEntry> entries) {
List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
final List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
for (final StreamHistoryEntry entry : entries) {
entities.add(entry.toStreamHistoryEntity());
}
@@ -163,7 +163,7 @@ public class HistoryRecordManager {
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry();
final SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry();
if (latestEntry != null && latestEntry.hasEqualValues(newEntry)) {
latestEntry.setCreationDate(currentTime);
return (long) searchHistoryTable.update(latestEntry);
@@ -256,7 +256,7 @@ public class HistoryRecordManager {
public Single<List<StreamStateEntity>> loadStreamStateBatch(final List<InfoItem> infos) {
return Single.fromCallable(() -> {
final List<StreamStateEntity> result = new ArrayList<>(infos.size());
for (InfoItem info : infos) {
for (final InfoItem info : infos) {
final List<StreamEntity> entities = streamTable
.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
if (entities.isEmpty()) {
@@ -279,8 +279,8 @@ public class HistoryRecordManager {
final List<? extends LocalItem> items) {
return Single.fromCallable(() -> {
final List<StreamStateEntity> result = new ArrayList<>(items.size());
for (LocalItem item : items) {
long streamId;
for (final LocalItem item : items) {
final long streamId;
if (item instanceof StreamStatisticsEntry) {
streamId = ((StreamStatisticsEntry) item).getStreamId();
} else if (item instanceof PlaylistStreamEntity) {

View File

@@ -321,7 +321,7 @@ public class StatisticsPlaylistFragment
}
headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view ->
@@ -455,7 +455,7 @@ public class StatisticsPlaylistFragment
}
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
final List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
for (final LocalItem item : infoItems) {
if (item instanceof StreamStatisticsEntry) {
streamInfoItems.add(((StreamStatisticsEntry) item).toStreamInfoItem());

View File

@@ -70,7 +70,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
StreamStateEntity state = historyRecordManager
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
@@ -116,7 +116,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
}
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
StreamStateEntity state = historyRecordManager
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);

View File

@@ -98,7 +98,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
StreamStateEntity state = historyRecordManager
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
@@ -146,7 +146,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
}
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
StreamStateEntity state = historyRecordManager
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);

View File

@@ -98,7 +98,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private boolean isRemovingWatched = false;
public static LocalPlaylistFragment getInstance(final long playlistId, final String name) {
LocalPlaylistFragment instance = new LocalPlaylistFragment();
final LocalPlaylistFragment instance = new LocalPlaylistFragment();
instance.setInitialData(playlistId, name);
return instance;
}
@@ -177,7 +177,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
public void selected(final LocalItem selectedItem) {
if (selectedItem instanceof PlaylistStreamEntry) {
final PlaylistStreamEntry item = (PlaylistStreamEntry) selectedItem;
NavigationHelper.openVideoDetailFragment(getFragmentManager(),
NavigationHelper.openVideoDetailFragment(getFM(),
item.getStreamEntity().getServiceId(), item.getStreamEntity().getUrl(),
item.getStreamEntity().getTitle());
}
@@ -411,7 +411,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
if (removePartiallyWatched) {
while (playlistIter.hasNext()) {
final PlaylistStreamEntry playlistItem = playlistIter.next();
int indexInHistory = Collections.binarySearch(historyStreamIds,
final int indexInHistory = Collections.binarySearch(historyStreamIds,
playlistItem.getStreamId());
if (indexInHistory < 0) {
@@ -427,7 +427,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
.loadLocalStreamStateBatch(playlist).blockingGet().iterator();
while (playlistIter.hasNext()) {
PlaylistStreamEntry playlistItem = playlistIter.next();
final PlaylistStreamEntry playlistItem = playlistIter.next();
final int indexInHistory = Collections.binarySearch(historyStreamIds,
playlistItem.getStreamId());
@@ -492,7 +492,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
setVideoCount(itemListAdapter.getItemsList().size());
headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view ->
@@ -544,7 +544,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
}
final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
EditText nameEdit = dialogView.findViewById(R.id.playlist_name);
final EditText nameEdit = dialogView.findViewById(R.id.playlist_name);
nameEdit.setText(name);
nameEdit.setSelection(nameEdit.getText().length());
@@ -553,9 +553,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
.setView(dialogView)
.setCancelable(true)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.rename, (dialogInterface, i) -> {
changePlaylistName(nameEdit.getText().toString());
});
.setPositiveButton(R.string.rename, (dialogInterface, i) ->
changePlaylistName(nameEdit.getText().toString()));
dialogBuilder.show();
}
@@ -601,7 +600,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
}
private void updateThumbnailUrl() {
String newThumbnailUrl;
final String newThumbnailUrl;
if (!itemListAdapter.getItemsList().isEmpty()) {
newThumbnailUrl = ((PlaylistStreamEntry) itemListAdapter.getItemsList().get(0))
@@ -662,7 +661,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
}
final List<LocalItem> items = itemListAdapter.getItemsList();
List<Long> streamIds = new ArrayList<>(items.size());
final List<Long> streamIds = new ArrayList<>(items.size());
for (final LocalItem item : items) {
if (item instanceof PlaylistStreamEntry) {
streamIds.add(((PlaylistStreamEntry) item).getStreamId());
@@ -815,7 +814,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
}
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
final List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
for (final LocalItem item : infoItems) {
if (item instanceof PlaylistStreamEntry) {
streamInfoItems.add(((PlaylistStreamEntry) item).toStreamInfoItem());

View File

@@ -61,7 +61,7 @@ public class LocalPlaylistManager {
final List<StreamEntity> streams,
final int indexOffset) {
List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size());
final List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size());
final List<Long> streamIds = streamTable.upsertAll(streams);
for (int index = 0; index < streamIds.size(); index++) {
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(index),
@@ -71,7 +71,7 @@ public class LocalPlaylistManager {
}
public Completable updateJoin(final long playlistId, final List<Long> streamIds) {
List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streamIds.size());
final List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streamIds.size());
for (int i = 0; i < streamIds.size(); i++) {
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(i), i));
}
@@ -115,7 +115,7 @@ public class LocalPlaylistManager {
.firstElement()
.filter(playlistEntities -> !playlistEntities.isEmpty())
.map(playlistEntities -> {
PlaylistEntity playlist = playlistEntities.get(0);
final PlaylistEntity playlist = playlistEntities.get(0);
if (name != null) {
playlist.setName(name);
}

View File

@@ -42,7 +42,7 @@ public class RemotePlaylistManager {
public Single<Integer> onUpdate(final long playlistId, final PlaylistInfo playlistInfo) {
return Single.fromCallable(() -> {
PlaylistRemoteEntity playlist = new PlaylistRemoteEntity(playlistInfo);
final PlaylistRemoteEntity playlist = new PlaylistRemoteEntity(playlistInfo);
playlist.setUid(playlistId);
return playlistRemoteTable.update(playlist);
}).subscribeOn(Schedulers.io());

View File

@@ -11,15 +11,15 @@ import android.content.res.Configuration
import android.os.Bundle
import android.os.Environment
import android.os.Parcelable
import android.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.ViewModelProvider
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import com.nononsenseapps.filepicker.Utils
import com.xwray.groupie.Group
@@ -277,7 +277,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
}
items_list.adapter = groupAdapter
viewModel = ViewModelProviders.of(this).get(SubscriptionViewModel::class.java)
viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java)
viewModel.stateLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer { it?.let(this::handleResult) })
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer { it?.let(this::handleFeedGroups) })
}

View File

@@ -2,9 +2,11 @@ package org.schabi.newpipe.local.subscription
import android.content.Context
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.subscription.SubscriptionDAO
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.extractor.ListInfo
@@ -21,9 +23,28 @@ class SubscriptionManager(context: Context) {
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
fun subscriptions() = subscriptionTable.all
fun getSubscriptions(
currentGroupId: Long = FeedGroupEntity.GROUP_ALL_ID,
filterQuery: String = "",
showOnlyUngrouped: Boolean = false
): Flowable<List<SubscriptionEntity>> {
return when {
filterQuery.isNotEmpty() -> {
return if (showOnlyUngrouped) {
subscriptionTable.getSubscriptionsOnlyUngroupedFiltered(
currentGroupId, filterQuery)
} else {
subscriptionTable.getSubscriptionsFiltered(filterQuery)
}
}
showOnlyUngrouped -> subscriptionTable.getSubscriptionsOnlyUngrouped(currentGroupId)
else -> subscriptionTable.all
}
}
fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> {
val listEntities = subscriptionTable.upsertAll(
infoList.map { SubscriptionEntity.from(it) })
infoList.map { SubscriptionEntity.from(it) })
database.runInTransaction {
infoList.forEachIndexed { index, info ->
@@ -35,13 +56,13 @@ class SubscriptionManager(context: Context) {
}
fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url)
.flatMapCompletable {
Completable.fromRunnable {
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
subscriptionTable.update(it)
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
}
.flatMapCompletable {
Completable.fromRunnable {
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
subscriptionTable.update(it)
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
}
}
fun updateFromInfo(subscriptionId: Long, info: ListInfo<StreamInfoItem>) {
val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId)
@@ -57,8 +78,8 @@ class SubscriptionManager(context: Context) {
fun deleteSubscription(serviceId: Int, url: String): Completable {
return Completable.fromCallable { subscriptionTable.deleteSubscription(serviceId, url) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun insertSubscription(subscriptionEntity: SubscriptionEntity, info: ChannelInfo) {

View File

@@ -64,7 +64,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
private Button inputButton;
public static SubscriptionsImportFragment getInstance(final int serviceId) {
SubscriptionsImportFragment instance = new SubscriptionsImportFragment();
final SubscriptionsImportFragment instance = new SubscriptionsImportFragment();
instance.setInitialData(serviceId);
return instance;
}
@@ -140,7 +140,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
setInfoText("");
}
ActionBar supportActionBar = activity.getSupportActionBar();
final ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayShowTitleEnabled(true);
setTitle(getString(R.string.import_title));
@@ -206,7 +206,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
relatedUrl = extractor.getRelatedUrl();
instructionsString = ServiceHelper.getImportInstructions(currentServiceId);
return;
} catch (ExtractionException ignored) {
} catch (final ExtractionException ignored) {
}
}

View File

@@ -5,6 +5,7 @@ import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
@@ -13,34 +14,22 @@ import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.OnItemClickListener
import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
import icepick.State
import java.io.Serializable
import kotlinx.android.synthetic.main.dialog_feed_group_create.cancel_button
import kotlinx.android.synthetic.main.dialog_feed_group_create.confirm_button
import kotlinx.android.synthetic.main.dialog_feed_group_create.delete_button
import kotlinx.android.synthetic.main.dialog_feed_group_create.delete_screen_message
import kotlinx.android.synthetic.main.dialog_feed_group_create.group_name_input
import kotlinx.android.synthetic.main.dialog_feed_group_create.group_name_input_container
import kotlinx.android.synthetic.main.dialog_feed_group_create.icon_preview
import kotlinx.android.synthetic.main.dialog_feed_group_create.icon_selector
import kotlinx.android.synthetic.main.dialog_feed_group_create.options_root
import kotlinx.android.synthetic.main.dialog_feed_group_create.select_channel_button
import kotlinx.android.synthetic.main.dialog_feed_group_create.selected_subscription_count_view
import kotlinx.android.synthetic.main.dialog_feed_group_create.separator
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector_header_info
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector_list
import kotlin.collections.contains
import kotlinx.android.synthetic.main.dialog_feed_group_create.*
import kotlinx.android.synthetic.main.toolbar_search_layout.*
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.fragments.BackPressable
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.DeleteScreen
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.IconPickerScreen
@@ -51,9 +40,10 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.Dia
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
import org.schabi.newpipe.local.subscription.item.PickerIconItem
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
import org.schabi.newpipe.util.DeviceUtils
import org.schabi.newpipe.util.ThemeHelper
class FeedGroupDialog : DialogFragment() {
class FeedGroupDialog : DialogFragment(), BackPressable {
private lateinit var viewModel: FeedGroupDialogViewModel
private var groupId: Long = NO_GROUP_SELECTED
private var groupIcon: FeedGroupIcon? = null
@@ -66,22 +56,20 @@ class FeedGroupDialog : DialogFragment() {
object DeleteScreen : ScreenState()
}
@State
@JvmField
var selectedIcon: FeedGroupIcon? = null
@State
@JvmField
var selectedSubscriptions: HashSet<Long> = HashSet()
@State
@JvmField
var currentScreen: ScreenState = InitialScreen
@State @JvmField var selectedIcon: FeedGroupIcon? = null
@State @JvmField var selectedSubscriptions: HashSet<Long> = HashSet()
@State @JvmField var wasSubscriptionSelectionChanged: Boolean = false
@State @JvmField var currentScreen: ScreenState = InitialScreen
@State
@JvmField
var subscriptionsListState: Parcelable? = null
@State
@JvmField
var iconsListState: Parcelable? = null
@State @JvmField var subscriptionsListState: Parcelable? = null
@State @JvmField var iconsListState: Parcelable? = null
@State @JvmField var wasSearchSubscriptionsVisible = false
@State @JvmField var subscriptionsCurrentSearchQuery = ""
@State @JvmField var subscriptionsShowOnlyUngrouped = false
private val subscriptionMainSection = Section()
private val subscriptionEmptyFooter = Section()
private lateinit var subscriptionGroupAdapter: GroupAdapter<GroupieViewHolder>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -91,22 +79,30 @@ class FeedGroupDialog : DialogFragment() {
groupId = arguments?.getLong(KEY_GROUP_ID, NO_GROUP_SELECTED) ?: NO_GROUP_SELECTED
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.dialog_feed_group_create, container)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return object : Dialog(requireActivity(), theme) {
override fun onBackPressed() {
if (currentScreen !is InitialScreen) {
showScreen(InitialScreen)
} else {
if (!this@FeedGroupDialog.onBackPressed()) {
super.onBackPressed()
}
}
}
}
override fun onPause() {
super.onPause()
wasSearchSubscriptionsVisible = isSearchVisible()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
@@ -119,11 +115,15 @@ class FeedGroupDialog : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this, FeedGroupDialogViewModel.Factory(requireContext(), groupId))
.get(FeedGroupDialogViewModel::class.java)
viewModel = ViewModelProvider(this,
FeedGroupDialogViewModel.Factory(requireContext(),
groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped)
).get(FeedGroupDialogViewModel::class.java)
viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup))
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer { setupSubscriptionPicker(it.first, it.second) })
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer {
setupSubscriptionPicker(it.first, it.second)
})
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
when (it) {
ProcessingEvent -> disableInput()
@@ -131,15 +131,54 @@ class FeedGroupDialog : DialogFragment() {
}
})
subscriptionGroupAdapter = GroupAdapter<GroupieViewHolder>().apply {
add(subscriptionMainSection)
add(subscriptionEmptyFooter)
spanCount = 4
}
subscriptions_selector_list.apply {
// Disable animations, too distracting.
itemAnimator = null
adapter = subscriptionGroupAdapter
layoutManager = GridLayoutManager(requireContext(), subscriptionGroupAdapter.spanCount,
RecyclerView.VERTICAL, false).apply {
spanSizeLookup = subscriptionGroupAdapter.spanSizeLookup
}
}
setupIconPicker()
setupListeners()
showScreen(currentScreen)
if (currentScreen == SubscriptionsPickerScreen && wasSearchSubscriptionsVisible) {
showSearch()
} else if (currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED) {
showKeyboard()
}
}
// /////////////////////////////////////////////////////////////////////////
override fun onDestroyView() {
super.onDestroyView()
subscriptions_selector_list?.adapter = null
icon_selector?.adapter = null
}
/*///////////////////////////////////////////////////////////////////////////
// Setup
// /////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////// */
override fun onBackPressed(): Boolean {
if (currentScreen is SubscriptionsPickerScreen && isSearchVisible()) {
hideSearch()
return true
} else if (currentScreen !is InitialScreen) {
showScreen(InitialScreen)
return true
}
return false
}
private fun setupListeners() {
delete_button.setOnClickListener { showScreen(DeleteScreen) }
@@ -163,13 +202,64 @@ class FeedGroupDialog : DialogFragment() {
}
})
confirm_button.setOnClickListener {
when (currentScreen) {
InitialScreen -> handlePositiveButtonInitialScreen()
DeleteScreen -> viewModel.deleteGroup()
else -> showScreen(InitialScreen)
confirm_button.setOnClickListener { handlePositiveButton() }
select_channel_button.setOnClickListener {
subscriptions_selector_list.scrollToPosition(0)
showScreen(SubscriptionsPickerScreen)
}
val headerMenu = subscriptions_header_toolbar.menu
requireActivity().menuInflater.inflate(R.menu.menu_feed_group_dialog, headerMenu)
headerMenu.findItem(R.id.action_search).setOnMenuItemClickListener {
showSearch()
true
}
headerMenu.findItem(R.id.feed_group_toggle_show_only_ungrouped_subscriptions).apply {
isChecked = subscriptionsShowOnlyUngrouped
setOnMenuItemClickListener {
subscriptionsShowOnlyUngrouped = !subscriptionsShowOnlyUngrouped
it.isChecked = subscriptionsShowOnlyUngrouped
viewModel.toggleShowOnlyUngrouped(subscriptionsShowOnlyUngrouped)
true
}
}
toolbar_search_clear.setOnClickListener {
if (TextUtils.isEmpty(toolbar_search_edit_text.text)) {
hideSearch()
return@setOnClickListener
}
resetSearch()
showKeyboardSearch()
}
toolbar_search_edit_text.setOnClickListener {
if (DeviceUtils.isTv(context)) {
showKeyboardSearch()
}
}
toolbar_search_edit_text.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
override fun afterTextChanged(s: Editable) = Unit
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
val newQuery: String = toolbar_search_edit_text.text.toString()
subscriptionsCurrentSearchQuery = newQuery
viewModel.filterSubscriptionsBy(newQuery)
}
})
subscriptionGroupAdapter.setOnItemClickListener(subscriptionPickerItemListener)
}
private fun handlePositiveButton() = when {
currentScreen is InitialScreen -> handlePositiveButtonInitialScreen()
currentScreen is DeleteScreen -> viewModel.deleteGroup()
currentScreen is SubscriptionsPickerScreen && isSearchVisible() -> hideSearch()
else -> showScreen(InitialScreen)
}
private fun handlePositiveButtonInitialScreen() {
@@ -202,80 +292,73 @@ class FeedGroupDialog : DialogFragment() {
groupIcon = feedGroupEntity?.icon
groupSortOrder = feedGroupEntity?.sortOrder ?: -1
icon_preview.setImageResource((if (selectedIcon == null) icon else selectedIcon!!).getDrawableRes(requireContext()))
val feedGroupIcon = if (selectedIcon == null) icon else selectedIcon!!
icon_preview.setImageResource(feedGroupIcon.getDrawableRes(requireContext()))
if (group_name_input.text.isNullOrBlank()) {
group_name_input.setText(name)
}
}
private fun setupSubscriptionPicker(subscriptions: List<SubscriptionEntity>, selectedSubscriptions: Set<Long>) {
this.selectedSubscriptions.addAll(selectedSubscriptions)
val useGridLayout = subscriptions.isNotEmpty()
private val subscriptionPickerItemListener = OnItemClickListener { item, view ->
if (item is PickerSubscriptionItem) {
val subscriptionId = item.subscriptionEntity.uid
wasSubscriptionSelectionChanged = true
val groupAdapter = GroupAdapter<GroupieViewHolder>()
groupAdapter.spanCount = if (useGridLayout) 4 else 1
val subscriptionsCount = this.selectedSubscriptions.size
val selectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
selected_subscription_count_view.text = selectedCountText
subscriptions_selector_header_info.text = selectedCountText
Section().apply {
addAll(subscriptions.map {
val isSelected = this@FeedGroupDialog.selectedSubscriptions.contains(it.uid)
PickerSubscriptionItem(it, isSelected)
})
setPlaceholder(EmptyPlaceholderItem())
groupAdapter.add(this)
}
subscriptions_selector_list.apply {
layoutManager = if (useGridLayout) {
GridLayoutManager(requireContext(), groupAdapter.spanCount, RecyclerView.VERTICAL, false)
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
this.selectedSubscriptions.remove(subscriptionId)
false
} else {
LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
this.selectedSubscriptions.add(subscriptionId)
true
}
adapter = groupAdapter
item.updateSelected(view, isSelected)
updateSubscriptionSelectedCount()
}
}
if (subscriptionsListState != null) {
layoutManager?.onRestoreInstanceState(subscriptionsListState)
subscriptionsListState = null
}
private fun setupSubscriptionPicker(
subscriptions: List<PickerSubscriptionItem>,
selectedSubscriptions: Set<Long>
) {
if (!wasSubscriptionSelectionChanged) {
this.selectedSubscriptions.addAll(selectedSubscriptions)
}
groupAdapter.setOnItemClickListener { item, _ ->
when (item) {
is PickerSubscriptionItem -> {
val subscriptionId = item.subscriptionEntity.uid
updateSubscriptionSelectedCount()
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
this.selectedSubscriptions.remove(subscriptionId)
false
} else {
this.selectedSubscriptions.add(subscriptionId)
true
}
item.isSelected = isSelected
item.notifyChanged(PickerSubscriptionItem.UPDATE_SELECTED)
val subscriptionsCount = this.selectedSubscriptions.size
val updateSelectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
selected_subscription_count_view.text = updateSelectedCountText
subscriptions_selector_header_info.text = updateSelectedCountText
}
}
if (subscriptions.isEmpty()) {
subscriptionEmptyFooter.clear()
subscriptionEmptyFooter.add(EmptyPlaceholderItem())
} else {
subscriptionEmptyFooter.clear()
}
select_channel_button.setOnClickListener {
subscriptions.forEach {
it.isSelected = this@FeedGroupDialog.selectedSubscriptions
.contains(it.subscriptionEntity.uid)
}
subscriptionMainSection.update(subscriptions, false)
if (subscriptionsListState != null) {
subscriptions_selector_list.layoutManager?.onRestoreInstanceState(subscriptionsListState)
subscriptionsListState = null
} else {
subscriptions_selector_list.scrollToPosition(0)
showScreen(SubscriptionsPickerScreen)
}
}
private fun updateSubscriptionSelectedCount() {
val selectedCount = this.selectedSubscriptions.size
val selectedCountText = resources.getQuantityString(
R.plurals.feed_group_dialog_selection_count,
selectedCount, selectedCount)
selected_subscription_count_view.text = selectedCountText
subscriptions_header_info.text = selectedCountText
}
private fun setupIconPicker() {
val groupAdapter = GroupAdapter<GroupieViewHolder>()
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(requireContext(), it) })
@@ -311,9 +394,9 @@ class FeedGroupDialog : DialogFragment() {
}
}
// /////////////////////////////////////////////////////////////////////////
/*///////////////////////////////////////////////////////////////////////////
// Screen Selector
// /////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////// */
private fun showScreen(screen: ScreenState) {
currentScreen = screen
@@ -337,7 +420,8 @@ class FeedGroupDialog : DialogFragment() {
else -> View.VISIBLE
}
if (currentScreen != InitialScreen) hideKeyboard()
hideKeyboard()
hideSearch()
}
private fun View.onlyVisibleIn(vararg screens: ScreenState) {
@@ -347,13 +431,58 @@ class FeedGroupDialog : DialogFragment() {
}
}
// /////////////////////////////////////////////////////////////////////////
/*///////////////////////////////////////////////////////////////////////////
// Utils
// /////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////// */
private fun isSearchVisible() = subscriptions_header_search_container?.visibility == View.VISIBLE
private fun resetSearch() {
toolbar_search_edit_text.setText("")
subscriptionsCurrentSearchQuery = ""
viewModel.clearSubscriptionsFilter()
}
private fun hideSearch() {
resetSearch()
subscriptions_header_search_container.visibility = View.GONE
subscriptions_header_info_container.visibility = View.VISIBLE
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = true
hideKeyboardSearch()
}
private fun showSearch() {
subscriptions_header_search_container.visibility = View.VISIBLE
subscriptions_header_info_container.visibility = View.GONE
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = false
showKeyboardSearch()
}
private val inputMethodManager by lazy {
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
}
private fun showKeyboardSearch() {
if (toolbar_search_edit_text.requestFocus()) {
inputMethodManager.showSoftInput(toolbar_search_edit_text, InputMethodManager.SHOW_IMPLICIT)
}
}
private fun hideKeyboardSearch() {
inputMethodManager.hideSoftInputFromWindow(toolbar_search_edit_text.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN)
toolbar_search_edit_text.clearFocus()
}
private fun showKeyboard() {
if (group_name_input.requestFocus()) {
inputMethodManager.showSoftInput(group_name_input, InputMethodManager.SHOW_IMPLICIT)
}
}
private fun hideKeyboard() {
val inputMethodManager = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN)
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN)
group_name_input.clearFocus()
}

View File

@@ -9,42 +9,56 @@ import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.disposables.Disposable
import io.reactivex.functions.BiFunction
import io.reactivex.processors.BehaviorProcessor
import io.reactivex.schedulers.Schedulers
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import org.schabi.newpipe.local.subscription.SubscriptionManager
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return FeedGroupDialogViewModel(context.applicationContext, groupId) as T
}
}
class FeedGroupDialogViewModel(
applicationContext: Context,
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
initialQuery: String = "",
initialShowOnlyUngrouped: Boolean = false
) : ViewModel() {
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext)
private var subscriptionManager = SubscriptionManager(applicationContext)
private var filterSubscriptions = BehaviorProcessor.create<String>()
private var toggleShowOnlyUngrouped = BehaviorProcessor.create<Boolean>()
private var subscriptionsFlowable = Flowable
.combineLatest(
filterSubscriptions.startWith(initialQuery),
toggleShowOnlyUngrouped.startWith(initialShowOnlyUngrouped),
BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
)
.distinctUntilChanged()
.switchMap { filter ->
subscriptionManager.getSubscriptions(groupId, filter.query, filter.showOnlyUngrouped)
}.map { list -> list.map { PickerSubscriptionItem(it) } }
private val mutableGroupLiveData = MutableLiveData<FeedGroupEntity>()
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<SubscriptionEntity>, Set<Long>>>()
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>>()
private val mutableDialogEventLiveData = MutableLiveData<DialogEvent>()
val groupLiveData: LiveData<FeedGroupEntity> = mutableGroupLiveData
val subscriptionsLiveData: LiveData<Pair<List<SubscriptionEntity>, Set<Long>>> = mutableSubscriptionsLiveData
val subscriptionsLiveData: LiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>> = mutableSubscriptionsLiveData
val dialogEventLiveData: LiveData<DialogEvent> = mutableDialogEventLiveData
private var actionProcessingDisposable: Disposable? = null
private var feedGroupDisposable = feedDatabaseManager.getGroup(groupId)
.subscribeOn(Schedulers.io())
.subscribe(mutableGroupLiveData::postValue)
.subscribeOn(Schedulers.io())
.subscribe(mutableGroupLiveData::postValue)
private var subscriptionsDisposable = Flowable
.combineLatest(subscriptionManager.subscriptions(), feedDatabaseManager.subscriptionIdsForGroup(groupId),
BiFunction { t1: List<SubscriptionEntity>, t2: List<Long> -> t1 to t2.toSet() })
.subscribeOn(Schedulers.io())
.subscribe(mutableSubscriptionsLiveData::postValue)
.combineLatest(subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() })
.subscribeOn(Schedulers.io())
.subscribe(mutableSubscriptionsLiveData::postValue)
override fun onCleared() {
super.onCleared()
@@ -55,14 +69,14 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
doAction(feedDatabaseManager.createGroup(name, selectedIcon)
.flatMapCompletable {
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
})
.flatMapCompletable {
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
})
}
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
}
fun deleteGroup() {
@@ -74,13 +88,40 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
actionProcessingDisposable = completable
.subscribeOn(Schedulers.io())
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
.subscribeOn(Schedulers.io())
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
}
}
fun filterSubscriptionsBy(query: String) {
filterSubscriptions.onNext(query)
}
fun clearSubscriptionsFilter() {
filterSubscriptions.onNext("")
}
fun toggleShowOnlyUngrouped(showOnlyUngrouped: Boolean) {
toggleShowOnlyUngrouped.onNext(showOnlyUngrouped)
}
sealed class DialogEvent {
object ProcessingEvent : DialogEvent()
object SuccessEvent : DialogEvent()
}
data class Filter(val query: String, val showOnlyUngrouped: Boolean)
class Factory(
private val context: Context,
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
private val initialQuery: String = "",
private val initialShowOnlyUngrouped: Boolean = false
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return FeedGroupDialogViewModel(context.applicationContext,
groupId, initialQuery, initialShowOnlyUngrouped) as T
}
}
}

View File

@@ -6,7 +6,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback
import androidx.recyclerview.widget.LinearLayoutManager
@@ -49,7 +49,7 @@ class FeedGroupReorderDialog : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this).get(FeedGroupReorderDialogViewModel::class.java)
viewModel = ViewModelProvider(this).get(FeedGroupReorderDialogViewModel::class.java)
viewModel.groupsLiveData.observe(viewLifecycleOwner, Observer(::handleGroups))
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
when (it) {

View File

@@ -7,4 +7,5 @@ import org.schabi.newpipe.R
class EmptyPlaceholderItem : Item() {
override fun getLayout(): Int = R.layout.list_empty_view
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
override fun getSpanSize(spanCount: Int, position: Int): Int = spanCount
}

View File

@@ -1,39 +1,28 @@
package org.schabi.newpipe.local.subscription.item
import android.view.View
import com.nostra13.universalimageloader.core.DisplayImageOptions
import com.nostra13.universalimageloader.core.ImageLoader
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
import kotlinx.android.synthetic.main.picker_subscription_item.selected_highlight
import kotlinx.android.synthetic.main.picker_subscription_item.thumbnail_view
import kotlinx.android.synthetic.main.picker_subscription_item.title_view
import kotlinx.android.synthetic.main.picker_subscription_item.*
import kotlinx.android.synthetic.main.picker_subscription_item.view.*
import org.schabi.newpipe.R
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.util.AnimationUtils
import org.schabi.newpipe.util.AnimationUtils.animateView
import org.schabi.newpipe.util.ImageDisplayConstants
data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, var isSelected: Boolean = false) : Item() {
companion object {
const val UPDATE_SELECTED = 123
val IMAGE_LOADING_OPTIONS: DisplayImageOptions = ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
}
data class PickerSubscriptionItem(
val subscriptionEntity: SubscriptionEntity,
var isSelected: Boolean = false
) : Item() {
override fun getId(): Long = subscriptionEntity.uid
override fun getLayout(): Int = R.layout.picker_subscription_item
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.contains(UPDATE_SELECTED)) {
animateView(viewHolder.selected_highlight, AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
return
}
super.bind(viewHolder, position, payloads)
}
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl, viewHolder.thumbnail_view, IMAGE_LOADING_OPTIONS)
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl,
viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS)
viewHolder.title_view.text = subscriptionEntity.name
viewHolder.selected_highlight.visibility = if (isSelected) View.VISIBLE else View.GONE
@@ -47,7 +36,9 @@ data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, va
viewHolder.selected_highlight.alpha = 1F
}
override fun getId(): Long {
return subscriptionEntity.uid
fun updateSelected(containerView: View, isSelected: Boolean) {
this.isSelected = isSelected
animateView(containerView.selected_highlight,
AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
}
}

View File

@@ -86,12 +86,12 @@ public final class ImportExportJsonHelper {
eventListener.onSizeReceived(channelsArray.size());
}
for (Object o : channelsArray) {
for (final Object o : channelsArray) {
if (o instanceof JsonObject) {
JsonObject itemObject = (JsonObject) o;
int serviceId = itemObject.getInt(JSON_SERVICE_ID_KEY, 0);
String url = itemObject.getString(JSON_URL_KEY);
String name = itemObject.getString(JSON_NAME_KEY);
final JsonObject itemObject = (JsonObject) o;
final int serviceId = itemObject.getInt(JSON_SERVICE_ID_KEY, 0);
final String url = itemObject.getString(JSON_URL_KEY);
final String name = itemObject.getString(JSON_NAME_KEY);
if (url != null && name != null && !url.isEmpty() && !name.isEmpty()) {
channels.add(new SubscriptionItem(serviceId, url, name));
@@ -101,7 +101,7 @@ public final class ImportExportJsonHelper {
}
}
}
} catch (Throwable e) {
} catch (final Throwable e) {
throw new InvalidSourceException("Couldn't parse json", e);
}
@@ -117,7 +117,7 @@ public final class ImportExportJsonHelper {
*/
public static void writeTo(final List<SubscriptionItem> items, final OutputStream out,
@Nullable final ImportExportEventListener eventListener) {
JsonAppendableWriter writer = JsonWriter.on(out);
final JsonAppendableWriter writer = JsonWriter.on(out);
writeTo(items, writer, eventListener);
writer.done();
}
@@ -140,7 +140,7 @@ public final class ImportExportJsonHelper {
writer.value(JSON_APP_VERSION_INT_KEY, BuildConfig.VERSION_CODE);
writer.array(JSON_SUBSCRIPTIONS_ARRAY_KEY);
for (SubscriptionItem item : items) {
for (final SubscriptionItem item : items) {
writer.object();
writer.value(JSON_SERVICE_ID_KEY, item.getServiceId());
writer.value(JSON_URL_KEY, item.getUrl());

View File

@@ -74,7 +74,7 @@ public class SubscriptionsExportService extends BaseImportExportService {
try {
outFile = new File(path);
outputStream = new FileOutputStream(outFile);
} catch (FileNotFoundException e) {
} catch (final FileNotFoundException e) {
handleError(e);
return START_NOT_STICKY;
}
@@ -109,7 +109,7 @@ public class SubscriptionsExportService extends BaseImportExportService {
.map(subscriptionEntities -> {
final List<SubscriptionItem> result
= new ArrayList<>(subscriptionEntities.size());
for (SubscriptionEntity entity : subscriptionEntities) {
for (final SubscriptionEntity entity : subscriptionEntities) {
result.add(new SubscriptionItem(entity.getServiceId(), entity.getUrl(),
entity.getName()));
}

View File

@@ -110,7 +110,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
try {
inputStream = new FileInputStream(new File(filePath));
} catch (FileNotFoundException e) {
} catch (final FileNotFoundException e) {
handleError(e);
return START_NOT_STICKY;
}
@@ -187,7 +187,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
.getChannelInfo(subscriptionItem.getServiceId(),
subscriptionItem.getUrl(), true)
.blockingGet());
} catch (Throwable e) {
} catch (final Throwable e) {
return Notification.createOnError(e);
}
})
@@ -239,7 +239,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
private Consumer<Notification<ChannelInfo>> getNotificationsConsumer() {
return notification -> {
if (notification.isOnNext()) {
String name = notification.getValue().getName();
final String name = notification.getValue().getName();
eventListener.onItemCompleted(!TextUtils.isEmpty(name) ? name : "");
} else if (notification.isOnError()) {
final Throwable error = notification.getError();
@@ -260,7 +260,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
private Function<List<Notification<ChannelInfo>>, List<SubscriptionEntity>> upsertBatch() {
return notificationList -> {
final List<ChannelInfo> infoList = new ArrayList<>(notificationList.size());
for (Notification<ChannelInfo> n : notificationList) {
for (final Notification<ChannelInfo> n : notificationList) {
if (n.isOnNext()) {
infoList.add(n.getValue());
}

View File

@@ -1,684 +0,0 @@
/*
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
* BackgroundPlayer.java is part of NewPipe
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.schabi.newpipe.player;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.source.MediaSource;
import com.nostra13.universalimageloader.core.assist.FailReason;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.util.BitmapUtils;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
/**
* Service Background Player implementing {@link VideoPlayer}.
*
* @author mauriciocolli
*/
public final class BackgroundPlayer extends Service {
public static final String ACTION_CLOSE
= "org.schabi.newpipe.player.BackgroundPlayer.CLOSE";
public static final String ACTION_PLAY_PAUSE
= "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE";
public static final String ACTION_REPEAT
= "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
public static final String ACTION_PLAY_NEXT
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT";
public static final String ACTION_PLAY_PREVIOUS
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS";
public static final String ACTION_FAST_REWIND
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
public static final String ACTION_FAST_FORWARD
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
public static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
private static final String TAG = "BackgroundPlayer";
private static final boolean DEBUG = BasePlayer.DEBUG;
private static final int NOTIFICATION_ID = 123789;
private static final int NOTIFICATION_UPDATES_BEFORE_RESET = 60;
private BasePlayerImpl basePlayerImpl;
/*//////////////////////////////////////////////////////////////////////////
// Service-Activity Binder
//////////////////////////////////////////////////////////////////////////*/
private SharedPreferences sharedPreferences;
/*//////////////////////////////////////////////////////////////////////////
// Notification
//////////////////////////////////////////////////////////////////////////*/
private PlayerEventListener activityListener;
private IBinder mBinder;
private NotificationManager notificationManager;
private NotificationCompat.Builder notBuilder;
private RemoteViews notRemoteView;
private RemoteViews bigNotRemoteView;
private boolean shouldUpdateOnProgress;
private int timesNotificationUpdated;
/*//////////////////////////////////////////////////////////////////////////
// Service's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate() {
if (DEBUG) {
Log.d(TAG, "onCreate() called");
}
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
assureCorrectAppLanguage(this);
ThemeHelper.setTheme(this);
basePlayerImpl = new BasePlayerImpl(this);
basePlayerImpl.setup();
mBinder = new PlayerServiceBinder(basePlayerImpl);
shouldUpdateOnProgress = true;
}
@Override
public int onStartCommand(final Intent intent, final int flags, final int 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;
}
@Override
public void onDestroy() {
if (DEBUG) {
Log.d(TAG, "destroy() called");
}
onClose();
}
@Override
protected void attachBaseContext(final Context base) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
}
@Override
public IBinder onBind(final Intent intent) {
return mBinder;
}
/*//////////////////////////////////////////////////////////////////////////
// Actions
//////////////////////////////////////////////////////////////////////////*/
private void onClose() {
if (DEBUG) {
Log.d(TAG, "onClose() called");
}
if (basePlayerImpl != null) {
basePlayerImpl.savePlaybackState();
basePlayerImpl.stopActivityBinding();
basePlayerImpl.destroy();
}
if (notificationManager != null) {
notificationManager.cancel(NOTIFICATION_ID);
}
mBinder = null;
basePlayerImpl = null;
stopForeground(true);
stopSelf();
}
private void onScreenOnOff(final boolean on) {
if (DEBUG) {
Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
}
shouldUpdateOnProgress = on;
basePlayerImpl.triggerProgressUpdate();
if (on) {
basePlayerImpl.startProgressLoop();
} else {
basePlayerImpl.stopProgressLoop();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Notification
//////////////////////////////////////////////////////////////////////////*/
private void resetNotification() {
notBuilder = createNotification();
timesNotificationUpdated = 0;
}
private NotificationCompat.Builder createNotification() {
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
R.layout.player_background_notification);
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
R.layout.player_background_notification_expanded);
setupNotification(notRemoteView);
setupNotification(bigNotRemoteView);
NotificationCompat.Builder builder = new NotificationCompat
.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCustomContentView(notRemoteView)
.setCustomBigContentView(bigNotRemoteView);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setLockScreenThumbnail(builder);
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
builder.setPriority(NotificationCompat.PRIORITY_MAX);
}
return builder;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void setLockScreenThumbnail(final NotificationCompat.Builder builder) {
boolean isLockScreenThumbnailEnabled = sharedPreferences.getBoolean(
getString(R.string.enable_lock_screen_video_thumbnail_key), true);
if (isLockScreenThumbnailEnabled) {
basePlayerImpl.mediaSessionManager.setLockScreenArt(
builder,
getCenteredThumbnailBitmap()
);
} else {
basePlayerImpl.mediaSessionManager.clearLockScreenArt(builder);
}
}
@Nullable
private Bitmap getCenteredThumbnailBitmap() {
final int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
final int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
return BitmapUtils.centerCrop(basePlayerImpl.getThumbnail(), screenWidth, screenHeight);
}
private void setupNotification(final RemoteViews remoteViews) {
if (basePlayerImpl == null) {
return;
}
remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle());
remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName());
remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationStop,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationRepeat,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
// Starts background player activity -- attempts to unlock lockscreen
final Intent intent = NavigationHelper.getBackgroundPlayerActivityIntent(this);
remoteViews.setOnClickPendingIntent(R.id.notificationContent,
PendingIntent.getActivity(this, NOTIFICATION_ID, intent,
PendingIntent.FLAG_UPDATE_CURRENT));
if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) {
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_previous);
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_next);
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
} else {
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_rewind);
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_fastforward);
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
}
setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode());
}
/**
* Updates the notification, and the play/pause button in it.
* Used for changes on the remoteView
*
* @param drawableId if != -1, sets the drawable with that id on the play/pause button
*/
private synchronized void updateNotification(final int drawableId) {
// if (DEBUG) {
// Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
// }
if (notBuilder == null) {
return;
}
if (drawableId != -1) {
if (notRemoteView != null) {
notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
}
if (bigNotRemoteView != null) {
bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
}
}
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
timesNotificationUpdated++;
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) {
switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_repeat_off);
break;
case Player.REPEAT_MODE_ONE:
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_repeat_one);
break;
case Player.REPEAT_MODE_ALL:
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_repeat_all);
break;
}
}
//////////////////////////////////////////////////////////////////////////
protected class BasePlayerImpl extends BasePlayer {
@NonNull
private final AudioPlaybackResolver resolver;
private int cachedDuration;
private String cachedDurationString;
BasePlayerImpl(final Context context) {
super(context);
this.resolver = new AudioPlaybackResolver(context, dataSource);
}
@Override
public void initPlayer(final boolean playOnReady) {
super.initPlayer(playOnReady);
}
@Override
public void handleIntent(final Intent intent) {
super.handleIntent(intent);
resetNotification();
if (bigNotRemoteView != null) {
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
}
if (notRemoteView != null) {
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
}
startForeground(NOTIFICATION_ID, notBuilder.build());
}
/*//////////////////////////////////////////////////////////////////////////
// Thumbnail Loading
//////////////////////////////////////////////////////////////////////////*/
private void updateNotificationThumbnail() {
if (basePlayerImpl == null) {
return;
}
if (notRemoteView != null) {
notRemoteView.setImageViewBitmap(R.id.notificationCover,
basePlayerImpl.getThumbnail());
}
if (bigNotRemoteView != null) {
bigNotRemoteView.setImageViewBitmap(R.id.notificationCover,
basePlayerImpl.getThumbnail());
}
}
@Override
public void onLoadingComplete(final String imageUri, final View view,
final Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
resetNotification();
updateNotificationThumbnail();
updateNotification(-1);
}
@Override
public void onLoadingFailed(final String imageUri, final View view,
final FailReason failReason) {
super.onLoadingFailed(imageUri, view, failReason);
resetNotification();
updateNotificationThumbnail();
updateNotification(-1);
}
/*//////////////////////////////////////////////////////////////////////////
// States Implementation
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onPrepared(final boolean playWhenReady) {
super.onPrepared(playWhenReady);
}
@Override
public void onShuffleClicked() {
super.onShuffleClicked();
updatePlayback();
}
@Override
public void onMuteUnmuteButtonClicked() {
super.onMuteUnmuteButtonClicked();
updatePlayback();
}
@Override
public void onUpdateProgress(final int currentProgress, final int duration,
final int bufferPercent) {
updateProgress(currentProgress, duration, bufferPercent);
if (!shouldUpdateOnProgress) {
return;
}
if (timesNotificationUpdated > NOTIFICATION_UPDATES_BEFORE_RESET) {
resetNotification();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Oreo*/) {
updateNotificationThumbnail();
}
}
if (bigNotRemoteView != null) {
if (cachedDuration != duration) {
cachedDuration = duration;
cachedDurationString = getTimeString(duration);
}
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration,
currentProgress, false);
bigNotRemoteView.setTextViewText(R.id.notificationTime,
getTimeString(currentProgress) + " / " + cachedDurationString);
}
if (notRemoteView != null) {
notRemoteView.setProgressBar(R.id.notificationProgressBar, duration,
currentProgress, false);
}
updateNotification(-1);
}
@Override
public void onPlayPrevious() {
super.onPlayPrevious();
triggerProgressUpdate();
}
@Override
public void onPlayNext() {
super.onPlayNext();
triggerProgressUpdate();
}
@Override
public void destroy() {
super.destroy();
if (notRemoteView != null) {
notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
}
if (bigNotRemoteView != null) {
bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, null);
}
}
/*//////////////////////////////////////////////////////////////////////////
// ExoPlayer Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) {
super.onPlaybackParametersChanged(playbackParameters);
updatePlayback();
}
@Override
public void onLoadingChanged(final boolean isLoading) {
// Disable default behavior
}
@Override
public void onRepeatModeChanged(final int i) {
resetNotification();
updateNotification(-1);
updatePlayback();
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
super.onMetadataChanged(tag);
resetNotification();
updateNotificationThumbnail();
updateNotification(-1);
updateMetadata();
}
@Override
@Nullable
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
return resolver.resolve(info);
}
@Override
public void onPlaybackShutdown() {
super.onPlaybackShutdown();
onClose();
}
/*//////////////////////////////////////////////////////////////////////////
// Activity Event Listener
//////////////////////////////////////////////////////////////////////////*/
/*package-private*/ void setActivityListener(final PlayerEventListener listener) {
activityListener = listener;
updateMetadata();
updatePlayback();
triggerProgressUpdate();
}
/*package-private*/ void removeActivityListener(final PlayerEventListener listener) {
if (activityListener == listener) {
activityListener = null;
}
}
private void updateMetadata() {
if (activityListener != null && getCurrentMetadata() != null) {
activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
}
}
private void updatePlayback() {
if (activityListener != null && simpleExoPlayer != null && playQueue != null) {
activityListener.onPlaybackUpdate(currentState, getRepeatMode(),
playQueue.isShuffled(), getPlaybackParameters());
}
}
private void updateProgress(final int currentProgress, final int duration,
final int bufferPercent) {
if (activityListener != null) {
activityListener.onProgressUpdate(currentProgress, duration, bufferPercent);
}
}
private void stopActivityBinding() {
if (activityListener != null) {
activityListener.onServiceStopped();
activityListener = null;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Broadcast Receiver
//////////////////////////////////////////////////////////////////////////*/
@Override
protected void setupBroadcastReceiver(final IntentFilter intentFltr) {
super.setupBroadcastReceiver(intentFltr);
intentFltr.addAction(ACTION_CLOSE);
intentFltr.addAction(ACTION_PLAY_PAUSE);
intentFltr.addAction(ACTION_REPEAT);
intentFltr.addAction(ACTION_PLAY_PREVIOUS);
intentFltr.addAction(ACTION_PLAY_NEXT);
intentFltr.addAction(ACTION_FAST_REWIND);
intentFltr.addAction(ACTION_FAST_FORWARD);
intentFltr.addAction(Intent.ACTION_SCREEN_ON);
intentFltr.addAction(Intent.ACTION_SCREEN_OFF);
intentFltr.addAction(Intent.ACTION_HEADSET_PLUG);
}
@Override
public void onBroadcastReceived(final Intent intent) {
super.onBroadcastReceived(intent);
if (intent == null || intent.getAction() == null) {
return;
}
if (DEBUG) {
Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
}
switch (intent.getAction()) {
case ACTION_CLOSE:
onClose();
break;
case ACTION_PLAY_PAUSE:
onPlayPause();
break;
case ACTION_REPEAT:
onRepeatClicked();
break;
case ACTION_PLAY_NEXT:
onPlayNext();
break;
case ACTION_PLAY_PREVIOUS:
onPlayPrevious();
break;
case ACTION_FAST_FORWARD:
onFastForward();
break;
case ACTION_FAST_REWIND:
onFastRewind();
break;
case Intent.ACTION_SCREEN_ON:
onScreenOnOff(true);
break;
case Intent.ACTION_SCREEN_OFF:
onScreenOnOff(false);
break;
}
}
/*//////////////////////////////////////////////////////////////////////////
// States
//////////////////////////////////////////////////////////////////////////*/
@Override
public void changeState(final int state) {
super.changeState(state);
updatePlayback();
}
@Override
public void onPlaying() {
super.onPlaying();
resetNotification();
updateNotificationThumbnail();
updateNotification(R.drawable.exo_controls_pause);
}
@Override
public void onPaused() {
super.onPaused();
resetNotification();
updateNotificationThumbnail();
updateNotification(R.drawable.exo_controls_play);
}
@Override
public void onCompleted() {
super.onCompleted();
resetNotification();
if (bigNotRemoteView != null) {
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
}
if (notRemoteView != null) {
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
}
updateNotificationThumbnail();
updateNotification(R.drawable.ic_replay_white_24dp);
}
}
}

View File

@@ -1,13 +1,13 @@
package org.schabi.newpipe.player;
import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import static org.schabi.newpipe.player.BackgroundPlayer.ACTION_CLOSE;
public final class BackgroundPlayerActivity extends ServicePlayerActivity {
private static final String TAG = "BackgroundPlayerActivity";
@@ -19,25 +19,25 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
@Override
public String getSupportActionTitle() {
return getResources().getString(R.string.title_activity_background_player);
return getResources().getString(R.string.title_activity_play_queue);
}
@Override
public Intent getBindIntent() {
return new Intent(this, BackgroundPlayer.class);
return new Intent(this, MainPlayer.class);
}
@Override
public void startPlayerListener() {
if (player != null && player instanceof BackgroundPlayer.BasePlayerImpl) {
((BackgroundPlayer.BasePlayerImpl) player).setActivityListener(this);
if (player instanceof VideoPlayerImpl) {
((VideoPlayerImpl) player).setActivityListener(this);
}
}
@Override
public void stopPlayerListener() {
if (player != null && player instanceof BackgroundPlayer.BasePlayerImpl) {
((BackgroundPlayer.BasePlayerImpl) player).removeActivityListener(this);
if (player instanceof VideoPlayerImpl) {
((VideoPlayerImpl) player).removeActivityListener(this);
}
}
@@ -56,18 +56,30 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
}
this.player.setRecovery();
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
getApplicationContext().startService(
getSwitchIntent(PopupVideoPlayer.class)
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
);
NavigationHelper.playOnPopupPlayer(
getApplicationContext(), player.playQueue, this.player.isPlaying());
return true;
}
if (item.getItemId() == R.id.action_switch_background) {
this.player.setRecovery();
NavigationHelper.playOnBackgroundPlayer(
getApplicationContext(), player.playQueue, this.player.isPlaying());
return true;
}
return false;
}
@Override
public Intent getPlayerShutdownIntent() {
return new Intent(ACTION_CLOSE);
public void setupMenu(final Menu menu) {
if (player == null) {
return;
}
menu.findItem(R.id.action_switch_popup)
.setVisible(!((VideoPlayerImpl) player).popupPlayerSelected());
menu.findItem(R.id.action_switch_background)
.setVisible(!((VideoPlayerImpl) player).audioPlayerSelected());
}
}

View File

@@ -27,7 +27,7 @@ import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
@@ -54,8 +54,8 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.local.history.HistoryRecordManager;
@@ -64,7 +64,6 @@ 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;
@@ -77,9 +76,9 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.SerializedCache;
import java.io.IOException;
import java.net.UnknownHostException;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.SerialDisposable;
@@ -99,7 +98,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
@SuppressWarnings({"WeakerAccess"})
public abstract class BasePlayer implements
Player.EventListener, PlaybackListener, ImageLoadingListener {
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
public static final boolean DEBUG = MainActivity.DEBUG;
@NonNull
public static final String TAG = "BasePlayer";
@@ -130,13 +129,15 @@ public abstract class BasePlayer implements
@NonNull
public static final String SELECT_ON_APPEND = "select_on_append";
@NonNull
public static final String PLAYER_TYPE = "player_type";
@NonNull
public static final String IS_MUTED = "is_muted";
/*//////////////////////////////////////////////////////////////////////////
// Playback
//////////////////////////////////////////////////////////////////////////*/
protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f};
protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
protected PlayQueue playQueue;
protected PlayQueueAdapter playQueueAdapter;
@@ -161,6 +162,10 @@ public abstract class BasePlayer implements
protected static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds
protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
public static final int PLAYER_TYPE_VIDEO = 0;
public static final int PLAYER_TYPE_AUDIO = 1;
public static final int PLAYER_TYPE_POPUP = 2;
protected SimpleExoPlayer simpleExoPlayer;
protected AudioReactor audioReactor;
protected MediaSessionManager mediaSessionManager;
@@ -175,6 +180,8 @@ public abstract class BasePlayer implements
@NonNull
protected final HistoryRecordManager recordManager;
@NonNull
protected final SharedPreferences sharedPreferences;
@NonNull
protected final CustomTrackSelector trackSelector;
@NonNull
protected final PlayerDataSource dataSource;
@@ -206,6 +213,7 @@ public abstract class BasePlayer implements
setupBroadcastReceiver(intentFilter);
this.recordManager = new HistoryRecordManager(context);
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
this.progressUpdateReactor = new SerialDisposable();
this.databaseUpdateReactor = new CompositeDisposable();
@@ -217,7 +225,7 @@ public abstract class BasePlayer implements
final TrackSelection.Factory trackSelectionFactory = PlayerHelper
.getQualitySelector(context);
this.trackSelector = new CustomTrackSelector(trackSelectionFactory);
this.trackSelector = new CustomTrackSelector(context, trackSelectionFactory);
this.loadControl = new LoadController();
this.renderFactory = new DefaultRenderersFactory(context);
@@ -225,7 +233,7 @@ public abstract class BasePlayer implements
public void setup() {
if (simpleExoPlayer == null) {
initPlayer(/*playOnInit=*/true);
initPlayer(true);
}
initListeners();
}
@@ -252,7 +260,8 @@ public abstract class BasePlayer implements
registerBroadcastReceiver();
}
public void initListeners() { }
public void initListeners() {
}
public void handleIntent(final Intent intent) {
if (DEBUG) {
@@ -274,7 +283,7 @@ public abstract class BasePlayer implements
// Resolve append intents
if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) {
int sizeBeforeAppend = playQueue.size();
final int sizeBeforeAppend = playQueue.size();
playQueue.append(queue.getStreams());
if ((intent.getBooleanExtra(SELECT_ON_APPEND, false)
@@ -290,34 +299,72 @@ public abstract class BasePlayer implements
final float playbackPitch = savedParameters.pitch;
final boolean playbackSkipSilence = savedParameters.skipSilence;
final boolean samePlayQueue = playQueue != null && playQueue.equals(queue);
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
final boolean isMuted = intent
.getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted());
/*
* There are 3 situations when playback shouldn't be started from scratch (zero timestamp):
* 1. User pressed on a timestamp link and the same video should be rewound to the timestamp
* 2. User changed a player from, for example. main to popup, or from audio to main, etc
* 3. User chose to resume a video based on a saved timestamp from history of played videos
* In those cases time will be saved because re-init of the play queue is a not an instant
* task and requires network calls
* */
// seek to timestamp if stream is already playing
if (simpleExoPlayer != null
&& queue.size() == 1
&& playQueue != null
&& playQueue.size() == 1
&& playQueue.getItem() != null
&& queue.getItem().getUrl().equals(playQueue.getItem().getUrl())
&& queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET
) {
&& queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
// Player can have state = IDLE when playback is stopped or failed
// and we should retry() in this case
if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) {
simpleExoPlayer.retry();
}
simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
return;
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false) && isPlaybackResumeEnabled()) {
} else if (samePlayQueue && !playQueue.isDisposed() && simpleExoPlayer != null) {
// Do not re-init the same PlayQueue. Save time
// Player can have state = IDLE when playback is stopped or failed
// and we should retry() in this case
if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) {
simpleExoPlayer.retry();
}
return;
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false)
&& isPlaybackResumeEnabled()
&& !samePlayQueue) {
final PlayQueueItem item = queue.getItem();
if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
stateLoader = recordManager.loadStreamState(item)
.observeOn(mainThread())
.doFinally(() -> initPlayback(queue, repeatMode, playbackSpeed,
playbackPitch, playbackSkipSilence, true, isMuted))
.observeOn(AndroidSchedulers.mainThread())
// Do not place initPlayback() in doFinally() because
// it restarts playback after destroy()
//.doFinally()
.subscribe(
state -> queue
.setRecovery(queue.getIndex(), state.getProgressTime()),
state -> {
queue.setRecovery(queue.getIndex(), state.getProgressTime());
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
playbackSkipSilence, true, isMuted);
},
error -> {
if (DEBUG) {
error.printStackTrace();
}
// In case any error we can start playback without history
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
playbackSkipSilence, true, isMuted);
},
() -> {
// Completed but not found in history
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
playbackSkipSilence, true, isMuted);
}
);
databaseUpdateReactor.add(stateLoader);
@@ -325,21 +372,23 @@ public abstract class BasePlayer implements
}
}
// Good to go...
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
/*playOnInit=*/!intent.getBooleanExtra(START_PAUSED, false), isMuted);
// In a case of equal PlayQueues we can re-init old one but only when it is disposed
initPlayback(samePlayQueue ? playQueue : queue, repeatMode,
playbackSpeed, playbackPitch, playbackSkipSilence,
!intent.getBooleanExtra(START_PAUSED, false),
isMuted);
}
private PlaybackParameters retrievePlaybackParametersFromPreferences() {
final SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(context);
final float speed = preferences
.getFloat(context.getString(R.string.playback_speed_key), getPlaybackSpeed());
final float pitch = preferences.getFloat(context.getString(R.string.playback_pitch_key),
getPlaybackPitch());
final boolean skipSilence = preferences
.getBoolean(context.getString(R.string.playback_skip_silence_key),
getPlaybackSkipSilence());
final float speed = preferences.getFloat(
context.getString(R.string.playback_speed_key), getPlaybackSpeed());
final float pitch = preferences.getFloat(
context.getString(R.string.playback_pitch_key), getPlaybackPitch());
final boolean skipSilence = preferences.getBoolean(
context.getString(R.string.playback_skip_silence_key), getPlaybackSkipSilence());
return new PlaybackParameters(speed, pitch, skipSilence);
}
@@ -413,6 +462,7 @@ public abstract class BasePlayer implements
databaseUpdateReactor.clear();
progressUpdateReactor.set(null);
ImageLoader.getInstance().stop();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -450,12 +500,19 @@ public abstract class BasePlayer implements
@Override
public void onLoadingComplete(final String imageUri, final View view,
final Bitmap loadedImage) {
final float width = Math.min(
context.getResources().getDimension(R.dimen.player_notification_thumbnail_width),
loadedImage.getWidth());
currentThumbnail = Bitmap.createScaledBitmap(loadedImage,
(int) width,
(int) (loadedImage.getHeight() / (loadedImage.getWidth() / width)), true);
if (DEBUG) {
Log.d(TAG, "Thumbnail - onLoadingComplete() called with: "
+ "imageUri = [" + imageUri + "], view = [" + view + "], "
+ "loadedImage = [" + loadedImage + "]");
+ "loadedImage = [" + loadedImage + "], "
+ loadedImage.getWidth() + "x" + loadedImage.getHeight()
+ ", scaled width = " + width);
}
currentThumbnail = loadedImage;
}
@Override
@@ -564,7 +621,8 @@ public abstract class BasePlayer implements
}
}
public void onPausedSeek() { }
public void onPausedSeek() {
}
public void onCompleted() {
if (DEBUG) {
@@ -832,19 +890,10 @@ public abstract class BasePlayer implements
}
setRecovery();
final Throwable cause = error.getCause();
if (error 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(/*noIdeaWhatHappenedAndLetUserChooseWhatToDo=*/true);
playQueue.error();
}
}
@@ -1028,14 +1077,6 @@ public abstract class BasePlayer implements
registerView();
}
@Override
public void onPlaybackShutdown() {
if (DEBUG) {
Log.d(TAG, "Shutting down...");
}
destroy();
}
/*//////////////////////////////////////////////////////////////////////////
// General Player
//////////////////////////////////////////////////////////////////////////*/
@@ -1100,6 +1141,7 @@ public abstract class BasePlayer implements
}
simpleExoPlayer.setPlayWhenReady(true);
savePlaybackState();
}
public void onPause() {
@@ -1112,6 +1154,7 @@ public abstract class BasePlayer implements
audioReactor.abandonAudioFocus();
simpleExoPlayer.setPlayWhenReady(false);
savePlaybackState();
}
public void onPlayPause() {
@@ -1131,6 +1174,7 @@ public abstract class BasePlayer implements
Log.d(TAG, "onFastRewind() called");
}
seekBy(-getSeekDuration());
triggerProgressUpdate();
}
public void onFastForward() {
@@ -1138,6 +1182,7 @@ public abstract class BasePlayer implements
Log.d(TAG, "onFastForward() called");
}
seekBy(getSeekDuration());
triggerProgressUpdate();
}
private int getSeekDuration() {
@@ -1204,7 +1249,15 @@ public abstract class BasePlayer implements
Log.d(TAG, "seekBy() called with: position = [" + positionMillis + "]");
}
if (simpleExoPlayer != null) {
simpleExoPlayer.seekTo(positionMillis);
// prevent invalid positions when fast-forwarding/-rewinding
long normalizedPositionMillis = positionMillis;
if (normalizedPositionMillis < 0) {
normalizedPositionMillis = 0;
} else if (normalizedPositionMillis > simpleExoPlayer.getDuration()) {
normalizedPositionMillis = simpleExoPlayer.getDuration();
}
simpleExoPlayer.seekTo(normalizedPositionMillis);
}
}
@@ -1304,6 +1357,11 @@ public abstract class BasePlayer implements
return;
}
final StreamInfo currentInfo = currentMetadata.getMetadata();
if (playQueue != null) {
// Save current position. It will help to restore this position once a user
// wants to play prev or next stream from the queue
playQueue.setRecovery(playQueue.getIndex(), simpleExoPlayer.getContentPosition());
}
savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
}
@@ -1370,6 +1428,11 @@ public abstract class BasePlayer implements
return currentMetadata;
}
@NonNull
public LoadController getLoadController() {
return (LoadController) loadControl;
}
@NonNull
public String getVideoUrl() {
return currentMetadata == null
@@ -1416,7 +1479,7 @@ public abstract class BasePlayer implements
return false;
}
Timeline.Window timelineWindow = new Timeline.Window();
final Timeline.Window timelineWindow = new Timeline.Window();
currentTimeline.getWindow(currentWindowIndex, timelineWindow);
return timelineWindow.getDefaultPositionMs() <= simpleExoPlayer.getCurrentPosition();
}
@@ -1427,7 +1490,7 @@ public abstract class BasePlayer implements
}
try {
return simpleExoPlayer.isCurrentWindowDynamic();
} catch (@NonNull IndexOutOfBoundsException e) {
} catch (@NonNull final IndexOutOfBoundsException e) {
// Why would this even happen =(
// But lets log it anyway. Save is save
if (DEBUG) {
@@ -1442,6 +1505,10 @@ public abstract class BasePlayer implements
return simpleExoPlayer != null && simpleExoPlayer.isPlaying();
}
public boolean isLoading() {
return simpleExoPlayer != null && simpleExoPlayer.isLoading();
}
@Player.RepeatMode
public int getRepeatMode() {
return simpleExoPlayer == null
@@ -1479,20 +1546,32 @@ public abstract class BasePlayer implements
return parameters == null ? PlaybackParameters.DEFAULT : parameters;
}
/**
* Sets the playback parameters of the player, and also saves them to shared preferences.
* Speed and pitch are rounded up to 2 decimal places before being used or saved.
*
* @param speed the playback speed, will be rounded to up to 2 decimal places
* @param pitch the playback pitch, will be rounded to up to 2 decimal places
* @param skipSilence skip silence during playback
*/
public void setPlaybackParameters(final float speed, final float pitch,
final boolean skipSilence) {
savePlaybackParametersToPreferences(speed, pitch, skipSilence);
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence));
final float roundedSpeed = Math.round(speed * 100.0f) / 100.0f;
final float roundedPitch = Math.round(pitch * 100.0f) / 100.0f;
savePlaybackParametersToPreferences(roundedSpeed, roundedPitch, skipSilence);
simpleExoPlayer.setPlaybackParameters(
new PlaybackParameters(roundedSpeed, roundedPitch, skipSilence));
}
private void savePlaybackParametersToPreferences(final float speed, final float pitch,
final boolean skipSilence) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putFloat(context.getString(R.string.playback_speed_key), speed)
.putFloat(context.getString(R.string.playback_pitch_key), pitch)
.putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence)
.apply();
.edit()
.putFloat(context.getString(R.string.playback_speed_key), speed)
.putFloat(context.getString(R.string.playback_pitch_key), pitch)
.putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence)
.apply();
}
public PlayQueue getPlayQueue() {

View File

@@ -0,0 +1,264 @@
/*
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
* Part of NewPipe
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.schabi.newpipe.player;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
/**
* One service for all players.
*
* @author mauriciocolli
*/
public final class MainPlayer extends Service {
private static final String TAG = "MainPlayer";
private static final boolean DEBUG = BasePlayer.DEBUG;
private VideoPlayerImpl playerImpl;
private WindowManager windowManager;
private final IBinder mBinder = new MainPlayer.LocalBinder();
public enum PlayerType {
VIDEO,
AUDIO,
POPUP
}
/*//////////////////////////////////////////////////////////////////////////
// Notification
//////////////////////////////////////////////////////////////////////////*/
static final String ACTION_CLOSE
= "org.schabi.newpipe.player.MainPlayer.CLOSE";
static final String ACTION_PLAY_PAUSE
= "org.schabi.newpipe.player.MainPlayer.PLAY_PAUSE";
static final String ACTION_OPEN_CONTROLS
= "org.schabi.newpipe.player.MainPlayer.OPEN_CONTROLS";
static final String ACTION_REPEAT
= "org.schabi.newpipe.player.MainPlayer.REPEAT";
static final String ACTION_PLAY_NEXT
= "org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT";
static final String ACTION_PLAY_PREVIOUS
= "org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS";
static final String ACTION_FAST_REWIND
= "org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND";
static final String ACTION_FAST_FORWARD
= "org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD";
static final String ACTION_SHUFFLE
= "org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE";
public static final String ACTION_RECREATE_NOTIFICATION
= "org.schabi.newpipe.player.MainPlayer.ACTION_RECREATE_NOTIFICATION";
/*//////////////////////////////////////////////////////////////////////////
// Service's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate() {
if (DEBUG) {
Log.d(TAG, "onCreate() called");
}
assureCorrectAppLanguage(this);
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
ThemeHelper.setTheme(this);
createView();
}
private void createView() {
final View layout = View.inflate(this, R.layout.player, null);
playerImpl = new VideoPlayerImpl(this);
playerImpl.setup(layout);
playerImpl.shouldUpdateOnProgress = true;
NotificationUtil.getInstance().createNotificationAndStartForeground(playerImpl, this);
}
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
if (DEBUG) {
Log.d(TAG, "onStartCommand() called with: intent = [" + intent
+ "], flags = [" + flags + "], startId = [" + startId + "]");
}
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
&& playerImpl.playQueue == null) {
// Player is not working, no need to process media button's action
return START_NOT_STICKY;
}
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
|| intent.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY) != null) {
NotificationUtil.getInstance().createNotificationAndStartForeground(playerImpl, this);
}
playerImpl.handleIntent(intent);
if (playerImpl.mediaSessionManager != null) {
playerImpl.mediaSessionManager.handleMediaButtonIntent(intent);
}
return START_NOT_STICKY;
}
public void stop(final boolean autoplayEnabled) {
if (DEBUG) {
Log.d(TAG, "stop() called");
}
if (playerImpl.getPlayer() != null) {
playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady();
// Releases wifi & cpu, disables keepScreenOn, etc.
if (!autoplayEnabled) {
playerImpl.onPause();
}
// We can't just pause the player here because it will make transition
// from one stream to a new stream not smooth
playerImpl.getPlayer().stop(false);
playerImpl.setRecovery();
// Android TV will handle back button in case controls will be visible
// (one more additional unneeded click while the player is hidden)
playerImpl.hideControls(0, 0);
playerImpl.onQueueClosed();
// Notification shows information about old stream but if a user selects
// a stream from backStack it's not actual anymore
// So we should hide the notification at all.
// When autoplay enabled such notification flashing is annoying so skip this case
if (!autoplayEnabled) {
NotificationUtil.getInstance().cancelNotificationAndStopForeground(this);
}
}
}
@Override
public void onTaskRemoved(final Intent rootIntent) {
super.onTaskRemoved(rootIntent);
if (!playerImpl.videoPlayerSelected()) {
return;
}
onDestroy();
// Unload from memory completely
Runtime.getRuntime().halt(0);
}
@Override
public void onDestroy() {
if (DEBUG) {
Log.d(TAG, "destroy() called");
}
onClose();
}
@Override
protected void attachBaseContext(final Context base) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
}
@Override
public IBinder onBind(final Intent intent) {
return mBinder;
}
/*//////////////////////////////////////////////////////////////////////////
// Actions
//////////////////////////////////////////////////////////////////////////*/
private void onClose() {
if (DEBUG) {
Log.d(TAG, "onClose() called");
}
if (playerImpl != null) {
// Exit from fullscreen when user closes the player via notification
if (playerImpl.isFullscreen()) {
playerImpl.toggleFullscreen();
}
removeViewFromParent();
playerImpl.setRecovery();
playerImpl.savePlaybackState();
playerImpl.stopActivityBinding();
playerImpl.removePopupFromView();
playerImpl.destroy();
}
NotificationUtil.getInstance().cancelNotificationAndStopForeground(this);
stopSelf();
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
boolean isLandscape() {
// DisplayMetrics from activity context knows about MultiWindow feature
// while DisplayMetrics from app context doesn't
final DisplayMetrics metrics = (playerImpl != null
&& playerImpl.getParentActivity() != null)
? playerImpl.getParentActivity().getResources().getDisplayMetrics()
: getResources().getDisplayMetrics();
return metrics.heightPixels < metrics.widthPixels;
}
public View getView() {
if (playerImpl == null) {
return null;
}
return playerImpl.getRootView();
}
public void removeViewFromParent() {
if (getView().getParent() != null) {
if (playerImpl.getParentActivity() != null) {
// This means view was added to fragment
final ViewGroup parent = (ViewGroup) getView().getParent();
parent.removeView(getView());
} else {
// This means view was added by windowManager for popup player
windowManager.removeViewImmediate(getView());
}
}
}
public class LocalBinder extends Binder {
public MainPlayer getService() {
return MainPlayer.this;
}
public VideoPlayerImpl getPlayer() {
return MainPlayer.this.playerImpl;
}
}
}

View File

@@ -0,0 +1,165 @@
package org.schabi.newpipe.player;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.Localization;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
public final class NotificationConstants {
private NotificationConstants() { }
public static final int NOTHING = 0;
public static final int PREVIOUS = 1;
public static final int NEXT = 2;
public static final int REWIND = 3;
public static final int FORWARD = 4;
public static final int SMART_REWIND_PREVIOUS = 5;
public static final int SMART_FORWARD_NEXT = 6;
public static final int PLAY_PAUSE = 7;
public static final int PLAY_PAUSE_BUFFERING = 8;
public static final int REPEAT = 9;
public static final int SHUFFLE = 10;
public static final int CLOSE = 11;
@Retention(RetentionPolicy.SOURCE)
@IntDef({NOTHING, PREVIOUS, NEXT, REWIND, FORWARD, SMART_REWIND_PREVIOUS, SMART_FORWARD_NEXT,
PLAY_PAUSE, PLAY_PAUSE_BUFFERING, REPEAT, SHUFFLE, CLOSE})
public @interface Action { }
@DrawableRes
public static final int[] ACTION_ICONS = {
0,
R.drawable.exo_icon_previous,
R.drawable.exo_icon_next,
R.drawable.exo_icon_rewind,
R.drawable.exo_icon_fastforward,
R.drawable.exo_icon_previous,
R.drawable.exo_icon_next,
R.drawable.ic_pause_white_24dp,
R.drawable.ic_hourglass_top_white_24dp,
R.drawable.exo_icon_repeat_all,
R.drawable.exo_icon_shuffle_on,
R.drawable.ic_close_white_24dp,
};
@Action
public static final int[] SLOT_DEFAULTS = {
SMART_REWIND_PREVIOUS,
PLAY_PAUSE_BUFFERING,
SMART_FORWARD_NEXT,
REPEAT,
CLOSE,
};
@Action
public static final int[][] SLOT_ALLOWED_ACTIONS = {
new int[] {PREVIOUS, REWIND, SMART_REWIND_PREVIOUS},
new int[] {REWIND, PLAY_PAUSE, PLAY_PAUSE_BUFFERING},
new int[] {NEXT, FORWARD, SMART_FORWARD_NEXT, PLAY_PAUSE, PLAY_PAUSE_BUFFERING},
new int[] {NOTHING, PREVIOUS, NEXT, REWIND, FORWARD, SMART_REWIND_PREVIOUS,
SMART_FORWARD_NEXT, REPEAT, SHUFFLE, CLOSE},
new int[] {NOTHING, NEXT, FORWARD, SMART_FORWARD_NEXT, REPEAT, SHUFFLE, CLOSE},
};
public static final int[] SLOT_PREF_KEYS = {
R.string.notification_slot_0_key,
R.string.notification_slot_1_key,
R.string.notification_slot_2_key,
R.string.notification_slot_3_key,
R.string.notification_slot_4_key,
};
public static final Integer[] SLOT_COMPACT_DEFAULTS = {0, 1, 2};
public static final int[] SLOT_COMPACT_PREF_KEYS = {
R.string.notification_slot_compact_0_key,
R.string.notification_slot_compact_1_key,
R.string.notification_slot_compact_2_key,
};
public static String getActionName(@NonNull final Context context, @Action final int action) {
switch (action) {
case PREVIOUS:
return context.getString(R.string.exo_controls_previous_description);
case NEXT:
return context.getString(R.string.exo_controls_next_description);
case REWIND:
return context.getString(R.string.exo_controls_rewind_description);
case FORWARD:
return context.getString(R.string.exo_controls_fastforward_description);
case SMART_REWIND_PREVIOUS:
return Localization.concatenateStrings(
context.getString(R.string.exo_controls_rewind_description),
context.getString(R.string.exo_controls_previous_description));
case SMART_FORWARD_NEXT:
return Localization.concatenateStrings(
context.getString(R.string.exo_controls_fastforward_description),
context.getString(R.string.exo_controls_next_description));
case PLAY_PAUSE:
return Localization.concatenateStrings(
context.getString(R.string.exo_controls_play_description),
context.getString(R.string.exo_controls_pause_description));
case PLAY_PAUSE_BUFFERING:
return Localization.concatenateStrings(
context.getString(R.string.exo_controls_play_description),
context.getString(R.string.exo_controls_pause_description),
context.getString(R.string.notification_action_buffering));
case REPEAT:
return context.getString(R.string.notification_action_repeat);
case SHUFFLE:
return context.getString(R.string.notification_action_shuffle);
case CLOSE:
return context.getString(R.string.close);
case NOTHING: default:
return context.getString(R.string.notification_action_nothing);
}
}
/**
* @param context the context to use
* @param sharedPreferences the shared preferences to query values from
* @param slotCount remove indices >= than this value (set to {@code 5} to do nothing, or make
* it lower if there are slots with empty actions)
* @return a sorted list of the indices of the slots to use as compact slots
*/
public static List<Integer> getCompactSlotsFromPreferences(
@NonNull final Context context,
final SharedPreferences sharedPreferences,
final int slotCount) {
final SortedSet<Integer> compactSlots = new TreeSet<>();
for (int i = 0; i < 3; i++) {
final int compactSlot = sharedPreferences.getInt(
context.getString(SLOT_COMPACT_PREF_KEYS[i]), Integer.MAX_VALUE);
if (compactSlot == Integer.MAX_VALUE) {
// settings not yet populated, return default values
return new ArrayList<>(Arrays.asList(SLOT_COMPACT_DEFAULTS));
}
// a negative value (-1) is set when the user does not want a particular compact slot
if (compactSlot >= 0 && compactSlot < slotCount) {
compactSlots.add(compactSlot);
}
}
return new ArrayList<>(compactSlots);
}
}

View File

@@ -0,0 +1,371 @@
package org.schabi.newpipe.player;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.os.Build;
import android.util.Log;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.List;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE;
import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD;
import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND;
import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT;
import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PAUSE;
import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS;
import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT;
import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE;
/**
* This is a utility class for player notifications.
*
* @author cool-student
*/
public final class NotificationUtil {
private static final String TAG = NotificationUtil.class.getSimpleName();
private static final boolean DEBUG = BasePlayer.DEBUG;
private static final int NOTIFICATION_ID = 123789;
@Nullable private static NotificationUtil instance = null;
@NotificationConstants.Action
private int[] notificationSlots = NotificationConstants.SLOT_DEFAULTS.clone();
private NotificationManagerCompat notificationManager;
private NotificationCompat.Builder notificationBuilder;
private NotificationUtil() {
}
public static NotificationUtil getInstance() {
if (instance == null) {
instance = new NotificationUtil();
}
return instance;
}
/////////////////////////////////////////////////////
// NOTIFICATION
/////////////////////////////////////////////////////
/**
* Creates the notification if it does not exist already and recreates it if forceRecreate is
* true. Updates the notification with the data in the player.
* @param player the player currently open, to take data from
* @param forceRecreate whether to force the recreation of the notification even if it already
* exists
*/
synchronized void createNotificationIfNeededAndUpdate(final VideoPlayerImpl player,
final boolean forceRecreate) {
if (forceRecreate || notificationBuilder == null) {
notificationBuilder = createNotification(player);
}
updateNotification(player);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
private synchronized NotificationCompat.Builder createNotification(
final VideoPlayerImpl player) {
if (DEBUG) {
Log.d(TAG, "createNotification()");
}
notificationManager = NotificationManagerCompat.from(player.context);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(player.context,
player.context.getString(R.string.notification_channel_id));
initializeNotificationSlots(player);
// count the number of real slots, to make sure compact slots indices are not out of bound
int nonNothingSlotCount = 5;
if (notificationSlots[3] == NotificationConstants.NOTHING) {
--nonNothingSlotCount;
}
if (notificationSlots[4] == NotificationConstants.NOTHING) {
--nonNothingSlotCount;
}
// build the compact slot indices array (need code to convert from Integer... because Java)
final List<Integer> compactSlotList = NotificationConstants.getCompactSlotsFromPreferences(
player.context, player.sharedPreferences, nonNothingSlotCount);
final int[] compactSlots = new int[compactSlotList.size()];
for (int i = 0; i < compactSlotList.size(); i++) {
compactSlots[i] = compactSlotList.get(i);
}
builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(player.mediaSessionManager.getSessionToken())
.setShowActionsInCompactView(compactSlots))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setColor(ContextCompat.getColor(player.context, R.color.gray))
.setCategory(NotificationCompat.CATEGORY_TRANSPORT)
.setDeleteIntent(PendingIntent.getBroadcast(player.context, NOTIFICATION_ID,
new Intent(ACTION_CLOSE), FLAG_UPDATE_CURRENT));
return builder;
}
/**
* Updates the notification builder and the button icons depending on the playback state.
* @param player the player currently open, to take data from
*/
private synchronized void updateNotification(final VideoPlayerImpl player) {
if (DEBUG) {
Log.d(TAG, "updateNotification()");
}
// also update content intent, in case the user switched players
notificationBuilder.setContentIntent(PendingIntent.getActivity(player.context,
NOTIFICATION_ID, getIntentForNotification(player), FLAG_UPDATE_CURRENT));
notificationBuilder.setContentTitle(player.getVideoTitle());
notificationBuilder.setContentText(player.getUploaderName());
notificationBuilder.setTicker(player.getVideoTitle());
updateActions(notificationBuilder, player);
setLargeIcon(notificationBuilder, player);
}
@SuppressLint("RestrictedApi")
boolean shouldUpdateBufferingSlot() {
if (notificationBuilder.mActions.size() < 3) {
// this should never happen, but let's make sure notification actions are populated
return true;
}
// only second and third slot could contain PLAY_PAUSE_BUFFERING, update them only if they
// are not already in the buffering state (the only one with a null action intent)
return (notificationSlots[1] == NotificationConstants.PLAY_PAUSE_BUFFERING
&& notificationBuilder.mActions.get(1).actionIntent != null)
|| (notificationSlots[2] == NotificationConstants.PLAY_PAUSE_BUFFERING
&& notificationBuilder.mActions.get(2).actionIntent != null);
}
void createNotificationAndStartForeground(final VideoPlayerImpl player, final Service service) {
if (notificationBuilder == null) {
notificationBuilder = createNotification(player);
}
updateNotification(player);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
service.startForeground(NOTIFICATION_ID, notificationBuilder.build(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
service.startForeground(NOTIFICATION_ID, notificationBuilder.build());
}
}
void cancelNotificationAndStopForeground(final Service service) {
service.stopForeground(true);
if (notificationManager != null) {
notificationManager.cancel(NOTIFICATION_ID);
}
notificationManager = null;
notificationBuilder = null;
}
/////////////////////////////////////////////////////
// ACTIONS
/////////////////////////////////////////////////////
private void initializeNotificationSlots(final VideoPlayerImpl player) {
for (int i = 0; i < 5; ++i) {
notificationSlots[i] = player.sharedPreferences.getInt(
player.context.getString(NotificationConstants.SLOT_PREF_KEYS[i]),
NotificationConstants.SLOT_DEFAULTS[i]);
}
}
@SuppressLint("RestrictedApi")
private void updateActions(final NotificationCompat.Builder builder,
final VideoPlayerImpl player) {
builder.mActions.clear();
for (int i = 0; i < 5; ++i) {
addAction(builder, player, notificationSlots[i]);
}
}
private void addAction(final NotificationCompat.Builder builder,
final VideoPlayerImpl player,
@NotificationConstants.Action final int slot) {
final NotificationCompat.Action action = getAction(player, slot);
if (action != null) {
builder.addAction(action);
}
}
@Nullable
private NotificationCompat.Action getAction(
final VideoPlayerImpl player,
@NotificationConstants.Action final int selectedAction) {
final int baseActionIcon = NotificationConstants.ACTION_ICONS[selectedAction];
switch (selectedAction) {
case NotificationConstants.PREVIOUS:
return getAction(player, baseActionIcon,
R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS);
case NotificationConstants.NEXT:
return getAction(player, baseActionIcon,
R.string.exo_controls_next_description, ACTION_PLAY_NEXT);
case NotificationConstants.REWIND:
return getAction(player, baseActionIcon,
R.string.exo_controls_rewind_description, ACTION_FAST_REWIND);
case NotificationConstants.FORWARD:
return getAction(player, baseActionIcon,
R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD);
case NotificationConstants.SMART_REWIND_PREVIOUS:
if (player.playQueue != null && player.playQueue.size() > 1) {
return getAction(player, R.drawable.exo_notification_previous,
R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS);
} else {
return getAction(player, R.drawable.exo_controls_rewind,
R.string.exo_controls_rewind_description, ACTION_FAST_REWIND);
}
case NotificationConstants.SMART_FORWARD_NEXT:
if (player.playQueue != null && player.playQueue.size() > 1) {
return getAction(player, R.drawable.exo_notification_next,
R.string.exo_controls_next_description, ACTION_PLAY_NEXT);
} else {
return getAction(player, R.drawable.exo_controls_fastforward,
R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD);
}
case NotificationConstants.PLAY_PAUSE_BUFFERING:
if (player.getCurrentState() == BasePlayer.STATE_PREFLIGHT
|| player.getCurrentState() == BasePlayer.STATE_BLOCKED
|| player.getCurrentState() == BasePlayer.STATE_BUFFERING) {
// null intent -> show hourglass icon that does nothing when clicked
return new NotificationCompat.Action(R.drawable.ic_hourglass_top_white_24dp_png,
player.context.getString(R.string.notification_action_buffering),
null);
}
case NotificationConstants.PLAY_PAUSE:
if (player.getCurrentState() == BasePlayer.STATE_COMPLETED) {
return getAction(player, R.drawable.ic_replay_white_24dp_png,
R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE);
} else if (player.isPlaying()
|| player.getCurrentState() == BasePlayer.STATE_PREFLIGHT
|| player.getCurrentState() == BasePlayer.STATE_BLOCKED
|| player.getCurrentState() == BasePlayer.STATE_BUFFERING) {
return getAction(player, R.drawable.exo_notification_pause,
R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE);
} else {
return getAction(player, R.drawable.exo_notification_play,
R.string.exo_controls_play_description, ACTION_PLAY_PAUSE);
}
case NotificationConstants.REPEAT:
if (player.getRepeatMode() == REPEAT_MODE_ALL) {
return getAction(player, R.drawable.exo_media_action_repeat_all,
R.string.exo_controls_repeat_all_description, ACTION_REPEAT);
} else if (player.getRepeatMode() == REPEAT_MODE_ONE) {
return getAction(player, R.drawable.exo_media_action_repeat_one,
R.string.exo_controls_repeat_one_description, ACTION_REPEAT);
} else /* player.getRepeatMode() == REPEAT_MODE_OFF */ {
return getAction(player, R.drawable.exo_media_action_repeat_off,
R.string.exo_controls_repeat_off_description, ACTION_REPEAT);
}
case NotificationConstants.SHUFFLE:
if (player.playQueue != null && player.playQueue.isShuffled()) {
return getAction(player, R.drawable.exo_controls_shuffle_on,
R.string.exo_controls_shuffle_on_description, ACTION_SHUFFLE);
} else {
return getAction(player, R.drawable.exo_controls_shuffle_off,
R.string.exo_controls_shuffle_off_description, ACTION_SHUFFLE);
}
case NotificationConstants.CLOSE:
return getAction(player, R.drawable.ic_close_white_24dp_png,
R.string.close, ACTION_CLOSE);
case NotificationConstants.NOTHING:
default:
// do nothing
return null;
}
}
private NotificationCompat.Action getAction(final VideoPlayerImpl player,
@DrawableRes final int drawable,
@StringRes final int title,
final String intentAction) {
return new NotificationCompat.Action(drawable, player.context.getString(title),
PendingIntent.getBroadcast(player.context, NOTIFICATION_ID,
new Intent(intentAction), FLAG_UPDATE_CURRENT));
}
private Intent getIntentForNotification(final VideoPlayerImpl player) {
if (player.audioPlayerSelected() || player.popupPlayerSelected()) {
// Means we play in popup or audio only. Let's show the play queue
return NavigationHelper.getPlayQueueActivityIntent(player.context);
} else {
// We are playing in fragment. Don't open another activity just show fragment. That's it
final Intent intent = NavigationHelper.getPlayerIntent(
player.context, MainActivity.class, null, true);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
return intent;
}
}
/////////////////////////////////////////////////////
// BITMAP
/////////////////////////////////////////////////////
private void setLargeIcon(final NotificationCompat.Builder builder,
final VideoPlayerImpl player) {
final boolean scaleImageToSquareAspectRatio = player.sharedPreferences.getBoolean(
player.context.getString(R.string.scale_to_square_image_in_notifications_key),
false);
if (scaleImageToSquareAspectRatio) {
builder.setLargeIcon(getBitmapWithSquareAspectRatio(player.getThumbnail()));
} else {
builder.setLargeIcon(player.getThumbnail());
}
}
private Bitmap getBitmapWithSquareAspectRatio(final Bitmap bitmap) {
return getResizedBitmap(bitmap, bitmap.getWidth(), bitmap.getWidth());
}
private Bitmap getResizedBitmap(final Bitmap bitmap, final int newWidth, final int newHeight) {
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
final float scaleWidth = ((float) newWidth) / width;
final float scaleHeight = ((float) newHeight) / height;
final Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
}
}

View File

@@ -1,66 +0,0 @@
package org.schabi.newpipe.player;
import android.content.Intent;
import android.view.MenuItem;
import org.schabi.newpipe.R;
import static org.schabi.newpipe.player.PopupVideoPlayer.ACTION_CLOSE;
public final class PopupVideoPlayerActivity extends ServicePlayerActivity {
private static final String TAG = "PopupVideoPlayerActivity";
@Override
public String getTag() {
return TAG;
}
@Override
public String getSupportActionTitle() {
return getResources().getString(R.string.title_activity_popup_player);
}
@Override
public Intent getBindIntent() {
return new Intent(this, PopupVideoPlayer.class);
}
@Override
public void startPlayerListener() {
if (player != null && player instanceof PopupVideoPlayer.VideoPlayerImpl) {
((PopupVideoPlayer.VideoPlayerImpl) player).setActivityListener(this);
}
}
@Override
public void stopPlayerListener() {
if (player != null && player instanceof PopupVideoPlayer.VideoPlayerImpl) {
((PopupVideoPlayer.VideoPlayerImpl) player).removeActivityListener(this);
}
}
@Override
public int getPlayerOptionMenuResource() {
return R.menu.menu_play_queue_popup;
}
@Override
public boolean onPlayerOptionSelected(final MenuItem item) {
if (item.getItemId() == R.id.action_switch_background) {
this.player.setRecovery();
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
getApplicationContext().startService(
getSwitchIntent(BackgroundPlayer.class)
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
);
return true;
}
return false;
}
@Override
public Intent getPlayerShutdownIntent() {
return new Intent(ACTION_CLOSE);
}
}

View File

@@ -27,17 +27,21 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
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.player.playqueue.PlayQueue;
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.Constants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -45,7 +49,6 @@ 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;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@@ -84,14 +87,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private ImageButton repeatButton;
private ImageButton backwardButton;
private ImageButton fastRewindButton;
private ImageButton playPauseButton;
private ImageButton fastForwardButton;
private ImageButton forwardButton;
private ImageButton shuffleButton;
private ProgressBar progressBar;
private TextView playbackSpeedButton;
private TextView playbackPitchButton;
private Menu menu;
////////////////////////////////////////////////////////////////////////////
@@ -112,7 +114,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
public abstract boolean onPlayerOptionSelected(MenuItem item);
public abstract Intent getPlayerShutdownIntent();
public abstract void setupMenu(Menu m);
////////////////////////////////////////////////////////////////////////////
// Activity Lifecycle
////////////////////////////////////////////////////////////////////////////
@@ -154,6 +156,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return true;
}
// Allow to setup visibility of menuItems
@Override
public boolean onPrepareOptionsMenu(final Menu m) {
setupMenu(m);
return super.onPrepareOptionsMenu(m);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
@@ -166,6 +175,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
case R.id.action_append_playlist:
appendAllToPlaylist();
return true;
case R.id.action_playback_speed:
openPlaybackParameterDialog();
return true;
case R.id.action_mute:
player.onMuteUnmuteButtonClicked();
return true;
@@ -174,11 +186,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return true;
case R.id.action_switch_main:
this.player.setRecovery();
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
getApplicationContext().startActivity(
getSwitchIntent(MainVideoPlayer.class)
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
);
getSwitchIntent(MainActivity.class, MainPlayer.PlayerType.VIDEO)
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()));
return true;
}
return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item);
@@ -190,13 +200,22 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
unbind();
}
protected Intent getSwitchIntent(final Class clazz) {
protected Intent getSwitchIntent(final Class clazz, final MainPlayer.PlayerType playerType) {
return NavigationHelper.getPlayerIntent(getApplicationContext(), clazz,
this.player.getPlayQueue(), this.player.getRepeatMode(),
this.player.getPlaybackSpeed(), this.player.getPlaybackPitch(),
this.player.getPlaybackSkipSilence(), null, false, false, this.player.isMuted())
this.player.getPlaybackSkipSilence(),
null,
true,
!this.player.isPlaying(),
this.player.isMuted())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying());
.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM)
.putExtra(Constants.KEY_URL, this.player.getVideoUrl())
.putExtra(Constants.KEY_TITLE, this.player.getVideoTitle())
.putExtra(Constants.KEY_SERVICE_ID,
this.player.getCurrentMetadata().getMetadata().getServiceId())
.putExtra(VideoPlayer.PLAYER_TYPE, playerType);
}
////////////////////////////////////////////////////////////////////////////
@@ -246,6 +265,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
if (service instanceof PlayerServiceBinder) {
player = ((PlayerServiceBinder) service).getPlayerInstance();
} else if (service instanceof MainPlayer.LocalBinder) {
player = ((MainPlayer.LocalBinder) service).getPlayer();
}
if (player == null || player.getPlayQueue() == null
@@ -310,20 +331,20 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private void buildControls() {
repeatButton = rootView.findViewById(R.id.control_repeat);
backwardButton = rootView.findViewById(R.id.control_backward);
fastRewindButton = rootView.findViewById(R.id.control_fast_rewind);
playPauseButton = rootView.findViewById(R.id.control_play_pause);
fastForwardButton = rootView.findViewById(R.id.control_fast_forward);
forwardButton = rootView.findViewById(R.id.control_forward);
shuffleButton = rootView.findViewById(R.id.control_shuffle);
playbackSpeedButton = rootView.findViewById(R.id.control_playback_speed);
playbackPitchButton = rootView.findViewById(R.id.control_playback_pitch);
progressBar = rootView.findViewById(R.id.control_progress_bar);
repeatButton.setOnClickListener(this);
backwardButton.setOnClickListener(this);
fastRewindButton.setOnClickListener(this);
playPauseButton.setOnClickListener(this);
fastForwardButton.setOnClickListener(this);
forwardButton.setOnClickListener(this);
shuffleButton.setOnClickListener(this);
playbackSpeedButton.setOnClickListener(this);
playbackPitchButton.setOnClickListener(this);
}
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
@@ -473,16 +494,16 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
player.onRepeatClicked();
} else if (view.getId() == backwardButton.getId()) {
player.onPlayPrevious();
} else if (view.getId() == fastRewindButton.getId()) {
player.onFastRewind();
} else if (view.getId() == playPauseButton.getId()) {
player.onPlayPause();
} else if (view.getId() == fastForwardButton.getId()) {
player.onFastForward();
} else if (view.getId() == forwardButton.getId()) {
player.onPlayNext();
} else if (view.getId() == shuffleButton.getId()) {
player.onShuffleClicked();
} else if (view.getId() == playbackSpeedButton.getId()) {
openPlaybackParameterDialog();
} else if (view.getId() == playbackPitchButton.getId()) {
openPlaybackParameterDialog();
} else if (view.getId() == metadata.getId()) {
scrollToSelected();
} else if (view.getId() == progressLiveSync.getId()) {
@@ -499,7 +520,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return;
}
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
player.getPlaybackSkipSilence()).show(getSupportFragmentManager(), getTag());
player.getPlaybackSkipSilence(), this).show(getSupportFragmentManager(), getTag());
}
@Override
@@ -559,7 +580,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
////////////////////////////////////////////////////////////////////////////
private void shareUrl(final String subject, final String url) {
Intent intent = new Intent(Intent.ACTION_SEND);
final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, url);
@@ -570,6 +591,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
// Binding Service Listener
////////////////////////////////////////////////////////////////////////////
@Override
public void onQueueUpdate(final PlayQueue queue) {
}
@Override
public void onPlaybackUpdate(final int state, final int repeatMode, final boolean shuffled,
final PlaybackParameters parameters) {
@@ -609,7 +634,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
}
@Override
public void onMetadataUpdate(final StreamInfo info) {
public void onMetadataUpdate(final StreamInfo info, final PlayQueue queue) {
if (info != null) {
metadataTitle.setText(info.getName());
metadataArtist.setText(info.getUploaderName());
@@ -690,8 +715,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
if (parameters != null) {
playbackSpeedButton.setText(formatSpeed(parameters.speed));
playbackPitchButton.setText(formatPitch(parameters.pitch));
if (menu != null && player != null) {
final MenuItem item = menu.findItem(R.id.action_playback_speed);
item.setTitle(formatSpeed(parameters.speed));
}
}
}
@@ -707,7 +734,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private void onMaybeMuteChanged() {
if (menu != null && player != null) {
MenuItem item = menu.findItem(R.id.action_mute);
final MenuItem item = menu.findItem(R.id.action_mute);
//Change the mute-button item in ActionBar
//1) Text change:

View File

@@ -30,20 +30,21 @@ import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.os.Build;
import android.os.Handler;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceView;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
@@ -69,6 +70,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.views.ExpandableSurfaceView;
import java.util.ArrayList;
import java.util.List;
@@ -117,8 +119,7 @@ public abstract class VideoPlayer extends BasePlayer
private View rootView;
private AspectRatioFrameLayout aspectRatioFrameLayout;
private SurfaceView surfaceView;
private ExpandableSurfaceView surfaceView;
private View surfaceForeground;
private View loadingPanel;
@@ -127,6 +128,8 @@ public abstract class VideoPlayer extends BasePlayer
private View controlsRoot;
private TextView currentDisplaySeek;
private View playerTopShadow;
private View playerBottomShadow;
private View bottomControlsRoot;
private SeekBar playbackSeekBar;
@@ -135,7 +138,7 @@ public abstract class VideoPlayer extends BasePlayer
private TextView playbackLiveSync;
private TextView playbackSpeedTextView;
private View topControlsRoot;
private LinearLayout topControlsRoot;
private TextView qualityTextView;
private SubtitleView subtitleView;
@@ -167,7 +170,7 @@ public abstract class VideoPlayer extends BasePlayer
// workaround to match normalized captions like english to English or deutsch to Deutsch
private static boolean containsCaseInsensitive(final List<String> list, final String toFind) {
for (String i : list) {
for (final String i : list) {
if (i.equalsIgnoreCase(toFind)) {
return true;
}
@@ -182,7 +185,6 @@ public abstract class VideoPlayer extends BasePlayer
public void initViews(final View view) {
this.rootView = view;
this.aspectRatioFrameLayout = view.findViewById(R.id.aspectRatioLayout);
this.surfaceView = view.findViewById(R.id.surfaceView);
this.surfaceForeground = view.findViewById(R.id.surfaceForeground);
this.loadingPanel = view.findViewById(R.id.loading_panel);
@@ -190,6 +192,8 @@ public abstract class VideoPlayer extends BasePlayer
this.controlAnimationView = view.findViewById(R.id.controlAnimationView);
this.controlsRoot = view.findViewById(R.id.playbackControlRoot);
this.currentDisplaySeek = view.findViewById(R.id.currentDisplaySeek);
this.playerTopShadow = view.findViewById(R.id.playerTopShadow);
this.playerBottomShadow = view.findViewById(R.id.playerBottomShadow);
this.playbackSeekBar = view.findViewById(R.id.playbackSeekBar);
this.playbackCurrentTime = view.findViewById(R.id.playbackCurrentTime);
this.playbackEndTime = view.findViewById(R.id.playbackEndTime);
@@ -207,24 +211,22 @@ public abstract class VideoPlayer extends BasePlayer
this.resizeView = view.findViewById(R.id.resizeTextView);
resizeView.setText(PlayerHelper
.resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode()));
.resizeTypeOf(context, getSurfaceView().getResizeMode()));
this.captionTextView = view.findViewById(R.id.captionTextView);
//this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
}
this.playbackSeekBar.getProgressDrawable().
setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
playbackSeekBar.getThumb()
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
this.playbackSeekBar.getProgressDrawable()
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY));
this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeedTextView);
this.captionPopupMenu = new PopupMenu(context, captionTextView);
((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel))
.getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);
.getIndeterminateDrawable()
.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY));
}
protected abstract void setupSubtitleView(@NonNull SubtitleView view, float captionScale,
@@ -252,7 +254,7 @@ public abstract class VideoPlayer extends BasePlayer
simpleExoPlayer.addTextOutput(cues -> subtitleView.onCues(cues));
// Setup audio session with onboard equalizer
if (Build.VERSION.SDK_INT >= 21) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
trackSelector.setParameters(trackSelector.buildUponParameters()
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
}
@@ -282,7 +284,7 @@ public abstract class VideoPlayer extends BasePlayer
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
for (int i = 0; i < availableStreams.size(); i++) {
VideoStream videoStream = availableStreams.get(i);
final VideoStream videoStream = availableStreams.get(i);
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat
.getNameById(videoStream.getFormatId()) + " " + videoStream.resolution);
}
@@ -314,7 +316,7 @@ public abstract class VideoPlayer extends BasePlayer
}
captionPopupMenu.getMenu().removeGroup(captionPopupMenuGroupId);
String userPreferredLanguage = PreferenceManager.getDefaultSharedPreferences(context)
final String userPreferredLanguage = PreferenceManager.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.caption_user_set_key), null);
/*
* only search for autogenerated cc as fallback
@@ -326,7 +328,7 @@ public abstract class VideoPlayer extends BasePlayer
&& !userPreferredLanguage.contains("(");
// Add option for turning off caption
MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
final MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
0, Menu.NONE, R.string.caption_none);
captionOffItem.setOnMenuItemClickListener(menuItem -> {
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
@@ -342,7 +344,7 @@ public abstract class VideoPlayer extends BasePlayer
// Add all available captions
for (int i = 0; i < availableLanguages.size(); i++) {
final String captionLanguage = availableLanguages.get(i);
MenuItem captionItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
final MenuItem captionItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
i + 1, Menu.NONE, captionLanguage);
captionItem.setOnMenuItemClickListener(menuItem -> {
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
@@ -358,11 +360,11 @@ public abstract class VideoPlayer extends BasePlayer
return true;
});
// apply caption language from previous user preference
if (userPreferredLanguage != null && (captionLanguage.equals(userPreferredLanguage)
|| searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage)
|| userPreferredLanguage.contains("(") && captionLanguage.startsWith(
userPreferredLanguage
.substring(0, userPreferredLanguage.indexOf('('))))) {
if (userPreferredLanguage != null
&& (captionLanguage.equals(userPreferredLanguage)
|| (searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage))
|| (userPreferredLanguage.contains("(") && captionLanguage.startsWith(
userPreferredLanguage.substring(0, userPreferredLanguage.indexOf('(')))))) {
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
if (textRendererIndex != RENDERER_UNAVAILABLE) {
trackSelector.setPreferredTextLanguage(captionLanguage);
@@ -459,11 +461,8 @@ public abstract class VideoPlayer extends BasePlayer
animateView(controlsRoot, false, DEFAULT_CONTROLS_DURATION);
playbackSeekBar.setEnabled(false);
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-,
// so sets the color again
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
}
playbackSeekBar.getThumb()
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
loadingPanel.setBackgroundColor(Color.BLACK);
animateView(loadingPanel, true, 0);
@@ -479,11 +478,8 @@ public abstract class VideoPlayer extends BasePlayer
showAndAnimateControl(-1, true);
playbackSeekBar.setEnabled(true);
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-,
// so sets the color again
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
}
playbackSeekBar.getThumb()
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
loadingPanel.setVisibility(View.GONE);
@@ -520,7 +516,6 @@ public abstract class VideoPlayer extends BasePlayer
super.onCompleted();
showControls(500);
animateView(endScreen, true, 800);
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
loadingPanel.setVisibility(View.GONE);
@@ -555,7 +550,7 @@ public abstract class VideoPlayer extends BasePlayer
+ "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
+ "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
}
aspectRatioFrameLayout.setAspectRatio(((float) width) / height);
getSurfaceView().setAspectRatio(((float) width) / height);
}
@Override
@@ -583,7 +578,7 @@ public abstract class VideoPlayer extends BasePlayer
.getTrackGroups(textRenderer);
// Extract all loaded languages
List<String> availableLanguages = new ArrayList<>(textTracks.length);
final List<String> availableLanguages = new ArrayList<>(textTracks.length);
for (int i = 0; i < textTracks.length; i++) {
final TrackGroup textTrack = textTracks.get(i);
if (textTrack.length > 0 && textTrack.getFormat(0) != null) {
@@ -620,12 +615,6 @@ public abstract class VideoPlayer extends BasePlayer
playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
super.onPrepared(playWhenReady);
if (simpleExoPlayer.getCurrentPosition() != 0 && !isControlsVisible()) {
controlsVisibilityHandler.removeCallbacksAndMessages(null);
controlsVisibilityHandler
.postDelayed(this::showControlsThenHide, DEFAULT_CONTROLS_DURATION);
}
}
@Override
@@ -675,7 +664,7 @@ public abstract class VideoPlayer extends BasePlayer
}
}
protected void onFullScreenButtonClicked() {
protected void toggleFullscreen() {
changeState(STATE_BLOCKED);
}
@@ -739,8 +728,8 @@ public abstract class VideoPlayer extends BasePlayer
qualityTextView.setText(menuItem.getTitle());
return true;
} else if (playbackSpeedPopupMenuGroupId == menuItem.getGroupId()) {
int speedIndex = menuItem.getItemId();
float speed = PLAYBACK_SPEEDS[speedIndex];
final int speedIndex = menuItem.getItemId();
final float speed = PLAYBACK_SPEEDS[speedIndex];
setPlaybackSpeed(speed);
playbackSpeedTextView.setText(formatSpeed(speed));
@@ -769,7 +758,6 @@ public abstract class VideoPlayer extends BasePlayer
}
qualityPopupMenu.show();
isSomePopupMenuVisible = true;
showControls(DEFAULT_CONTROLS_DURATION);
final VideoStream videoStream = getSelectedVideoStream();
if (videoStream != null) {
@@ -787,7 +775,6 @@ public abstract class VideoPlayer extends BasePlayer
}
playbackSpeedPopupMenu.show();
isSomePopupMenuVisible = true;
showControls(DEFAULT_CONTROLS_DURATION);
}
private void onCaptionClicked() {
@@ -796,19 +783,18 @@ public abstract class VideoPlayer extends BasePlayer
}
captionPopupMenu.show();
isSomePopupMenuVisible = true;
showControls(DEFAULT_CONTROLS_DURATION);
}
private void onResizeClicked() {
if (getAspectRatioFrameLayout() != null) {
final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode();
void onResizeClicked() {
if (getSurfaceView() != null) {
final int currentResizeMode = getSurfaceView().getResizeMode();
final int newResizeMode = nextResizeMode(currentResizeMode);
setResizeMode(newResizeMode);
}
}
protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
getAspectRatioFrameLayout().setResizeMode(resizeMode);
getSurfaceView().setResizeMode(resizeMode);
getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode));
}
@@ -916,9 +902,9 @@ public abstract class VideoPlayer extends BasePlayer
if (drawableId == -1) {
if (controlAnimationView.getVisibility() == View.VISIBLE) {
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f),
PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1f)
PropertyValuesHolder.ofFloat(View.ALPHA, 1.0f, 0.0f),
PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1.0f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1.0f)
).setDuration(DEFAULT_CONTROLS_DURATION);
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -931,10 +917,10 @@ public abstract class VideoPlayer extends BasePlayer
return;
}
float scaleFrom = goneOnEnd ? 1f : 1f;
float scaleTo = goneOnEnd ? 1.8f : 1.4f;
float alphaFrom = goneOnEnd ? 1f : 0f;
float alphaTo = goneOnEnd ? 0f : 1f;
final float scaleFrom = goneOnEnd ? 1f : 1f;
final float scaleTo = goneOnEnd ? 1.8f : 1.4f;
final float alphaFrom = goneOnEnd ? 1f : 0f;
final float alphaTo = goneOnEnd ? 0f : 1f;
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
@@ -973,6 +959,7 @@ public abstract class VideoPlayer extends BasePlayer
? DEFAULT_CONTROLS_HIDE_TIME
: DPAD_CONTROLS_HIDE_TIME;
showHideShadow(true, DEFAULT_CONTROLS_DURATION, 0);
animateView(controlsRoot, true, DEFAULT_CONTROLS_DURATION, 0,
() -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime));
}
@@ -982,6 +969,7 @@ public abstract class VideoPlayer extends BasePlayer
Log.d(TAG, "showControls() called");
}
controlsVisibilityHandler.removeCallbacksAndMessages(null);
showHideShadow(true, duration, 0);
animateView(controlsRoot, true, duration);
}
@@ -1001,8 +989,10 @@ public abstract class VideoPlayer extends BasePlayer
Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
}
controlsVisibilityHandler.removeCallbacksAndMessages(null);
controlsVisibilityHandler.postDelayed(() ->
animateView(controlsRoot, false, duration), delay);
controlsVisibilityHandler.postDelayed(() -> {
showHideShadow(false, duration, 0);
animateView(controlsRoot, false, duration);
}, delay);
}
public void hideControlsAndButton(final long duration, final long delay, final View button) {
@@ -1020,6 +1010,14 @@ public abstract class VideoPlayer extends BasePlayer
animateView(controlsRoot, false, duration);
};
}
void showHideShadow(final boolean show, final long duration, final long delay) {
animateView(playerTopShadow, show, duration, delay, null);
animateView(playerBottomShadow, show, duration, delay, null);
}
public abstract void hideSystemUIIfNeeded();
/*//////////////////////////////////////////////////////////////////////////
// Getters and Setters
//////////////////////////////////////////////////////////////////////////*/
@@ -1033,11 +1031,7 @@ public abstract class VideoPlayer extends BasePlayer
this.resolver.setPlaybackQuality(quality);
}
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
return aspectRatioFrameLayout;
}
public SurfaceView getSurfaceView() {
public ExpandableSurfaceView getSurfaceView() {
return surfaceView;
}
@@ -1096,7 +1090,7 @@ public abstract class VideoPlayer extends BasePlayer
return playbackEndTime;
}
public View getTopControlsRoot() {
public LinearLayout getTopControlsRoot() {
return topControlsRoot;
}
@@ -1108,6 +1102,10 @@ public abstract class VideoPlayer extends BasePlayer
return qualityPopupMenu;
}
public TextView getPlaybackSpeedTextView() {
return playbackSpeedTextView;
}
public PopupMenu getPlaybackSpeedPopupMenu() {
return playbackSpeedPopupMenu;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
package org.schabi.newpipe.player.event;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import org.schabi.newpipe.R;
import java.util.Arrays;
import java.util.List;
public class CustomBottomSheetBehavior extends BottomSheetBehavior<FrameLayout> {
public CustomBottomSheetBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
Rect globalRect = new Rect();
private boolean skippingInterception = false;
private final List<Integer> skipInterceptionOfElements = Arrays.asList(
R.id.detail_content_root_layout, R.id.relatedStreamsLayout,
R.id.playQueuePanel, R.id.viewpager, R.id.bottomControls,
R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton);
@Override
public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent,
@NonNull final FrameLayout child,
final MotionEvent event) {
// Drop following when action ends
if (event.getAction() == MotionEvent.ACTION_CANCEL
|| event.getAction() == MotionEvent.ACTION_UP) {
skippingInterception = false;
}
// Found that user still swiping, continue following
if (skippingInterception || getState() == BottomSheetBehavior.STATE_SETTLING) {
return false;
}
// Don't need to do anything if bottomSheet isn't expanded
if (getState() == BottomSheetBehavior.STATE_EXPANDED
&& event.getAction() == MotionEvent.ACTION_DOWN) {
// Without overriding scrolling will not work when user touches these elements
for (final Integer element : skipInterceptionOfElements) {
final View view = child.findViewById(element);
if (view != null) {
final boolean visible = view.getGlobalVisibleRect(globalRect);
if (visible
&& globalRect.contains((int) event.getRawX(), (int) event.getRawY())) {
// Makes bottom part of the player draggable in portrait when
// playbackControlRoot is hidden
if (element == R.id.bottomControls
&& child.findViewById(R.id.playbackControlRoot)
.getVisibility() != View.VISIBLE) {
return super.onInterceptTouchEvent(parent, child, event);
}
skippingInterception = true;
return false;
}
}
}
}
return super.onInterceptTouchEvent(parent, child, event);
}
}

View File

@@ -0,0 +1,5 @@
package org.schabi.newpipe.player.event;
public interface OnKeyDownListener {
boolean onKeyDown(int keyCode);
}

View File

@@ -4,14 +4,13 @@ package org.schabi.newpipe.player.event;
import com.google.android.exoplayer2.PlaybackParameters;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.playqueue.PlayQueue;
public interface PlayerEventListener {
void onQueueUpdate(PlayQueue queue);
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled,
PlaybackParameters parameters);
void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
void onMetadataUpdate(StreamInfo info);
void onMetadataUpdate(StreamInfo info, PlayQueue queue);
void onServiceStopped();
}

View File

@@ -0,0 +1,629 @@
package org.schabi.newpipe.player.event;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ProgressBar;
import androidx.appcompat.content.res.AppCompatResources;
import org.schabi.newpipe.R;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.VideoPlayerImpl;
import org.schabi.newpipe.player.helper.PlayerHelper;
import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING;
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION;
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
import static org.schabi.newpipe.util.AnimationUtils.Type.SCALE_AND_ALPHA;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class PlayerGestureListener
extends GestureDetector.SimpleOnGestureListener
implements View.OnTouchListener {
private static final String TAG = ".PlayerGestureListener";
private static final boolean DEBUG = BasePlayer.DEBUG;
private final VideoPlayerImpl playerImpl;
private final MainPlayer service;
private int initialPopupX;
private int initialPopupY;
private boolean isMovingInMain;
private boolean isMovingInPopup;
private boolean isResizing;
private final int tossFlingVelocity;
private final boolean isVolumeGestureEnabled;
private final boolean isBrightnessGestureEnabled;
private final int maxVolume;
private static final int MOVEMENT_THRESHOLD = 40;
// [popup] initial coordinates and distance between fingers
private double initPointerDistance = -1;
private float initFirstPointerX = -1;
private float initFirstPointerY = -1;
private float initSecPointerX = -1;
private float initSecPointerY = -1;
public PlayerGestureListener(final VideoPlayerImpl playerImpl, final MainPlayer service) {
this.playerImpl = playerImpl;
this.service = service;
this.tossFlingVelocity = PlayerHelper.getTossFlingVelocity(service);
isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service);
isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(service);
maxVolume = playerImpl.getAudioReactor().getMaxVolume();
}
/*//////////////////////////////////////////////////////////////////////////
// Helpers
//////////////////////////////////////////////////////////////////////////*/
/*
* Main and popup players' gesture listeners is too different.
* So it will be better to have different implementations of them
* */
@Override
public boolean onDoubleTap(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = "
+ e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
}
if (playerImpl.popupPlayerSelected()) {
return onDoubleTapInPopup(e);
} else {
return onDoubleTapInMain(e);
}
}
@Override
public boolean onSingleTapConfirmed(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onSingleTapConfirmedInPopup(e);
} else {
return onSingleTapConfirmedInMain(e);
}
}
@Override
public boolean onDown(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onDown() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onDownInPopup(e);
} else {
return true;
}
}
@Override
public void onLongPress(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
onLongPressInPopup(e);
}
}
@Override
public boolean onScroll(final MotionEvent initialEvent, final MotionEvent movingEvent,
final float distanceX, final float distanceY) {
if (playerImpl.popupPlayerSelected()) {
return onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY);
} else {
return onScrollInMain(initialEvent, movingEvent, distanceX, distanceY);
}
}
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2,
final float velocityX, final float velocityY) {
if (DEBUG) {
Log.d(TAG, "onFling() called with velocity: dX=["
+ velocityX + "], dY=[" + velocityY + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onFlingInPopup(e1, e2, velocityX, velocityY);
} else {
return true;
}
}
@Override
public boolean onTouch(final View v, final MotionEvent event) {
/*if (DEBUG && false) {
Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]");
}*/
if (playerImpl.popupPlayerSelected()) {
return onTouchInPopup(v, event);
} else {
return onTouchInMain(v, event);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Main player listener
//////////////////////////////////////////////////////////////////////////*/
private boolean onDoubleTapInMain(final MotionEvent e) {
if (e.getX() > playerImpl.getRootView().getWidth() * 2.0 / 3.0) {
playerImpl.onFastForward();
} else if (e.getX() < playerImpl.getRootView().getWidth() / 3.0) {
playerImpl.onFastRewind();
} else {
playerImpl.getPlayPauseButton().performClick();
}
return true;
}
private boolean onSingleTapConfirmedInMain(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
}
if (playerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED) {
return true;
}
if (playerImpl.isControlsVisible()) {
playerImpl.hideControls(150, 0);
} else {
if (playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
playerImpl.showControls(0);
} else {
playerImpl.showControlsThenHide();
}
}
return true;
}
private boolean onScrollInMain(final MotionEvent initialEvent, final MotionEvent movingEvent,
final float distanceX, final float distanceY) {
if ((!isVolumeGestureEnabled && !isBrightnessGestureEnabled)
|| !playerImpl.isFullscreen()) {
return false;
}
final boolean isTouchingStatusBar = initialEvent.getY() < getStatusBarHeight(service);
final boolean isTouchingNavigationBar = initialEvent.getY()
> playerImpl.getRootView().getHeight() - getNavigationBarHeight(service);
if (isTouchingStatusBar || isTouchingNavigationBar) {
return false;
}
/*if (DEBUG && false) Log.d(TAG, "onScrollInMain = " +
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" +
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]");*/
final boolean insideThreshold =
Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD;
if (!isMovingInMain && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY))
|| playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
return false;
}
isMovingInMain = true;
final boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
final boolean acceptVolumeArea = acceptAnyArea
|| initialEvent.getX() > playerImpl.getRootView().getWidth() / 2.0;
if (isVolumeGestureEnabled && acceptVolumeArea) {
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) playerImpl
.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
final int currentVolume = (int) (maxVolume * currentProgressPercent);
playerImpl.getAudioReactor().setVolume(currentVolume);
if (DEBUG) {
Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
}
playerImpl.getVolumeImageView().setImageDrawable(
AppCompatResources.getDrawable(service, currentProgressPercent <= 0
? R.drawable.ic_volume_off_white_24dp
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_24dp
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_24dp
: R.drawable.ic_volume_up_white_24dp)
);
if (playerImpl.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
}
} else {
final Activity parent = playerImpl.getParentActivity();
if (parent == null) {
return true;
}
final Window window = parent.getWindow();
final WindowManager.LayoutParams layoutParams = window.getAttributes();
final ProgressBar bar = playerImpl.getBrightnessProgressBar();
final float oldBrightness = layoutParams.screenBrightness;
bar.setProgress((int) (bar.getMax() * Math.max(0, Math.min(1, oldBrightness))));
bar.incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) bar.getProgress() / bar.getMax();
layoutParams.screenBrightness = currentProgressPercent;
window.setAttributes(layoutParams);
// Save current brightness level
PlayerHelper.setScreenBrightness(parent, currentProgressPercent);
if (DEBUG) {
Log.d(TAG, "onScroll().brightnessControl, "
+ "currentBrightness = " + currentProgressPercent);
}
playerImpl.getBrightnessImageView().setImageDrawable(
AppCompatResources.getDrawable(service,
currentProgressPercent < 0.25
? R.drawable.ic_brightness_low_white_24dp
: currentProgressPercent < 0.75
? R.drawable.ic_brightness_medium_white_24dp
: R.drawable.ic_brightness_high_white_24dp)
);
if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
}
}
return true;
}
private void onScrollEndInMain() {
if (DEBUG) {
Log.d(TAG, "onScrollEnd() called");
}
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
}
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
}
private boolean onTouchInMain(final View v, final MotionEvent event) {
playerImpl.getGestureDetector().onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP && isMovingInMain) {
isMovingInMain = false;
onScrollEndInMain();
}
// This hack allows to stop receiving touch events on appbar
// while touching video player's view
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
v.getParent().requestDisallowInterceptTouchEvent(playerImpl.isFullscreen());
return true;
case MotionEvent.ACTION_UP:
v.getParent().requestDisallowInterceptTouchEvent(false);
return false;
default:
return true;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Popup player listener
//////////////////////////////////////////////////////////////////////////*/
private boolean onDoubleTapInPopup(final MotionEvent e) {
if (playerImpl == null || !playerImpl.isPlaying()) {
return false;
}
playerImpl.hideControls(0, 0);
if (e.getX() > playerImpl.getPopupWidth() / 2) {
playerImpl.onFastForward();
} else {
playerImpl.onFastRewind();
}
return true;
}
private boolean onSingleTapConfirmedInPopup(final MotionEvent e) {
if (playerImpl == null || playerImpl.getPlayer() == null) {
return false;
}
if (playerImpl.isControlsVisible()) {
playerImpl.hideControls(100, 100);
} else {
playerImpl.getPlayPauseButton().requestFocus();
playerImpl.showControlsThenHide();
}
return true;
}
private boolean onDownInPopup(final MotionEvent e) {
// Fix popup position when the user touch it, it may have the wrong one
// because the soft input is visible (the draggable area is currently resized).
playerImpl.updateScreenSize();
playerImpl.checkPopupPositionBounds();
initialPopupX = playerImpl.getPopupLayoutParams().x;
initialPopupY = playerImpl.getPopupLayoutParams().y;
playerImpl.setPopupWidth(playerImpl.getPopupLayoutParams().width);
playerImpl.setPopupHeight(playerImpl.getPopupLayoutParams().height);
return super.onDown(e);
}
private void onLongPressInPopup(final MotionEvent e) {
playerImpl.updateScreenSize();
playerImpl.checkPopupPositionBounds();
playerImpl.updatePopupSize((int) playerImpl.getScreenWidth(), -1);
}
private boolean onScrollInPopup(final MotionEvent initialEvent,
final MotionEvent movingEvent,
final float distanceX,
final float distanceY) {
if (isResizing || playerImpl == null) {
return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
}
if (!isMovingInPopup) {
animateView(playerImpl.getCloseOverlayButton(), true, 200);
}
isMovingInPopup = true;
final float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX());
float posX = (int) (initialPopupX + diffX);
final float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY());
float posY = (int) (initialPopupY + diffY);
if (posX > (playerImpl.getScreenWidth() - playerImpl.getPopupWidth())) {
posX = (int) (playerImpl.getScreenWidth() - playerImpl.getPopupWidth());
} else if (posX < 0) {
posX = 0;
}
if (posY > (playerImpl.getScreenHeight() - playerImpl.getPopupHeight())) {
posY = (int) (playerImpl.getScreenHeight() - playerImpl.getPopupHeight());
} else if (posY < 0) {
posY = 0;
}
playerImpl.getPopupLayoutParams().x = (int) posX;
playerImpl.getPopupLayoutParams().y = (int) posY;
final View closingOverlayView = playerImpl.getClosingOverlayView();
if (playerImpl.isInsideClosingRadius(movingEvent)) {
if (closingOverlayView.getVisibility() == View.GONE) {
animateView(closingOverlayView, true, 250);
}
} else {
if (closingOverlayView.getVisibility() == View.VISIBLE) {
animateView(closingOverlayView, false, 0);
}
}
// if (DEBUG) {
// Log.d(TAG, "onScrollInPopup = "
// + "e1.getRaw = [" + initialEvent.getRawX() + ", "
// + initialEvent.getRawY() + "], "
// + "e1.getX,Y = [" + initialEvent.getX() + ", "
// + initialEvent.getY() + "], "
// + "e2.getRaw = [" + movingEvent.getRawX() + ", "
// + movingEvent.getRawY() + "], "
// + "e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "], "
// + "distanceX,Y = [" + distanceX + ", " + distanceY + "], "
// + "posX,Y = [" + posX + ", " + posY + "], "
// + "popupW,H = [" + popupWidth + " x " + popupHeight + "]");
// }
playerImpl.windowManager
.updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
return true;
}
private void onScrollEndInPopup(final MotionEvent event) {
if (playerImpl == null) {
return;
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
if (playerImpl.isInsideClosingRadius(event)) {
playerImpl.closePopup();
} else {
animateView(playerImpl.getClosingOverlayView(), false, 0);
if (!playerImpl.isPopupClosing) {
animateView(playerImpl.getCloseOverlayButton(), false, 200);
}
}
}
private boolean onFlingInPopup(final MotionEvent e1,
final MotionEvent e2,
final float velocityX,
final float velocityY) {
if (playerImpl == null) {
return false;
}
final float absVelocityX = Math.abs(velocityX);
final float absVelocityY = Math.abs(velocityY);
if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
if (absVelocityX > tossFlingVelocity) {
playerImpl.getPopupLayoutParams().x = (int) velocityX;
}
if (absVelocityY > tossFlingVelocity) {
playerImpl.getPopupLayoutParams().y = (int) velocityY;
}
playerImpl.checkPopupPositionBounds();
playerImpl.windowManager
.updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
return true;
}
return false;
}
private boolean onTouchInPopup(final View v, final MotionEvent event) {
if (playerImpl == null) {
return false;
}
playerImpl.getGestureDetector().onTouchEvent(event);
if (event.getPointerCount() == 2 && !isMovingInPopup && !isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
}
playerImpl.showAndAnimateControl(-1, true);
playerImpl.getLoadingPanel().setVisibility(View.GONE);
playerImpl.hideControls(0, 0);
animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
animateView(playerImpl.getResizingIndicator(), true, 200, 0);
//record coordinates of fingers
initFirstPointerX = event.getX(0);
initFirstPointerY = event.getY(0);
initSecPointerX = event.getX(1);
initSecPointerY = event.getY(1);
//record distance between fingers
initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX,
initFirstPointerY - initSecPointerY);
isResizing = true;
}
if (event.getAction() == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], "
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
}
return handleMultiDrag(event);
}
if (event.getAction() == MotionEvent.ACTION_UP) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], "
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
}
if (isMovingInPopup) {
isMovingInPopup = false;
onScrollEndInPopup(event);
}
if (isResizing) {
isResizing = false;
initPointerDistance = -1;
initFirstPointerX = -1;
initFirstPointerY = -1;
initSecPointerX = -1;
initSecPointerY = -1;
animateView(playerImpl.getResizingIndicator(), false, 100, 0);
playerImpl.changeState(playerImpl.getCurrentState());
}
if (!playerImpl.isPopupClosing) {
playerImpl.savePositionAndSize();
}
}
v.performClick();
return true;
}
private boolean handleMultiDrag(final MotionEvent event) {
if (initPointerDistance != -1 && event.getPointerCount() == 2) {
// get the movements of the fingers
final double firstPointerMove = Math.hypot(event.getX(0) - initFirstPointerX,
event.getY(0) - initFirstPointerY);
final double secPointerMove = Math.hypot(event.getX(1) - initSecPointerX,
event.getY(1) - initSecPointerY);
// minimum threshold beyond which pinch gesture will work
final int minimumMove = ViewConfiguration.get(service).getScaledTouchSlop();
if (Math.max(firstPointerMove, secPointerMove) > minimumMove) {
// calculate current distance between the pointers
final double currentPointerDistance =
Math.hypot(event.getX(0) - event.getX(1),
event.getY(0) - event.getY(1));
final double popupWidth = playerImpl.getPopupWidth();
// change co-ordinates of popup so the center stays at the same position
final double newWidth = (popupWidth * currentPointerDistance / initPointerDistance);
initPointerDistance = currentPointerDistance;
playerImpl.getPopupLayoutParams().x += (popupWidth - newWidth) / 2;
playerImpl.checkPopupPositionBounds();
playerImpl.updateScreenSize();
playerImpl.updatePopupSize(
(int) Math.min(playerImpl.getScreenWidth(), newWidth),
-1);
return true;
}
}
return false;
}
/*
* Utils
* */
private int getNavigationBarHeight(final Context context) {
final int resId = context.getResources()
.getIdentifier("navigation_bar_height", "dimen", "android");
if (resId > 0) {
return context.getResources().getDimensionPixelSize(resId);
}
return 0;
}
private int getStatusBarHeight(final Context context) {
final int resId = context.getResources()
.getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0) {
return context.getResources().getDimensionPixelSize(resId);
}
return 0;
}
}

View File

@@ -0,0 +1,15 @@
package org.schabi.newpipe.player.event;
import com.google.android.exoplayer2.ExoPlaybackException;
public interface PlayerServiceEventListener extends PlayerEventListener {
void onFullscreenStateChanged(boolean fullscreen);
void onScreenRotationButtonClicked();
void onMoreOptionsLongClicked();
void onPlayerError(ExoPlaybackException error);
void hideSystemUiIfNeeded();
}

View File

@@ -0,0 +1,11 @@
package org.schabi.newpipe.player.event;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.VideoPlayerImpl;
public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener {
void onServiceConnected(VideoPlayerImpl player,
MainPlayer playerService,
boolean playAfterConnect);
void onServiceDisconnected();
}

View File

@@ -114,7 +114,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
private void onAudioFocusGain() {
Log.d(TAG, "onAudioFocusGain() called");
player.setVolume(DUCK_AUDIO_TO);
animateAudio(DUCK_AUDIO_TO, 1f);
animateAudio(DUCK_AUDIO_TO, 1.0f);
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
player.setPlayWhenReady(true);
@@ -133,7 +133,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
}
private void animateAudio(final float from, final float to) {
ValueAnimator valueAnimator = new ValueAnimator();
final ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setFloatValues(from, to);
valueAnimator.setDuration(AudioReactor.DUCK_DURATION);
valueAnimator.addListener(new AnimatorListenerAdapter() {

View File

@@ -80,13 +80,13 @@ import java.io.File;
}
try {
for (File file : cacheDir.listFiles()) {
for (final File file : cacheDir.listFiles()) {
final String filePath = file.getAbsolutePath();
final boolean deleteSuccessful = file.delete();
Log.d(TAG, "tryDeleteCacheFiles: " + filePath + " deleted = " + deleteSuccessful);
}
} catch (Exception ignored) {
} catch (final Exception ignored) {
Log.e(TAG, "Failed to delete file.", ignored);
}
}

View File

@@ -13,6 +13,7 @@ public class LoadController implements LoadControl {
private final long initialPlaybackBufferUs;
private final LoadControl internalLoadControl;
private boolean preloadingEnabled = true;
/*//////////////////////////////////////////////////////////////////////////
// Default Load Control
@@ -29,7 +30,7 @@ public class LoadController implements LoadControl {
final int optimalPlaybackBufferMs) {
this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000;
DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs,
initialPlaybackBufferMs, initialPlaybackBufferMs);
internalLoadControl = builder.createDefaultLoadControl();
@@ -41,6 +42,7 @@ public class LoadController implements LoadControl {
@Override
public void onPrepared() {
preloadingEnabled = true;
internalLoadControl.onPrepared();
}
@@ -52,11 +54,13 @@ public class LoadController implements LoadControl {
@Override
public void onStopped() {
preloadingEnabled = true;
internalLoadControl.onStopped();
}
@Override
public void onReleased() {
preloadingEnabled = true;
internalLoadControl.onReleased();
}
@@ -78,6 +82,9 @@ public class LoadController implements LoadControl {
@Override
public boolean shouldContinueLoading(final long bufferedDurationUs,
final float playbackSpeed) {
if (!preloadingEnabled) {
return false;
}
return internalLoadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
}
@@ -90,4 +97,8 @@ public class LoadController implements LoadControl {
.shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering);
return isInitialPlaybackBufferFilled || isInternalStartingPlayback;
}
public void disablePreloadingOfCurrentTrack() {
preloadingEnabled = false;
}
}

View File

@@ -3,44 +3,58 @@ package org.schabi.newpipe.player.helper;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.MediaMetadata;
import android.os.Build;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.media.app.NotificationCompat.MediaStyle;
import androidx.media.session.MediaButtonReceiver;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
import org.schabi.newpipe.player.mediasession.PlayQueueNavigator;
import org.schabi.newpipe.player.mediasession.PlayQueuePlaybackController;
public class MediaSessionManager {
private static final String TAG = "MediaSessionManager";
private static final String TAG = MediaSessionManager.class.getSimpleName();
public static final boolean DEBUG = MainActivity.DEBUG;
@NonNull
private final MediaSessionCompat mediaSession;
@NonNull
private final MediaSessionConnector sessionConnector;
private int lastAlbumArtHashCode;
public MediaSessionManager(@NonNull final Context context,
@NonNull final Player player,
@NonNull final MediaSessionCallback callback) {
this.mediaSession = new MediaSessionCompat(context, TAG);
this.mediaSession.setActive(true);
mediaSession = new MediaSessionCompat(context, TAG);
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setActive(true);
this.sessionConnector = new MediaSessionConnector(mediaSession);
this.sessionConnector.setControlDispatcher(new PlayQueuePlaybackController(callback));
this.sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback));
this.sessionConnector.setPlayer(player);
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_NONE, -1, 1)
.setActions(PlaybackStateCompat.ACTION_SEEK_TO
| PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PAUSE // was play and pause now play/pause
| PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
| PlaybackStateCompat.ACTION_SET_REPEAT_MODE
| PlaybackStateCompat.ACTION_STOP)
.build());
sessionConnector = new MediaSessionConnector(mediaSession);
sessionConnector.setControlDispatcher(new PlayQueuePlaybackController(callback));
sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback));
sessionConnector.setPlayer(player);
}
@Nullable
@@ -49,46 +63,78 @@ public class MediaSessionManager {
return MediaButtonReceiver.handleIntent(mediaSession, intent);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void setLockScreenArt(final NotificationCompat.Builder builder,
@Nullable final Bitmap thumbnailBitmap) {
if (thumbnailBitmap == null || !mediaSession.isActive()) {
public MediaSessionCompat.Token getSessionToken() {
return mediaSession.getSessionToken();
}
public void setMetadata(final String title,
final String artist,
final Bitmap albumArt,
final long duration) {
if (albumArt == null || !mediaSession.isActive()) {
return;
}
mediaSession.setMetadata(
new MediaMetadataCompat.Builder()
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, thumbnailBitmap)
.build()
);
if (DEBUG) {
if (getMetadataAlbumArt() == null) {
Log.d(TAG, "N_getMetadataAlbumArt: thumb == null");
}
if (getMetadataTitle() == null) {
Log.d(TAG, "N_getMetadataTitle: title == null");
}
if (getMetadataArtist() == null) {
Log.d(TAG, "N_getMetadataArtist: artist == null");
}
if (getMetadataDuration() <= 1) {
Log.d(TAG, "N_getMetadataDuration: duration <= 1; " + getMetadataDuration());
}
}
MediaStyle mediaStyle = new MediaStyle()
.setMediaSession(mediaSession.getSessionToken());
if (getMetadataAlbumArt() == null || getMetadataTitle() == null
|| getMetadataArtist() == null || getMetadataDuration() <= 1
|| albumArt.hashCode() != lastAlbumArtHashCode) {
if (DEBUG) {
Log.d(TAG, "setMetadata: N_Metadata update: t: " + title + " a: " + artist
+ " thumb: " + albumArt.hashCode() + " d: " + duration);
}
builder.setStyle(mediaStyle);
mediaSession.setMetadata(new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt)
.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArt)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration).build());
lastAlbumArtHashCode = albumArt.hashCode();
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void clearLockScreenArt(final NotificationCompat.Builder builder) {
mediaSession.setMetadata(
new MediaMetadataCompat.Builder()
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, null)
.build()
);
private Bitmap getMetadataAlbumArt() {
return mediaSession.getController().getMetadata()
.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART);
}
MediaStyle mediaStyle = new MediaStyle()
.setMediaSession(mediaSession.getSessionToken());
private String getMetadataTitle() {
return mediaSession.getController().getMetadata()
.getString(MediaMetadataCompat.METADATA_KEY_TITLE);
}
builder.setStyle(mediaStyle);
private String getMetadataArtist() {
return mediaSession.getController().getMetadata()
.getString(MediaMetadataCompat.METADATA_KEY_ARTIST);
}
private long getMetadataDuration() {
return mediaSession.getController().getMetadata()
.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
}
/**
* Should be called on player destruction to prevent leakage.
*/
public void dispose() {
this.sessionConnector.setPlayer(null);
this.sessionConnector.setQueueNavigator(null);
this.mediaSession.setActive(false);
this.mediaSession.release();
sessionConnector.setPlayer(null);
sessionConnector.setQueueNavigator(null);
mediaSession.setActive(false);
mediaSession.release();
}
}

View File

@@ -3,7 +3,7 @@ package org.schabi.newpipe.player.helper;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
@@ -92,8 +92,10 @@ public class PlaybackParameterDialog extends DialogFragment {
public static PlaybackParameterDialog newInstance(final double playbackTempo,
final double playbackPitch,
final boolean playbackSkipSilence) {
PlaybackParameterDialog dialog = new PlaybackParameterDialog();
final boolean playbackSkipSilence,
final Callback callback) {
final PlaybackParameterDialog dialog = new PlaybackParameterDialog();
dialog.callback = callback;
dialog.initialTempo = playbackTempo;
dialog.initialPitch = playbackPitch;
@@ -111,9 +113,9 @@ public class PlaybackParameterDialog extends DialogFragment {
@Override
public void onAttach(final Context context) {
super.onAttach(context);
if (context != null && context instanceof Callback) {
if (context instanceof Callback) {
callback = (Callback) context;
} else {
} else if (callback == null) {
dismiss();
}
}
@@ -185,8 +187,8 @@ public class PlaybackParameterDialog extends DialogFragment {
private void setupTempoControl(@NonNull final View rootView) {
tempoSlider = rootView.findViewById(R.id.tempoSeekbar);
TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
final TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
final TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText);
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
@@ -210,8 +212,8 @@ public class PlaybackParameterDialog extends DialogFragment {
private void setupPitchControl(@NonNull final View rootView) {
pitchSlider = rootView.findViewById(R.id.pitchSeekbar);
TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
final TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
final TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText);
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
@@ -237,12 +239,13 @@ public class PlaybackParameterDialog extends DialogFragment {
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
if (unhookingCheckbox != null) {
// restore whether pitch and tempo are unhooked or not
unhookingCheckbox.setChecked(PreferenceManager.getDefaultSharedPreferences(getContext())
unhookingCheckbox.setChecked(PreferenceManager
.getDefaultSharedPreferences(requireContext())
.getBoolean(getString(R.string.playback_unhook_key), true));
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
// save whether pitch and tempo are unhooked or not
PreferenceManager.getDefaultSharedPreferences(getContext())
PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit()
.putBoolean(getString(R.string.playback_unhook_key), isChecked)
.apply();
@@ -267,12 +270,12 @@ public class PlaybackParameterDialog extends DialogFragment {
}
private void setupStepSizeSelector(@NonNull final View rootView) {
TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
TextView stepSizeTwentyFivePercentText = rootView
final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
final TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
final TextView stepSizeTwentyFivePercentText = rootView
.findViewById(R.id.stepSizeTwentyFivePercent);
TextView stepSizeOneHundredPercentText = rootView
final TextView stepSizeOneHundredPercentText = rootView
.findViewById(R.id.stepSizeOneHundredPercent);
if (stepSizeOnePercentText != null) {

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