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

Compare commits

..

232 Commits

Author SHA1 Message Date
Mohammed Anas
0ae04b8ead Merge pull request #7291 from TeamNewPipe/release/0.21.13
Release/0.21.13
2021-10-21 22:11:54 +03:00
TobiGr
5d59025b3c Update changelog 2021-10-21 20:30:10 +02:00
TobiGr
768bb0bbcd Start service for update checks in onPastCreate() 2021-10-20 23:55:18 +02:00
Stypox
ac071b383f Revert part of #6872 and fix playback resuming 2021-10-20 23:20:26 +02:00
TobiGr
b79ea7b51b NewPipe 0.21.13 (979) 2021-10-17 12:55:06 +02:00
TobiGr
28c72e7f63 Fix new version check still occassionally started in background 2021-10-17 12:55:06 +02:00
TobiGr
5fcc3b4dab [Player] Fix resuming playback
This was caused by #6872
2021-10-17 12:13:38 +02:00
Tobi
7eb13a9b93 Merge pull request #7232 from TeamNewPipe/release/0.21.12
Fix check for new Version and release 0.21.12
2021-10-12 19:15:19 +02:00
TobiGr
7c9896beaf Release NewPipe 0.21.12 (978) 2021-10-12 16:45:01 +02:00
TobiGr
54d3bff26d Move checking for new version to MainAcitvity
When the service is started from the Application class, the app might be still in the background. This is definetly not the case when MainActivity.onCreate() is called.

Fixes #7227
2021-10-12 16:45:01 +02:00
Tobi
e0ba9b3902 Merge pull request #7192 from TeamNewPipe/release/0.21.11
Release/0.21.11
2021-10-10 21:03:38 +02:00
TobiGr
f11b5ae7a1 Release NewPipep 0.21.11 (977) 2021-10-10 20:52:38 +02:00
Hosted Weblate
7baeb6eca7 Translated using Weblate (Indonesian)
Currently translated at 98.8% (614 of 621 strings)

Translated using Weblate (Persian)

Currently translated at 17.2% (10 of 58 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Ukrainian)

Currently translated at 79.3% (46 of 58 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (Polish)

Currently translated at 53.4% (31 of 58 strings)

Translated using Weblate (Hebrew)

Currently translated at 48.2% (28 of 58 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (621 of 621 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: MS-PC <MSPCtranslator@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: chr56 <chr0056@gmail.com>
Co-authored-by: zmni <zmni@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2021-10-10 20:51:58 +02:00
Tobi
4e8519a1b9 Merge pull request #7213 from Redirion/npefix
Fix crash on missing title when sharing livestream
2021-10-09 23:35:50 +02:00
Robin
e6fffc0d5b Fix crash on missing title when sharing livestream 2021-10-08 21:42:01 +02:00
XiangRongLin
4c5fc7fa7c Merge pull request #7196 from litetex/use-correct-tag-NavigationHelper
Use the correct TAG in NavigationHelper
2021-10-04 08:40:41 +02:00
litetex
b633108a4c Use the correct TAG in NavigationHelper 2021-10-03 22:04:34 +02:00
Tobi
3d42da5ff5 Merge pull request #6642 from magicbrothers/add-eduvid
Add Tubus (Invidious) and EduVid (PeerTube)
2021-10-02 18:32:00 +02:00
TobiGr
1b869199f4 Update extractor to 0.21.11 2021-10-02 18:25:26 +02:00
CloverBadland
f3cd2f6c9d Update README.es.md (#7188) 2021-10-02 15:56:12 +00:00
opusforlife2
2e3e7f9bf2 Drastically simplify Feature Request template (#7165)
Making the experience of filling it out as hassle-free as possible would ensure fewer users ignore/misread the template.
2021-10-02 12:17:14 +03:00
TobiGr
92327dd9e3 Add changelog for NewPIpe 0.21.11 (977) 2021-10-02 10:17:57 +02:00
Hosted Weblate
d40b432f46 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (French)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Korean)

Currently translated at 73.7% (458 of 621 strings)

Translated using Weblate (French)

Currently translated at 66.6% (38 of 57 strings)

Translated using Weblate (Ukrainian)

Currently translated at 78.9% (45 of 57 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (French)

Currently translated at 99.8% (620 of 621 strings)

Translated using Weblate (Ukrainian)

Currently translated at 78.9% (45 of 57 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.8% (620 of 621 strings)

Translated using Weblate (Hungarian)

Currently translated at 0.0% (0 of 57 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Croatian)

Currently translated at 97.7% (607 of 621 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (German)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Turkish)

Currently translated at 99.6% (619 of 621 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (621 of 621 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Andrij Mizyk <andmizyk@gmail.com>
Co-authored-by: Boros Zsombor <zsombor2626@gmail.com>
Co-authored-by: ButterflyOfFire <ButterflyOfFire@protonmail.com>
Co-authored-by: Eduardo Caron <eduardocaron10@gmail.com>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Krysa Czech <krysaczek@gmail.com>
Co-authored-by: Ldm Public <ldmpub@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: San Kang <devrivmt@gmail.com>
Co-authored-by: Sergio Varela <sergitroll9@gmail.com>
Co-authored-by: Sérgio Marques <smarquespt@gmail.com>
Co-authored-by: Terry Louwers <t.louwers@gmail.com>
Co-authored-by: ToldYouThat <itoldyouthat@protonmail.com>
Co-authored-by: bruh <quangtrung02hn16@gmail.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: nzgha <nzghafoss.ldxwe@slmail.me>
Co-authored-by: translator <yasinoc375@advew.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translation: NewPipe/Metadata
2021-10-02 09:50:04 +02:00
Tobi
5b3137093f Merge pull request #6824 from phigjm/player_queue_control_rearrangement
Swap seekbar position with player buttons in Queue screen
2021-10-02 09:47:25 +02:00
Tobi
4fc9f2e5fd Merge pull request #7176 from litetex/fix-progress-bar-end-not-synced
Fixed seekbar not completed after video end
2021-10-02 09:24:59 +02:00
Tobi
ce592f4baf Merge pull request #6980 from bravenewpipe/check-for-update-after-toggle-and-restart
resets the expire date for checking for updates
2021-10-02 09:07:05 +02:00
Tobi
2b3edcf2d1 Add a comment 2021-10-02 08:53:03 +02:00
ygz213
f165f97bd9 Update READMEs
* Change "Github" as "GitHub"
* Change translation of "Screenshots" with its more common translation (README.tr.md)
2021-10-02 08:38:57 +02:00
litetex
4ec572372e Merge pull request #7177 from tsiflimagas/minimized-player-buttons-clickable-area
Expand minimized player buttons clickable area
2021-10-01 18:05:59 +02:00
litetex
672eb34049 Merge pull request #7150 from TacoTheDank/groupieBump
Update Groupie library to 2.9.0
2021-09-30 21:26:58 +02:00
Mohammed Anas
a0b042091b Add YouTube shorts path prefix to intent filter (#7181)
* Add YouTube shorts path prefix to intent filter
2021-09-29 21:44:47 +02:00
Tobi
b753705a84 Merge pull request #7178 from Redirion/dsblmdtnnelrealtekatv
Disable media tunneling on RealtekATV
2021-09-28 07:53:11 +02:00
Robin
f48ff610a3 Merge pull request #7166 from litetex/various-fixes-for-mediasession-player
Various fixes related to MediasessionManager
2021-09-27 23:45:38 +02:00
Robin
93aed9f34c Merge pull request #7169 from laksh-21/fixSettingsImport
Fix Settings import
2021-09-27 23:42:54 +02:00
Robin
3cf94382e6 Disable media tunneling on RealtekATV 2021-09-27 13:21:04 +02:00
Laksh
f52cb3bbe0 Make requested changes 2021-09-27 01:45:33 +05:30
litetex
d45182cb5c Referenced issue in code 2021-09-26 20:58:49 +02:00
litetex
22847c6c92 Fixed progress bar not completing after video end 2021-09-26 20:50:24 +02:00
Kostas Giapis
a70c51b71c Remove margins from minimized player buttons 2021-09-26 18:26:40 +03:00
Kostas Giapis
02d417476e Increase minimized player buttons clickable area 2021-09-26 18:23:11 +03:00
Laksh
bc3139e5f9 Fix Settings import 2021-09-25 15:37:07 +05:30
litetex
c1f7b2653c Merge pull request #7152 from TacoTheDank/regenDrawables
Regenerate some drawables to their latest design
2021-09-25 10:33:21 +02:00
litetex
72dbb9441e Improved streamType check and documentation 2021-09-25 10:31:42 +02:00
litetex
bbc13756f3 Removed useless check for livestreams 2021-09-25 00:04:53 +02:00
litetex
ba0876b43b Reformatted MediaSessionManager code 2021-09-24 23:55:06 +02:00
litetex
c0d41661e8 Fixed performance problems with mediaSessionManager
* Built on top of Redirons changes
* MediaSessionManager-Metadata get's only update when metadata changes - not every second as it only changes when the metadata changes
* Reworked mediasessionmanager
2021-09-24 23:40:12 +02:00
Robin
b2e2551e33 Two fixes for setMetadata 2021-09-24 23:28:55 +02:00
Robin
ac371e6fb4 Merge pull request #7156 from TacoTheDank/removeDeprecatedPrefTheme
Remove deprecated preferenceTheme style attribute
2021-09-24 22:36:15 +02:00
TacoTheDank
a225ac5deb Remove deprecated preferenceTheme style attribute 2021-09-23 15:09:37 -04:00
TacoTheDank
920695f90a Regenerate some drawables to their latest design 2021-09-22 23:38:01 -04:00
TacoTheDank
49fc57eee9 Remove unused drawables 2021-09-22 21:54:30 -04:00
TacoTheDank
b61d44aaa6 Update Groupie to 2.9.0 2021-09-22 19:08:03 -04:00
Jhon Baron
f36fd2f7b2 Simple Codebase Improvement (#7132)
* feat: it was added a string variable to avoid replication one.

* Update LocalPlaylistManagerTest.kt

* Update LocalPlaylistManagerTest.kt

* Update LocalPlaylistManagerTest.kt

Co-authored-by: Jhon Camilo Baron Berdugo <jbaron@mutualser.org>
2021-09-22 09:17:53 +02:00
Stypox
7e26748dc4 Merge branch 'master' into dev 2021-09-19 22:37:00 +02:00
Tobi
ba6fdecbae Merge pull request #7063 from TeamNewPipe/release/0.21.10
Release 0.21.10
2021-09-19 20:59:54 +02:00
Hosted Weblate
f791e83380 Translated using Weblate (Lithuanian)
Currently translated at 10.5% (6 of 57 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (French)

Currently translated at 99.8% (620 of 621 strings)

Translated using Weblate (German)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Romanian)

Currently translated at 89.9% (557 of 619 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Japanese)

Currently translated at 99.8% (618 of 619 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Javanese)

Currently translated at 10.5% (6 of 57 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Javanese)

Currently translated at 9.8% (61 of 619 strings)

Translated using Weblate (Nepali)

Currently translated at 77.8% (482 of 619 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (619 of 619 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Ahmad Firdaus <rin.hikaru@gmail.com>
Co-authored-by: ButterflyOfFire <ButterflyOfFire@protonmail.com>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: GnuPGを使うべきだ <dieeeazpnnqbpddh@cock.email>
Co-authored-by: Gontzal Manuel Pujana Onaindia <thadahdenyse@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Marian Hanzel <marulinko@gmail.com>
Co-authored-by: MohammedSR Vevo <mohammednajmidin@gmail.com>
Co-authored-by: Prajwol Pradhan <076bei023.prajwol@pcampus.edu.np>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/jv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/lt/
Translation: NewPipe/Metadata
2021-09-19 20:35:19 +02:00
Hosted Weblate
dd7f914b8d Translated using Weblate (Lithuanian)
Currently translated at 10.5% (6 of 57 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (French)

Currently translated at 99.8% (620 of 621 strings)

Translated using Weblate (German)

Currently translated at 100.0% (621 of 621 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Romanian)

Currently translated at 89.9% (557 of 619 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Japanese)

Currently translated at 99.8% (618 of 619 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Javanese)

Currently translated at 10.5% (6 of 57 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Javanese)

Currently translated at 9.8% (61 of 619 strings)

Translated using Weblate (Nepali)

Currently translated at 77.8% (482 of 619 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (619 of 619 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Ahmad Firdaus <rin.hikaru@gmail.com>
Co-authored-by: ButterflyOfFire <ButterflyOfFire@protonmail.com>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: GnuPGを使うべきだ <dieeeazpnnqbpddh@cock.email>
Co-authored-by: Gontzal Manuel Pujana Onaindia <thadahdenyse@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Marian Hanzel <marulinko@gmail.com>
Co-authored-by: MohammedSR Vevo <mohammednajmidin@gmail.com>
Co-authored-by: Prajwol Pradhan <076bei023.prajwol@pcampus.edu.np>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/jv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/lt/
Translation: NewPipe/Metadata
2021-09-19 20:31:36 +02:00
Stypox
7667b2ce59 Fix restoring orientation in onBack 2021-09-19 19:09:11 +02:00
Stypox
8272b2508b Make all buttons in player have selectable item background borderless (#7042)
Except buttons with long text or text that changes on click
2021-09-18 11:29:15 +02:00
z3r0r4
70354eb73e Add play next to long press menu & refactor enqueue methods (#6872)
* added mvp play next button in long press menu; new intent handling, new long press dialog entry, new dialog functions, new strings

* changed line length for checkstyle pass

* cleaned comments, moved strings

* Update app/src/main/res/values/strings.xml

to make long press entry more descriptive

Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com>

* Update app/src/main/res/values/strings.xml

Co-authored-by: Stypox <stypox@pm.me>

* replace redundant nextOnVideoPlayer methods

Co-authored-by: Stypox <stypox@pm.me>

* add enqueueNextOnPlayer and enqueueOnPlayer without selectOnAppend and RESUME_PLAYBACK/ deprecate enqueueNextOn*Player and enqueueOn*Player methods
add getPlayerIntent, getPlayerEnqueueIntent and getPlayerEnqueueNextIntent without selectOnAppend and RESUME_PLAYBACK/ deprecate those with
add section comments

* removed deprecated methods
removed redundant methods

* removed deprecated methods
removed redundant methods

* replaced APPEND_ONLY, removed SELECT_ON_APPEND / replaced remaining enqueueOn*Player methods

* now works with playlists

* renamed dialog entry

* checking for >1 items in the queue using the PlayerHolder

* making enqueue*OnPlayer safe to call when no video is playing (defaulting to audio)

* corrected strings

* improve getQueueSize in PlayerHolder

* long press to enqueue only if queue isnt empty

* add Whitespace

Co-authored-by: Stypox <stypox@pm.me>

* clarify comments / add spaces

* PlayerType as parameter of the enqueueOnPlayer method
add Helper method

* using the helper function everywhere (except for the background and popup long-press actions (also on playlists, history, ...)), so basically nowhere
/ passing checkstyle

* assimilated the enqueue*OnPlayer methods

* removed redundant comment, variable

* simplify code line

Co-authored-by: Stypox <stypox@pm.me>

* move if

* replace workaround for isPlayerOpen()

Co-authored-by: Stypox <stypox@pm.me>

* replaced workarounds (getType), corrected static access with getInstance

* remove unused imports

* changed method call to original, new method doesnt exist yet.

* Use getter method instead of property access syntax.

* improve conditional for play next entry

Co-authored-by: Stypox <stypox@pm.me>

* show play next btn in feed fragment

Co-authored-by: Stypox <stypox@pm.me>

* add play next to local playlist and statistics fragment

Co-authored-by: Stypox <stypox@pm.me>

* formating

Co-authored-by: Stypox <stypox@pm.me>

* correcting logic

Co-authored-by: Stypox <stypox@pm.me>

* remove 2 year old unused string, formating

Co-authored-by: Stypox <stypox@pm.me>

* correct enqueue (next) conditionals, default to background if no player is open. Dont generally default to background play.

* remove player open checks from button long press enqueue actions

* improve log msg

* Rename next to enqueue_next

* Refactor kotlin

Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com>
Co-authored-by: Stypox <stypox@pm.me>
2021-09-18 11:22:49 +02:00
XiangRongLin
63083ac0c3 Merge pull request #7098 from XiangRongLin/6420_package_suffix
Allow passing in package suffix for release builds through CLI param
2021-09-18 11:10:42 +02:00
TobiGr
9346f9b0f3 Update extractor version to 0.21.10 2021-09-13 16:47:40 +02:00
TobiGr
605e5d265c Fix syntax 2021-09-13 14:02:08 +02:00
TobiGr
25456b15e7 Fix duplicate dashes in string resource 2021-09-13 14:01:52 +02:00
TobiGr
ebbe7ef944 Fix Chinese plurals 2021-09-13 14:01:17 +02:00
Hosted Weblate
60a272e70a Translated using Weblate (Spanish)
Currently translated at 56.1% (32 of 57 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.8% (618 of 619 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Japanese)

Currently translated at 99.5% (616 of 619 strings)

Translated using Weblate (Ukrainian)

Currently translated at 77.1% (44 of 57 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Galician)

Currently translated at 94.9% (588 of 619 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Slovak)

Currently translated at 96.2% (596 of 619 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (French)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (German)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (English)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Polish)

Currently translated at 52.6% (30 of 57 strings)

Translated using Weblate (Hebrew)

Currently translated at 47.3% (27 of 57 strings)

Translated using Weblate (Ukrainian)

Currently translated at 61.4% (35 of 57 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.6% (617 of 619 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (57 of 57 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Persian)

Currently translated at 15.7% (9 of 57 strings)

Translated using Weblate (Ukrainian)

Currently translated at 54.3% (31 of 57 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (619 of 619 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: AioiLight <info@aioilight.space>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jakub <online.reg1@pm.me>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: WB <web0nst@tuta.io>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bruh <quangtrung02hn16@gmail.com>
Co-authored-by: chr56 <chr0056@gmail.com>
Co-authored-by: nzgha <nzghafoss.ldxwe@slmail.me>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2021-09-13 14:01:12 +02:00
TobiGr
672fcb9ce3 Fix syntax 2021-09-13 13:54:32 +02:00
TobiGr
870d50ebcd Fix duplicate dashes in string resource 2021-09-13 13:52:58 +02:00
TobiGr
b62b3e91a0 Fix Chinese plurals 2021-09-13 13:52:33 +02:00
Hosted Weblate
b022d90303 Translated using Weblate (Spanish)
Currently translated at 56.1% (32 of 57 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.8% (618 of 619 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Japanese)

Currently translated at 99.5% (616 of 619 strings)

Translated using Weblate (Ukrainian)

Currently translated at 77.1% (44 of 57 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Galician)

Currently translated at 94.9% (588 of 619 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Slovak)

Currently translated at 96.2% (596 of 619 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (French)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (German)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (English)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Polish)

Currently translated at 52.6% (30 of 57 strings)

Translated using Weblate (Hebrew)

Currently translated at 47.3% (27 of 57 strings)

Translated using Weblate (Ukrainian)

Currently translated at 61.4% (35 of 57 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.6% (617 of 619 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (57 of 57 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Persian)

Currently translated at 15.7% (9 of 57 strings)

Translated using Weblate (Ukrainian)

Currently translated at 54.3% (31 of 57 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (619 of 619 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (619 of 619 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: AioiLight <info@aioilight.space>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jakub <online.reg1@pm.me>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: WB <web0nst@tuta.io>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bruh <quangtrung02hn16@gmail.com>
Co-authored-by: chr56 <chr0056@gmail.com>
Co-authored-by: nzgha <nzghafoss.ldxwe@slmail.me>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2021-09-13 13:29:04 +02:00
Tobi
02af529551 Merge pull request #6974 from litetex/update-android-build-tools-gradle
Update Android Gradle Plugin to version 7.0.2
2021-09-13 13:13:57 +02:00
XiangRongLin
dd9cc619ed Add documentation for the custom gradle parameters 2021-09-12 20:43:28 +02:00
XiangRongLin
75c9e959de Allow passing in package suffix for release builds through CLI param 2021-09-12 20:19:45 +02:00
litetex
fb8afec1bf Updated `com.android.tools.build:gradle to 7.0.2` 2021-09-12 14:54:40 +02:00
Tobi
a2887034a6 Merge pull request #7068 from litetex/fix-restart
Fixed restarting not working properly
2021-09-11 12:51:38 +02:00
Tobi
7eb5aa1bc5 Merge pull request #7056 from TeamNewPipe/fix/playOnPopup
Fix handling exception in `playOnPopup` and toggle description tab
2021-09-10 18:21:41 +02:00
Tobi
08ebd7d39a Merge pull request #7085 from litetex/fix-splash-screen-navbar-color
Fixed the navbar color for darkmode
2021-09-09 22:35:51 +02:00
litetex
9ea263f72e Fixed the navbar color for darkmode 2021-09-09 21:39:40 +02:00
Tobi
e4a2d2f3c1 Merge pull request #7071 from thefalsedev/cpufix-1
Change player progress bar update from 500 ms to 1 s
2021-09-09 13:37:44 +02:00
thefalsedev
892b4a15f6 Change player progress bar update from 500 ms to 1 s
Just like in the issue 7062, https://github.com/TeamNewPipe/NewPipe/issues/7062, this doesn't affect UI as it updates every one second anyway, but reduces very heavy android widget progress bar high cpu usage. With every 500s there is 6% cpu usage and with 1s only 4%. However further changes will have to be made to disable updating of player progress bar when screen is off to further reduce power consumption. With this, total power savings would be 20% in mAh consumption.
2021-09-07 00:04:05 +02:00
litetex
fda0a550fd Fixed the app restarting not working properly
* Using [``process-phoenix``](https://github.com/JakeWharton/ProcessPhoenix)
2021-09-06 20:47:44 +02:00
TobiGr
638825cdff Release NewPipe 0.21.10 (976) 2021-09-05 21:47:28 +02:00
TobiGr
6a1d81fcf3 Add changelog for NewPipe 0.21.10 (976) 2021-09-05 21:47:19 +02:00
TobiGr
8afd44a72f Update extractor version 2021-09-05 21:31:39 +02:00
Tobi
22c5135740 Merge pull request #7055 from sauravrao637/7048
Added night variant for splash_background.xml
2021-09-05 20:42:32 +02:00
TobiGr
4d51ebc37a Fix a few SonarLint warnings 2021-09-05 19:54:28 +02:00
TobiGr
433c6dc33b Fix OnErrorNotImplementedException in SearchFragment.initSuggestionObserver()
Hopefully also fix the cause of the original error.
2021-09-05 19:54:28 +02:00
TobiGr
ed4fdadd4d Fix OnErrorNotImplementedException in playOnPopup 2021-09-05 19:54:28 +02:00
TobiGr
298e96b821 Fix updating the wrong tabs when changing settings while running the minimized player in VideoDetailFragment
The comments tab was updated although the settings for the description tab were changed.
2021-09-04 22:36:47 +02:00
TobiGr
9006667b4d Merge remote-tracking branch 'origin/dev' into dev 2021-09-04 21:23:50 +02:00
Allan Nordhøy
abbf71982d Translated using Weblate (Norwegian Bokmål)
Currently translated at 97.0% (665 of 685 strings)
2021-09-04 20:57:35 +02:00
Joel A
57110717d3 Translated using Weblate (Swedish)
Currently translated at 98.8% (677 of 685 strings)
2021-09-04 20:57:35 +02:00
Yaron Shahrabani
c3b5444281 Translated using Weblate (Hebrew)
Currently translated at 100.0% (685 of 685 strings)
2021-09-04 20:57:35 +02:00
chr56
7a542975ca Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (685 of 685 strings)
2021-09-04 20:57:34 +02:00
Danial Behzadi
490aff5846 Translated using Weblate (Persian)
Currently translated at 100.0% (685 of 685 strings)
2021-09-04 20:57:34 +02:00
Agnieszka C
1dfc036ead Translated using Weblate (Polish)
Currently translated at 100.0% (685 of 685 strings)
2021-09-04 20:57:34 +02:00
Oğuz Ersen
360d6b998c Translated using Weblate (Turkish)
Currently translated at 100.0% (685 of 685 strings)
2021-09-04 20:57:34 +02:00
Ihor Hordiichuk
be7307cf39 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (685 of 685 strings)
2021-09-04 20:57:33 +02:00
bomzhellino
12096ab050 Translated using Weblate (Russian)
Currently translated at 100.0% (685 of 685 strings)
2021-09-04 20:57:33 +02:00
Ldm Public
225f23ce02 Translated using Weblate (French)
Currently translated at 100.0% (685 of 685 strings)
2021-09-04 20:57:33 +02:00
nautilusx
9c15ee7285 Translated using Weblate (German)
Currently translated at 100.0% (685 of 685 strings)
2021-09-04 20:57:32 +02:00
Tobi
8dd617fc6b Merge pull request #7043 from Stypox/optimize-resources
Remove unused resources
2021-09-04 20:57:07 +02:00
camo0112
ae8e72f34b added night variant for splash_background.xml 2021-09-04 15:55:31 +05:30
litetex
fc52a6e871 Merge branch 'dev' into update-android-build-tools-gradle 2021-09-03 23:18:16 +02:00
Hosted Weblate
722b47b86f Translated using Weblate (Persian)
Currently translated at 12.5% (7 of 56 strings)

Translated using Weblate (Spanish)

Currently translated at 55.3% (31 of 56 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Lithuanian)

Currently translated at 99.8% (682 of 683 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Arabic)

Currently translated at 99.7% (681 of 683 strings)

Translated using Weblate (Greek)

Currently translated at 99.8% (682 of 683 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Japanese)

Currently translated at 99.1% (677 of 683 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (French)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (French)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (German)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Galician)

Currently translated at 95.9% (655 of 683 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 97.0% (663 of 683 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Persian)

Currently translated at 10.7% (6 of 56 strings)

Translated using Weblate (French)

Currently translated at 66.0% (37 of 56 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 97.0% (663 of 683 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (French)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (German)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (French)

Currently translated at 100.0% (683 of 683 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: AioiLight <info@aioilight.space>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Andrij Mizyk <andmizyk@gmail.com>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Ldm Public <ldmpub@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Sergio Varela <sergitroll9@gmail.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: WB <web0nst@tuta.io>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bomzhellino <adm.bomzh@gmail.com>
Co-authored-by: bruh <quangtrung02hn16@gmail.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: nzgha <nzghafoss.ldxwe@slmail.me>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: translator <yasinoc375@advew.com>
Co-authored-by: zeritti <woodenmo@posteo.de>
Co-authored-by: Éfrit <efrit@posteo.net>
Co-authored-by: Óscar Fernández Díaz <oscfdezdz@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translation: NewPipe/Metadata
2021-09-03 15:59:03 +02:00
Stypox
3a09039b93 Remove unused resources 2021-09-02 21:13:54 +02:00
evermind
669a35bc78 check immediately for updates if user enables 'check for updates setting'
- Convert CheckForNewAppVersion to IntentService
- reset expire date to 0 after user enables check for updates setting
2021-09-02 07:20:35 +02:00
Tobi
81fa0c1558 Merge pull request #5459 from Stypox/fullscreen-autoplay
Add option to directly open fullscreen when the main player starts
2021-09-01 23:01:09 +02:00
Stypox
ed408b2094 Move fullscreen-related comments to javadocs 2021-09-01 20:13:27 +02:00
Stypox
3bc661f583 Fix null pointer exception in player initialization 2021-09-01 20:13:27 +02:00
Stypox
cf9b482be2 Completely close player when changing stream w/o autoplay 2021-09-01 20:13:27 +02:00
Stypox
1d935b46f9 Open fullscreen when switching from popup to main player 2021-09-01 20:13:24 +02:00
Stypox
520ac2e935 Fix bottom sheet state after automatic fullscreen 2021-09-01 20:12:14 +02:00
Stypox
c6316abbce Fix opening directly fullscreen on tablets 2021-09-01 20:12:09 +02:00
Stypox
2dfe837c35 Extract isLandscape and isInMultiWindow to DeviceUtils 2021-09-01 20:09:08 +02:00
Stypox
3c2ea7697c Add option to directly open fullscreen when the main player starts 2021-09-01 20:08:37 +02:00
Stypox
faa7a91764 Merge pull request #7004 from litetex/fix-showTextError-and-rework-ErrorPanel
Reworked the ErrorPanel
2021-08-31 18:26:51 +02:00
Robin
f629a4d206 Merge pull request #6993 from Redirion/closeaudioeffectsession
Close audio effect control session properly
2021-08-31 12:37:20 +02:00
Stypox
4b7c37e919 Merge pull request #6955 from ktprograms/queue-long-press-menu
Show popup menu when long pressing in play queue (Full screen player)
2021-08-31 12:22:59 +02:00
Stypox
a4c9732916 Merge pull request #6965 from ktprograms/indication-content-main-page
Add how to remove tab from main page text
2021-08-31 12:12:33 +02:00
Stypox
f8f2dfce4b Merge pull request #6882 from talanc/dev
Add support for CSV+ZIP subscriptions (Google Takeout)
2021-08-31 12:10:12 +02:00
Stypox
5284072b8d Improve mime type deduction on subscription import 2021-08-31 12:07:34 +02:00
talanc
e603dddc54 Added support for CSV+ZIP subscriptions
Updated import instructions string
2021-08-31 12:07:34 +02:00
Stypox
15691ba41a Merge pull request #7002 from litetex/gh-actions-use-integrated-cache-setup-java
Using integrated cache in ``actions/setup-java``
2021-08-31 12:01:26 +02:00
Stypox
a555aab3e7 Merge pull request #7024 from Stypox/string-fixes
Never use ``android.R.string``s; remove unused ``add`` string
2021-08-31 12:00:07 +02:00
Stypox
88f1c3a808 Merge pull request #6985 from litetex/set-seekbarjump-when-using-dpad-to-seek-duration-from-preferences
Set ``KeyProgressIncrement`` manually / Fix long seekbar jumps when using a DPad
2021-08-30 23:36:29 +02:00
Tobi
0e6668636d Merge pull request #6986 from litetex/fix-build-problems-update-kotlin
Fix build problems and updated kotlin
2021-08-30 21:20:45 +02:00
Stypox
d0f4d8b132 Remove unused string "add": "New mission" 2021-08-30 16:37:01 +02:00
Stypox
cfdcb92fa3 Always use our strings, not android ones 2021-08-30 16:37:01 +02:00
Stypox
039bd5d413 Rename string finish to ok, as its content was "OK" 2021-08-30 16:36:57 +02:00
Stypox
5ffba55b4a Merge pull request #6990 from CBSkarmory/dev
fix typo / reword part of bug report template
2021-08-30 15:51:47 +02:00
Robin
57ca281c80 Merge pull request #6634 from Isira-Seneviratne/Use_PackageInfoCompat
Use PackageInfoCompat.getSignatures().
2021-08-29 23:13:12 +02:00
Tobi
46f74b908a Merge pull request #7014 from litetex/fix-double-tapping-replay-button
Fixed double tapping the replay button
2021-08-29 18:43:28 +02:00
litetex
703f1550d8 Fixed double tapping the replay button 2021-08-29 17:53:09 +02:00
Robin
8bfd380b89 Merge pull request #6515 from Redirion/buffersharmonization
Use ExoPlayer default values for buffers
2021-08-29 17:44:43 +02:00
Agnieszka C
43e91ae4ae Added plural forms for download related strings (#6930)
Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2021-08-29 13:28:01 +02:00
Hosted Weblate
023a2c1d9c Translated using Weblate (German)
Currently translated at 53.5% (30 of 56 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Malay)

Currently translated at 67.8% (464 of 684 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Korean)

Currently translated at 75.5% (517 of 684 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Malay)

Currently translated at 67.3% (461 of 684 strings)

Translated using Weblate (Malay)

Currently translated at 67.3% (461 of 684 strings)

Translated using Weblate (Swedish)

Currently translated at 99.1% (678 of 684 strings)

Translated using Weblate (Japanese)

Currently translated at 99.5% (681 of 684 strings)

Translated using Weblate (Somali)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Malayalam)

Currently translated at 7.1% (4 of 56 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Malayalam)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Catalan)

Currently translated at 98.5% (674 of 684 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (French)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (German)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Lithuanian)

Currently translated at 10.7% (6 of 56 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (French)

Currently translated at 99.8% (683 of 684 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Vietnamese)

Currently translated at 39.2% (22 of 56 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Somali)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Finnish)

Currently translated at 12.5% (7 of 56 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: AioiLight <info@aioilight.space>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Bruno Guerreiro <american.jesus.pt@gmail.com>
Co-authored-by: Dayongdo <dayongdo@protonmail.ch>
Co-authored-by: Deleted User <noreply+23276@weblate.org>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Joel A <joeax910@student.liu.se>
Co-authored-by: Kaantaja <ufdbvgoljrjkrkyyub@ianvvn.com>
Co-authored-by: Karl Tammik <karltammik@protonmail.com>
Co-authored-by: Lavin Tom K Abraham <lavintom007@gmail.com>
Co-authored-by: Ldm Public <ldmpub@gmail.com>
Co-authored-by: Lim Jia Ming <jiaminglimjm@protonmail.com>
Co-authored-by: MohammedSR Vevo <mohammednajmidin@gmail.com>
Co-authored-by: Nadir Nour <dudethatwascool2@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Rehaz Feddit <rehafa8425@fxseller.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Terry Louwers <t.louwers@gmail.com>
Co-authored-by: Thien Bui <thien.bui.84436@gmail.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bruh <quangtrung02hn16@gmail.com>
Co-authored-by: chr56 <chr0056@gmail.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: tch69 <ifa26417@outlook.com.vn>
Co-authored-by: Éfrit <efrit@posteo.net>
Co-authored-by: Óscar Fernández Díaz <oscfdezdz@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/lt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ml/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translation: NewPipe/Metadata
2021-08-29 13:15:02 +02:00
litetex
d931d058d9 Reworked the ErrorPanel
* All element on the error panel are now hidden by default (expect for the ``errorTextView``) as they are only optional shown
  * Added a method to ensure the above
  * This deduplicates a lot of code
* Fixed format of some LoC
* Added new method: ``showAndSetErrorButtonAction``
* Fixed  ``showTextError``
* Named buttons more logically: ``errorButtonAction`` -> ``errorActionButton``
2021-08-28 17:05:12 +02:00
litetex
a825253b7f Using integrated cache in `actions/setup-java`
https://github.com/actions/setup-java#caching-gradle-dependencies
2021-08-28 15:22:04 +02:00
acti0
d9086300f3 Re-add sharing of the content name with the "Share" command (#6957)
The title of the content is re-added as the EXTRA_SUBJECT of the share intent.
2021-08-27 19:26:32 +02:00
litetex
f18a7c91ca Suppressed warning
There seems to be a bug in the kotlin plugin as it tells you when building that this can't be null:
"Condition 'throwable != null' is always 'true'"
However it can indeed be null as seen in https://github.com/TeamNewPipe/NewPipe/pull/6986#issuecomment-906822218
2021-08-27 16:32:59 +02:00
Robin
556aad0114 Merge pull request #6995 from litetex/use-eclipse-temurin-in-gh-actions
Using Eclipse ``temurin`` in GH actions
2021-08-27 15:59:57 +02:00
litetex
05f6ea6401 Using Eclipse `temurin`
as AdoptOpenJDK is getting deprecated.

Eclipse ``temurin`` is a defacto renamed AdoptOpenJDK.

Ref:
* https://blog.adoptopenjdk.net/2021/08/goodbye-adoptopenjdk-hello-adoptium/
* https://github.com/actions/setup-java#supported-distributions
2021-08-27 15:21:48 +02:00
Robin
43d0543b9f close audio effect control session properly 2021-08-27 10:53:44 +02:00
ktprograms
e95637f7b7 Add help text in fragment_choose_tabs.xml, convert to ConstraintLayout 2021-08-27 09:20:23 +08:00
George T
4cd7c42b9e fix typo / reword part of bug report template 2021-08-26 19:02:50 -05:00
Tobi
0787d62254 Merge pull request #6820 from Stypox/picker-mime-type
Provide mime type to file picker to gray out unselectable files
2021-08-26 21:39:35 +02:00
litetex
b061423847 Changed package as the old one is deprecated 2021-08-26 18:09:27 +02:00
litetex
dbd90299bd Replaced deprecated `kotlin-android-extensions with kotlin-parcelize`
References:
* https://developer.android.com/topic/libraries/view-binding/migration#groovy
* https://developer.android.com/kotlin/parcelize#groovy
2021-08-26 18:08:54 +02:00
litetex
1faf1b261c Updated to latest kotlin version 2021-08-26 18:08:00 +02:00
litetex
c6ead351c0 Set `KeyProgressIncrement` manually
* Set ``KeyProgressIncrement`` manually to the value of the seek duration in the settings so that it works when using the DPad
* consolidated code inside a new method to avoid duplication
2021-08-26 17:16:51 +02:00
Stypox
bbcfdf2969 Merge pull request #6917 from sherlockbeard/sherlockbeard-notAvailableVector
Change "not available" image from PNG to vector format
2021-08-25 17:12:07 +02:00
litetex
36e72d5a41 Update ci.yml
Using Java 11
2021-08-25 17:05:10 +02:00
litetex
f8297a8a9b Updated `com.android.tools.build:gradle` to 7.0.1
Release notes are available here: https://developer.android.com/studio/releases/gradle-plugin#7-0-0
2021-08-25 16:48:18 +02:00
ktprograms
a4503eb609 Remove TAG parameter, refactor method calls 2021-08-25 17:04:15 +08:00
ktprograms
a1cb3e59d6 Move opening popup menu to utility class 2021-08-25 09:30:40 +08:00
ktprograms
ef94458249 Remove xerial sqlite dependency 2021-08-25 09:00:36 +08:00
ktprograms
1b05c404d5 Remove Details option in Main Player Queue menu 2021-08-25 08:56:26 +08:00
ktprograms
5de455bb86 Change type of themeWrapper to ContextThemeWrapper 2021-08-25 08:56:26 +08:00
ktprograms
acdfee5c25 Show popup menu when long pressing in play queue (Full screen player) 2021-08-25 08:56:26 +08:00
Tobi
a6d6ed6474 Merge pull request #3546 from Stypox/search-history
Allow choosing which types of search suggestions to show
2021-08-24 19:27:36 +02:00
Stypox
87e7d95966 Do not show suggestions error snackbar for interrupted I/O
Fix formatting
2021-08-24 18:16:17 +02:00
Stypox
d37ee1e0dc First run migrations, then setDefaultValues, since the latter requires the correct types 2021-08-24 18:16:17 +02:00
Stypox
1d33e7ab49 Allow choosing which types of search suggestions to show
local, remote, both, none
Replacing the old on-off setting
2021-08-24 18:16:16 +02:00
Stypox
2027b743b4 Merge pull request #6919 from ktprograms/channel-details-all-places
Add Show Channel Details where it's missing
2021-08-24 16:43:21 +02:00
ktprograms
7e27e73532 Remove xerial sqlite dependency 2021-08-24 22:07:30 +08:00
Tobi
3705a1adad Merge pull request #6942 from mhmdanas/add-no-response-action
Add no-response workflow
2021-08-24 15:59:47 +02:00
Tobi
793b88a7d4 Merge pull request #5928 from Stypox/picasso
Replace UniversalImageLoader with Picasso
2021-08-24 15:54:16 +02:00
ktprograms
2928df0cc9 Fix checkstyle ParenPad error 2021-08-24 21:17:08 +08:00
ktprograms
4f5e772157 Remove xerial sqlite dependency 2021-08-24 19:43:27 +08:00
ktprograms
f7a0b9951e Move Choose Tabs help message to Action Bar subtitle 2021-08-24 17:28:28 +08:00
Stypox
44128f9145 Remove placeholder image while loading thumbnails 2021-08-24 10:56:25 +02:00
Stypox
6eaff5ca6a Apply review: move thumbnail loading out of Player 2021-08-24 10:56:25 +02:00
Stypox
c0664c1cb6 Add Picasso to licences and remove Universal Image Loader 2021-08-24 10:56:25 +02:00
Stypox
e229e5355d Always create new bitmap when resizing thumbnail
This prevents strange crashes on some devices, fixes #4638
2021-08-24 10:56:25 +02:00
Stypox
52189fc5df Add debug setting to enable Picasso indicators 2021-08-24 10:56:25 +02:00
Stypox
314964c5f9 Recycle Bitmap in transformation 2021-08-24 10:56:25 +02:00
Stypox
fcef783bbb Replace UniversalImageLoader with Picasso 2021-08-24 10:56:25 +02:00
Stypox
9c5ac069d7 Merge pull request #6244 from sauravrao637/6203
changed dark theme colors to darker variant
2021-08-24 10:48:36 +02:00
ktprograms
160f9df64e Add how to remove tab from main page text 2021-08-24 09:39:18 +08:00
Tobi
bdbb9bead2 Merge pull request #6848 from Stypox/somali-cancel
Use custom cancel string everywhere
2021-08-22 22:36:14 +02:00
Tobi
e4dfce9ee2 Merge pull request #6952 from Aga-C/wrap-settings-titles
Added wrapping settings titles to the next line
2021-08-22 22:34:07 +02:00
TobiGr
6fbb601802 Merge branch 'master' into dev 2021-08-22 22:22:37 +02:00
TobiGr
bf029ddd9f Removed unsued string from translations: item_in_history 2021-08-22 20:19:37 +02:00
TobiGr
af5f0c042a Fixed plurals 2021-08-22 20:18:15 +02:00
Hosted Weblate
b566355c4f Translated using Weblate (Finnish)
Currently translated at 10.7% (6 of 56 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Spanish)

Currently translated at 53.5% (30 of 56 strings)

Translated using Weblate (Polish)

Currently translated at 51.7% (29 of 56 strings)

Translated using Weblate (Swedish)

Currently translated at 99.5% (675 of 678 strings)

Translated using Weblate (Swedish)

Currently translated at 99.5% (675 of 678 strings)

Translated using Weblate (Swedish)

Currently translated at 99.5% (675 of 678 strings)

Translated using Weblate (Galician)

Currently translated at 93.6% (635 of 678 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.8% (677 of 678 strings)

Translated using Weblate (Japanese)

Currently translated at 99.8% (677 of 678 strings)

Translated using Weblate (Croatian)

Currently translated at 97.0% (658 of 678 strings)

Translated using Weblate (Croatian)

Currently translated at 97.0% (658 of 678 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Somali)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Swedish)

Currently translated at 3.5% (2 of 56 strings)

Translated using Weblate (Swedish)

Currently translated at 98.0% (665 of 678 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (German)

Currently translated at 99.8% (677 of 678 strings)

Translated using Weblate (Ukrainian)

Currently translated at 53.5% (30 of 56 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (French)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 53.5% (30 of 56 strings)

Translated using Weblate (Portuguese)

Currently translated at 53.5% (30 of 56 strings)

Translated using Weblate (Croatian)

Currently translated at 96.7% (656 of 678 strings)

Translated using Weblate (Swedish)

Currently translated at 97.1% (659 of 678 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (English)

Currently translated at 99.8% (678 of 679 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Ukrainian)

Currently translated at 53.5% (30 of 56 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Albanian)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (678 of 679 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Polish)

Currently translated at 48.2% (27 of 56 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 94.2% (640 of 679 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Persian)

Currently translated at 94.4% (641 of 679 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (French)

Currently translated at 99.8% (678 of 679 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (678 of 679 strings)

Translated using Weblate (Somali)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Hebrew)

Currently translated at 48.1% (26 of 54 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Arabic)

Currently translated at 99.7% (677 of 679 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Greek)

Currently translated at 99.7% (677 of 679 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (French)

Currently translated at 99.8% (678 of 679 strings)

Translated using Weblate (Spanish)

Currently translated at 99.8% (678 of 679 strings)

Translated using Weblate (German)

Currently translated at 99.8% (678 of 679 strings)

Translated using Weblate (German)

Currently translated at 99.8% (678 of 679 strings)

Translated using Weblate (German)

Currently translated at 99.8% (678 of 679 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (679 of 679 strings)

Translated using Weblate (Tamil)

Currently translated at 36.6% (248 of 677 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Esperanto)

Currently translated at 85.6% (580 of 677 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 26.4% (14 of 53 strings)

Translated using Weblate (Estonian)

Currently translated at 99.8% (676 of 677 strings)

Translated using Weblate (Swedish)

Currently translated at 97.4% (660 of 677 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Korean)

Currently translated at 76.0% (515 of 677 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Spanish)

Currently translated at 28.3% (15 of 53 strings)

Translated using Weblate (Estonian)

Currently translated at 97.0% (657 of 677 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Hungarian)

Currently translated at 85.6% (580 of 677 strings)

Translated using Weblate (Hungarian)

Currently translated at 85.6% (580 of 677 strings)

Translated using Weblate (Hungarian)

Currently translated at 85.6% (580 of 677 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Swedish)

Currently translated at 3.7% (2 of 53 strings)

Translated using Weblate (French)

Currently translated at 67.9% (36 of 53 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Swedish)

Currently translated at 97.3% (659 of 677 strings)

Translated using Weblate (Swedish)

Currently translated at 97.3% (659 of 677 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Latvian)

Currently translated at 94.5% (640 of 677 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 24.5% (13 of 53 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (German)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Galician)

Currently translated at 91.5% (620 of 677 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (French)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Bengali (Bangladesh))

Currently translated at 59.6% (404 of 677 strings)

Translated using Weblate (Somali)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 97.1% (658 of 677 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.8% (676 of 677 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Russian)

Currently translated at 99.8% (676 of 677 strings)

Translated using Weblate (French)

Currently translated at 99.8% (676 of 677 strings)

Translated using Weblate (Romanian)

Currently translated at 93.0% (626 of 673 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 3.7% (2 of 53 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 90.3% (608 of 673 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 87.9% (592 of 673 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (French)

Currently translated at 100.0% (673 of 673 strings)

Translated using Weblate (Somali)

Currently translated at 100.0% (672 of 672 strings)

Translated using Weblate (Albanian)

Currently translated at 100.0% (672 of 672 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (672 of 672 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (672 of 672 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (672 of 672 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (672 of 672 strings)

Translated using Weblate (Bulgarian)

Currently translated at 1.8% (1 of 53 strings)

Translated using Weblate (Bulgarian)

Currently translated at 57.8% (389 of 672 strings)

Translated using Weblate (Bulgarian)

Currently translated at 57.8% (389 of 672 strings)

Translated using Weblate (Bulgarian)

Currently translated at 57.8% (389 of 672 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (672 of 672 strings)

Translated using Weblate (Bulgarian)

Currently translated at 57.4% (386 of 672 strings)

Translated using Weblate (Bulgarian)

Currently translated at 57.4% (386 of 672 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (672 of 672 strings)

Translated using Weblate (Gujarati)

Currently translated at 15.3% (103 of 672 strings)

Translated using Weblate (Hindi)

Currently translated at 81.6% (549 of 672 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (672 of 672 strings)

Added translation using Weblate (Gujarati)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: AioiLight <info@aioilight.space>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Andrij Mizyk <andmizyk@gmail.com>
Co-authored-by: AntonAkovP <anton.akov@gmail.com>
Co-authored-by: Anxhelo Lushka <anxhelo1995@gmail.com>
Co-authored-by: Ashune <ashune@protonmail.com>
Co-authored-by: Blaise Pascal <blaisepcl00@gmail.com>
Co-authored-by: ButterflyOfFire <ButterflyOfFire@protonmail.com>
Co-authored-by: Cerins <cerins4141@gmail.com>
Co-authored-by: Christian Draxl <draxl.koever@gmail.com>
Co-authored-by: Christian Eichert <c@zp1.net>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Deleted User <noreply+34051@weblate.org>
Co-authored-by: Eduardo Caron <eduardocaron10@gmail.com>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Evo <weblate@verahawk.com>
Co-authored-by: Garden Hose <maxmammath@gmail.com>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: GnuPGを使うべきだ <dieeeazpnnqbpddh@cock.email>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Isak Holmström <isak@kajko.se>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: JY3 <GeeyunJY3@gmail.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Jesus Cass <cjesusenrique1@gmail.com>
Co-authored-by: Joel A <joeax910@student.liu.se>
Co-authored-by: Jonatan Nyberg <jonatan@autistici.org>
Co-authored-by: Kaantaja <ufdbvgoljrjkrkyyub@ianvvn.com>
Co-authored-by: Kristjan Räts <kristjanrats@gmail.com>
Co-authored-by: Laszlo Almasi <almalaci@posteo.net>
Co-authored-by: Ldm Public <ldmpub@gmail.com>
Co-authored-by: Martin Constantino–Bodin <martin.bodin@ens-lyon.org>
Co-authored-by: Matyas-Cerny <matyas.c.404@gmail.com>
Co-authored-by: MohammedSR Vevo <mohammednajmidin@gmail.com>
Co-authored-by: Nadir Nour <dudethatwascool2@gmail.com>
Co-authored-by: Nikita Epifanov <nikgreens@protonmail.com>
Co-authored-by: Ordtrogen Översättning <johan@ordtrogen.se>
Co-authored-by: Rahul Dev Sharma <sci94tune@gmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Ricardo <contatorms7@tutamail.com>
Co-authored-by: S3aBreeze <paperwork@evilcorp.ltd>
Co-authored-by: Saravanan Selvaraju <saravanan036@outlook.com>
Co-authored-by: Sergio Varela <sergitroll9@gmail.com>
Co-authored-by: SomeRetardedThatTranslatesStuff <the.eumitosis@simplelogin.fr>
Co-authored-by: Thiago Carmona Monteiro <Guarakami1807@protonmail.ch>
Co-authored-by: TiA4f8R <avdivers84@gmail.com>
Co-authored-by: TobiGr <tobigr@mail.de>
Co-authored-by: ToldYouThat <itoldyouthat@protonmail.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: WB <web0nst@tuta.io>
Co-authored-by: WaldiS <sto@tutanota.de>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bomzhellino <adm.bomzh@gmail.com>
Co-authored-by: brokenPipe <ythunar@btcminers.tk>
Co-authored-by: bruh <quangtrung02hn16@gmail.com>
Co-authored-by: chr56 <chr0056@gmail.com>
Co-authored-by: michaloM <michalsvoboda2004@gmail.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: nzgha <nzghafoss.ldxwe@slmail.me>
Co-authored-by: nzgha <osmshrn21.upogs@slmail.me>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: thami simo <simo.azad@gmail.com>
Co-authored-by: translator <yasinoc375@advew.com>
Co-authored-by: zeritti <woodenmo@posteo.de>
Co-authored-by: zmni <zmni@outlook.com>
Co-authored-by: Ács Zoltán <acszoltan111@gmail.com>
Co-authored-by: Ákos Surányi <akosuranyi@tutanota.com>
Co-authored-by: Андрей Станков <astankov84@gmail.com>
Co-authored-by: мачко <martinpeev@tutanota.com>
Co-authored-by: 정주찬 <ju1801@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bg/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ckb/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2021-08-22 19:50:37 +02:00
Agnieszka C
5c31dff72d Added title wrap to other preferences 2021-08-21 18:49:12 +02:00
Agnieszka C
d69672e113 Added wrapping settings titles to the next line (#6951) 2021-08-21 14:21:55 +02:00
ktprograms
a209e87c69 Add Loading Channel Details Toast 2021-08-21 09:30:40 +08:00
Mohammed Anas
71610a365f Make workflow run daily instead of hourly 2021-08-19 21:08:23 +00:00
mhmdanas
44860f2ea7 Add no-response workflow 2021-08-19 00:46:31 +03:00
ktprograms
967bdf8f08 Remove migration test, add manual testing reminder to Migrations.java 2021-08-17 08:57:03 +08:00
ktprograms
02aa6fcab0 Remove v2 to v3 migration test, add v3 to v4 test 2021-08-16 21:12:54 +08:00
ktprograms
712985ced1 Save uploader url when adding from PlayQueueItem 2021-08-16 08:08:50 +08:00
litetex
0683dafa55 Merge pull request #6851 from litetex/make-parsing-of-timestamp-links-more-robust
Catch errors while processing timestamp-links
2021-08-14 21:10:20 +02:00
ktprograms
6f1958d398 Remove setting uploaderUrl to empty string if null 2021-08-14 20:59:38 +08:00
ktprograms
85fbd2560d Fix typo in app/build.gradle 2021-08-14 20:56:29 +08:00
ktprograms
65f2730261 Add comment about xerial sqlite workaround 2021-08-14 20:54:23 +08:00
ktprograms
21bcadeecb Make uploader_url column nullable 2021-08-14 17:48:35 +08:00
ktprograms
bd0427c79f Refactor duplicated code into method 2021-08-14 17:32:38 +08:00
ktprograms
241054fd26 Remove hardcoded string 2021-08-14 15:38:57 +08:00
ktprograms
d8888e3495 Catch error from ExtractorHelper.getStreamInfo, remove blockingGet 2021-08-14 09:07:27 +08:00
sherlockbeard
137d9e6d6e testing 2021-08-13 20:54:22 +05:30
sherlockbeard
d0cbd1e663 Replaced not avaliable image with a vector 2021-08-13 20:42:06 +05:30
sherlockbeard
da51e1ed72 Merge branch 'TeamNewPipe:dev' into dev 2021-08-13 19:05:50 +05:30
ktprograms
76803bfcb1 Save channelUrl to Database if it doesn't exist 2021-08-13 18:02:53 +08:00
ktprograms
c248741c00 Add Show Channel Details to Subscription Feed & History 2021-08-13 17:04:10 +08:00
ktprograms
759a078ce0 Add uploader_url column to StreamEntity 2021-08-13 16:44:50 +08:00
evermind-zz
a536311d56 name the regions according to the comments (#6854)
if a region is named android studio will show its name in the structure view.
2021-08-10 22:38:23 +00:00
Stypox
4d50a66e40 Un-nest some text views in play queue activity 2021-08-09 20:11:31 +02:00
phigjm
e6c56cacc6 Rearrange the play queue control elements
from bottom to top: Playback_controls, progress bar, center, (seek display)
2021-08-09 20:11:18 +02:00
XiangRongLin
c3b9465aa3 Merge pull request #6858 from XiangRongLin/ci_ktlint
Add gradle parameter to skip formatKtLint and use in CI
2021-08-07 12:36:56 +02:00
litetex
5f3b8bea52 Fixed format 2021-08-06 22:12:49 +02:00
litetex
0e4c8ea8af Added tests for the `TimestampExtractor` 2021-08-06 22:09:03 +02:00
litetex
f9ab23bb4a Removed useless fiedl 2021-08-06 22:08:42 +02:00
litetex
9f8b2264a2 Use better pattern for matching timestamp in text and some reworks
Also extracted overhead code into ``TimestampExtractor``
2021-08-06 22:08:29 +02:00
XiangRongLin
52cc3f10c1 Add gradle parameter to skip formatKtLint and use in CI 2021-08-06 18:11:22 +02:00
litetex
1d61bb58f5 Set loglevel to error
Co-authored-by: Stypox <stypox@pm.me>
2021-08-05 20:26:17 +02:00
litetex
51c60e5261 Catch errors while processing timestamp-links
Otherwise the complete app crashes, which is bad
2021-08-04 22:35:41 +02:00
litetex
12e46e0a36 Allow manual execution of ci workflow (#6809) 2021-08-04 17:00:15 +00:00
Stypox
f8caed139a Use custom cancel string everywhere
to fix missing somali translation for android.R.string.cancel
2021-08-04 18:58:35 +02:00
K0RR
26c39381a8 Optimize assets. (#6827)
Lossless compression.
2021-08-04 11:54:32 +00:00
Stypox
a4742ad9e9 v0.21.9 (975) changelog 2021-08-04 12:07:02 +02:00
Stypox
32dffb577c Provide mime type to file picker to gray out unselectable files 2021-08-01 13:52:32 +02:00
Isira Seneviratne
9458b9f37d Use PackageInfoCompat.getSignatures(). 2021-07-19 19:48:24 +05:30
magicfelix
79102a20d2 Add Tubus (Invidious) and EduVid (PeerTube) 2021-07-11 13:08:10 +02:00
Saurav Rao
0782410a14 Update colors.xml 2021-06-19 04:05:35 +05:30
Robin
f5d015e8f9 Use ExoPlayer default values for buffers 2021-06-18 20:18:24 +02:00
camo0112
f00cffd17e improved dark theme for all services 2021-06-18 04:53:55 +05:30
camo0112
7fdb6e1425 did missed changes 2021-05-07 03:27:59 +05:30
camo0112
621f049a5c improved dark_youtube_primary_color and dark_youtube_dark_color 2021-05-06 08:45:44 +05:30
sherlockbeard
eb6968fb3f Merge pull request #1 from TeamNewPipe/dev
uptodate
2021-03-27 19:13:18 +05:30
426 changed files with 5335 additions and 7748 deletions

View File

@@ -57,7 +57,7 @@ Oh no, a bug! It happens. Thanks for reporting an issue with NewPipe. To make it
<!-- Please fill this out when you do not provide a log generate by NewPipe -->
<!-- Please fill this section if you did not provide a log generated by NewPipe -->
### Device info

View File

@@ -5,10 +5,9 @@ labels: enhancement
assignees: ''
---
<!-- IF YOU DON'T FILL IN THE TEMPLATE PROPERLY, YOUR ISSUE IS LIABLE TO BE CLOSED. If you feel tired/lazy right now, open your issue some other time. We'll wait. -->
<!-- IF YOU DON'T FILL IN THE TEMPLATE PROPERLY, YOUR ISSUE IS LIABLE TO BE CLOSED. If you are currently unable to do so for any reason, open your issue some other time. We'll wait. -->
<!-- The comments between these brackets won't show up in the submitted issue (as you can see in the Preview). -->
<!-- The comments between these brackets won't show up in the submitted issue (as you can see in the Preview tab). -->
### Checklist
<!-- This checklist is COMPULSORY. The first box has been checked for you to show you how it is done. -->
@@ -17,30 +16,9 @@ assignees: ''
- [ ] I have read the contribution guidelines given at https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md.
- [ ] This issue contains only one feature request. I will open one issue for every feature I want to request.
#### Describe the feature you want
<!-- A clear and concise description of what you wish should happen.
Example: *I think it would be nice if you add feature Y which makes X possible.*
Optionally, also describe alternatives you've considered.
Example: *Z is also a good alternative. Not as good as Y, but at least...* or *I considered Z, but that didn't turn out to be a good idea because...* -->
#### What feature do you want?
<!-- Explain how you want the app's look or behavior to change to suit your needs. -->
#### Is your feature request related to a problem? Please describe it
<!-- A clear and concise description of what the problem is. Maybe the developers and the community could brainstorm and come up with a better solution to your problem. If they exist, link to related Issues and/or PRs for developers to keep track easier.
Example: *I want to do X, but there is no way to do it.* -->
#### Additional context
<!-- Add any other context, like screenshots, about the feature request here.
Example: *Here's a photo of my cat!* -->
#### How will you/everyone benefit from this feature?
<!-- Convince us! How does it change your NewPipe experience and/or your life?
The better this paragraph is, the more likely a developer will think about working on it.
Example: *This feature will help us colonize the galaxy! -->
#### Why do you want this feature?
<!-- Describe any problem or limitation you come across while using the app which would be solved by this feature. -->

View File

@@ -1,6 +1,7 @@
name: CI
on:
workflow_dispatch:
pull_request:
branches:
- dev
@@ -32,24 +33,15 @@ jobs:
if: github.event_name == 'pull_request'
run: git checkout -B ${{ github.head_ref }}
- name: set up JDK 8
- name: set up JDK 11
uses: actions/setup-java@v2
with:
java-version: 8
distribution: "adopt"
- name: Cache Gradle dependencies
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
java-version: 11
distribution: "temurin"
cache: 'gradle'
- name: Check if kotlin files are formatted correctly
run: ./gradlew runKtlint
- name: Build debug APK and run jvm tests
run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace
run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint
- name: Upload APK
uses: actions/upload-artifact@v2
@@ -67,18 +59,12 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: set up JDK 8
- name: set up JDK 11
uses: actions/setup-java@v2
with:
java-version: 8
distribution: "adopt"
- name: Cache Gradle dependencies
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
java-version: 11
distribution: "temurin"
cache: 'gradle'
- name: Run android tests
uses: reactivecircus/android-emulator-runner@v2
@@ -99,7 +85,8 @@ jobs:
# uses: actions/setup-java@v2
# with:
# java-version: 11 # Sonar requires JDK 11
# distribution: "adopt"
# distribution: "temurin"
# cache: 'gradle'
# - name: Cache SonarCloud packages
# uses: actions/cache@v2
@@ -108,13 +95,6 @@ jobs:
# key: ${{ runner.os }}-sonar
# restore-keys: ${{ runner.os }}-sonar
# - name: Cache Gradle packages
# uses: actions/cache@v2
# with:
# path: ~/.gradle/caches
# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
# restore-keys: ${{ runner.os }}-gradle
# - name: Build and analyze
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any

20
.github/workflows/no-response.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: No Response
# Both `issue_comment` and `scheduled` event types are required for this Action
# to work properly.
on:
issue_comment:
types: [created]
schedule:
# Run daily at midnight.
- cron: '0 0 * * *'
jobs:
noResponse:
runs-on: ubuntu-latest
steps:
- uses: lee-dohm/no-response@v0.5.0
with:
token: ${{ github.token }}
daysUntilClose: 14
responseRequiredLabel: waiting-for-author

View File

@@ -1,30 +1,30 @@
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
<h2 align="center"><b>NewPipe</b></h2>
<h4 align="center">Una interfaz de streaming lijera y libre para Android.</h4>
<h4 align="center">Una interfaz de streaming ligera y libre para Android.</h4>
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-es.svg"></a></p>
<p align="center">
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/badge/Lanzamiento-v0.20.11-blue.svg" ></a>
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/Licencia-GPL%20v3-blue.svg"></a>
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
<a href="https://hosted.weblate.org/engage/newpipe/es/" alt="Estado de la traducción"><img src="https://hosted.weblate.org/widgets/newpipe/es/svg-badge.svg"></a>
<a href="https://web.libera.chat/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/Canal%20de%20IRC%20-%23newpipe-brightgreen.svg"></a>
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="Lanzamientos GitHub"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="Licencia: GPLv3"><img src="https://img.shields.io/badge/Licencia-GPL%20v3-blue.svg"></a>
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Estado del Build"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
<a href="https://hosted.weblate.org/engage/newpipe/es/" alt="Estado de la Traducción"><img src="https://hosted.weblate.org/widgets/newpipe/es/svg-badge.svg"></a>
<a href="https://web.libera.chat/#newpipe" alt="Canal de IRC: #newpipe"><img src="https://img.shields.io/badge/Canal%20de%20IRC%20-%23newpipe-brightgreen.svg"></a>
<a href="https://www.bountysource.com/teams/newpipe" alt="Recompensas en Bountysource"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
</p>
<hr>
<p align="center"><a href="#capturas-de-pantalla">Capturas de pantalla</a> &bull; <a href="#descripción">Descripción</a> &bull; <a href="#características">Características</a> &bull; <a href="#installación-y-actualizaciones">Installación y actualizaciones</a> &bull; <a href="#contribución">Contribución</a> &bull; <a href="#donar">Donar</a> &bull; <a href="#licencias">Licencias</a></p>
<p align="center"><a href="https://newpipe.net">Sitio web</a> &bull; <a href="https://newpipe.net/blog/">Blog</a> &bull; <a href="https://newpipe.net/FAQ/">Preguntas Frecuentes</a> &bull; <a href="https://newpipe.net/press/">Prensa</a></p>
<p align="center"><a href="#capturas-de-pantalla">Capturas de Pantalla</a> &bull; <a href="#descripción">Descripción</a> &bull; <a href="#características">Características</a> &bull; <a href="#instalación-y-actualizaciones">Instalación y Actualizaciones</a> &bull; <a href="#contribución">Contribución</a> &bull; <a href="#donar">Donar</a> &bull; <a href="#licencia">Licencia</a></p>
<p align="center"><a href="https://newpipe.net">Sitio Web</a> &bull; <a href="https://newpipe.net/blog/">Blog</a> &bull; <a href="https://newpipe.net/FAQ/">Preguntas Frecuentes</a> &bull; <a href="https://newpipe.net/press/">Prensa</a></p>
<hr>
*Lea esto en otros idiomas: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
<b>AVISO: ESTA ES UNA VERSIÓN BETA, POR LO TANTO, PUEDE ENCONTRAR BUGS (ERRORES). SI ENCUENTRA UNO, ABRA UN ISSUE A TRAVÉS DE NUESTRO REPOSITORIO GITHUB.</b>
<b>AVISO: ESTA ES UNA VERSIÓN BETA, POR LO TANTO, PUEDE ENCONTRAR BUGS. SI ENCUENTRA UNO ABRA UN ISSUE A TRAVÉS DE NUESTRO REPOSITORIO DE GITHUB.</b>
<b>COLOCAR NEWPIPE O CUALQUIER FORK (BIFURCACIÓN) REALIZADO DE ELLO EN GOOGLE PLAY STORE VIOLA SUS TÉRMINOS Y CONDICIONES.</b>
<b>COLOCAR NEWPIPE O CUALQUIER FORK DE NEWPIPE EN LA GOOGLE PLAY STORE VIOLA SUS TÉRMINOS Y CONDICIONES.</b>
## Capturas de pantalla
## Capturas de Pantalla
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
@@ -40,39 +40,43 @@
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
## Descripción
NewPipe no usa ninguna librería de framework de Google, ni la API de YouTube. Los sitios web solamente se analizan para extraer la información requerida, asi que esta app se puede usar sin los servicios de Google instalados. Además, no se necesita una cuenta de YouTube para usar NewPipe, lo cual es un software libre de copyleft.
NewPipe no usa ninguna librería del framework de Google, ni la API de YouTube. Los sitios web solamente se analizan para extraer la información requerida, por lo que esta app se puede usar sin los servicios de Google instalados. Además, no se necesita una cuenta de YouTube para usar NewPipe, lo cual es un software libre de copyleft.
### Características
* Buscar videos
* No requiere inicio de sesión
* Mostrar información general sobre videos
* Mirar videos de YouTube
* Escuchar audio de YouTube
* Modo popup (reproductor flotante)
* Elegir reproductor para mirar el video
* Modo de solo audio en videos de YouTube
* Modo pop-up (reproductor flotante)
* Elegir un reproductor de video externo para mirar videos
* Descargar videos
* Descargar solamente audio
* Abrir video en Kodi
* Descargar solo audio
* Abrir videos en Kodi
* Mostrar videos próximos/relacionados
* Buscar a través de YouTube en un idioma específico
* Mirar/Bloquear materiales restringidas por edad.
* Mirar/Bloquear videos restringidos por edad
* Mostrar información general sobre canales
* Buscar canales
* Buscar de canales
* Mirar videos de un canal
* Apoyo Orbot/Tor (todavía no directamente)
* Apoyo 1080p/2K/4K
* Ver historias
* Subscribirse a canales
* Buscar historias
* Buscar/mirar listas de reproducción
* Mirar listas de reproducción en fila
* Poner videos en fila
* Listas locales de reproducción
* Soporte Orbot/Tor (todavía no directamente)
* Soporte para videos en 1080p/2K/4K
* Historial de videos vistos
* Suscripción a canales
* Historial de búsquedas
* Buscar/Mirar listas de reproducción
* Mirar listas de reproducción en cola
* Poner videos en cola
* Listas de reproducción locales
* Subtítulos
* Apoyo de medios en directo
* Soporte para transmisiones en vivo
* Mostrar comentarios
### Servicios apoyados
NewPipe apoya varios servicios. Nuestras [documentaciones](https://teamnewpipe.github.io/documentation/) proveen más información en como se puede agregar un servicio nuevo a la app y el extractor. Por favor contáctenos si pretende agregar uno nuevo. Actualmente los servicios apoyados son:
### Servicios Soportados
NewPipe soporta varios servicios. Nuestras [documentaciones](https://teamnewpipe.github.io/documentation/) ofrecen más información sobre cómo se puede agregar un servicio nuevo a la app y al extractor. Por favor ponte en contacto con nosotros si tienes pensado agregar uno nuevo. Actualmente los servicios soportados son:
* YouTube
* SoundCloud \[beta\]
@@ -80,61 +84,60 @@ NewPipe apoya varios servicios. Nuestras [documentaciones](https://teamnewpipe.g
* PeerTube instances \[beta\]
* Bandcamp \[beta\]
<!-- Brecha escondida para mantener compatibles los enlaces viejos. -->
<span id="actualizaciones"></span>
## Instalación y Actualizaciones
## Installación y actualizaciones
Se puede instalar NewPipe usando uno de los métodos siguientes:
1. Agregar nuestro repositorio personalizado a F-Droid e instalarlo desde allí. Las instrucciones están aquí: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
2. Descargar el archivo APK del enlace [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) e instalarlo.
3. Actualizar a través de F-Droid. Este es el método más lento para obtener la actualización, como F-Droid debe reconocer cambios, construir el APK aparte, firmarlo con una clave, y finalmente empujar la actualización a los usuarios.
4. Construir un APK de depuración por si mismo. Este es el modo más rápido para realizar nuevas características en su dispositivo, pero es mucho más complicado, asi que recomendamos uno de los otros métodos.
Recomendamos el método 1 para la mayoría de usuarios. Los APKs instalados usando método 1 o 2 son compatibles el uno con el otro, pero no con las instalaciones usando método 3. Esta es debida a la misma clave digital (la nuestra), siendo utilizado en los métodos 1 y 2, pero una clave digital diferente (la de F-Droid) siendo utilizado en el método 3. Construir un APK de depuración usando método 4 excluye una clave enteramente. Firmando con claves digitales ayuda a asegurar de que un
usuario no esté engañado para instalar una actualización maliciosa a una app.
1. Agregando nuestro repositorio personalizado a F-Droid e instalarlo desde allí. Las instrucciones están [aquí](https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/).
2. Descargando el archivo APK de [aquí](https://github.com/TeamNewPipe/NewPipe/releases) y posteriormente instalarlo.
3. Usando el repositorio oficial de F-Droid. Este es el método más lento para obtener actualizaciones, ya que F-Droid debe reconocer los cambios, construir el APK aparte, firmarlo con una clave, y finalmente publicar la actualización.
4. Construyendo la app usted mismo. Este es el modo más rápido para obtener nuevas características en su dispositivo, pero es mucho más complicado, así que recomendamos uno de los otros métodos.
Mientras tanto, si quiere cambiar los fuentes por alguna razón (por ejemplo, la funcionalidad del nucleo de NewPipe se rompe y F-Droid aun no tiene la actualización), recomendamos el siguiente procedimiento:
1. Repaldear sus datos a través de Ajustes > Contenido > Exporta base de datos para guardar su historia, subscripciones, y listas de reproducción
2. Desinstalar NewPipe
3. Descargar el APK del nuevo fuente e instalarlo.
4. Importar los datos del paso 1 a través de Ajustes > Contenido > Importa base de datos.
Recomendamos el método 1 para la mayoría de usuarios. Los APKs instalados usando método 1 y 2 son compatibles entre sí, pero no lo son con los instalados usando el método 3. Esto es debido a la clave de firmado, ya que los métodos 1 y 2 usan la misma clave (la nuestra), pero el método 3 usa una clave diferente (la de F-Droid). El método 4 excluye totalmente una clave de firmado. Las claves de firmado aseguran que el usuario no esté siendo engañado para instalar/actualizar una APK maliciosa.
Además, si quiere cambiar el método de instalación por alguna razón (por ejemplo: la funcionalidad del núcleo de NewPipe se rompe o F-Droid aún no publica la actualización), recomendamos el siguiente procedimiento:
1. Respalde su información a través de Ajustes > Contenido > Exportar base de datos; esto guardará su historial (videos vistos y búsquedas), suscripciones, listas de reproducción y ajustes.
2. Desinstale NewPipe.
3. Descargue el APK con un método distinto e instálelo.
4. Importe la información (la base de datos extraída del paso 1) a través de Ajustes > Contenido > Importar base de datos. Tenga en cuenta que esta opción superpondrá su historial actual (tanto de vídeos como de búsquedas), sus listas de reproducción y (opcionalmente) sus configuraciones.
## Contribución
Si tiene ideas, traducciónes, cambios de diseño, limpieza de código, o cambios grandes de código, su ayuda es siempre bienvenida.
Cuanto más realizamos, mejor se pone la aplicación!
Si tiene ideas, traducciones, cambios de diseño, limpieza de código o cambios grandes de código, su ayuda es siempre bienvenida. ¡Cuanto más hagamos, NewPipe será mucho mejor!
Si quiere involucrarse, fíjese en nuestras [notas de contribución](.github/CONTRIBUTING.md).
<a href="https://hosted.weblate.org/engage/newpipe/es/">
<img src="https://hosted.weblate.org/widgets/newpipe/es/287x66-grey.png" alt="Estado de la traducción" />
<img src="https://hosted.weblate.org/widgets/newpipe/es/287x66-grey.png" alt="Estado de la Traducción" />
</a>
## Donar
Si le gusta el NewPipe estaremos felices con una donación. O puede enviar bitcoin o donar a través de Bountysource o Liberapay. Para obtener más información sobre como donar a NewPipe, por favor visita nuestro [sitio web](https://newpipe.net/donate).
Si te gusta NewPipe, estaremos felices con una donación. Puede enviar bitcoin o donar a través de Bountysource o Liberapay. Visita nuestro [sitio web](https://newpipe.net/donate) para más información.
<table>
<tr>
<td><img src="https://bitcoin.org/img/icons/logotop.svg" alt="Bitcoin"></td>
<td><img src="assets/bitcoin_qr_code.png" alt="Bitcoin QR code" width="100px"></td>
<td><img src="assets/bitcoin_qr_code.png" alt="Código QR del Bitcoin" width="100px"></td>
<td><samp>16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh</samp></td>
</tr>
<tr>
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="https://upload.wikimedia.org/wikipedia/commons/2/27/Liberapay_logo_v2_white-on-yellow.svg" alt="Liberapay" width="80px" ></a></td>
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="assets/liberapay_qr_code.png" alt="Visit NewPipe at liberapay.com" width="100px"></a></td>
<td><a href="https://liberapay.com/TeamNewPipe/donate"><img src="assets/liberapay_donate_button.svg" alt="Donate via Liberapay" height="35px"></a></td>
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="assets/liberapay_qr_code.png" alt="Visita NewPipe en liberapay.com" width="100px"></a></td>
<td><a href="https://liberapay.com/TeamNewPipe/donate"><img src="assets/liberapay_donate_button.svg" alt="Dona vía Liberapay" height="35px"></a></td>
</tr>
<tr>
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Bountysource.png/320px-Bountysource.png" alt="Bountysource" width="190px"></a></td>
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="assets/bountysource_qr_code.png" alt="Visit NewPipe at bountysource.com" width="100px"></a></td>
<td><a href="https://www.bountysource.com/teams/newpipe/issues"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f" height="30px" alt="Check out how many bounties you can earn."></a></td>
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="assets/bountysource_qr_code.png" alt="Visita NewPipe en bountysource.com" width="100px"></a></td>
<td><a href="https://www.bountysource.com/teams/newpipe/issues"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f" height="30px" alt="Revisa cuántas recompensas puedes obtener."></a></td>
</tr>
</table>
## Política de privacidad
El proyecto NewPipe tiene como objetivo proveer una experience privada y anónima para usar servicios de medios web.
Por lo tanto, la app no colecciona ningunos datos sin su consentimiento. La politica de privacidad de NewPipe explica en detalle los datos enviados y almacenados cuando envia un informe de error, o comentario en nuestro blog. Puede encontrar el documento [aqui](https://newpipe.net/legal/privacy/).
## Política de Privacidad
El proyecto NewPipe tiene como objetivo ofrecer una experience privada y anónima al usar servicios web multimedia. Por lo tanto, la app no recoleta ningún tipo de información sin su consentimiento. La politica de privacidad de NewPipe explica en detalle qué información es enviada y almacenada cuando envía un informe de error o comenta en [nuestro blog](https://newpipe.net/blog/). Puede encontrar el documento [aquí](https://newpipe.net/legal/privacy/).
## Licencia
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)
NewPipe es Software Libre: Puede usar, estudiar, compartir, y mejorarlo a su voluntad. Especificamente puede redistribuir y/o modificarlo bajo los términos de la [GNU General Public License](https://www.gnu.org/licenses/gpl.html) como publicado por la Free Software Foundation, o versión 3 de la licencia, o (en su opción) cualquier versión posterior.
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.html)
NewPipe es Software Libre: Puede usarlo, estudiarlo, compartirlo y mejorarlo a su voluntad. Más específicamente, puede redistribuirlo y/o modificarlo bajo los términos de la [GNU General Public License](https://www.gnu.org/licenses/gpl.html) publicada por la Free Software Foundation tanto si usa la versión 3 o posterior de la licencia.

View File

@@ -89,7 +89,7 @@ NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로
따라서 우리는 다른 방법들 중 하나를 사용하는 것을 추천합니다.
2. 우리의 커스텀 저장소를 F-Droid에 추가하고 우리가 릴리즈를 게시하는 대로 저곳에서 릴리즈를 설치할 수 있습니다.
이에 대한 설명서는 이곳에서 확인할 수 있습니다: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
3. 우리가 릴리즈를 게시하는 대로 [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases)에서 APK를 다운받고 이것을 설치할 수 있습니다.
3. 우리가 릴리즈를 게시하는 대로 [GitHub Releases](https://github.com/TeamNewPipe/NewPipe/releases)에서 APK를 다운받고 이것을 설치할 수 있습니다.
4. F-Droid를 통해 업데이트 할 수 있습니다. F-Droid는 변화를 인식하고, 스스로 APK를 생성하고, 이것에 서명하고, 사용자들에서 업데이트를 전달해야만 하기 때문에,
이것은 업데이트를 받는 가장 느린 방법입니다.

View File

@@ -89,7 +89,7 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
## Installation and updates
You can install NewPipe using one of the following methods:
1. Add our custom repo to F-Droid and install it from there. The instructions are here: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
2. Download the APK from [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it.
2. Download the APK from [GitHub Releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it.
3. Update via F-Droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, then push the update to users.
4. Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods.

View File

@@ -87,7 +87,7 @@ O NewPipe suporta vários serviços. Nosso [documentação](https://teamnewpipe.
Quando uma alteração no código NewPipe (devido à adição de recursos ou fixação de bugs), eventualmente ocorrerá uma versão. Estes estão no formato x.xx.x . A fim de obter esta nova versão, você pode:
1. Construa um APK de depuração você mesmo. Esta é a maneira mais rápida de obter novos recursos em seu dispositivo, mas é muito mais complicado, por isso recomendamos usar um dos outros métodos.
2. Adicione nosso repo personalizado ao F-Droid e instale-o a partir daí assim que publicarmos um lançamento. As instruções estão aqui.: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
3. Baixe o APK do [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) e instalá-lo assim que publicarmos um lançamento.
3. Baixe o APK do [GitHub Releases](https://github.com/TeamNewPipe/NewPipe/releases) e instalá-lo assim que publicarmos um lançamento.
4. Atualização via F-droid. Este é o método mais lento para obter atualizações, pois o F-Droid deve reconhecer alterações, construir o próprio APK, assiná-lo e, em seguida, enviar a atualização para os usuários.
Recomendamos o método 2 para a maioria dos usuários. Os APKs instalados usando o método 2 ou 3 são compatíveis entre si, mas não com aqueles instalados usando o método 4. Isso se deve à mesma chave de assinatura (nossa) sendo usada para 2 e 3, mas uma chave de assinatura diferente (F-Droid's) está sendo usada para 4. Construir um APK depuração usando o método 1 exclui totalmente uma chave. Assinar chaves ajudam a garantir que um usuário não seja enganado para instalar uma atualização maliciosa em um aplicativo.

View File

@@ -89,7 +89,7 @@ NewPipe suportă servicii multiple. [Documentele](https://teamnewpipe.github.io/
## Instalare şi actualizări
Puteţi instala NewPipe folosind una dintre următoarele metode:
1. Adăugaţi depozitul nostru F-droid personalizat. Instrucţiunile sunt aici: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
2. Descărcaţi APK-ul din [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) şi instalaţi-l.
2. Descărcaţi APK-ul din [GitHub Releases](https://github.com/TeamNewPipe/NewPipe/releases) şi instalaţi-l.
3. Actualizaţi via F-Droid. Aceasta este cea mai lentă metodă de a obţine actualizări, deoarece F-Droid trebuie să recunoască schimbările, să constriască APK-ul, să îl semneze, iar apoi să îl trimită utilizatorilor.
4. Construiţi un APK de depanare. Aceasta este cea mai rapidă metodă de a primi funcţii noi, dar este mult mai complicată, aşa că vă recomandăm să folosiţi una dintre celelalte metode.

View File

@@ -86,7 +86,7 @@ NewPipe wuxuu taageeraa adeegyo badan. [warqadan](https://teamnewpipe.github.io/
Marka koodhka NewPipe isbadal ku dhaco (wax cusub oo lagusoo kordhiyay ama cilad bixin), ugu dambayn waxaa lasii daayaa mid cusub (Siidayn). Siidaynta qaabkeedu waa x.xx.x . Si aad midka cusub u hesho, waxaad samayn kartaa:
1. Inaad mid cusub (APK) adigu dhisato. Tani waa mida ugu dagdag badan eed waxyaabaha cusub ku heli karto, laakiin way adagtahay, sidaa darteed waxaan soojeedinaynaa inaad isticmaasho qababka kale.
2. Ku dar qayb gaar ah xaganaga F-Droid oo xagaas kaga shub isla markay siidayn soobaxdo. Hagitaanka xagan ka eeg: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
3. Kasoo dajiso APK-ga xaga [Siidaynta Github](https://github.com/TeamNewPipe/NewPipe/releases) oo ku shubo isla markay siidayn soobaxdo.
3. Kasoo dajiso APK-ga xaga [Siidaynta GitHub](https://github.com/TeamNewPipe/NewPipe/releases) oo ku shubo isla markay siidayn soobaxdo.
4. Ka cusboonaysii xaga F-Droid. Tani waa mida ugu daahitaanka badan, sababtoo ah F-Droid waxay fiirin isbadalka waxayna iyadu dhisi mid (app), sixiixi, kadibna ay cusboonaysiinta usiidayn isticmaalayaasha.
Waxaan usoojeedinaynaa isticmaaalka qaabka 2 dadka badankood. APK-yada loogu shubo qaabka 2 ama 3 way isqaadan karaan, laakiin isma qaadan karaan kuwa loogu shubay qaabka 4. Sababtuna waxaa weeye furaha sixiixa oo iskumid ah (kaanaga weeye) oo loo isticmaalay 2 iyo 3, laakiin furo sixiixeed ka duwan (midka F-Droid) oo loo isticmaalay 4. Dhisida APK ayadoo la isticmaalayo qaabka 1 waxay gabi ahaanba ka reebtaa wax fure ah. Furayaasha sixiixa waxay xaqiijiyaan in isticmaalaha aan lagu khaldin inuu ku shubto cusboonaysiin khalad ah (wax lasoo dhexraaciyay) app-ka.

View File

@@ -23,7 +23,7 @@
<b>GOOGLE PLAY STORE'A NEWPIPE VEYA BAŞKA BİR KOPYASINI KOYMAK, PLAY STORE ŞARTLARINI VE KOŞULLARINI İHLAL EDER.</b>
## Ekran fotoğrafları
## Ekran görüntüleri
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
@@ -88,7 +88,7 @@ NewPipe birden fazla hizmeti destekler. Uygulamaya ve ayıklayıcıya yeni bir h
## Kurulum ve güncellemeler
Aşağıdaki yöntemlerden birini kullanarak NewPipe'ı kurabilirsiniz:
1. Özel depomuzu F-Droid'e ekleyin ve oradan yükleyin. Kılavuzu şurada bulabilirsiniz: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
2. APK'yı [Github sürümlerinden](https://github.com/TeamNewPipe/NewPipe/releases) indirin ve kurun.
2. APK'yı [GitHub sürümlerinden](https://github.com/TeamNewPipe/NewPipe/releases) indirin ve kurun.
3. F-Droid ile güncelleyin. Bu, güncellemeleri almanın en yavaş yöntemidir, çünkü F-Droid değişiklikleri tanımalı, APK'yı kendisi oluşturmalı, imzalamalı ve ardından güncellemeyi kullanıcılara dağıtmalıdır.
4. Kendiniz bir APK derleyin. Bu yöntem, cihazınızda yeni özellikler edinmenin en hızlı yoludur, ancak çok daha karmaşıktır, bu nedenle diğer yöntemlerden birini kullanmanızı öneririz.

View File

@@ -4,7 +4,7 @@ plugins {
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
apply plugin: 'checkstyle'
@@ -17,8 +17,8 @@ android {
resValue "string", "app_name", "NewPipe"
minSdkVersion 19
targetSdkVersion 29
versionCode 975
versionName "0.21.9"
versionCode 979
versionName "0.21.13"
multiDexEnabled true
@@ -54,6 +54,11 @@ android {
// debug build. This seems to be a Gradle bug, therefore
// TODO: update Gradle version
release {
if (System.properties.containsKey('packageSuffix')) {
applicationIdSuffix System.getProperty('packageSuffix')
resValue "string", "app_name", "NewPipe " + System.getProperty('packageSuffix')
archivesBaseName = 'NewPipe_' + System.getProperty('packageSuffix')
}
minifyEnabled true
shrinkResources false // disabled to fix F-Droid's reproducible build
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
@@ -84,11 +89,6 @@ android {
jvmTarget = JavaVersion.VERSION_1_8
}
// Required and used only by groupie
androidExtensions {
experimental = true
}
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
@@ -107,7 +107,7 @@ ext {
icepickVersion = '3.2.0'
exoPlayerVersion = '2.12.3'
googleAutoServiceVersion = '1.0'
groupieVersion = '2.8.1'
groupieVersion = '2.9.0'
markwonVersion = '4.6.2'
leakCanaryVersion = '2.5'
@@ -165,7 +165,10 @@ task formatKtlint(type: JavaExec) {
}
afterEvaluate {
preDebugBuild.dependsOn formatKtlint, runCheckstyle, runKtlint
if (!System.properties.containsKey('skipFormatKtlint')) {
preDebugBuild.dependsOn formatKtlint
}
preDebugBuild.dependsOn runCheckstyle, runKtlint
}
sonarqube {
@@ -186,7 +189,7 @@ dependencies {
// name and the commit hash with the commit hash of the (pushed) commit you want to test
// This works thanks to JitPack: https://jitpack.io/
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.9'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.11'
/** Checkstyle **/
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
@@ -243,7 +246,8 @@ dependencies {
// Circular ImageView
implementation "de.hdodenhof:circleimageview:3.1.0"
// Image loading
implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5"
//noinspection GradleDependency --> 2.8 is the last version, not 2.71828!
implementation "com.squareup.picasso:picasso:2.8"
// Markdown library for Android
implementation "io.noties.markwon:core:${markwonVersion}"
@@ -255,6 +259,9 @@ dependencies {
// Crash reporting
implementation "ch.acra:acra-core:5.7.0"
// Properly restarting
implementation 'com.jakewharton:process-phoenix:2.1.2'
// Reactive extensions for Java VM
implementation "io.reactivex.rxjava3:rxjava:3.0.7"
implementation "io.reactivex.rxjava3:rxandroid:3.0.0"

View File

@@ -0,0 +1,713 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "d8070091972a7011bce18aed62f80b90",
"entities": [
{
"tableName": "subscriptions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `name` TEXT, `avatar_url` TEXT, `subscriber_count` INTEGER, `description` TEXT)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "avatarUrl",
"columnName": "avatar_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subscriberCount",
"columnName": "subscriber_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_subscriptions_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_subscriptions_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "search_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `creation_date` INTEGER, `service_id` INTEGER NOT NULL, `search` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "creationDate",
"columnName": "creation_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "search",
"columnName": "search",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_search_history_search",
"unique": false,
"columnNames": [
"search"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_search_history_search` ON `${TABLE_NAME}` (`search`)"
}
],
"foreignKeys": []
},
{
"tableName": "streams",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `stream_type` TEXT NOT NULL, `duration` INTEGER NOT NULL, `uploader` TEXT NOT NULL, `uploader_url` TEXT, `thumbnail_url` TEXT, `view_count` INTEGER, `textual_upload_date` TEXT, `upload_date` INTEGER, `is_upload_date_approximation` INTEGER)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "streamType",
"columnName": "stream_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "uploaderUrl",
"columnName": "uploader_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "viewCount",
"columnName": "view_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "textualUploadDate",
"columnName": "textual_upload_date",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploadDate",
"columnName": "upload_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "isUploadDateApproximation",
"columnName": "is_upload_date_approximation",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_streams_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_streams_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "stream_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accessDate",
"columnName": "access_date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "repeatCount",
"columnName": "repeat_count",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"stream_id",
"access_date"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_stream_history_stream_id",
"unique": false,
"columnNames": [
"stream_id"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_stream_history_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
}
],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "stream_state",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "progressMillis",
"columnName": "progress_time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"stream_id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "playlists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `thumbnail_url` TEXT)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_playlists_name",
"unique": false,
"columnNames": [
"name"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_playlists_name` ON `${TABLE_NAME}` (`name`)"
}
],
"foreignKeys": []
},
{
"tableName": "playlist_stream_join",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "playlistUid",
"columnName": "playlist_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "index",
"columnName": "join_index",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"playlist_id",
"join_index"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_playlist_stream_join_playlist_id_join_index",
"unique": true,
"columnNames": [
"playlist_id",
"join_index"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_playlist_stream_join_playlist_id_join_index` ON `${TABLE_NAME}` (`playlist_id`, `join_index`)"
},
{
"name": "index_playlist_stream_join_stream_id",
"unique": false,
"columnNames": [
"stream_id"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_stream_join_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
}
],
"foreignKeys": [
{
"table": "playlists",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"playlist_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "remote_playlists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "streamCount",
"columnName": "stream_count",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_remote_playlists_name",
"unique": false,
"columnNames": [
"name"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_remote_playlists_name` ON `${TABLE_NAME}` (`name`)"
},
{
"name": "index_remote_playlists_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_playlists_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "feed",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `subscription_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "streamId",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"stream_id",
"subscription_id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_feed_subscription_id",
"unique": false,
"columnNames": [
"subscription_id"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
}
],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "feed_group",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `sort_order` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "sortOrder",
"columnName": "sort_order",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_feed_group_sort_order",
"unique": false,
"columnNames": [
"sort_order"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_sort_order` ON `${TABLE_NAME}` (`sort_order`)"
}
],
"foreignKeys": []
},
{
"tableName": "feed_group_subscription_join",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`group_id`, `subscription_id`), FOREIGN KEY(`group_id`) REFERENCES `feed_group`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "feedGroupId",
"columnName": "group_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"group_id",
"subscription_id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_feed_group_subscription_join_subscription_id",
"unique": false,
"columnNames": [
"subscription_id"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_subscription_join_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
}
],
"foreignKeys": [
{
"table": "feed_group",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"group_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "feed_last_updated",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`subscription_id` INTEGER NOT NULL, `last_updated` INTEGER, PRIMARY KEY(`subscription_id`), FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastUpdated",
"columnName": "last_updated",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"subscription_id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd8070091972a7011bce18aed62f80b90')"
]
}
}

View File

@@ -1,134 +0,0 @@
package org.schabi.newpipe.database
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import androidx.room.Room
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.schabi.newpipe.extractor.stream.StreamType
@RunWith(AndroidJUnit4::class)
class AppDatabaseTest {
companion object {
private const val DEFAULT_SERVICE_ID = 0
private const val DEFAULT_URL = "https://www.youtube.com/watch?v=cDphUib5iG4"
private const val DEFAULT_TITLE = "Test Title"
private val DEFAULT_TYPE = StreamType.VIDEO_STREAM
private const val DEFAULT_DURATION = 480L
private const val DEFAULT_UPLOADER_NAME = "Uploader Test"
private const val DEFAULT_THUMBNAIL = "https://example.com/example.jpg"
private const val DEFAULT_SECOND_SERVICE_ID = 0
private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
}
@get:Rule
val testHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory()
)
@Test
fun migrateDatabaseFrom2to3() {
val databaseInV2 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_2)
databaseInV2.run {
insert(
"streams", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
// put("uid", null)
put("service_id", DEFAULT_SERVICE_ID)
put("url", DEFAULT_URL)
put("title", DEFAULT_TITLE)
put("stream_type", DEFAULT_TYPE.name)
put("duration", DEFAULT_DURATION)
put("uploader", DEFAULT_UPLOADER_NAME)
put("thumbnail_url", DEFAULT_THUMBNAIL)
}
)
insert(
"streams", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
// put("uid", null)
put("service_id", DEFAULT_SECOND_SERVICE_ID)
put("url", DEFAULT_SECOND_URL)
// put("title", null)
// put("stream_type", null)
// put("duration", null)
// put("uploader", null)
// put("thumbnail_url", null)
}
)
insert(
"streams", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
// put("uid", null)
put("service_id", DEFAULT_SERVICE_ID)
// put("url", null)
// put("title", null)
// put("stream_type", null)
// put("duration", null)
// put("uploader", null)
// put("thumbnail_url", null)
}
)
close()
}
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
true, Migrations.MIGRATION_2_3
)
val migratedDatabaseV3 = getMigratedDatabase()
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
// Only expect 2, the one with the null url will be ignored
assertEquals(2, listFromDB.size)
val streamFromMigratedDatabase = listFromDB[0]
assertEquals(DEFAULT_SERVICE_ID, streamFromMigratedDatabase.serviceId)
assertEquals(DEFAULT_URL, streamFromMigratedDatabase.url)
assertEquals(DEFAULT_TITLE, streamFromMigratedDatabase.title)
assertEquals(DEFAULT_TYPE, streamFromMigratedDatabase.streamType)
assertEquals(DEFAULT_DURATION, streamFromMigratedDatabase.duration)
assertEquals(DEFAULT_UPLOADER_NAME, streamFromMigratedDatabase.uploader)
assertEquals(DEFAULT_THUMBNAIL, streamFromMigratedDatabase.thumbnailUrl)
assertNull(streamFromMigratedDatabase.viewCount)
assertNull(streamFromMigratedDatabase.textualUploadDate)
assertNull(streamFromMigratedDatabase.uploadDate)
assertNull(streamFromMigratedDatabase.isUploadDateApproximation)
val secondStreamFromMigratedDatabase = listFromDB[1]
assertEquals(DEFAULT_SECOND_SERVICE_ID, secondStreamFromMigratedDatabase.serviceId)
assertEquals(DEFAULT_SECOND_URL, secondStreamFromMigratedDatabase.url)
assertEquals("", secondStreamFromMigratedDatabase.title)
// Should fallback to VIDEO_STREAM
assertEquals(StreamType.VIDEO_STREAM, secondStreamFromMigratedDatabase.streamType)
assertEquals(0, secondStreamFromMigratedDatabase.duration)
assertEquals("", secondStreamFromMigratedDatabase.uploader)
assertEquals("", secondStreamFromMigratedDatabase.thumbnailUrl)
assertNull(secondStreamFromMigratedDatabase.viewCount)
assertNull(secondStreamFromMigratedDatabase.textualUploadDate)
assertNull(secondStreamFromMigratedDatabase.uploadDate)
assertNull(secondStreamFromMigratedDatabase.isUploadDateApproximation)
}
private fun getMigratedDatabase(): AppDatabase {
val database: AppDatabase = Room.databaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java, AppDatabase.DATABASE_NAME
)
.build()
testHelper.closeWhenFinished(database)
return database
}
}

View File

@@ -43,9 +43,11 @@ class LocalPlaylistManagerTest {
@Test
fun createPlaylist() {
val NEWPIPE_URL = "https://newpipe.net/"
val stream = StreamEntity(
serviceId = 1, url = "https://newpipe.net/", title = "title",
streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader"
serviceId = 1, url = NEWPIPE_URL, title = "title",
streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader",
uploaderUrl = NEWPIPE_URL
)
val result = manager.createPlaylist("name", listOf(stream))
@@ -69,12 +71,14 @@ class LocalPlaylistManagerTest {
fun createPlaylist_nonExistentStreamsAreUpserted() {
val stream = StreamEntity(
serviceId = 1, url = "https://newpipe.net/", title = "title",
streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader"
streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader",
uploaderUrl = "https://newpipe.net/"
)
database.streamDAO().insert(stream)
val upserted = StreamEntity(
serviceId = 1, url = "https://newpipe.net/2", title = "title2",
streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader"
streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader",
uploaderUrl = "https://newpipe.net/"
)
val result = manager.createPlaylist("name", listOf(stream, upserted))

View File

@@ -5,6 +5,7 @@ import android.os.Bundle;
import androidx.preference.Preference;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.PicassoHelper;
import leakcanary.LeakCanary;
@@ -15,10 +16,13 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
final Preference showMemoryLeaksPreference
= findPreference(getString(R.string.show_memory_leaks_key));
final Preference showImageIndicatorsPreference
= findPreference(getString(R.string.show_image_indicators_key));
final Preference crashTheAppPreference
= findPreference(getString(R.string.crash_the_app_key));
assert showMemoryLeaksPreference != null;
assert showImageIndicatorsPreference != null;
assert crashTheAppPreference != null;
showMemoryLeaksPreference.setOnPreferenceClickListener(preference -> {
@@ -26,6 +30,11 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
return true;
});
showImageIndicatorsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
PicassoHelper.setIndicatorsEnabled((Boolean) newValue);
return true;
});
crashTheAppPreference.setOnPreferenceClickListener(preference -> {
throw new RuntimeException();
});

View File

@@ -146,6 +146,7 @@
<data android:pathPrefix="/embed/" />
<data android:pathPrefix="/watch" />
<data android:pathPrefix="/attribution_link" />
<data android:pathPrefix="/shorts/" />
<!-- channel prefix -->
<data android:pathPrefix="/channel/" />
<data android:pathPrefix="/user/" />
@@ -224,6 +225,7 @@
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="tubus.eduvid.org" />
<data android:host="invidio.us" />
<data android:host="dev.invidio.us" />
<data android:host="www.invidio.us" />
@@ -309,6 +311,7 @@
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="eduvid.org" />
<data android:host="framatube.org" />
<data android:host="media.assassinate-you.net" />
<data android:host="peertube.co.uk" />
@@ -358,6 +361,9 @@
<service
android:name=".RouterActivity$FetcherService"
android:exported="false" />
<service
android:name=".CheckForNewAppVersion"
android:exported="false" />
<!-- opting out of sending metrics to Google in Android System WebView -->
<meta-data android:name="android.webkit.WebView.MetricsOptOut" android:value="true" />

View File

@@ -5,15 +5,12 @@ import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationChannelCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.multidex.MultiDexApplication;
import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.jakewharton.processphoenix.ProcessPhoenix;
import org.acra.ACRA;
import org.acra.config.ACRAConfigurationException;
@@ -28,6 +25,7 @@ import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
@@ -38,7 +36,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.exceptions.CompositeException;
import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
@@ -65,12 +62,9 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins;
*/
public class App extends MultiDexApplication {
protected static final String TAG = App.class.toString();
private static App app;
public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
@Nullable
private Disposable disposable = null;
private static final String TAG = App.class.toString();
private static App app;
@NonNull
public static App getApp() {
@@ -89,6 +83,12 @@ public class App extends MultiDexApplication {
app = this;
if (ProcessPhoenix.isPhoenixProcess(this)) {
Log.i(TAG, "This is a phoenix process! "
+ "Aborting initialization of App[onCreate]");
return;
}
// Initialize settings first because others inits can use its values
NewPipeSettings.initSettings(this);
@@ -103,20 +103,20 @@ public class App extends MultiDexApplication {
ServiceHelper.initServices(this);
// Initialize image loader
ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50));
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
PicassoHelper.init(this);
PicassoHelper.setShouldLoadImages(
prefs.getBoolean(getString(R.string.download_thumbnail_key), true));
PicassoHelper.setIndicatorsEnabled(MainActivity.DEBUG
&& prefs.getBoolean(getString(R.string.show_image_indicators_key), false));
configureRxJavaErrorHandler();
// Check for new version
disposable = CheckForNewAppVersion.checkNewVersion(this);
}
@Override
public void onTerminate() {
if (disposable != null) {
disposable.dispose();
}
super.onTerminate();
PicassoHelper.terminate();
}
protected Downloader getDownloader() {
@@ -201,15 +201,6 @@ public class App extends MultiDexApplication {
});
}
private ImageLoaderConfiguration getImageLoaderConfigurations(final int memoryCacheSizeMb,
final int diskCacheSizeMb) {
return new ImageLoaderConfiguration.Builder(this)
.memoryCache(new LRULimitedMemoryCache(memoryCacheSizeMb * 1024 * 1024))
.diskCacheSize(diskCacheSizeMb * 1024 * 1024)
.imageDownloader(new ImageDownloader(getApplicationContext()))
.build();
}
/**
* 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.
@@ -263,4 +254,5 @@ public class App extends MultiDexApplication {
protected boolean isDisposedRxExceptionsReported() {
return false;
}
}

View File

@@ -10,16 +10,13 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.nostra13.universalimageloader.core.ImageLoader;
import icepick.Icepick;
import icepick.State;
import leakcanary.AppWatcher;
public abstract class BaseFragment extends Fragment {
public static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance();
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
protected final boolean DEBUG = MainActivity.DEBUG;
protected static final boolean DEBUG = MainActivity.DEBUG;
protected AppCompatActivity activity;
//These values are used for controlling fragments when they are part of the frontpage
@State

View File

@@ -1,10 +1,10 @@
package org.schabi.newpipe;
import android.app.Application;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.ConnectivityManager;
@@ -16,6 +16,7 @@ import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.PackageInfoCompat;
import androidx.preference.PreferenceManager;
import com.grack.nanojson.JsonObject;
@@ -25,8 +26,11 @@ import com.grack.nanojson.JsonParserException;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.downloader.Response;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -34,14 +38,12 @@ import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
public final class CheckForNewAppVersion {
private CheckForNewAppVersion() { }
public final class CheckForNewAppVersion extends IntentService {
public CheckForNewAppVersion() {
super("CheckForNewAppVersion");
}
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = CheckForNewAppVersion.class.getSimpleName();
@@ -58,20 +60,22 @@ public final class CheckForNewAppVersion {
*/
@NonNull
private static String getCertificateSHA1Fingerprint(@NonNull final Application application) {
final PackageInfo packageInfo;
final List<Signature> signatures;
try {
packageInfo = application.getPackageManager().getPackageInfo(
application.getPackageName(), PackageManager.GET_SIGNATURES);
signatures = PackageInfoCompat.getSignatures(application.getPackageManager(),
application.getPackageName());
} catch (final PackageManager.NameNotFoundException e) {
ErrorActivity.reportError(application, new ErrorInfo(e,
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info"));
return "";
}
if (signatures.isEmpty()) {
return "";
}
final X509Certificate c;
try {
final Signature[] signatures = packageInfo.signatures;
final byte[] cert = signatures[0].toByteArray();
final byte[] cert = signatures.get(0).toByteArray();
final InputStream input = new ByteArrayInputStream(cert);
final CertificateFactory cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
@@ -165,78 +169,104 @@ public final class CheckForNewAppVersion {
return getCertificateSHA1Fingerprint(app).equals(GITHUB_APK_SHA1);
}
@Nullable
public static Disposable checkNewVersion(@NonNull final App app) {
private void checkNewVersion() throws IOException, ReCaptchaException {
final App app = App.getApp();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
final NewVersionManager manager = new NewVersionManager();
// Check if user has enabled/disabled update checking
// and if the current apk is a github one or not.
if (!prefs.getBoolean(app.getString(R.string.update_app_key), true) || !isGithubApk(app)) {
return null;
return;
}
// Check if the last request has happened a certain time ago
// to reduce the number of API requests.
final long expiry = prefs.getLong(app.getString(R.string.update_expiry_key), 0);
if (!manager.isExpired(expiry)) {
return null;
return;
}
return Maybe
.fromCallable(() -> {
if (!isConnected(app)) {
return null;
}
// Make a network request to get latest NewPipe data.
final Response response = DownloaderImpl.getInstance().get(NEWPIPE_API_URL);
handleResponse(response, manager, prefs, app);
}
// Make a network request to get latest NewPipe data.
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
response -> {
try {
// Store a timestamp which needs to be exceeded,
// before a new request to the API is made.
final long newExpiry = manager
.coerceExpiry(response.getHeader("expires"));
prefs.edit()
.putLong(app.getString(R.string.update_expiry_key), newExpiry)
.apply();
} catch (final Exception e) {
if (DEBUG) {
Log.w(TAG, "Could not extract and save new expiry date", e);
}
}
private void handleResponse(@NonNull final Response response,
@NonNull final NewVersionManager manager,
@NonNull final SharedPreferences prefs,
@NonNull final App app) {
try {
// Store a timestamp which needs to be exceeded,
// before a new request to the API is made.
final long newExpiry = manager
.coerceExpiry(response.getHeader("expires"));
prefs.edit()
.putLong(app.getString(R.string.update_expiry_key), newExpiry)
.apply();
} catch (final Exception e) {
if (DEBUG) {
Log.w(TAG, "Could not extract and save new expiry date", e);
}
}
// Parse the json from the response.
try {
final JsonObject githubStableObject = JsonParser.object()
.from(response.responseBody()).getObject("flavors")
.getObject("github").getObject("stable");
// Parse the json from the response.
try {
final JsonObject githubStableObject = JsonParser.object()
.from(response.responseBody()).getObject("flavors")
.getObject("github").getObject("stable");
final String versionName = githubStableObject
.getString("version");
final int versionCode = githubStableObject
.getInt("version_code");
final String apkLocationUrl = githubStableObject
.getString("apk");
final String versionName = githubStableObject
.getString("version");
final int versionCode = githubStableObject
.getInt("version_code");
final String apkLocationUrl = githubStableObject
.getString("apk");
compareAppVersionAndShowNotification(app, versionName,
apkLocationUrl, versionCode);
} catch (final JsonParserException e) {
// Most likely something is wrong in data received from NEWPIPE_API_URL.
// Do not alarm user and fail silently.
if (DEBUG) {
Log.w(TAG, "Could not get NewPipe API: invalid json", e);
}
}
}
/**
* Start a new service which
* checks if all conditions for performing a version check are met,
* fetches the API endpoint {@link #NEWPIPE_API_URL} containing info
* about the latest NewPipe version
* and displays a notification about ana available update.
* <br>
* Following conditions need to be met, before data is request from the server:
* <ul>
* <li> The app is signed with the correct signing key (by TeamNewPipe / schabi).
* If the signing key differs from the one used upstream, the update cannot be installed.</li>
* <li>The user enabled searching for and notifying about updates in the settings.</li>
* <li>The app did not recently check for updates.
* We do not want to make unnecessary connections and DOS our servers.</li>
* </ul>
* <b>Must not be executed</b> when the app is in background.
*/
public static void startNewVersionCheckService() {
final Intent intent = new Intent(App.getApp().getApplicationContext(),
CheckForNewAppVersion.class);
App.getApp().startService(intent);
}
@Override
protected void onHandleIntent(@Nullable final Intent intent) {
try {
checkNewVersion();
} catch (final IOException e) {
Log.w(TAG, "Could not fetch NewPipe API: probably network problem", e);
} catch (final ReCaptchaException e) {
Log.e(TAG, "ReCaptchaException should never happen here.", e);
}
compareAppVersionAndShowNotification(app, versionName,
apkLocationUrl, versionCode);
} catch (final JsonParserException e) {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, "Could not get NewPipe API: invalid json", e);
}
}
},
e -> {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, "Could not get NewPipe API: network problem", e);
}
});
}
}

View File

@@ -17,7 +17,6 @@ import org.schabi.newpipe.util.InfoCache;
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -194,36 +193,6 @@ public final class DownloaderImpl extends Downloader {
}
}
public InputStream stream(final String siteUrl) throws IOException {
try {
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
.method("GET", null).url(siteUrl)
.addHeader("User-Agent", USER_AGENT);
final String cookies = getCookies(siteUrl);
if (!cookies.isEmpty()) {
requestBuilder.addHeader("Cookie", cookies);
}
final okhttp3.Request request = requestBuilder.build();
final okhttp3.Response response = client.newCall(request).execute();
final ResponseBody body = response.body();
if (response.code() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
}
if (body == null) {
response.close();
return null;
}
return body.byteStream();
} catch (final ReCaptchaException e) {
throw new IOException(e.getMessage(), e.getCause());
}
}
@Override
public Response execute(@NonNull final Request request)
throws IOException, ReCaptchaException {

View File

@@ -1,48 +0,0 @@
package org.schabi.newpipe;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
import org.schabi.newpipe.extractor.NewPipe;
import java.io.IOException;
import java.io.InputStream;
public class ImageDownloader extends BaseImageDownloader {
private final Resources resources;
private final SharedPreferences preferences;
private final String downloadThumbnailKey;
public ImageDownloader(final Context context) {
super(context);
this.resources = context.getResources();
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
this.downloadThumbnailKey = context.getString(R.string.download_thumbnail_key);
}
private boolean isDownloadingThumbnail() {
return preferences.getBoolean(downloadThumbnailKey, true);
}
@SuppressLint("ResourceType")
@Override
public InputStream getStream(final String imageUri, final Object extra) throws IOException {
if (isDownloadingThumbnail()) {
return super.getStream(imageUri, extra);
} else {
return resources.openRawResource(R.drawable.dummy_thumbnail_dark);
}
}
protected InputStream getStreamFromNetwork(final String imageUri, final Object extra)
throws IOException {
final DownloaderImpl downloader = (DownloaderImpl) NewPipe.getDownloader();
return downloader.stream(imageUri);
}
}

View File

@@ -20,6 +20,9 @@
package org.schabi.newpipe;
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -91,8 +94,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@SuppressWarnings("ConstantConditions")
@@ -165,6 +166,15 @@ public class MainActivity extends AppCompatActivity {
openMiniPlayerUponPlayerStarted();
}
@Override
protected void onPostCreate(final Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Start the service which is checking all conditions
// and eventually searching for a new version.
// The service searching for a new NewPipe version must not be started in background.
startNewVersionCheckService();
}
private void setupDrawer() throws Exception {
//Tabs
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);

View File

@@ -11,6 +11,7 @@ import org.schabi.newpipe.database.AppDatabase;
import static org.schabi.newpipe.database.AppDatabase.DATABASE_NAME;
import static org.schabi.newpipe.database.Migrations.MIGRATION_1_2;
import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3;
import static org.schabi.newpipe.database.Migrations.MIGRATION_3_4;
public final class NewPipeDatabase {
private static volatile AppDatabase databaseInstance;
@@ -22,7 +23,7 @@ public final class NewPipeDatabase {
private static AppDatabase getDatabase(final Context context) {
return Room
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
.build();
}

View File

@@ -0,0 +1,70 @@
package org.schabi.newpipe;
import static org.schabi.newpipe.util.external_communication.ShareUtils.shareText;
import android.content.Context;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.widget.PopupMenu;
import androidx.fragment.app.FragmentManager;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.Collections;
public final class QueueItemMenuUtil {
public static void openPopupMenu(final PlayQueue playQueue,
final PlayQueueItem item,
final View view,
final boolean hideDetails,
final FragmentManager fragmentManager,
final Context context) {
final ContextThemeWrapper themeWrapper =
new ContextThemeWrapper(context, R.style.DarkPopupMenu);
final PopupMenu popupMenu = new PopupMenu(themeWrapper, view);
popupMenu.inflate(R.menu.menu_play_queue_item);
if (hideDetails) {
popupMenu.getMenu().findItem(R.id.menu_item_details).setVisible(false);
}
popupMenu.setOnMenuItemClickListener(menuItem -> {
switch (menuItem.getItemId()) {
case R.id.menu_item_remove:
final int index = playQueue.indexOf(item);
playQueue.remove(index);
return true;
case R.id.menu_item_details:
// playQueue is null since we don't want any queue change
NavigationHelper.openVideoDetail(context, item.getServiceId(),
item.getUrl(), item.getTitle(), null,
false);
return true;
case R.id.menu_item_append_playlist:
final PlaylistAppendDialog d = PlaylistAppendDialog.fromPlayQueueItems(
Collections.singletonList(item)
);
PlaylistAppendDialog.onPlaylistFound(context,
() -> d.show(fragmentManager, "QueueItemMenuUtil@append_playlist"),
() -> PlaylistCreationDialog.newInstance(d)
.show(fragmentManager, "QueueItemMenuUtil@append_playlist"));
return true;
case R.id.menu_item_share:
shareText(context, item.getTitle(), item.getUrl(),
item.getThumbnailUrl());
return true;
}
return false;
});
popupMenu.show();
}
private QueueItemMenuUtil() { }
}

View File

@@ -162,10 +162,18 @@ class AboutActivity : AppCompatActivity() {
"OkHttp", "2019", "Square, Inc.",
"https://square.github.io/okhttp/", StandardLicenses.APACHE2
),
SoftwareComponent(
"Picasso", "2013", "Square, Inc.",
"https://square.github.io/picasso/", StandardLicenses.APACHE2
),
SoftwareComponent(
"PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
"https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2
),
SoftwareComponent(
"ProcessPhoenix", "2015", "Jake Wharton",
"https://github.com/JakeWharton/ProcessPhoenix", StandardLicenses.APACHE2
),
SoftwareComponent(
"RxAndroid", "2015", "The RxAndroid authors",
"https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2
@@ -177,11 +185,6 @@ class AboutActivity : AppCompatActivity() {
SoftwareComponent(
"RxJava", "2016 - 2020", "RxJava Contributors",
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2
),
SoftwareComponent(
"Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
"https://github.com/nostra13/Android-Universal-Image-Loader",
StandardLicenses.APACHE2
)
)
private const val POS_ABOUT = 0

View File

@@ -1,7 +1,7 @@
package org.schabi.newpipe.about
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
import java.io.Serializable
/**

View File

@@ -108,7 +108,7 @@ object LicenseFragmentHelper {
alert.setView(webView)
Localization.assureCorrectAppLanguage(context)
alert.setNegativeButton(
context.getString(R.string.finish)
context.getString(R.string.ok)
) { dialog, _ -> dialog.dismiss() }
alert.show()
}

View File

@@ -1,7 +1,7 @@
package org.schabi.newpipe.about
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
@Parcelize
class SoftwareComponent

View File

@@ -27,7 +27,7 @@ import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.database.subscription.SubscriptionDAO;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import static org.schabi.newpipe.database.Migrations.DB_VER_3;
import static org.schabi.newpipe.database.Migrations.DB_VER_4;
@TypeConverters({Converters.class})
@Database(
@@ -38,7 +38,7 @@ import static org.schabi.newpipe.database.Migrations.DB_VER_3;
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
FeedLastUpdatedEntity.class
},
version = DB_VER_3
version = DB_VER_4
)
public abstract class AppDatabase extends RoomDatabase {
public static final String DATABASE_NAME = "newpipe.db";

View File

@@ -9,9 +9,19 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
import org.schabi.newpipe.MainActivity;
public final class Migrations {
/////////////////////////////////////////////////////////////////////////////
// Test new migrations manually by importing a database from daily usage //
// and checking if the migration works (Use the Database Inspector //
// https://developer.android.com/studio/inspect/database). //
// If you add a migration point it out in the pull request, so that //
// others remember to test it themselves. //
/////////////////////////////////////////////////////////////////////////////
public static final int DB_VER_1 = 1;
public static final int DB_VER_2 = 2;
public static final int DB_VER_3 = 3;
public static final int DB_VER_4 = 4;
private static final String TAG = Migrations.class.getName();
public static final boolean DEBUG = MainActivity.DEBUG;
@@ -160,5 +170,14 @@ public final class Migrations {
}
};
public static final Migration MIGRATION_3_4 = new Migration(DB_VER_3, DB_VER_4) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
database.execSQL(
"ALTER TABLE streams ADD COLUMN uploader_url TEXT"
);
}
};
private Migrations() { }
}

View File

@@ -27,6 +27,7 @@ data class PlaylistStreamEntry(
val item = StreamInfoItem(streamEntity.serviceId, streamEntity.url, streamEntity.title, streamEntity.streamType)
item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnailUrl = streamEntity.thumbnailUrl
return item

View File

@@ -29,6 +29,7 @@ class StreamStatisticsEntry(
val item = StreamInfoItem(streamEntity.serviceId, streamEntity.url, streamEntity.title, streamEntity.streamType)
item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnailUrl = streamEntity.thumbnailUrl
return item

View File

@@ -6,6 +6,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Flowable
import org.schabi.newpipe.database.BasicDAO
import org.schabi.newpipe.database.stream.model.StreamEntity
@@ -29,6 +30,9 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
@Query("SELECT * FROM streams WHERE url = :url AND service_id = :serviceId")
abstract fun getStream(serviceId: Long, url: String): Flowable<List<StreamEntity>>
@Query("UPDATE streams SET uploader_url = :uploaderUrl WHERE url = :url AND service_id = :serviceId")
abstract fun setUploaderUrl(serviceId: Long, url: String, uploaderUrl: String): Completable
@Insert(onConflict = OnConflictStrategy.IGNORE)
internal abstract fun silentInsertInternal(stream: StreamEntity): Long

View File

@@ -45,6 +45,9 @@ data class StreamEntity(
@ColumnInfo(name = STREAM_UPLOADER)
var uploader: String,
@ColumnInfo(name = STREAM_UPLOADER_URL)
var uploaderUrl: String? = null,
@ColumnInfo(name = STREAM_THUMBNAIL_URL)
var thumbnailUrl: String? = null,
@@ -64,7 +67,7 @@ data class StreamEntity(
constructor(item: StreamInfoItem) : this(
serviceId = item.serviceId, url = item.url, title = item.name,
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount,
uploaderUrl = item.uploaderUrl, thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount,
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(),
isUploadDateApproximation = item.uploadDate?.isApproximation
)
@@ -73,7 +76,7 @@ data class StreamEntity(
constructor(info: StreamInfo) : this(
serviceId = info.serviceId, url = info.url, title = info.name,
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount,
uploaderUrl = info.uploaderUrl, thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount,
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(),
isUploadDateApproximation = info.uploadDate?.isApproximation
)
@@ -82,13 +85,14 @@ data class StreamEntity(
constructor(item: PlayQueueItem) : this(
serviceId = item.serviceId, url = item.url, title = item.title,
streamType = item.streamType, duration = item.duration, uploader = item.uploader,
thumbnailUrl = item.thumbnailUrl
uploaderUrl = item.uploaderUrl, thumbnailUrl = item.thumbnailUrl
)
fun toStreamInfoItem(): StreamInfoItem {
val item = StreamInfoItem(serviceId, url, title, streamType)
item.duration = duration
item.uploaderName = uploader
item.uploaderUrl = uploaderUrl
item.thumbnailUrl = thumbnailUrl
if (viewCount != null) item.viewCount = viewCount as Long
@@ -109,6 +113,7 @@ data class StreamEntity(
const val STREAM_TYPE = "stream_type"
const val STREAM_DURATION = "duration"
const val STREAM_UPLOADER = "uploader"
const val STREAM_UPLOADER_URL = "uploader_url"
const val STREAM_THUMBNAIL_URL = "thumbnail_url"
const val STREAM_VIEWS = "view_count"

View File

@@ -681,7 +681,7 @@ public class DownloadDialog extends DialogFragment
new AlertDialog.Builder(context)
.setTitle(R.string.general_error)
.setMessage(msg)
.setNegativeButton(getString(R.string.finish), null)
.setNegativeButton(getString(R.string.ok), null)
.create()
.show();
}
@@ -864,7 +864,7 @@ public class DownloadDialog extends DialogFragment
final AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
.setTitle(R.string.download_dialog_title)
.setMessage(msgBody)
.setNegativeButton(android.R.string.cancel, null);
.setNegativeButton(R.string.cancel, null);
final StoredFileHelper finalStorage = storage;

View File

@@ -2,7 +2,7 @@ package org.schabi.newpipe.error
import android.os.Parcelable
import androidx.annotation.StringRes
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.Info
import org.schabi.newpipe.extractor.NewPipe

View File

@@ -6,6 +6,8 @@ import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.annotation.Nullable
import androidx.annotation.StringRes
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import com.jakewharton.rxbinding4.view.clicks
@@ -37,22 +39,39 @@ class ErrorPanelHelper(
onRetry: Runnable
) {
private val context: Context = rootView.context!!
private val errorPanelRoot: View = rootView.findViewById(R.id.error_panel)
private val errorTextView: TextView = errorPanelRoot.findViewById(R.id.error_message_view)
private val errorServiceInfoTextView: TextView = errorPanelRoot.findViewById(R.id.error_message_service_info_view)
private val errorServiceExplenationTextView: TextView = errorPanelRoot.findViewById(R.id.error_message_service_explenation_view)
private val errorButtonAction: Button = errorPanelRoot.findViewById(R.id.error_button_action)
private val errorButtonRetry: Button = errorPanelRoot.findViewById(R.id.error_button_retry)
// the only element that is visible by default
private val errorTextView: TextView =
errorPanelRoot.findViewById(R.id.error_message_view)
private val errorServiceInfoTextView: TextView =
errorPanelRoot.findViewById(R.id.error_message_service_info_view)
private val errorServiceExplanationTextView: TextView =
errorPanelRoot.findViewById(R.id.error_message_service_explanation_view)
private val errorActionButton: Button =
errorPanelRoot.findViewById(R.id.error_action_button)
private val errorRetryButton: Button =
errorPanelRoot.findViewById(R.id.error_retry_button)
private var errorDisposable: Disposable? = null
init {
errorDisposable = errorButtonRetry.clicks()
errorDisposable = errorRetryButton.clicks()
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { onRetry.run() }
}
private fun ensureDefaultVisibility() {
errorTextView.isVisible = true
errorServiceInfoTextView.isVisible = false
errorServiceExplanationTextView.isVisible = false
errorActionButton.isVisible = false
errorRetryButton.isVisible = false
}
fun showError(errorInfo: ErrorInfo) {
if (errorInfo.throwable != null && errorInfo.throwable!!.isInterruptedCaused) {
@@ -62,10 +81,14 @@ class ErrorPanelHelper(
return
}
errorButtonAction.isVisible = true
ensureDefaultVisibility()
if (errorInfo.throwable is ReCaptchaException) {
errorButtonAction.setText(R.string.recaptcha_solve)
errorButtonAction.setOnClickListener {
errorTextView.setText(R.string.recaptcha_request_toast)
showAndSetErrorButtonAction(
R.string.recaptcha_solve
) {
// Starting ReCaptcha Challenge Activity
val intent = Intent(context, ReCaptchaActivity::class.java)
intent.putExtra(
@@ -73,78 +96,70 @@ class ErrorPanelHelper(
(errorInfo.throwable as ReCaptchaException).url
)
fragment.startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST)
errorButtonAction.setOnClickListener(null)
errorActionButton.setOnClickListener(null)
}
errorTextView.setText(R.string.recaptcha_request_toast)
// additional info is only provided by AccountTerminatedException
errorServiceInfoTextView.isVisible = false
errorServiceExplenationTextView.isVisible = false
errorButtonRetry.isVisible = true
errorRetryButton.isVisible = true
} else if (errorInfo.throwable is AccountTerminatedException) {
errorButtonRetry.isVisible = false
errorButtonAction.isVisible = false
errorTextView.setText(R.string.account_terminated)
if (!isNullOrEmpty((errorInfo.throwable as AccountTerminatedException).message)) {
errorServiceInfoTextView.setText(
context.resources.getString(
R.string.service_provides_reason,
NewPipe.getNameOfService(ServiceHelper.getSelectedServiceId(context))
)
)
errorServiceExplenationTextView.setText(
(errorInfo.throwable as AccountTerminatedException).message
errorServiceInfoTextView.text = context.resources.getString(
R.string.service_provides_reason,
NewPipe.getNameOfService(ServiceHelper.getSelectedServiceId(context))
)
errorServiceInfoTextView.isVisible = true
errorServiceExplenationTextView.isVisible = true
} else {
errorServiceInfoTextView.isVisible = false
errorServiceExplenationTextView.isVisible = false
errorServiceExplanationTextView.text =
(errorInfo.throwable as AccountTerminatedException).message
errorServiceExplanationTextView.isVisible = true
}
} else {
errorButtonAction.setText(R.string.error_snackbar_action)
errorButtonAction.setOnClickListener {
showAndSetErrorButtonAction(
R.string.error_snackbar_action
) {
ErrorActivity.reportError(context, errorInfo)
}
// additional info is only provided by AccountTerminatedException
errorServiceInfoTextView.isVisible = false
errorServiceExplenationTextView.isVisible = false
errorTextView.setText(getExceptionDescription(errorInfo.throwable))
// hide retry button by default, then show only if not unavailable/unsupported content
errorButtonRetry.isVisible = false
errorTextView.setText(
when (errorInfo.throwable) {
is AgeRestrictedContentException -> R.string.restricted_video_no_stream
is GeographicRestrictionException -> R.string.georestricted_content
is PaidContentException -> R.string.paid_content
is PrivateContentException -> R.string.private_content
is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content
is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content
is ContentNotAvailableException -> R.string.content_not_available
is ContentNotSupportedException -> R.string.content_not_supported
else -> {
// show retry button only for content which is not unavailable or unsupported
errorButtonRetry.isVisible = true
if (errorInfo.throwable != null && errorInfo.throwable!!.isNetworkRelated) {
R.string.network_error
} else {
R.string.error_snackbar_message
}
}
}
)
if (errorInfo.throwable !is ContentNotAvailableException &&
errorInfo.throwable !is ContentNotSupportedException
) {
// show retry button only for content which is not unavailable or unsupported
errorRetryButton.isVisible = true
}
}
errorPanelRoot.animate(true, 300)
setRootVisible()
}
/**
* Shows the errorButtonAction, sets a text into it and sets the click listener.
*/
private fun showAndSetErrorButtonAction(
@StringRes resid: Int,
@Nullable listener: View.OnClickListener
) {
errorActionButton.isVisible = true
errorActionButton.setText(resid)
errorActionButton.setOnClickListener(listener)
}
fun showTextError(errorString: String) {
errorButtonAction.isVisible = false
errorButtonRetry.isVisible = false
ensureDefaultVisibility()
errorTextView.text = errorString
setRootVisible()
}
private fun setRootVisible() {
errorPanelRoot.animate(true, 300)
}
fun hide() {
errorButtonAction.setOnClickListener(null)
errorActionButton.setOnClickListener(null)
errorPanelRoot.animate(false, 150)
}
@@ -153,13 +168,35 @@ class ErrorPanelHelper(
}
fun dispose() {
errorButtonAction.setOnClickListener(null)
errorButtonRetry.setOnClickListener(null)
errorActionButton.setOnClickListener(null)
errorRetryButton.setOnClickListener(null)
errorDisposable?.dispose()
}
companion object {
val TAG: String = ErrorPanelHelper::class.simpleName!!
val DEBUG: Boolean = MainActivity.DEBUG
@StringRes
public fun getExceptionDescription(throwable: Throwable?): Int {
return when (throwable) {
is AgeRestrictedContentException -> R.string.restricted_video_no_stream
is GeographicRestrictionException -> R.string.georestricted_content
is PaidContentException -> R.string.paid_content
is PrivateContentException -> R.string.private_content
is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content
is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content
is ContentNotAvailableException -> R.string.content_not_available
is ContentNotSupportedException -> R.string.content_not_supported
else -> {
// show retry button only for content which is not unavailable or unsupported
if (throwable != null && throwable.isNetworkRelated) {
R.string.network_error
} else {
R.string.error_snackbar_message
}
}
}
}
}
}

View File

@@ -1,5 +1,6 @@
package org.schabi.newpipe.error;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
@@ -66,6 +67,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
private ActivityRecaptchaBinding recaptchaBinding;
private String foundCookies = "";
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(final Bundle savedInstanceState) {
ThemeHelper.setTheme(this);

View File

@@ -48,9 +48,7 @@ import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.tabs.TabLayout;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import com.squareup.picasso.Callback;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
@@ -79,6 +77,7 @@ import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.MainPlayer.PlayerType;
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
@@ -90,14 +89,14 @@ import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.util.ArrayList;
import java.util.Iterator;
@@ -151,6 +150,8 @@ public final class VideoDetailFragment
private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB";
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
private static final String PICASSO_VIDEO_DETAILS_TAG = "PICASSO_VIDEO_DETAILS_TAG";
// tabs
private boolean showComments;
private boolean showRelatedItems;
@@ -201,7 +202,7 @@ public final class VideoDetailFragment
@Nullable
private MainPlayer playerService;
private Player player;
private PlayerHolder playerHolder = PlayerHolder.getInstance();
private final PlayerHolder playerHolder = PlayerHolder.getInstance();
/*//////////////////////////////////////////////////////////////////////////
// Service management
@@ -220,7 +221,7 @@ public final class VideoDetailFragment
return;
}
if (isLandscape()) {
if (DeviceUtils.isLandscape(requireContext())) {
// If the video is playing but orientation changed
// let's make the video in fullscreen again
checkLandscape();
@@ -241,7 +242,7 @@ public final class VideoDetailFragment
&& isAutoplayEnabled()
&& player.getParentActivity() == null)) {
autoPlayEnabled = true; // forcefully start playing
openVideoPlayer();
openVideoPlayerAutoFullscreen();
}
}
@@ -423,7 +424,7 @@ public final class VideoDetailFragment
showRelatedItems = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true;
} else if (key.equals(getString(R.string.show_description_key))) {
showComments = sharedPreferences.getBoolean(key, true);
showDescription = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true;
}
}
@@ -499,7 +500,7 @@ public final class VideoDetailFragment
break;
case R.id.detail_thumbnail_root_layout:
autoPlayEnabled = true; // forcefully start playing
openVideoPlayer();
openVideoPlayerAutoFullscreen();
break;
case R.id.detail_title_root_layout:
toggleTitleAndSecondaryControls();
@@ -516,7 +517,7 @@ public final class VideoDetailFragment
showSystemUi();
} else {
autoPlayEnabled = true; // forcefully start playing
openVideoPlayer();
openVideoPlayer(false);
}
setOverlayPlayPauseImage(isPlayerAvailable() && player.isPlaying());
@@ -686,33 +687,24 @@ public final class VideoDetailFragment
}
private void initThumbnailViews(@NonNull final StreamInfo info) {
binding.detailThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
PicassoHelper.loadThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
.into(binding.detailThumbnailImageView, new Callback() {
@Override
public void onSuccess() {
// nothing to do, the image was loaded correctly into the thumbnail
}
if (!isEmpty(info.getThumbnailUrl())) {
final ImageLoadingListener onFailListener = new SimpleImageLoadingListener() {
@Override
public void onLoadingFailed(final String imageUri, final View view,
final FailReason failReason) {
showSnackBarError(new ErrorInfo(failReason.getCause(), UserAction.LOAD_IMAGE,
imageUri, info));
}
};
@Override
public void onError(final Exception e) {
showSnackBarError(new ErrorInfo(e, UserAction.LOAD_IMAGE,
info.getThumbnailUrl(), info));
}
});
IMAGE_LOADER.displayImage(info.getThumbnailUrl(), binding.detailThumbnailImageView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener);
}
if (!isEmpty(info.getSubChannelAvatarUrl())) {
IMAGE_LOADER.displayImage(info.getSubChannelAvatarUrl(),
binding.detailSubChannelThumbnailView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
}
if (!isEmpty(info.getUploaderAvatarUrl())) {
IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(),
binding.detailUploaderThumbnailView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
}
PicassoHelper.loadAvatar(info.getSubChannelAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
.into(binding.detailSubChannelThumbnailView);
PicassoHelper.loadAvatar(info.getUploaderAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
.into(binding.detailUploaderThumbnailView);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -751,27 +743,26 @@ public final class VideoDetailFragment
&& player.getPlayQueue() != null
&& player.videoPlayerSelected()
&& player.getPlayQueue().previous()) {
return true;
return true; // no code here, as previous() was used in the if
}
// That means that we are on the start of the stack,
// return false to let the MainActivity handle the onBack
if (stack.size() <= 1) {
restoreDefaultOrientation();
return false;
return false; // let MainActivity handle the onBack (e.g. to minimize the mini player)
}
// Remove top
stack.pop();
// Get stack item from the new top
assert stack.peek() != null;
setupFromHistoryItem(stack.peek());
setupFromHistoryItem(Objects.requireNonNull(stack.peek()));
return true;
}
private void setupFromHistoryItem(final StackItem item) {
setAutoPlay(false);
hideMainPlayer();
hideMainPlayerOnLoadingNewStream();
setInitialData(item.getServiceId(), item.getUrl(),
item.getTitle() == null ? "" : item.getTitle(), item.getPlayQueue());
@@ -891,7 +882,7 @@ public final class VideoDetailFragment
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
isLoading.set(false);
hideMainPlayer();
hideMainPlayerOnLoadingNewStream();
if (result.getAgeLimit() != NO_AGE_LIMIT && !prefs.getBoolean(
getString(R.string.show_age_restricted_content), false)) {
hideAgeRestrictedContent();
@@ -906,8 +897,9 @@ public final class VideoDetailFragment
stack.push(new StackItem(serviceId, url, title, playQueue));
}
}
if (isAutoplayEnabled()) {
openVideoPlayer();
openVideoPlayerAutoFullscreen();
}
}
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
@@ -1104,15 +1096,37 @@ public final class VideoDetailFragment
toggleFullscreenIfInFullscreenMode();
final PlayQueue queue = setupPlayQueueForIntent(append);
if (append) {
NavigationHelper.enqueueOnPopupPlayer(activity, queue, false);
if (append) { //resumePlayback: false
NavigationHelper.enqueueOnPlayer(activity, queue, PlayerType.POPUP);
} else {
replaceQueueIfUserConfirms(() -> NavigationHelper
.playOnPopupPlayer(activity, queue, true));
}
}
public void openVideoPlayer() {
/**
* Opens the video player, in fullscreen if needed. In order to open fullscreen, the activity
* is toggled to landscape orientation (which will then cause fullscreen mode).
*
* @param directlyFullscreenIfApplicable whether to open fullscreen if we are not already
* in landscape and screen orientation is locked
*/
public void openVideoPlayer(final boolean directlyFullscreenIfApplicable) {
if (directlyFullscreenIfApplicable
&& !DeviceUtils.isLandscape(requireContext())
&& PlayerHelper.globalScreenOrientationLocked(requireContext())) {
// Make sure the bottom sheet turns out expanded. When this code kicks in the bottom
// sheet could not have fully expanded yet, and thus be in the STATE_SETTLING state.
// When the activity is rotated, and its state is saved and then restored, the bottom
// sheet would forget what it was doing, since even if STATE_SETTLING is restored, it
// doesn't tell which state it was settling to, and thus the bottom sheet settles to
// STATE_COLLAPSED. This can be solved by manually setting the state that will be
// restored (i.e. bottomSheetState) to STATE_EXPANDED.
bottomSheetState = BottomSheetBehavior.STATE_EXPANDED;
// toggle landscape in order to open directly in fullscreen
onScreenRotationButtonClicked();
}
if (PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
showExternalPlaybackDialog();
@@ -1121,6 +1135,18 @@ public final class VideoDetailFragment
}
}
/**
* If the option to start directly fullscreen is enabled, calls
* {@link #openVideoPlayer(boolean)} with {@code directlyFullscreenIfApplicable = true}, so that
* if the user is not already in landscape and he has screen orientation locked the activity
* rotates and fullscreen starts. Otherwise, if the option to start directly fullscreen is
* disabled, calls {@link #openVideoPlayer(boolean)} with {@code directlyFullscreenIfApplicable
* = false}, hence preventing it from going directly fullscreen.
*/
public void openVideoPlayerAutoFullscreen() {
openVideoPlayer(PlayerHelper.isStartMainPlayerFullscreenEnabled(requireContext()));
}
private void openNormalBackgroundPlayer(final boolean append) {
// See UI changes while remote playQueue changes
if (!isPlayerAvailable()) {
@@ -1129,7 +1155,7 @@ public final class VideoDetailFragment
final PlayQueue queue = setupPlayQueueForIntent(append);
if (append) {
NavigationHelper.enqueueOnBackgroundPlayer(activity, queue, false);
NavigationHelper.enqueueOnPlayer(activity, queue, PlayerType.AUDIO);
} else {
replaceQueueIfUserConfirms(() -> NavigationHelper
.playOnBackgroundPlayer(activity, queue, true));
@@ -1154,12 +1180,19 @@ public final class VideoDetailFragment
}
addVideoPlayerView();
final Intent playerIntent = NavigationHelper
.getPlayerIntent(requireContext(), MainPlayer.class, queue, true, autoPlayEnabled);
final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(),
MainPlayer.class, queue, true, autoPlayEnabled);
ContextCompat.startForegroundService(activity, playerIntent);
}
private void hideMainPlayer() {
/**
* When the video detail fragment is already showing details for a video and the user opens a
* new one, the video detail fragment changes all of its old data to the new stream, so if there
* is a video player currently open it should be hidden. This method does exactly that. If
* autoplay is enabled, the underlying player is not stopped completely, since it is going to
* be reused in a few milliseconds and the flickering would be annoying.
*/
private void hideMainPlayerOnLoadingNewStream() {
if (!isPlayerServiceAvailable()
|| playerService.getView() == null
|| !player.videoPlayerSelected()) {
@@ -1167,8 +1200,12 @@ public final class VideoDetailFragment
}
removeVideoPlayerView();
playerService.stop(isAutoplayEnabled());
playerService.getView().setVisibility(View.GONE);
if (isAutoplayEnabled()) {
playerService.stopForImmediateReusing();
playerService.getView().setVisibility(View.GONE);
} else {
playerHolder.stopService();
}
}
private PlayQueue setupPlayQueueForIntent(final boolean append) {
@@ -1261,7 +1298,7 @@ public final class VideoDetailFragment
final DisplayMetrics metrics = getResources().getDisplayMetrics();
if (getView() != null) {
final int height = (isInMultiWindow()
final int height = (DeviceUtils.isInMultiWindow(activity)
? requireView()
: activity.getWindow().getDecorView()).getHeight();
setHeightThumbnail(height, metrics);
@@ -1284,7 +1321,7 @@ public final class VideoDetailFragment
requireView().getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
if (isPlayerAvailable() && player.isFullscreen()) {
final int height = (isInMultiWindow()
final int height = (DeviceUtils.isInMultiWindow(activity)
? requireView()
: activity.getWindow().getDecorView()).getHeight();
// Height is zero when the view is not yet displayed like after orientation change
@@ -1395,17 +1432,15 @@ public final class VideoDetailFragment
//////////////////////////////////////////////////////////////////////////*/
private void restoreDefaultOrientation() {
if (!isPlayerAvailable() || !player.videoPlayerSelected() || activity == null) {
return;
if (isPlayerAvailable() && player.videoPlayerSelected()) {
toggleFullscreenIfInFullscreenMode();
}
toggleFullscreenIfInFullscreenMode();
// This will show systemUI and pause the player.
// User can tap on Play button and video will be in fullscreen mode again
// Note for tablet: trying to avoid orientation changes since it's not easy
// to physically rotate the tablet every time
if (!DeviceUtils.isTablet(activity)) {
if (activity != null && !DeviceUtils.isTablet(activity)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
}
@@ -1446,8 +1481,7 @@ public final class VideoDetailFragment
}
}
IMAGE_LOADER.cancelDisplayTask(binding.detailThumbnailImageView);
IMAGE_LOADER.cancelDisplayTask(binding.detailSubChannelThumbnailView);
PicassoHelper.cancelTag(PICASSO_VIDEO_DETAILS_TAG);
binding.detailThumbnailImageView.setImageBitmap(null);
binding.detailSubChannelThumbnailView.setImageBitmap(null);
}
@@ -1818,7 +1852,7 @@ public final class VideoDetailFragment
|| error.type == ExoPlaybackException.TYPE_UNEXPECTED) {
// Properly exit from fullscreen
toggleFullscreenIfInFullscreenMode();
hideMainPlayer();
hideMainPlayerOnLoadingNewStream();
}
}
@@ -1874,13 +1908,14 @@ public final class VideoDetailFragment
// from landscape to portrait every time.
// Just turn on fullscreen mode in landscape orientation
// or portrait & unlocked global orientation
final boolean isLandscape = DeviceUtils.isLandscape(requireContext());
if (DeviceUtils.isTablet(activity)
&& (!globalScreenOrientationLocked(activity) || isLandscape())) {
&& (!globalScreenOrientationLocked(activity) || isLandscape)) {
player.toggleFullscreen();
return;
}
final int newOrientation = isLandscape()
final int newOrientation = isLandscape
? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
: ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
@@ -1952,15 +1987,17 @@ public final class VideoDetailFragment
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
// In multiWindow mode status bar is not transparent for devices with cutout
// if I include this flag. So without it is better in this case
if (!isInMultiWindow()) {
final boolean isInMultiWindow = DeviceUtils.isInMultiWindow(activity);
if (!isInMultiWindow) {
visibility |= View.SYSTEM_UI_FLAG_FULLSCREEN;
}
activity.getWindow().getDecorView().setSystemUiVisibility(visibility);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& (isInMultiWindow() || (isPlayerAvailable() && player.isFullscreen()))) {
&& (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen()))) {
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
activity.getWindow().setNavigationBarColor(Color.TRANSPARENT);
}
@@ -2032,15 +2069,6 @@ public final class VideoDetailFragment
}
}
private boolean isLandscape() {
return getResources().getDisplayMetrics().heightPixels < getResources()
.getDisplayMetrics().widthPixels;
}
private boolean isInMultiWindow() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && activity.isInMultiWindowMode();
}
/*
* Means that the player fragment was swiped away via BottomSheetLayout
* and is empty but ready for any new actions. See cleanUp()
@@ -2080,8 +2108,8 @@ public final class VideoDetailFragment
private void showClearingQueueConfirmation(final Runnable onAllow) {
new AlertDialog.Builder(activity)
.setTitle(R.string.clear_queue_confirmation_description)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, (dialog, which) -> {
onAllow.run();
dialog.dismiss();
}).show();
@@ -2096,7 +2124,7 @@ public final class VideoDetailFragment
resolutions[i] = sortedVideoStreams.get(i).getResolution();
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
.setNegativeButton(android.R.string.cancel, null)
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.open_in_browser, (dialog, i) ->
ShareUtils.openUrlInBrowser(requireActivity(), url)
);
@@ -2223,7 +2251,7 @@ public final class VideoDetailFragment
setOverlayElementsClickable(false);
hideSystemUiIfNeeded();
// Conditions when the player should be expanded to fullscreen
if (isLandscape()
if (DeviceUtils.isLandscape(requireContext())
&& isPlayerAvailable()
&& player.isPlaying()
&& !player.isFullscreen()
@@ -2278,10 +2306,8 @@ public final class VideoDetailFragment
binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle);
binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
binding.overlayThumbnail.setImageResource(R.drawable.dummy_thumbnail_dark);
if (!isEmpty(thumbnailUrl)) {
IMAGE_LOADER.displayImage(thumbnailUrl, binding.overlayThumbnail,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, null);
}
PicassoHelper.loadThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG)
.into(binding.overlayThumbnail);
}
private void setOverlayPlayPauseImage(final boolean playerIsPlaying) {

View File

@@ -350,12 +350,16 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
if (context == null || context.getResources() == null || activity == null) {
return;
}
final List<StreamDialogEntry> entries = new ArrayList<>();
if (PlayerHolder.getInstance().getType() != null) {
if (PlayerHolder.getInstance().isPlayerOpen()) {
entries.add(StreamDialogEntry.enqueue);
if (PlayerHolder.getInstance().getQueueSize() > 1) {
entries.add(StreamDialogEntry.enqueue_next);
}
}
if (item.getStreamType() == StreamType.AUDIO_STREAM) {
entries.addAll(Arrays.asList(
StreamDialogEntry.start_here_on_background,

View File

@@ -37,13 +37,14 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.player.MainPlayer.PlayerType;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
@@ -66,7 +67,10 @@ import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor;
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
implements View.OnClickListener {
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
private static final String PICASSO_CHANNEL_TAG = "PICASSO_CHANNEL_TAG";
private final CompositeDisposable disposables = new CompositeDisposable();
private Disposable subscribeButtonMonitor;
@@ -421,10 +425,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
@Override
public void showLoading() {
super.showLoading();
IMAGE_LOADER.cancelDisplayTask(headerBinding.channelBannerImage);
IMAGE_LOADER.cancelDisplayTask(headerBinding.channelAvatarView);
IMAGE_LOADER.cancelDisplayTask(headerBinding.subChannelAvatarView);
PicassoHelper.cancelTag(PICASSO_CHANNEL_TAG);
animate(headerBinding.channelSubscribeButton, false, 100);
}
@@ -433,13 +434,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
super.handleResult(result);
headerBinding.getRoot().setVisibility(View.VISIBLE);
IMAGE_LOADER.displayImage(result.getBannerUrl(), headerBinding.channelBannerImage,
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerBinding.channelAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
IMAGE_LOADER.displayImage(result.getParentChannelAvatarUrl(),
headerBinding.subChannelAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
PicassoHelper.loadBanner(result.getBannerUrl()).tag(PICASSO_CHANNEL_TAG)
.into(headerBinding.channelBannerImage);
PicassoHelper.loadAvatar(result.getAvatarUrl()).tag(PICASSO_CHANNEL_TAG)
.into(headerBinding.channelAvatarView);
PicassoHelper.loadAvatar(result.getParentChannelAvatarUrl()).tag(PICASSO_CHANNEL_TAG)
.into(headerBinding.subChannelAvatarView);
headerBinding.channelSubscriberView.setVisibility(View.VISIBLE);
if (result.getSubscriberCount() >= 0) {
@@ -496,12 +496,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
.playOnBackgroundPlayer(activity, getPlayQueue(), false));
playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true);
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.POPUP);
return true;
});
playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue(), true);
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.AUDIO);
return true;
});
}

View File

@@ -37,11 +37,12 @@ import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.player.MainPlayer.PlayerType;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
@@ -64,12 +65,16 @@ import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
private static final String PICASSO_PLAYLIST_TAG = "PICASSO_PLAYLIST_TAG";
private CompositeDisposable disposables;
private Subscription bookmarkReactor;
private AtomicBoolean isBookmarkButtonReady;
private RemotePlaylistManager remotePlaylistManager;
private PlaylistRemoteEntity playlistEntity;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
@@ -144,9 +149,14 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
final ArrayList<StreamDialogEntry> entries = new ArrayList<>();
if (PlayerHolder.getInstance().getType() != null) {
if (PlayerHolder.getInstance().isPlayerOpen()) {
entries.add(StreamDialogEntry.enqueue);
if (PlayerHolder.getInstance().getQueueSize() > 1) {
entries.add(StreamDialogEntry.enqueue_next);
}
}
if (item.getStreamType() == StreamType.AUDIO_STREAM) {
entries.addAll(Arrays.asList(
StreamDialogEntry.start_here_on_background,
@@ -274,7 +284,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
animate(headerBinding.getRoot(), false, 200);
animateHideRecyclerViewAllowingScrolling(itemsList);
IMAGE_LOADER.cancelDisplayTask(headerBinding.uploaderAvatarView);
PicassoHelper.cancelTag(PICASSO_PLAYLIST_TAG);
animate(headerBinding.uploaderLayout, false, 200);
}
@@ -317,8 +327,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
R.drawable.ic_radio)
);
} else {
IMAGE_LOADER.displayImage(avatarUrl, headerBinding.uploaderAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
PicassoHelper.loadAvatar(avatarUrl).tag(PICASSO_PLAYLIST_TAG)
.into(headerBinding.uploaderAvatarView);
}
headerBinding.playlistStreamCount.setText(Localization
@@ -343,12 +353,12 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true);
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.POPUP);
return true;
});
playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue(), true);
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.AUDIO);
return true;
});
}

View File

@@ -57,6 +57,7 @@ import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper;
@@ -65,16 +66,19 @@ import org.schabi.newpipe.util.ServiceHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
@@ -143,7 +147,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Nullable private Map<Integer, String> menuItemToFilterName = null;
private StreamingService service;
private Page nextPage;
private boolean isSuggestionsEnabled = true;
private boolean showLocalSuggestions = true;
private boolean showRemoteSuggestions = true;
private Disposable searchDisposable;
private Disposable suggestionDisposable;
@@ -194,26 +199,14 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
public void onAttach(@NonNull final Context context) {
super.onAttach(context);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
showLocalSuggestions = NewPipeSettings.showLocalSearchSuggestions(activity, prefs);
showRemoteSuggestions = NewPipeSettings.showRemoteSearchSuggestions(activity, prefs);
suggestionListAdapter = new SuggestionListAdapter(activity);
final SharedPreferences preferences
= PreferenceManager.getDefaultSharedPreferences(activity);
final boolean isSearchHistoryEnabled = preferences
.getBoolean(getString(R.string.enable_search_history_key), true);
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
historyRecordManager = new HistoryRecordManager(context);
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final SharedPreferences preferences
= PreferenceManager.getDefaultSharedPreferences(activity);
isSuggestionsEnabled = preferences
.getBoolean(getString(R.string.show_search_suggestions_key), true);
}
@Override
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
@@ -222,6 +215,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
searchBinding = FragmentSearchBinding.bind(rootView);
super.onViewCreated(rootView, savedInstanceState);
showSearchOnStart();
initSearchListeners();
@@ -348,7 +342,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
searchBinding = FragmentSearchBinding.bind(rootView);
searchBinding.suggestionsList.setAdapter(suggestionListAdapter);
new ItemTouchHelper(new ItemTouchHelper.Callback() {
@@ -554,7 +547,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (DEBUG) {
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
if (isSuggestionsEnabled && !isErrorPanelVisible()) {
if ((showLocalSuggestions || showRemoteSuggestions) && !isErrorPanelVisible()) {
showSuggestionsPanel();
}
if (DeviceUtils.isTv(getContext())) {
@@ -567,7 +560,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
Log.d(TAG, "onFocusChange() called with: "
+ "v = [" + v + "], hasFocus = [" + hasFocus + "]");
}
if (isSuggestionsEnabled && hasFocus && !isErrorPanelVisible()) {
if ((showLocalSuggestions || showRemoteSuggestions)
&& hasFocus && !isErrorPanelVisible()) {
showSuggestionsPanel();
}
});
@@ -743,6 +737,34 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
return false;
}
private Observable<List<SuggestionItem>> getLocalSuggestionsObservable(
final String query, final int similarQueryLimit) {
return historyRecordManager
.getRelatedSearches(query, similarQueryLimit, 25)
.toObservable()
.map(searchHistoryEntries -> {
final Set<SuggestionItem> result = new HashSet<>(); // remove duplicates
for (final SearchHistoryEntry entry : searchHistoryEntries) {
result.add(new SuggestionItem(true, entry.getSearch()));
}
return new ArrayList<>(result);
});
}
private Observable<List<SuggestionItem>> getRemoteSuggestionsObservable(final String query) {
return ExtractorHelper
.suggestionsFor(serviceId, query)
.toObservable()
.map(strings -> {
final List<SuggestionItem> result = new ArrayList<>();
for (final String entry : strings) {
result.add(new SuggestionItem(false, entry));
}
return result;
});
}
private void initSuggestionObserver() {
if (DEBUG) {
Log.d(TAG, "initSuggestionObserver() called");
@@ -753,73 +775,53 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
suggestionDisposable = suggestionPublisher
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
.startWithItem(searchString != null
? searchString
: "")
.filter(ss -> isSuggestionsEnabled)
.startWithItem(searchString == null ? "" : searchString)
.switchMap(query -> {
final Flowable<List<SearchHistoryEntry>> flowable = historyRecordManager
.getRelatedSearches(query, 3, 25);
final Observable<List<SuggestionItem>> local = flowable.toObservable()
.map(searchHistoryEntries -> {
final List<SuggestionItem> result = new ArrayList<>();
for (final SearchHistoryEntry entry : searchHistoryEntries) {
result.add(new SuggestionItem(true, entry.getSearch()));
}
return result;
});
// Only show remote suggestions if they are enabled in settings and
// the query length is at least THRESHOLD_NETWORK_SUGGESTION
final boolean shallShowRemoteSuggestionsNow = showRemoteSuggestions
&& query.length() >= THRESHOLD_NETWORK_SUGGESTION;
if (query.length() < THRESHOLD_NETWORK_SUGGESTION) {
// Only pass through if the query length
// is equal or greater than THRESHOLD_NETWORK_SUGGESTION
return local.materialize();
if (showLocalSuggestions && shallShowRemoteSuggestionsNow) {
return Observable.zip(
getLocalSuggestionsObservable(query, 3),
getRemoteSuggestionsObservable(query),
(local, remote) -> {
remote.removeIf(remoteItem -> local.stream().anyMatch(
localItem -> localItem.equals(remoteItem)));
local.addAll(remote);
return local;
})
.materialize();
} else if (showLocalSuggestions) {
return getLocalSuggestionsObservable(query, 25)
.materialize();
} else if (shallShowRemoteSuggestionsNow) {
return getRemoteSuggestionsObservable(query)
.materialize();
} else {
return Single.fromCallable(Collections::<SuggestionItem>emptyList)
.toObservable()
.materialize();
}
final Observable<List<SuggestionItem>> network = ExtractorHelper
.suggestionsFor(serviceId, query)
.onErrorReturn(throwable -> {
if (!ExceptionUtils.isNetworkRelated(throwable)) {
showSnackBarError(new ErrorInfo(throwable,
UserAction.GET_SUGGESTIONS, searchString, serviceId));
}
return new ArrayList<>();
})
.toObservable()
.map(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) -> {
final List<SuggestionItem> result = new ArrayList<>();
if (localResult.size() > 0) {
result.addAll(localResult);
}
// Remove duplicates
networkResult.removeIf(networkItem ->
localResult.stream().anyMatch(localItem ->
localItem.query.equals(networkItem.query)));
if (networkResult.size() > 0) {
result.addAll(networkResult);
}
return result;
}).materialize();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(listNotification -> {
if (listNotification.isOnNext()) {
handleSuggestions(listNotification.getValue());
} else if (listNotification.isOnError()) {
showError(new ErrorInfo(listNotification.getError(),
UserAction.GET_SUGGESTIONS, searchString, serviceId));
}
});
.subscribe(
listNotification -> {
if (listNotification.isOnNext()) {
if (listNotification.getValue() != null) {
handleSuggestions(listNotification.getValue());
}
} else if (listNotification.isOnError()
&& listNotification.getError() != null
&& !ExceptionUtils.isInterruptedCaused(
listNotification.getError())) {
showSnackBarError(new ErrorInfo(listNotification.getError(),
UserAction.GET_SUGGESTIONS, searchString, serviceId));
}
}, throwable -> showSnackBarError(new ErrorInfo(
throwable, UserAction.GET_SUGGESTIONS, searchString, serviceId)));
}
@Override

View File

@@ -1,5 +1,7 @@
package org.schabi.newpipe.fragments.list.search;
import androidx.annotation.NonNull;
public class SuggestionItem {
final boolean fromHistory;
public final String query;
@@ -9,6 +11,20 @@ public class SuggestionItem {
this.query = query;
}
@Override
public boolean equals(final Object o) {
if (o instanceof SuggestionItem) {
return query.equals(((SuggestionItem) o).query);
}
return false;
}
@Override
public int hashCode() {
return query.hashCode();
}
@NonNull
@Override
public String toString() {
return "[" + fromHistory + "" + query + "]";

View File

@@ -19,7 +19,6 @@ public class SuggestionListAdapter
private final ArrayList<SuggestionItem> items = new ArrayList<>();
private final Context context;
private OnSuggestionItemSelected listener;
private boolean showSuggestionHistory = true;
public SuggestionListAdapter(final Context context) {
this.context = context;
@@ -27,16 +26,7 @@ public class SuggestionListAdapter
public void setItems(final List<SuggestionItem> items) {
this.items.clear();
if (showSuggestionHistory) {
this.items.addAll(items);
} else {
// remove history items if history is disabled
for (final SuggestionItem item : items) {
if (!item.fromHistory) {
this.items.add(item);
}
}
}
this.items.addAll(items);
notifyDataSetChanged();
}
@@ -44,10 +34,6 @@ public class SuggestionListAdapter
this.listener = listener;
}
public void setShowSuggestionHistory(final boolean v) {
showSuggestionHistory = v;
}
@Override
public SuggestionItemHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new SuggestionItemHolder(LayoutInflater.from(context)

View File

@@ -6,8 +6,6 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
@@ -51,7 +49,6 @@ import org.schabi.newpipe.util.OnClickGesture;
public class InfoItemBuilder {
private final Context context;
private final ImageLoader imageLoader = ImageLoader.getInstance();
private OnClickGesture<StreamInfoItem> onStreamSelectedListener;
private OnClickGesture<ChannelInfoItem> onChannelSelectedListener;
@@ -101,10 +98,6 @@ public class InfoItemBuilder {
return context;
}
public ImageLoader getImageLoader() {
return imageLoader;
}
public OnClickGesture<StreamInfoItem> getOnStreamSelectedListener() {
return onStreamSelectedListener;
}

View File

@@ -1,8 +1,7 @@
package org.schabi.newpipe.info_list
import android.util.Log
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.GroupieViewHolder
import com.xwray.groupie.GroupieAdapter
import org.schabi.newpipe.extractor.stream.StreamInfo
import kotlin.math.max
@@ -11,7 +10,7 @@ import kotlin.math.max
*/
class StreamSegmentAdapter(
private val listener: StreamSegmentListener
) : GroupAdapter<GroupieViewHolder>() {
) : GroupieAdapter() {
var currentIndex: Int = 0
private set

View File

@@ -3,13 +3,12 @@ package org.schabi.newpipe.info_list
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.nostra13.universalimageloader.core.ImageLoader
import com.xwray.groupie.GroupieViewHolder
import com.xwray.groupie.Item
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.stream.StreamSegment
import org.schabi.newpipe.util.ImageDisplayConstants
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.PicassoHelper
class StreamSegmentItem(
private val item: StreamSegment,
@@ -24,10 +23,8 @@ class StreamSegmentItem(
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
item.previewUrl?.let {
ImageLoader.getInstance().displayImage(
it, viewHolder.root.findViewById<ImageView>(R.id.previewImage),
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS
)
PicassoHelper.loadThumbnail(it)
.into(viewHolder.root.findViewById<ImageView>(R.id.previewImage))
}
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).text = item.title
if (item.channelName == null) {

View File

@@ -8,7 +8,7 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.Localization;
import de.hdodenhof.circleimageview.CircleImageView;
@@ -43,10 +43,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
itemTitleView.setText(item.getName());
itemAdditionalDetailView.setText(getDetailLine(item));
itemBuilder.getImageLoader()
.displayImage(item.getThumbnailUrl(),
itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnChannelSelectedListener() != null) {

View File

@@ -1,17 +1,16 @@
package org.schabi.newpipe.info_list.holder;
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.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorActivity;
@@ -21,30 +20,29 @@ import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.CommentTextOnTouchListener;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.external_communication.TimestampExtractor;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.hdodenhof.circleimageview.CircleImageView;
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
private static final String TAG = "CommentsMiniIIHolder";
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;
private final TextView itemDislikesCountView;
private final TextView itemPublishedTime;
private String commentText;
@@ -53,20 +51,21 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() {
@Override
public String transformUrl(final Matcher match, final String url) {
int timestamp = 0;
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);
try {
final TimestampExtractor.TimestampMatchDTO timestampMatchDTO =
TimestampExtractor.getTimestampFromMatcher(match, commentText);
if (timestampMatchDTO == null) {
return url;
}
return streamUrl + url.replace(
match.group(0),
"#timestamp=" + timestampMatchDTO.seconds());
} catch (final Exception ex) {
Log.e(TAG, "Unable to process url='" + url + "' as timestampLink", ex);
return url;
}
if (minutes != null) {
timestamp += (Integer.parseInt(minutes.replace(":", "")) * 60);
}
if (seconds != null) {
timestamp += (Integer.parseInt(seconds));
}
return streamUrl + url.replace(match.group(0), "#timestamp=" + timestamp);
}
};
@@ -77,13 +76,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
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()
@@ -103,14 +98,8 @@ 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)) {
PicassoHelper.loadAvatar(item.getUploaderAvatarUrl()).into(itemThumbnailView);
if (PicassoHelper.getShouldLoadImages()) {
itemThumbnailView.setVisibility(View.VISIBLE);
itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
commentVerticalPadding, commentVerticalPadding);
@@ -254,7 +243,14 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
}
private void linkify() {
Linkify.addLinks(itemContentView, Linkify.WEB_URLS);
Linkify.addLinks(itemContentView, PATTERN, null, null, timestampLink);
Linkify.addLinks(
itemContentView,
Linkify.WEB_URLS);
Linkify.addLinks(
itemContentView,
TimestampExtractor.TIMESTAMPS_PATTERN,
null,
null,
timestampLink);
}
}

View File

@@ -9,7 +9,7 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.Localization;
public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
@@ -46,9 +46,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
.localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount()));
itemUploaderView.setText(item.getUploaderName());
itemBuilder.getImageLoader()
.displayImage(item.getThumbnailUrl(), itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnPlaylistSelectedListener() != null) {

View File

@@ -15,7 +15,7 @@ import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
@@ -83,10 +83,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
}
// Default thumbnail is shown on error, while loading and if the url is empty
itemBuilder.getImageLoader()
.displayImage(item.getThumbnailUrl(),
itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnStreamSelectedListener() != null) {

View File

@@ -1,10 +1,6 @@
package org.schabi.newpipe.local;
import android.content.Context;
import android.widget.ImageView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.util.OnClickGesture;
@@ -31,7 +27,6 @@ import org.schabi.newpipe.util.OnClickGesture;
public class LocalItemBuilder {
private final Context context;
private final ImageLoader imageLoader = ImageLoader.getInstance();
private OnClickGesture<LocalItem> onSelectedListener;
@@ -43,11 +38,6 @@ public class LocalItemBuilder {
return context;
}
public void displayImage(final String url, final ImageView view,
final DisplayImageOptions options) {
imageLoader.displayImage(url, view, options);
}
public OnClickGesture<LocalItem> getOnItemSelectedListener() {
return onSelectedListener;
}

View File

@@ -40,8 +40,7 @@ import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.GroupieViewHolder
import com.xwray.groupie.GroupieAdapter
import com.xwray.groupie.Item
import com.xwray.groupie.OnItemClickListener
import com.xwray.groupie.OnItemLongClickListener
@@ -91,7 +90,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
private var groupName = ""
private var oldestSubscriptionUpdate: OffsetDateTime? = null
private lateinit var groupAdapter: GroupAdapter<GroupieViewHolder>
private lateinit var groupAdapter: GroupieAdapter
@State @JvmField var showPlayedItems: Boolean = true
private var onSettingsChangeListener: SharedPreferences.OnSharedPreferenceChangeListener? = null
@@ -131,7 +130,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
viewModel = ViewModelProvider(this, factory).get(FeedViewModel::class.java)
viewModel.stateLiveData.observe(viewLifecycleOwner, { it?.let(::handleResult) })
groupAdapter = GroupAdapter<GroupieViewHolder>().apply {
groupAdapter = GroupieAdapter().apply {
setOnItemClickListener(listenerStreamItem)
setOnItemLongClickListener(listenerStreamItem)
}
@@ -206,7 +205,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), !usingDedicatedMethod)
}
}
.setPositiveButton(resources.getString(R.string.finish), null)
.setPositiveButton(resources.getString(R.string.ok), null)
.create()
.show()
return true
@@ -326,9 +325,14 @@ class FeedFragment : BaseStateFragment<FeedState>() {
if (context == null || context.resources == null || activity == null) return
val entries = ArrayList<StreamDialogEntry>()
if (PlayerHolder.getInstance().getType() != null) {
if (PlayerHolder.getInstance().isPlayerOpen) {
entries.add(StreamDialogEntry.enqueue)
if (PlayerHolder.getInstance().queueSize > 1) {
entries.add(StreamDialogEntry.enqueue_next)
}
}
if (item.streamType == StreamType.AUDIO_STREAM) {
entries.addAll(
listOf(
@@ -362,6 +366,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
StreamDialogEntry.mark_as_watched
)
}
entries.add(StreamDialogEntry.show_channel_details)
StreamDialogEntry.setEnabledEntries(entries)
InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context)) { _, which ->

View File

@@ -5,7 +5,6 @@ import android.text.TextUtils
import android.view.View
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import com.nostra13.universalimageloader.core.ImageLoader
import com.xwray.groupie.viewbinding.BindableItem
import org.schabi.newpipe.MainActivity
import org.schabi.newpipe.R
@@ -16,8 +15,9 @@ import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.VIDEO_STREAM
import org.schabi.newpipe.util.ImageDisplayConstants
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.PicassoHelper
import org.schabi.newpipe.util.StreamTypeUtil
import java.util.concurrent.TimeUnit
data class StreamItem(
@@ -59,8 +59,6 @@ data class StreamItem(
viewBinding.itemVideoTitleView.text = stream.title
viewBinding.itemUploaderView.text = stream.uploader
val isLiveStream = stream.streamType == LIVE_STREAM || stream.streamType == AUDIO_LIVE_STREAM
if (stream.duration > 0) {
viewBinding.itemDurationView.text = Localization.getDurationString(stream.duration)
viewBinding.itemDurationView.setBackgroundColor(
@@ -78,7 +76,7 @@ data class StreamItem(
} else {
viewBinding.itemProgressView.visibility = View.GONE
}
} else if (isLiveStream) {
} else if (StreamTypeUtil.isLiveStream(stream.streamType)) {
viewBinding.itemDurationView.setText(R.string.duration_live)
viewBinding.itemDurationView.setBackgroundColor(
ContextCompat.getColor(
@@ -93,10 +91,7 @@ data class StreamItem(
viewBinding.itemProgressView.visibility = View.GONE
}
ImageLoader.getInstance().displayImage(
stream.thumbnailUrl, viewBinding.itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS
)
PicassoHelper.loadThumbnail(stream.thumbnailUrl).into(viewBinding.itemThumbnailView)
if (itemVersion != ItemVersion.MINI) {
viewBinding.itemAdditionalDetails.text =

View File

@@ -300,6 +300,12 @@ class FeedLoadService : Service() {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { _, throwable ->
// There seems to be a bug in the kotlin plugin as it tells you when
// building that this can't be null:
// "Condition 'throwable != null' is always 'true'"
// However it can indeed be null
// The suppression may be removed in further versions
@Suppress("SENSELESS_COMPARISON")
if (throwable != null) {
Log.e(TAG, "Error while storing result", throwable)
handleError(throwable)

View File

@@ -53,8 +53,6 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
public class StatisticsPlaylistFragment
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {
private final CompositeDisposable disposables = new CompositeDisposable();
@@ -340,9 +338,14 @@ public class StatisticsPlaylistFragment
final ArrayList<StreamDialogEntry> entries = new ArrayList<>();
if (PlayerHolder.getInstance().getType() != null) {
if (PlayerHolder.getInstance().isPlayerOpen()) {
entries.add(StreamDialogEntry.enqueue);
if (PlayerHolder.getInstance().getQueueSize() > 1) {
entries.add(StreamDialogEntry.enqueue_next);
}
}
if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) {
entries.addAll(Arrays.asList(
StreamDialogEntry.start_here_on_background,
@@ -363,10 +366,7 @@ public class StatisticsPlaylistFragment
if (KoreUtils.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
entries.add(StreamDialogEntry.play_with_kodi);
}
if (!isNullOrEmpty(infoItem.getUploaderUrl())) {
entries.add(StreamDialogEntry.show_channel_details);
}
entries.add(StreamDialogEntry.show_channel_details);
StreamDialogEntry.setEnabledEntries(entries);

View File

@@ -7,7 +7,7 @@ import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.Localization;
import java.time.format.DateTimeFormatter;
@@ -36,8 +36,7 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
itemStreamCountView.getContext(), item.streamCount));
itemUploaderView.setVisibility(View.INVISIBLE);
itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView,
ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS);
PicassoHelper.loadPlaylistThumbnail(item.thumbnailUrl).into(itemThumbnailView);
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
}

View File

@@ -15,7 +15,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
@@ -81,8 +81,8 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
}
// Default thumbnail is shown on error, while loading and if the url is empty
itemBuilder.displayImage(item.getStreamEntity().getThumbnailUrl(), itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl())
.into(itemThumbnailView);
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnItemSelectedListener() != null) {

View File

@@ -15,7 +15,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
@@ -114,8 +114,8 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
}
// Default thumbnail is shown on error, while loading and if the url is empty
itemBuilder.displayImage(item.getStreamEntity().getThumbnailUrl(), itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl())
.into(itemThumbnailView);
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnItemSelectedListener() != null) {

View File

@@ -8,7 +8,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.Localization;
import java.time.format.DateTimeFormatter;
@@ -44,9 +44,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
itemUploaderView.setText(NewPipe.getNameOfService(item.getServiceId()));
}
itemBuilder.displayImage(item.getThumbnailUrl(), itemThumbnailView,
ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS);
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
}

View File

@@ -42,6 +42,7 @@ import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.MainPlayer.PlayerType;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
@@ -67,7 +68,6 @@ import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
@@ -494,12 +494,12 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true);
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.POPUP);
return true;
});
playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue(), true);
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.AUDIO);
return true;
});
@@ -753,8 +753,12 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
final ArrayList<StreamDialogEntry> entries = new ArrayList<>();
if (PlayerHolder.getInstance().getType() != null) {
if (PlayerHolder.getInstance().isPlayerOpen()) {
entries.add(StreamDialogEntry.enqueue);
if (PlayerHolder.getInstance().getQueueSize() > 1) {
entries.add(StreamDialogEntry.enqueue_next);
}
}
if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) {
entries.addAll(Arrays.asList(
@@ -778,10 +782,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
if (KoreUtils.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
entries.add(StreamDialogEntry.play_with_kodi);
}
if (!isNullOrEmpty(infoItem.getUploaderUrl())) {
entries.add(StreamDialogEntry.show_channel_details);
}
entries.add(StreamDialogEntry.show_channel_details);
StreamDialogEntry.setEnabledEntries(entries);

View File

@@ -22,7 +22,7 @@ enum class FeedGroupIcon(
COMPUTER(5, R.drawable.ic_computer),
GAMING(6, R.drawable.ic_videogame_asset),
SPORTS(7, R.drawable.ic_directions_bike),
NEWS(8, R.drawable.ic_megaphone),
NEWS(8, R.drawable.ic_campaign),
FAVORITES(9, R.drawable.ic_favorite),
CAR(10, R.drawable.ic_directions_car),
MOTORCYCLE(11, R.drawable.ic_motorcycle),

View File

@@ -40,7 +40,7 @@ public class ImportConfirmationDialog extends DialogFragment {
.setMessage(R.string.import_network_expensive_warning)
.setCancelable(true)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.finish, (dialogInterface, i) -> {
.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
if (resultServiceIntent != null && getContext() != null) {
getContext().startService(resultServiceIntent);
}

View File

@@ -179,7 +179,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
}
private fun onImportPreviousSelected() {
requestImportLauncher.launch(StoredFileHelper.getPicker(activity))
requestImportLauncher.launch(StoredFileHelper.getPicker(activity, JSON_MIME_TYPE))
}
private fun onExportSelected() {
@@ -187,7 +187,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
val exportName = "newpipe_subscriptions_$date.json"
requestExportLauncher.launch(
StoredFileHelper.getNewPicker(activity, exportName, "application/json", null)
StoredFileHelper.getNewPicker(activity, exportName, JSON_MIME_TYPE, null)
)
}
@@ -195,7 +195,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
FeedGroupReorderDialog().show(parentFragmentManager, null)
}
fun requestExportResult(result: ActivityResult) {
private fun requestExportResult(result: ActivityResult) {
if (result.data != null && result.resultCode == Activity.RESULT_OK) {
activity.startService(
Intent(activity, SubscriptionsExportService::class.java)
@@ -204,7 +204,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
}
}
fun requestImportResult(result: ActivityResult) {
private fun requestImportResult(result: ActivityResult) {
if (result.data != null && result.resultCode == Activity.RESULT_OK) {
ImportConfirmationDialog.show(
this,
@@ -407,4 +407,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
super.hideLoading()
binding.itemsList.animate(true, 200)
}
companion object {
const val JSON_MIME_TYPE = "application/json"
}
}

View File

@@ -177,7 +177,8 @@ public class SubscriptionsImportFragment extends BaseFragment {
}
public void onImportFile() {
requestImportFileLauncher.launch(StoredFileHelper.getPicker(activity));
// leave */* mime type to support all services with different mime types and file extensions
requestImportFileLauncher.launch(StoredFileHelper.getPicker(activity, "*/*"));
}
private void requestImportFileResult(final ActivityResult result) {

View File

@@ -21,8 +21,7 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.GroupieViewHolder
import com.xwray.groupie.GroupieAdapter
import com.xwray.groupie.OnItemClickListener
import com.xwray.groupie.Section
import icepick.Icepick
@@ -78,7 +77,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
private val subscriptionMainSection = Section()
private val subscriptionEmptyFooter = Section()
private lateinit var subscriptionGroupAdapter: GroupAdapter<GroupieViewHolder>
private lateinit var subscriptionGroupAdapter: GroupieAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -143,23 +142,17 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
).get(FeedGroupDialogViewModel::class.java)
viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup))
viewModel.subscriptionsLiveData.observe(
viewLifecycleOwner,
Observer {
setupSubscriptionPicker(it.first, it.second)
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner) {
setupSubscriptionPicker(it.first, it.second)
}
viewModel.dialogEventLiveData.observe(viewLifecycleOwner) {
when (it) {
ProcessingEvent -> disableInput()
SuccessEvent -> dismiss()
}
)
viewModel.dialogEventLiveData.observe(
viewLifecycleOwner,
Observer {
when (it) {
ProcessingEvent -> disableInput()
SuccessEvent -> dismiss()
}
}
)
}
subscriptionGroupAdapter = GroupAdapter<GroupieViewHolder>().apply {
subscriptionGroupAdapter = GroupieAdapter().apply {
add(subscriptionMainSection)
add(subscriptionEmptyFooter)
spanCount = 4
@@ -385,7 +378,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
}
private fun setupIconPicker() {
val groupAdapter = GroupAdapter<GroupieViewHolder>()
val groupAdapter = GroupieAdapter()
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(it) })
feedGroupCreateBinding.iconSelector.apply {
@@ -437,7 +430,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
feedGroupCreateBinding.confirmButton.setText(
when {
currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED -> R.string.create
else -> android.R.string.ok
else -> R.string.ok
}
)

View File

@@ -11,8 +11,7 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.GroupieViewHolder
import com.xwray.groupie.GroupieAdapter
import com.xwray.groupie.TouchCallback
import icepick.Icepick
import icepick.State
@@ -38,7 +37,7 @@ class FeedGroupReorderDialog : DialogFragment() {
@State
@JvmField
var groupOrderedIdList = ArrayList<Long>()
private val groupAdapter = GroupAdapter<GroupieViewHolder>()
private val groupAdapter = GroupieAdapter()
private val itemTouchHelper = ItemTouchHelper(getItemTouchCallback())
override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -3,14 +3,13 @@ package org.schabi.newpipe.local.subscription.item
import android.content.Context
import android.widget.ImageView
import android.widget.TextView
import com.nostra13.universalimageloader.core.ImageLoader
import com.xwray.groupie.GroupieViewHolder
import com.xwray.groupie.Item
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
import org.schabi.newpipe.util.ImageDisplayConstants
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.PicassoHelper
class ChannelItem(
private val infoItem: ChannelInfoItem,
@@ -40,10 +39,7 @@ class ChannelItem(
itemChannelDescriptionView.text = infoItem.description
}
ImageLoader.getInstance().displayImage(
infoItem.thumbnailUrl, itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS
)
PicassoHelper.loadThumbnail(infoItem.thumbnailUrl).into(itemThumbnailView)
gesturesListener?.run {
viewHolder.root.setOnClickListener { selected(infoItem) }

View File

@@ -1,23 +0,0 @@
package org.schabi.newpipe.local.subscription.item
import android.view.View
import android.view.View.OnClickListener
import com.xwray.groupie.viewbinding.BindableItem
import org.schabi.newpipe.R
import org.schabi.newpipe.databinding.HeaderItemBinding
class HeaderItem(
val title: String,
private val onClickListener: (() -> Unit)? = null
) : BindableItem<HeaderItemBinding>() {
override fun getLayout(): Int = R.layout.header_item
override fun bind(viewBinding: HeaderItemBinding, position: Int) {
viewBinding.headerTitle.text = title
val listener = onClickListener?.let { OnClickListener { onClickListener.invoke() } }
viewBinding.root.setOnClickListener(listener)
}
override fun initializeViewBinding(view: View) = HeaderItemBinding.bind(view)
}

View File

@@ -3,7 +3,6 @@ package org.schabi.newpipe.local.subscription.item
import android.view.View
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.nostra13.universalimageloader.core.ImageLoader
import com.xwray.groupie.viewbinding.BindableItem
import com.xwray.groupie.viewbinding.GroupieViewHolder
import org.schabi.newpipe.R
@@ -11,7 +10,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.databinding.PickerSubscriptionItemBinding
import org.schabi.newpipe.ktx.AnimationType
import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.util.ImageDisplayConstants
import org.schabi.newpipe.util.PicassoHelper
data class PickerSubscriptionItem(
val subscriptionEntity: SubscriptionEntity,
@@ -22,11 +21,7 @@ data class PickerSubscriptionItem(
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
override fun bind(viewBinding: PickerSubscriptionItemBinding, position: Int) {
ImageLoader.getInstance().displayImage(
subscriptionEntity.avatarUrl,
viewBinding.thumbnailView, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
)
PicassoHelper.loadAvatar(subscriptionEntity.avatarUrl).into(viewBinding.thumbnailView)
viewBinding.titleView.text = subscriptionEntity.name
viewBinding.selectedHighlight.isVisible = isSelected
}

View File

@@ -19,6 +19,9 @@
package org.schabi.newpipe.local.subscription.services;
import static org.schabi.newpipe.MainActivity.DEBUG;
import static org.schabi.newpipe.streams.io.StoredFileHelper.DEFAULT_MIME;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
@@ -46,6 +49,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
@@ -54,9 +58,6 @@ import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.MainActivity.DEBUG;
import static org.schabi.newpipe.streams.io.StoredFileHelper.DEFAULT_MIME;
public class SubscriptionsImportService extends BaseImportExportService {
public static final int CHANNEL_URL_MODE = 0;
public static final int INPUT_STREAM_MODE = 1;
@@ -89,6 +90,8 @@ public class SubscriptionsImportService extends BaseImportExportService {
private String channelUrl;
@Nullable
private InputStream inputStream;
@Nullable
private String inputStreamType;
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
@@ -111,8 +114,20 @@ public class SubscriptionsImportService extends BaseImportExportService {
}
try {
inputStream = new SharpInputStream(
new StoredFileHelper(this, uri, DEFAULT_MIME).getStream());
final StoredFileHelper fileHelper = new StoredFileHelper(this, uri, DEFAULT_MIME);
inputStream = new SharpInputStream(fileHelper.getStream());
inputStreamType = fileHelper.getType();
if (inputStreamType == null || inputStreamType.equals(DEFAULT_MIME)) {
// mime type could not be determined, just take file extension
final String name = fileHelper.getName();
final int pointIndex = name.lastIndexOf('.');
if (pointIndex == -1 || pointIndex >= name.length() - 1) {
inputStreamType = DEFAULT_MIME; // no extension, will fail in the extractor
} else {
inputStreamType = name.substring(pointIndex + 1);
}
}
} catch (final IOException e) {
handleError(e);
return START_NOT_STICKY;
@@ -248,9 +263,9 @@ public class SubscriptionsImportService extends BaseImportExportService {
final Throwable error = notification.getError();
final Throwable cause = error.getCause();
if (error instanceof IOException) {
throw (IOException) error;
throw error;
} else if (cause instanceof IOException) {
throw (IOException) cause;
throw cause;
} else if (ExceptionUtils.isNetworkRelated(error)) {
throw new IOException(error);
}
@@ -280,9 +295,12 @@ public class SubscriptionsImportService extends BaseImportExportService {
}
private Flowable<List<SubscriptionItem>> importFromInputStream() {
Objects.requireNonNull(inputStream);
Objects.requireNonNull(inputStreamType);
return Flowable.fromCallable(() -> NewPipe.getService(currentServiceId)
.getSubscriptionExtractor()
.fromInputStream(inputStream));
.fromInputStream(inputStream, inputStreamType));
}
private Flowable<List<SubscriptionItem>> importFromPreviousExport() {

View File

@@ -24,7 +24,6 @@ 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.LayoutInflater;
import android.view.View;
@@ -36,6 +35,7 @@ import androidx.core.content.ContextCompat;
import org.schabi.newpipe.App;
import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@@ -133,32 +133,29 @@ public final class MainPlayer extends Service {
return START_NOT_STICKY;
}
public void stop(final boolean autoplayEnabled) {
public void stopForImmediateReusing() {
if (DEBUG) {
Log.d(TAG, "stop() called");
Log.d(TAG, "stopForImmediateReusing() called");
}
if (!player.exoPlayerIsNull()) {
player.saveWasPlaying();
// Releases wifi & cpu, disables keepScreenOn, etc.
if (!autoplayEnabled) {
player.pause();
}
// We can't just pause the player here because it will make transition
// from one stream to a new stream not smooth
player.smoothStopPlayer();
player.setRecovery();
// Android TV will handle back button in case controls will be visible
// (one more additional unneeded click while the player is hidden)
player.hideControls(0, 0);
player.closeItemsList();
// 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);
}
}
}
@@ -222,11 +219,8 @@ public final class MainPlayer extends Service {
boolean isLandscape() {
// DisplayMetrics from activity context knows about MultiWindow feature
// while DisplayMetrics from app context doesn't
final DisplayMetrics metrics = (player != null
&& player.getParentActivity() != null
? player.getParentActivity().getResources()
: getResources()).getDisplayMetrics();
return metrics.heightPixels < metrics.widthPixels;
return DeviceUtils.isLandscape(player != null && player.getParentActivity() != null
? player.getParentActivity() : this);
}
@Nullable

View File

@@ -1,9 +1,5 @@
package org.schabi.newpipe.player;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import static org.schabi.newpipe.util.external_communication.ShareUtils.shareText;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -16,7 +12,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupMenu;
import android.widget.SeekBar;
import androidx.annotation.Nullable;
@@ -47,16 +42,18 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.Collections;
import java.util.List;
import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public final class PlayQueueActivity extends AppCompatActivity
implements PlayerEventListener, SeekBar.OnSeekBarChangeListener,
View.OnClickListener, PlaybackParameterDialog.Callback {
private static final String TAG = PlayQueueActivity.class.getSimpleName();
private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47;
private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80;
protected Player player;
@@ -279,49 +276,6 @@ public final class PlayQueueActivity extends AppCompatActivity
queueControlBinding.controlShuffle.setOnClickListener(this);
}
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
final PopupMenu popupMenu = new PopupMenu(this, view);
final MenuItem remove = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 0,
Menu.NONE, R.string.play_queue_remove);
remove.setOnMenuItemClickListener(menuItem -> {
if (player == null) {
return false;
}
final int index = player.getPlayQueue().indexOf(item);
if (index != -1) {
player.getPlayQueue().remove(index);
}
return true;
});
final MenuItem detail = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 1,
Menu.NONE, R.string.play_queue_stream_detail);
detail.setOnMenuItemClickListener(menuItem -> {
// playQueue is null since we don't want any queue change
NavigationHelper.openVideoDetail(this, item.getServiceId(), item.getUrl(),
item.getTitle(), null, false);
return true;
});
final MenuItem append = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 2,
Menu.NONE, R.string.append_playlist);
append.setOnMenuItemClickListener(menuItem -> {
openPlaylistAppendDialog(Collections.singletonList(item));
return true;
});
final MenuItem share = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 3,
Menu.NONE, R.string.share);
share.setOnMenuItemClickListener(menuItem -> {
shareText(getApplicationContext(), item.getTitle(), item.getUrl(),
item.getThumbnailUrl());
return true;
});
popupMenu.show();
}
////////////////////////////////////////////////////////////////////////////
// Component Helpers
////////////////////////////////////////////////////////////////////////////
@@ -369,13 +323,9 @@ public final class PlayQueueActivity extends AppCompatActivity
@Override
public void held(final PlayQueueItem item, final View view) {
if (player == null) {
return;
}
final int index = player.getPlayQueue().indexOf(item);
if (index != -1) {
buildItemPopupMenu(item, view);
if (player != null && player.getPlayQueue().indexOf(item) != -1) {
openPopupMenu(player.getPlayQueue(), item, view, false,
getSupportFragmentManager(), PlayQueueActivity.this);
}
}

View File

@@ -1,5 +1,53 @@
package org.schabi.newpipe.player;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AD_INSERTION;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
import static com.google.android.exoplayer2.Player.DiscontinuityReason;
import static com.google.android.exoplayer2.Player.EventListener;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
import static com.google.android.exoplayer2.Player.RepeatMode;
import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu;
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateRotation;
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_RECREATE_NOTIFICATION;
import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT;
import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction;
import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked;
import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled;
import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode;
import static org.schabi.newpipe.player.helper.PlayerHelper.nextResizeModeAndSaveToPrefs;
import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs;
import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent;
import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs;
import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences;
import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs;
import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex;
import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import static org.schabi.newpipe.util.Localization.containsCaseInsensitive;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -18,6 +66,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
@@ -82,9 +131,8 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoListener;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.MainActivity;
@@ -94,7 +142,6 @@ import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamSegment;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
@@ -127,10 +174,11 @@ import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper;
import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder;
import org.schabi.newpipe.util.StreamTypeUtil;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.SerializedCache;
import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.external_communication.ShareUtils;
@@ -140,6 +188,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
@@ -147,56 +196,9 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.disposables.SerialDisposable;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AD_INSERTION;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
import static com.google.android.exoplayer2.Player.DiscontinuityReason;
import static com.google.android.exoplayer2.Player.EventListener;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
import static com.google.android.exoplayer2.Player.RepeatMode;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateRotation;
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_RECREATE_NOTIFICATION;
import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT;
import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction;
import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked;
import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled;
import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode;
import static org.schabi.newpipe.player.helper.PlayerHelper.nextResizeModeAndSaveToPrefs;
import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs;
import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent;
import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs;
import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences;
import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs;
import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex;
import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import static org.schabi.newpipe.util.Localization.containsCaseInsensitive;
public final class Player implements
EventListener,
PlaybackListener,
ImageLoadingListener,
VideoListener,
SeekBar.OnSeekBarChangeListener,
View.OnClickListener,
@@ -225,10 +227,10 @@ public final class Player implements
public static final String REPEAT_MODE = "repeat_mode";
public static final String PLAYBACK_QUALITY = "playback_quality";
public static final String PLAY_QUEUE_KEY = "play_queue_key";
public static final String APPEND_ONLY = "append_only";
public static final String ENQUEUE = "enqueue";
public static final String ENQUEUE_NEXT = "enqueue_next";
public static final String RESUME_PLAYBACK = "resume_playback";
public static final String PLAY_WHEN_READY = "play_when_ready";
public static final String SELECT_ON_APPEND = "select_on_append";
public static final String PLAYER_TYPE = "player_type";
public static final String IS_MUTED = "is_muted";
@@ -237,7 +239,7 @@ public final class Player implements
//////////////////////////////////////////////////////////////////////////*/
public static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds
public static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500; // 500 millis
public static final int PROGRESS_LOOP_INTERVAL_MILLIS = 1000; // 1 second
public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis
public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
public static final int DPAD_CONTROLS_HIDE_TIME = 7000; // 7 Seconds
@@ -390,7 +392,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////*/
//region
//region Constructor
public Player(@NonNull final MainPlayer service) {
this.service = service;
@@ -437,7 +439,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Setup and initialization
//////////////////////////////////////////////////////////////////////////*/
//region
//region Setup and initialization
public void setupFromView(@NonNull final PlayerBinding playerBinding) {
initViews(playerBinding);
@@ -585,7 +587,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Playback initialization via intent
//////////////////////////////////////////////////////////////////////////*/
//region
//region Playback initialization via intent
public void handleIntent(@NonNull final Intent intent) {
// fail fast if no play queue was provided
@@ -607,19 +609,22 @@ public final class Player implements
setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY));
}
// Resolve append intents
if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) {
final int sizeBeforeAppend = playQueue.size();
// Resolve enqueue intents
if (intent.getBooleanExtra(ENQUEUE, false) && playQueue != null) {
playQueue.append(newQueue.getStreams());
return;
if ((intent.getBooleanExtra(SELECT_ON_APPEND, false)
|| currentState == STATE_COMPLETED) && newQueue.getStreams().size() > 0) {
playQueue.setIndex(sizeBeforeAppend);
}
// Resolve enqueue next intents
} else if (intent.getBooleanExtra(ENQUEUE_NEXT, false) && playQueue != null) {
final int currentIndex = playQueue.getIndex();
playQueue.append(newQueue.getStreams());
playQueue.move(playQueue.size() - 1, currentIndex + 1);
return;
}
// needed for tablets, check the function for a better explanation
directlyOpenFullscreenIfNeeded();
final PlaybackParameters savedParameters = retrievePlaybackParametersFromPrefs(this);
final float playbackSpeed = savedParameters.speed;
final float playbackPitch = savedParameters.pitch;
@@ -671,6 +676,7 @@ public final class Player implements
&& isPlaybackResumeEnabled(this)
&& !samePlayQueue
&& !newQueue.isEmpty()
&& newQueue.getItem() != null
&& newQueue.getItem().getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
databaseUpdateDisposable.add(recordManager.loadStreamState(newQueue.getItem())
.observeOn(AndroidSchedulers.mainThread())
@@ -742,6 +748,22 @@ public final class Player implements
NavigationHelper.sendPlayerStartedEvent(context);
}
/**
* Open fullscreen on tablets where the option to have the main player start automatically in
* fullscreen mode is on. Rotating the device to landscape is already done in {@link
* VideoDetailFragment#openVideoPlayer(boolean)} when the thumbnail is clicked, and that's
* enough for phones, but not for tablets since the mini player can be also shown in landscape.
*/
private void directlyOpenFullscreenIfNeeded() {
if (fragmentListener != null
&& PlayerHelper.isStartMainPlayerFullscreenEnabled(service)
&& DeviceUtils.isTablet(service)
&& videoPlayerSelected()
&& PlayerHelper.globalScreenOrientationLocked(service)) {
fragmentListener.onScreenRotationButtonClicked();
}
}
private void initPlayback(@NonNull final PlayQueue queue,
@RepeatMode final int repeatMode,
final float playbackSpeed,
@@ -774,7 +796,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Destroy and recovery
//////////////////////////////////////////////////////////////////////////*/
//region
//region Destroy and recovery
private void destroyPlayer() {
if (DEBUG) {
@@ -820,7 +842,7 @@ public final class Player implements
databaseUpdateDisposable.clear();
progressUpdateDisposable.set(null);
ImageLoader.getInstance().stop();
PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading
if (binding != null) {
binding.endScreen.setImageBitmap(null);
@@ -883,7 +905,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Player type specific setup
//////////////////////////////////////////////////////////////////////////*/
//region
//region Player type specific setup
private void initVideoPlayer() {
// restore last resize mode
@@ -945,7 +967,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Elements visibility and size: popup and main players have different look
//////////////////////////////////////////////////////////////////////////*/
//region
//region Elements visibility and size: popup and main players have different look
/**
* This method ensures that popup and main players have different look.
@@ -1059,7 +1081,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Broadcast receiver
//////////////////////////////////////////////////////////////////////////*/
//region
//region Broadcast receiver
private void setupBroadcastReceiver() {
if (DEBUG) {
@@ -1211,18 +1233,49 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Thumbnail loading
//////////////////////////////////////////////////////////////////////////*/
//region
//region Thumbnail loading
private void initThumbnail(final String url) {
if (DEBUG) {
Log.d(TAG, "Thumbnail - initThumbnail() called");
Log.d(TAG, "Thumbnail - initThumbnail() called with url = ["
+ (url == null ? "null" : url) + "]");
}
if (url == null || url.isEmpty()) {
if (isNullOrEmpty(url)) {
return;
}
ImageLoader.getInstance().resume();
ImageLoader.getInstance()
.loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, this);
// scale down the notification thumbnail for performance
PicassoHelper.loadScaledDownThumbnail(context, url).into(new Target() {
@Override
public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) {
if (DEBUG) {
Log.d(TAG, "Thumbnail - onLoadingComplete() called with: url = [" + url
+ "], " + "loadedImage = [" + bitmap + " -> " + bitmap.getWidth() + "x"
+ bitmap.getHeight() + "], from = [" + from + "]");
}
currentThumbnail = bitmap;
NotificationUtil.getInstance()
.createNotificationIfNeededAndUpdate(Player.this, false);
// there is a new thumbnail, so changed the end screen thumbnail, too.
updateEndScreenThumbnail();
}
@Override
public void onBitmapFailed(final Exception e, final Drawable errorDrawable) {
Log.e(TAG, "Thumbnail - onBitmapFailed() called with: url = [" + url + "]", e);
currentThumbnail = null;
NotificationUtil.getInstance()
.createNotificationIfNeededAndUpdate(Player.this, false);
}
@Override
public void onPrepareLoad(final Drawable placeHolderDrawable) {
if (DEBUG) {
Log.d(TAG, "Thumbnail - onLoadingStarted() called with: url = [" + url + "]");
}
}
});
}
/**
@@ -1296,61 +1349,6 @@ public final class Player implements
return Math.min(currentThumbnail.getHeight(), screenHeight);
}
}
@Override
public void onLoadingStarted(final String imageUri, final View view) {
if (DEBUG) {
Log.d(TAG, "Thumbnail - onLoadingStarted() called on: "
+ "imageUri = [" + imageUri + "], view = [" + view + "]");
}
}
@Override
public void onLoadingFailed(final String imageUri, final View view,
final FailReason failReason) {
Log.e(TAG, "Thumbnail - onLoadingFailed() called on imageUri = [" + imageUri + "]",
failReason.getCause());
currentThumbnail = null;
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
}
@Override
public void onLoadingComplete(final String imageUri, final View view,
final Bitmap loadedImage) {
// scale down the notification thumbnail for performance
final float notificationThumbnailWidth = Math.min(
context.getResources().getDimension(R.dimen.player_notification_thumbnail_width),
loadedImage.getWidth());
currentThumbnail = Bitmap.createScaledBitmap(
loadedImage,
(int) notificationThumbnailWidth,
(int) (loadedImage.getHeight()
/ (loadedImage.getWidth() / notificationThumbnailWidth)),
true);
if (DEBUG) {
Log.d(TAG, "Thumbnail - onLoadingComplete() called with: "
+ "imageUri = [" + imageUri + "], view = [" + view + "], "
+ "loadedImage = [" + loadedImage + "], "
+ loadedImage.getWidth() + "x" + loadedImage.getHeight()
+ ", scaled notification width = " + notificationThumbnailWidth);
}
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
// there is a new thumbnail, thus the end screen thumbnail needs to be changed, too.
updateEndScreenThumbnail();
}
@Override
public void onLoadingCancelled(final String imageUri, final View view) {
if (DEBUG) {
Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: "
+ "imageUri = [" + imageUri + "], view = [" + view + "]");
}
currentThumbnail = null;
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
}
//endregion
@@ -1358,7 +1356,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Popup player utils
//////////////////////////////////////////////////////////////////////////*/
//region
//region Popup player utils
/**
* Check if {@link #popupLayoutParams}' position is within a arbitrary boundary
@@ -1533,7 +1531,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Playback parameters
//////////////////////////////////////////////////////////////////////////*/
//region
//region Playback parameters
public float getPlaybackSpeed() {
return getPlaybackParameters().speed;
@@ -1586,7 +1584,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Progress loop and updates
//////////////////////////////////////////////////////////////////////////*/
//region
//region Progress loop and updates
private void onUpdateProgress(final int currentProgress,
final int duration,
@@ -1596,14 +1594,10 @@ public final class Player implements
}
if (duration != binding.playbackSeekBar.getMax()) {
binding.playbackEndTime.setText(getTimeString(duration));
binding.playbackSeekBar.setMax(duration);
setVideoDurationToControls(duration);
}
if (currentState != STATE_PAUSED) {
if (currentState != STATE_PAUSED_SEEK) {
binding.playbackSeekBar.setProgress(currentProgress);
}
binding.playbackCurrentTime.setText(getTimeString(currentProgress));
updatePlayBackElementsCurrentDuration(currentProgress);
}
if (simpleExoPlayer.isLoading() || bufferPercent > 90) {
binding.playbackSeekBar.setSecondaryProgress(
@@ -1626,12 +1620,6 @@ public final class Player implements
if (isQueueVisible) {
updateQueueTime(currentProgress);
}
final boolean showThumbnail = prefs.getBoolean(
context.getString(R.string.show_thumbnail_key), true);
// setMetadata only updates the metadata when any of the metadata keys are null
mediaSessionManager.setMetadata(getVideoTitle(), getUploaderName(),
showThumbnail ? getThumbnail() : null, duration);
}
private void startProgressLoop() {
@@ -1656,10 +1644,10 @@ public final class Player implements
// TODO: revert #6307 when introducing proper HLS support
final int duration;
if (currentItem != null
&& currentItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM
&& currentItem.getStreamType() != StreamType.LIVE_STREAM) {
&& !StreamTypeUtil.isLiveStream(currentItem.getStreamType())
) {
// convert seconds to milliseconds
duration = (int) (currentItem.getDuration() * 1000);
duration = (int) (currentItem.getDuration() * 1000);
} else {
duration = (int) simpleExoPlayer.getDuration();
}
@@ -1802,7 +1790,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Controls showing / hiding
//////////////////////////////////////////////////////////////////////////*/
//region
//region Controls showing / hiding
public boolean isControlsVisible() {
return binding != null && binding.playbackControlRoot.getVisibility() == View.VISIBLE;
@@ -1972,7 +1960,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Playback states
//////////////////////////////////////////////////////////////////////////*/
//region
//region Playback states
@Override // exoplayer listener
public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
@@ -2097,8 +2085,8 @@ public final class Player implements
Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
}
binding.playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
binding.playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
setVideoDurationToControls((int) simpleExoPlayer.getDuration());
binding.playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
if (playWhenReady) {
@@ -2257,6 +2245,9 @@ public final class Player implements
stopProgressLoop();
}
// When a (short) video ends the elements have to display the correct values - see #6180
updatePlayBackElementsCurrentDuration(binding.playbackSeekBar.getMax());
showControls(500);
animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA);
binding.loadingPanel.setVisibility(View.GONE);
@@ -2293,7 +2284,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Repeat and shuffle
//////////////////////////////////////////////////////////////////////////*/
//region
//region Repeat and shuffle
public void onRepeatClicked() {
if (DEBUG) {
@@ -2330,7 +2321,7 @@ public final class Player implements
Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: "
+ "repeatMode = [" + repeatMode + "]");
}
setRepeatModeButton(((AppCompatImageButton) binding.repeatButton), repeatMode);
setRepeatModeButton(binding.repeatButton, repeatMode);
onShuffleOrRepeatModeChanged();
}
@@ -2382,7 +2373,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Mute / Unmute
//////////////////////////////////////////////////////////////////////////*/
//region
//region Mute / Unmute
public void onMuteUnmuteButtonClicked() {
if (DEBUG) {
@@ -2408,7 +2399,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// ExoPlayer listeners (that didn't fit in other categories)
//////////////////////////////////////////////////////////////////////////*/
//region
//region ExoPlayer listeners (that didn't fit in other categories)
@Override
public void onTimelineChanged(@NonNull final Timeline timeline, final int reason) {
@@ -2496,7 +2487,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Errors
//////////////////////////////////////////////////////////////////////////*/
//region
//region Errors
/**
* Process exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}.
* <p>There are multiple types of errors:</p>
@@ -2597,7 +2588,19 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Playback position and seek
//////////////////////////////////////////////////////////////////////////*/
//region
//region Playback position and seek
/**
* Sets the current duration into the corresponding elements.
* @param currentProgress
*/
private void updatePlayBackElementsCurrentDuration(final int currentProgress) {
// Don't set seekbar progress while user is seeking
if (currentState != STATE_PAUSED_SEEK) {
binding.playbackSeekBar.setProgress(currentProgress);
}
binding.playbackCurrentTime.setText(getTimeString(currentProgress));
}
@Override // own playback listener (this is a getter)
public boolean isApproachingPlaybackEdge(final long timeToEndMillis) {
@@ -2740,6 +2743,20 @@ public final class Player implements
simpleExoPlayer.seekToDefaultPosition();
}
}
/**
* Sets the video duration time into all control components (e.g. seekbar).
* @param duration
*/
private void setVideoDurationToControls(final int duration) {
binding.playbackEndTime.setText(getTimeString(duration));
binding.playbackSeekBar.setMax(duration);
// This is important for Android TVs otherwise it would apply the default from
// setMax/Min methods which is (max - min) / 20
binding.playbackSeekBar.setKeyProgressIncrement(
PlayerHelper.retrieveSeekDurationFromPreferences(this));
}
//endregion
@@ -2747,7 +2764,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Player actions (play, pause, previous, fast-forward, ...)
//////////////////////////////////////////////////////////////////////////*/
//region
//region Player actions (play, pause, previous, fast-forward, ...)
public void play() {
if (DEBUG) {
@@ -2789,7 +2806,9 @@ public final class Player implements
Log.d(TAG, "onPlayPause() called");
}
if (getPlayWhenReady()) {
if (getPlayWhenReady()
// When state is completed (replay button is shown) then (re)play and do not pause
&& currentState != STATE_COMPLETED) {
pause();
} else {
play();
@@ -2855,7 +2874,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// StreamInfo history: views and progress
//////////////////////////////////////////////////////////////////////////*/
//region
//region StreamInfo history: views and progress
private void registerStreamViewed() {
if (currentMetadata != null) {
@@ -2913,7 +2932,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Metadata
//////////////////////////////////////////////////////////////////////////*/
//region
//region Metadata
private void onMetadataChanged(@NonNull final MediaSourceTag tag) {
final StreamInfo info = tag.getMetadata();
@@ -2934,6 +2953,18 @@ public final class Player implements
tag.getMetadata().getPreviewFrames());
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
final boolean showThumbnail = prefs.getBoolean(
context.getString(R.string.show_thumbnail_key), true);
mediaSessionManager.setMetadata(
getVideoTitle(),
getUploaderName(),
showThumbnail ? Optional.ofNullable(getThumbnail()) : Optional.empty(),
StreamTypeUtil.isLiveStream(tag.getMetadata().getStreamType())
? -1
: tag.getMetadata().getDuration()
);
notifyMetadataUpdateToListeners();
if (areSegmentsVisible) {
@@ -3011,9 +3042,11 @@ public final class Player implements
@Nullable
public Bitmap getThumbnail() {
return currentThumbnail == null
? BitmapFactory.decodeResource(context.getResources(), R.drawable.dummy_thumbnail)
: currentThumbnail;
if (currentThumbnail == null) {
currentThumbnail = BitmapFactory.decodeResource(
context.getResources(), R.drawable.dummy_thumbnail);
}
return currentThumbnail;
}
//endregion
@@ -3022,7 +3055,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Play queue, segments and streams
//////////////////////////////////////////////////////////////////////////*/
//region
//region Play queue, segments and streams
private void maybeAutoQueueNextStream(@NonNull final MediaSourceTag metadata) {
if (playQueue == null || playQueue.getIndex() != playQueue.size() - 1
@@ -3177,7 +3210,7 @@ public final class Player implements
private StreamSegmentAdapter.StreamSegmentListener getStreamSegmentListener() {
return (item, seconds) -> {
segmentAdapter.selectSegment(item);
seekTo(seconds * 1000);
seekTo(seconds * 1000L);
triggerProgressUpdate();
};
}
@@ -3187,7 +3220,7 @@ public final class Player implements
final List<StreamSegment> segments = currentMetadata.getMetadata().getStreamSegments();
for (int i = 0; i < segments.size(); i++) {
if (segments.get(i).getStartTimeSeconds() * 1000 > playbackPosition) {
if (segments.get(i).getStartTimeSeconds() * 1000L > playbackPosition) {
break;
}
nearestPosition++;
@@ -3222,9 +3255,9 @@ public final class Player implements
@Override
public void held(final PlayQueueItem item, final View view) {
final int index = playQueue.indexOf(item);
if (index != -1) {
playQueue.remove(index);
if (playQueue.indexOf(item) != -1) {
openPopupMenu(playQueue, item, view, true,
getParentActivity().getSupportFragmentManager(), context);
}
}
@@ -3338,7 +3371,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Popup menus ("popup" means that they pop up, not that they belong to the popup player)
//////////////////////////////////////////////////////////////////////////*/
//region
//region Popup menus ("popup" means that they pop up, not that they belong to the popup player)
private void buildQualityMenu() {
if (qualityPopupMenu == null) {
@@ -3541,7 +3574,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Captions (text tracks)
//////////////////////////////////////////////////////////////////////////*/
//region
//region Captions (text tracks)
private void setupSubtitleView() {
final float captionScale = PlayerHelper.getCaptionScale(context);
@@ -3620,7 +3653,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Click listeners
//////////////////////////////////////////////////////////////////////////*/
//region
//region Click listeners
@Override
public void onClick(final View v) {
@@ -3808,7 +3841,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Video size, resize, orientation, fullscreen
//////////////////////////////////////////////////////////////////////////*/
//region
//region Video size, resize, orientation, fullscreen
private void setupScreenRotationButton() {
binding.screenRotationButton.setVisibility(videoPlayerSelected()
@@ -3863,11 +3896,9 @@ public final class Player implements
if (DEBUG) {
Log.d(TAG, "toggleFullscreen() called");
}
if (popupPlayerSelected() || exoPlayerIsNull() || currentMetadata == null
|| fragmentListener == null) {
if (popupPlayerSelected() || exoPlayerIsNull() || fragmentListener == null) {
return;
}
//changeState(STATE_BLOCKED); TODO check what this does
isFullscreen = !isFullscreen;
if (!isFullscreen) {
@@ -3915,7 +3946,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Gestures
//////////////////////////////////////////////////////////////////////////*/
//region
//region Gestures
@SuppressWarnings("checkstyle:ParameterNumber")
private void onLayoutChange(final View view, final int l, final int t, final int r, final int b,
@@ -3979,7 +4010,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Activity / fragment binding
//////////////////////////////////////////////////////////////////////////*/
//region
//region Activity / fragment binding
public void setFragmentListener(final PlayerServiceEventListener listener) {
fragmentListener = listener;
@@ -4118,7 +4149,7 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Getters
//////////////////////////////////////////////////////////////////////////*/
//region
//region Getters
public int getCurrentState() {
return currentState;
@@ -4304,6 +4335,7 @@ public final class Player implements
// SurfaceHolderCallback helpers
//////////////////////////////////////////////////////////////////////////*/
//region SurfaceHolderCallback helpers
private void setupVideoSurface() {
// make sure there is nothing left over from previous calls
cleanupVideoSurface();
@@ -4331,5 +4363,5 @@ public final class Player implements
}
}
}
//endregion SurfaceHolderCallback helpers
//endregion
}

View File

@@ -16,6 +16,7 @@ import androidx.media.AudioManagerCompat;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.analytics.AnalyticsListener;
import com.google.android.exoplayer2.decoder.DecoderCounters;
public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, AnalyticsListener {
@@ -50,6 +51,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
public void dispose() {
abandonAudioFocus();
player.removeAnalyticsListener(this);
notifyAudioSessionUpdate(false, player.getAudioSessionId());
}
/*//////////////////////////////////////////////////////////////////////////
@@ -149,11 +151,21 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
@Override
public void onAudioSessionId(final EventTime eventTime, final int audioSessionId) {
notifyAudioSessionUpdate(true, audioSessionId);
}
@Override
public void onAudioDisabled(final EventTime eventTime, final DecoderCounters counters) {
notifyAudioSessionUpdate(false, player.getAudioSessionId());
}
private void notifyAudioSessionUpdate(final boolean active, final int audioSessionId) {
if (!PlayerHelper.isUsingDSP()) {
return;
}
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
final Intent intent = new Intent(active
? AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION
: AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId);
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
context.sendBroadcast(intent);

View File

@@ -20,18 +20,16 @@ public class LoadController implements LoadControl {
//////////////////////////////////////////////////////////////////////////*/
public LoadController() {
this(PlayerHelper.getPlaybackStartBufferMs(),
PlayerHelper.getPlaybackMinimumBufferMs(),
PlayerHelper.getPlaybackOptimalBufferMs());
this(PlayerHelper.getPlaybackStartBufferMs());
}
private LoadController(final int initialPlaybackBufferMs,
final int minimumPlaybackBufferMs,
final int optimalPlaybackBufferMs) {
private LoadController(final int initialPlaybackBufferMs) {
this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000;
final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
builder.setBufferDurationsMs(minimumPlaybackBufferMs, optimalPlaybackBufferMs,
builder.setBufferDurationsMs(
DefaultLoadControl.DEFAULT_MIN_BUFFER_MS,
DefaultLoadControl.DEFAULT_MAX_BUFFER_MS,
initialPlaybackBufferMs,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS);
internalLoadControl = builder.build();

View File

@@ -21,6 +21,8 @@ import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
import org.schabi.newpipe.player.mediasession.PlayQueueNavigator;
import org.schabi.newpipe.player.mediasession.PlayQueuePlaybackController;
import java.util.Optional;
public class MediaSessionManager {
private static final String TAG = MediaSessionManager.class.getSimpleName();
public static final boolean DEBUG = MainActivity.DEBUG;
@@ -30,6 +32,9 @@ public class MediaSessionManager {
@NonNull
private final MediaSessionConnector sessionConnector;
private int lastTitleHashCode;
private int lastArtistHashCode;
private long lastDuration;
private int lastAlbumArtHashCode;
public MediaSessionManager(@NonNull final Context context,
@@ -65,47 +70,127 @@ public class MediaSessionManager {
return mediaSession.getSessionToken();
}
public void setMetadata(final String title,
final String artist,
final Bitmap albumArt,
final long duration) {
if (albumArt == null || !mediaSession.isActive()) {
/**
* sets the Metadata - if required.
*
* @param title {@link MediaMetadataCompat#METADATA_KEY_TITLE}
* @param artist {@link MediaMetadataCompat#METADATA_KEY_ARTIST}
* @param optAlbumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART}
* @param duration {@link MediaMetadataCompat#METADATA_KEY_DURATION}
* - should be a negative value for unknown durations, e.g. for livestreams
*/
public void setMetadata(@NonNull final String title,
@NonNull final String artist,
@NonNull final Optional<Bitmap> optAlbumArt,
final long duration
) {
if (DEBUG) {
Log.d(TAG, "setMetadata called:"
+ " t: " + title
+ " a: " + artist
+ " thumb: " + (
optAlbumArt.isPresent()
? optAlbumArt.get().hashCode()
: "<none>")
+ " d: " + duration);
}
if (!mediaSession.isActive()) {
if (DEBUG) {
Log.d(TAG, "setMetadata: mediaSession not active - exiting");
}
return;
}
if (!checkIfMetadataShouldBeSet(title, artist, optAlbumArt, duration)) {
if (DEBUG) {
Log.d(TAG, "setMetadata: No update required - exiting");
}
return;
}
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());
}
Log.d(TAG, "setMetadata: N_Metadata update:"
+ " t: " + title
+ " a: " + artist
+ " thumb: " + (
optAlbumArt.isPresent()
? optAlbumArt.get().hashCode()
: "<none>")
+ " d: " + duration);
}
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);
}
final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration);
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();
if (optAlbumArt.isPresent()) {
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, optAlbumArt.get());
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, optAlbumArt.get());
}
mediaSession.setMetadata(builder.build());
lastTitleHashCode = title.hashCode();
lastArtistHashCode = artist.hashCode();
lastDuration = duration;
if (optAlbumArt.isPresent()) {
lastAlbumArtHashCode = optAlbumArt.get().hashCode();
}
}
private boolean checkIfMetadataShouldBeSet(
@NonNull final String title,
@NonNull final String artist,
@NonNull final Optional<Bitmap> optAlbumArt,
final long duration
) {
// Check if the values have changed since the last time
if (title.hashCode() != lastTitleHashCode
|| artist.hashCode() != lastArtistHashCode
|| duration != lastDuration
|| (optAlbumArt.isPresent() && optAlbumArt.get().hashCode() != lastAlbumArtHashCode)
) {
if (DEBUG) {
Log.d(TAG,
"checkIfMetadataShouldBeSet: true - reason: changed values since last");
}
return true;
}
// Check if the currently set metadata is valid
if (getMetadataTitle() == null
|| getMetadataArtist() == null
// Note that the duration can be <= 0 for live streams
) {
if (DEBUG) {
if (getMetadataTitle() == null) {
Log.d(TAG,
"N_getMetadataTitle: title == null");
} else if (getMetadataArtist() == null) {
Log.d(TAG,
"N_getMetadataArtist: artist == null");
}
}
return true;
}
// If we got an album art check if the current set AlbumArt is null
if (optAlbumArt.isPresent() && getMetadataAlbumArt() == null) {
if (DEBUG) {
if (getMetadataAlbumArt() == null) {
Log.d(TAG, "N_getMetadataAlbumArt: thumb == null");
}
}
return true;
}
// Default - no update required
return false;
}
private Bitmap getMetadataAlbumArt() {
return mediaSession.getController().getMetadata()
.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART);
@@ -121,11 +206,6 @@ public class MediaSessionManager {
.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.
*/

View File

@@ -164,7 +164,7 @@ public class PlaybackParameterDialog extends DialogFragment {
setPlaybackParameters(initialTempo, initialPitch, initialSkipSilence))
.setNeutralButton(R.string.playback_reset, (dialogInterface, i) ->
setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH, DEFAULT_SKIP_SILENCE))
.setPositiveButton(R.string.finish, (dialogInterface, i) ->
.setPositiveButton(R.string.ok, (dialogInterface, i) ->
setCurrentPlaybackParameters());
return dialogBuilder.create();

View File

@@ -239,6 +239,11 @@ public final class PlayerHelper {
.getBoolean(context.getString(R.string.brightness_gesture_control_key), true);
}
public static boolean isStartMainPlayerFullscreenEnabled(@NonNull final Context context) {
return getPreferences(context)
.getBoolean(context.getString(R.string.start_main_player_fullscreen_key), false);
}
public static boolean isAutoQueueEnabled(@NonNull final Context context) {
return getPreferences(context)
.getBoolean(context.getString(R.string.auto_queue_key), false);
@@ -307,22 +312,6 @@ public final class PlayerHelper {
return 500;
}
/**
* @return the minimum number of milliseconds the player always buffers to
* after starting playback.
*/
public static int getPlaybackMinimumBufferMs() {
return 25000;
}
/**
* @return the maximum/optimal number of milliseconds the player will buffer to once the buffer
* hits the point of {@link #getPlaybackMinimumBufferMs()}.
*/
public static int getPlaybackOptimalBufferMs() {
return 60000;
}
public static TrackSelection.Factory getQualitySelector() {
return new AdaptiveTrackSelection.Factory(
1000,

View File

@@ -70,6 +70,10 @@ public final class PlayerHolder {
return player != null;
}
public int getQueueSize() {
return isPlayerOpen() ? player.getPlayQueue().size() : 0;
}
public void setListener(@Nullable final PlayerServiceExtendedEventListener newListener) {
listener = newListener;

View File

@@ -27,6 +27,7 @@ public class PlayQueueItem implements Serializable {
private final String thumbnailUrl;
@NonNull
private final String uploader;
private final String uploaderUrl;
@NonNull
private final StreamType streamType;
@@ -37,7 +38,8 @@ public class PlayQueueItem implements Serializable {
PlayQueueItem(@NonNull final StreamInfo info) {
this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(),
info.getThumbnailUrl(), info.getUploaderName(), info.getStreamType());
info.getThumbnailUrl(), info.getUploaderName(),
info.getUploaderUrl(), info.getStreamType());
if (info.getStartPosition() > 0) {
setRecoveryPosition(info.getStartPosition() * 1000);
@@ -46,19 +48,21 @@ public class PlayQueueItem implements Serializable {
PlayQueueItem(@NonNull final StreamInfoItem item) {
this(item.getName(), item.getUrl(), item.getServiceId(), item.getDuration(),
item.getThumbnailUrl(), item.getUploaderName(), item.getStreamType());
item.getThumbnailUrl(), item.getUploaderName(),
item.getUploaderUrl(), item.getStreamType());
}
private PlayQueueItem(@Nullable final String name, @Nullable final String url,
final int serviceId, final long duration,
@Nullable final String thumbnailUrl, @Nullable final String uploader,
@NonNull final StreamType streamType) {
final String uploaderUrl, @NonNull final StreamType streamType) {
this.title = name != null ? name : EMPTY_STRING;
this.url = url != null ? url : EMPTY_STRING;
this.serviceId = serviceId;
this.duration = duration;
this.thumbnailUrl = thumbnailUrl != null ? thumbnailUrl : EMPTY_STRING;
this.uploader = uploader != null ? uploader : EMPTY_STRING;
this.uploaderUrl = uploaderUrl;
this.streamType = streamType;
this.recoveryPosition = RECOVERY_UNSET;
@@ -92,6 +96,10 @@ public class PlayQueueItem implements Serializable {
return uploader;
}
public String getUploaderUrl() {
return uploaderUrl;
}
@NonNull
public StreamType getStreamType() {
return streamType;

View File

@@ -5,11 +5,9 @@ import android.text.TextUtils;
import android.view.MotionEvent;
import android.view.View;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PicassoHelper;
public class PlayQueueItemBuilder {
private static final String TAG = PlayQueueItemBuilder.class.toString();
@@ -35,8 +33,7 @@ public class PlayQueueItemBuilder {
holder.itemDurationView.setVisibility(View.GONE);
}
ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(holder.itemThumbnailView);
holder.itemRoot.setOnClickListener(view -> {
if (onItemClickListener != null) {

View File

@@ -14,6 +14,7 @@ import com.google.android.exoplayer2.util.Util;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.player.helper.PlayerDataSource;
import org.schabi.newpipe.util.StreamTypeUtil;
public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
@@ -21,7 +22,7 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
default MediaSource maybeBuildLiveMediaSource(@NonNull final PlayerDataSource dataSource,
@NonNull final StreamInfo info) {
final StreamType streamType = info.getStreamType();
if (!(streamType == StreamType.AUDIO_LIVE_STREAM || streamType == StreamType.LIVE_STREAM)) {
if (!StreamTypeUtil.isLiveStream(streamType)) {
return null;
}

View File

@@ -1,16 +1,18 @@
package org.schabi.newpipe.player.seekbarpreview;
import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.SeekbarPreviewThumbnailType;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.common.base.Stopwatch;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.extractor.stream.Frameset;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.Comparator;
import java.util.HashMap;
@@ -21,11 +23,8 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.SeekbarPreviewThumbnailType;
public class SeekbarPreviewThumbnailHolder {
// This has to be <= 23 chars on devices running Android 7 or lower (API <= 25)
@@ -174,6 +173,7 @@ public class SeekbarPreviewThumbnailHolder {
}
}
@Nullable
private Bitmap getBitMapFrom(final String url) {
if (url == null) {
Log.w(TAG, "url is null; This should never happen");
@@ -182,24 +182,11 @@ public class SeekbarPreviewThumbnailHolder {
final Stopwatch sw = Log.isLoggable(TAG, Log.DEBUG) ? Stopwatch.createStarted() : null;
try {
final SyncImageLoadingListener syncImageLoadingListener =
new SyncImageLoadingListener();
Log.d(TAG, "Downloading bitmap for seekbarPreview from '" + url + "'");
// Ensure that everything is running
ImageLoader.getInstance().resume();
// Load the image
// Impl-Note:
// Gets the bitmap within the timeout of 15 seconds imposed by default by OkHttpClient
// Ensure that your are not running on the main-Thread this will otherwise hang
ImageLoader.getInstance().loadImage(
url,
ImageDisplayConstants.DISPLAY_SEEKBAR_PREVIEW_OPTIONS,
syncImageLoadingListener);
// Get the bitmap within the timeout
final Bitmap bitmap =
syncImageLoadingListener.waitForBitmapOrThrow(30, TimeUnit.SECONDS);
final Bitmap bitmap = PicassoHelper.loadSeekbarThumbnailPreview(url).get();
if (sw != null) {
Log.d(TAG,

View File

@@ -1,87 +0,0 @@
package org.schabi.newpipe.player.seekbarpreview;
import android.graphics.Bitmap;
import android.view.View;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Listener for synchronously downloading of an image/bitmap.
*/
public class SyncImageLoadingListener extends SimpleImageLoadingListener {
private final CountDownLatch countDownLatch = new CountDownLatch(1);
private Bitmap bitmap;
private boolean cancelled = false;
private FailReason failReason = null;
@SuppressWarnings("checkstyle:HiddenField")
@Override
public void onLoadingFailed(
final String imageUri,
final View view,
final FailReason failReason) {
this.failReason = failReason;
countDownLatch.countDown();
}
@Override
public void onLoadingComplete(
final String imageUri,
final View view,
final Bitmap loadedImage) {
bitmap = loadedImage;
countDownLatch.countDown();
}
@Override
public void onLoadingCancelled(final String imageUri, final View view) {
cancelled = true;
countDownLatch.countDown();
}
public Bitmap waitForBitmapOrThrow(final long timeout, final TimeUnit timeUnit)
throws InterruptedException, TimeoutException {
// Wait for the download to finish
if (!countDownLatch.await(timeout, timeUnit)) {
throw new TimeoutException("Couldn't get the image in time");
}
if (isCancelled()) {
throw new CancellationException("Download of image was cancelled");
}
if (getFailReason() != null) {
throw new RuntimeException("Failed to download image" + getFailReason().getType(),
getFailReason().getCause());
}
if (getBitmap() == null) {
throw new NullPointerException("Bitmap is null");
}
return getBitmap();
}
public Bitmap getBitmap() {
return bitmap;
}
public boolean isCancelled() {
return cancelled;
}
public FailReason getFailReason() {
return failReason;
}
}

View File

@@ -17,8 +17,6 @@ import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
@@ -29,6 +27,7 @@ import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.ZipHelper;
import java.io.File;
@@ -50,7 +49,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
private ContentSettingsManager manager;
private String importExportDataPathKey;
private String thumbnailLoadToggleKey;
private String youtubeRestrictedModeEnabledKey;
private Localization initialSelectedLocalization;
@@ -69,7 +67,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
manager.deleteSettingsFile();
importExportDataPathKey = getString(R.string.import_export_data_path);
thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key);
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
addPreferencesFromResource(R.xml.content_settings);
@@ -77,7 +74,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
final Preference importDataPreference = requirePreference(R.string.import_data);
importDataPreference.setOnPreferenceClickListener((Preference p) -> {
requestImportPathLauncher.launch(
StoredFileHelper.getPicker(requireContext(), getImportExportDataUri()));
StoredFileHelper.getPicker(requireContext(),
ZIP_MIME_TYPE, getImportExportDataUri()));
return true;
});
@@ -95,8 +93,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
.getPreferredLocalization(requireContext());
initialSelectedContentCountry = org.schabi.newpipe.util.Localization
.getPreferredContentCountry(requireContext());
initialLanguage = PreferenceManager
.getDefaultSharedPreferences(requireContext()).getString("app_language_key", "en");
initialLanguage = defaultPreferences.getString(getString(R.string.app_language_key), "en");
final Preference clearCookiePref = requirePreference(R.string.clear_cookie_key);
clearCookiePref.setOnPreferenceClickListener(preference -> {
@@ -112,20 +109,24 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
if (defaultPreferences.getString(getString(R.string.recaptcha_cookies_key), "").isEmpty()) {
clearCookiePref.setVisible(false);
}
findPreference(getString(R.string.download_thumbnail_key)).setOnPreferenceChangeListener(
(preference, newValue) -> {
PicassoHelper.setShouldLoadImages((Boolean) newValue);
try {
PicassoHelper.clearCache(preference.getContext());
Toast.makeText(preference.getContext(),
R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT)
.show();
} catch (final IOException e) {
Log.e(TAG, "Unable to clear Picasso cache", e);
}
return true;
});
}
@Override
public boolean onPreferenceTreeClick(final Preference preference) {
if (preference.getKey().equals(thumbnailLoadToggleKey)) {
final ImageLoader imageLoader = ImageLoader.getInstance();
imageLoader.stop();
imageLoader.clearDiskCache();
imageLoader.clearMemoryCache();
imageLoader.resume();
Toast.makeText(preference.getContext(), R.string.thumbnail_cache_wipe_complete_notice,
Toast.LENGTH_SHORT).show();
}
if (preference.getKey().equals(youtubeRestrictedModeEnabledKey)) {
final Context context = getContext();
if (context != null) {
@@ -146,8 +147,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
.getPreferredLocalization(requireContext());
final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization
.getPreferredContentCountry(requireContext());
final String selectedLanguage = PreferenceManager
.getDefaultSharedPreferences(requireContext()).getString("app_language_key", "en");
final String selectedLanguage =
defaultPreferences.getString(getString(R.string.app_language_key), "en");
if (!selectedLocalization.equals(initialSelectedLocalization)
|| !selectedContentCountry.equals(initialSelectedContentCountry)
@@ -183,7 +184,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
new AlertDialog.Builder(requireActivity())
.setMessage(R.string.override_current_data)
.setPositiveButton(R.string.finish, (d, id) ->
.setPositiveButton(R.string.ok, (d, id) ->
importDatabase(file, lastImportDataUri))
.setNegativeButton(R.string.cancel, (d, id) ->
d.cancel())
@@ -231,11 +232,11 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
final AlertDialog.Builder alert = new AlertDialog.Builder(requireContext());
alert.setTitle(R.string.import_settings);
alert.setNegativeButton(android.R.string.no, (dialog, which) -> {
alert.setNegativeButton(R.string.cancel, (dialog, which) -> {
dialog.dismiss();
finishImport(importDataUri);
});
alert.setPositiveButton(getString(R.string.finish), (dialog, which) -> {
alert.setPositiveButton(R.string.ok, (dialog, which) -> {
dialog.dismiss();
manager.loadSharedPreferences(PreferenceManager
.getDefaultSharedPreferences(requireContext()));

View File

@@ -91,6 +91,11 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
is String -> {
preferenceEditor.putString(key, value)
}
is Set<*> -> {
// There are currently only Sets with type String possible
@Suppress("UNCHECKED_CAST")
preferenceEditor.putStringSet(key, value as Set<String>?)
}
}
}
preferenceEditor.commit()

View File

@@ -179,7 +179,7 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
final AlertDialog.Builder msg = new AlertDialog.Builder(ctx);
msg.setTitle(title);
msg.setMessage(message);
msg.setPositiveButton(getString(R.string.finish), null);
msg.setPositiveButton(getString(R.string.ok), null);
msg.show();
}

View File

@@ -6,6 +6,7 @@ import android.os.Build;
import android.os.Environment;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
@@ -59,6 +60,10 @@ public final class NewPipeSettings {
isFirstRun = true;
}
// first run migrations, then setDefaultValues, since the latter requires the correct types
SettingMigrations.initMigrations(context, isFirstRun);
// readAgain is true so that if new settings are added their default value is set
PreferenceManager.setDefaultValues(context, R.xml.main_settings, true);
PreferenceManager.setDefaultValues(context, R.xml.video_audio_settings, true);
PreferenceManager.setDefaultValues(context, R.xml.download_settings, true);
@@ -71,8 +76,6 @@ public final class NewPipeSettings {
saveDefaultVideoDownloadDirectory(context);
saveDefaultAudioDownloadDirectory(context);
SettingMigrations.initMigrations(context, isFirstRun);
}
static void saveDefaultVideoDownloadDirectory(final Context context) {
@@ -124,4 +127,29 @@ public final class NewPipeSettings {
return prefs.getBoolean(key, true);
}
private static boolean showSearchSuggestions(final Context context,
final SharedPreferences sharedPreferences,
@StringRes final int key) {
final Set<String> enabledSearchSuggestions = sharedPreferences.getStringSet(
context.getString(R.string.show_search_suggestions_key), null);
if (enabledSearchSuggestions == null) {
return true; // defaults to true
} else {
return enabledSearchSuggestions.contains(context.getString(key));
}
}
public static boolean showLocalSearchSuggestions(final Context context,
final SharedPreferences sharedPreferences) {
return showSearchSuggestions(context, sharedPreferences,
R.string.show_local_search_suggestions_key);
}
public static boolean showRemoteSearchSuggestions(final Context context,
final SharedPreferences sharedPreferences) {
return showSearchSuggestions(context, sharedPreferences,
R.string.show_remote_search_suggestions_key);
}
}

View File

@@ -218,7 +218,7 @@ public class PeertubeInstanceListFragment extends Fragment {
.setIcon(R.drawable.place_holder_peertube)
.setView(dialogBinding.getRoot())
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.finish, (dialog1, which) -> {
.setPositiveButton(R.string.ok, (dialog1, which) -> {
final String url = dialogBinding.dialogEditText.getText().toString();
addInstance(url);
})

View File

@@ -14,13 +14,11 @@ import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.List;
@@ -54,13 +52,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
*/
public class SelectChannelFragment extends DialogFragment {
/**
* This contains the base display options for images.
*/
private static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS
= new DisplayImageOptions.Builder().cacheInMemory(true).build();
private final ImageLoader imageLoader = ImageLoader.getInstance();
private OnSelectedListener onSelectedListener = null;
private OnCancelListener onCancelListener = null;
@@ -199,8 +190,7 @@ public class SelectChannelFragment extends DialogFragment {
final SubscriptionEntity entry = subscriptions.get(position);
holder.titleView.setText(entry.getName());
holder.view.setOnClickListener(view -> clickedItem(position));
imageLoader.displayImage(entry.getAvatarUrl(), holder.thumbnailView,
DISPLAY_IMAGE_OPTIONS);
PicassoHelper.loadAvatar(entry.getAvatarUrl()).into(holder.thumbnailView);
}
@Override

View File

@@ -14,9 +14,6 @@ import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.AppDatabase;
@@ -29,6 +26,7 @@ import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.List;
import java.util.Vector;
@@ -38,13 +36,6 @@ import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.disposables.Disposable;
public class SelectPlaylistFragment extends DialogFragment {
/**
* This contains the base display options for images.
*/
private static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS
= new DisplayImageOptions.Builder().cacheInMemory(true).build();
private final ImageLoader imageLoader = ImageLoader.getInstance();
private OnSelectedListener onSelectedListener = null;
@@ -170,16 +161,15 @@ public class SelectPlaylistFragment extends DialogFragment {
holder.titleView.setText(entry.name);
holder.view.setOnClickListener(view -> clickedItem(position));
imageLoader.displayImage(entry.thumbnailUrl, holder.thumbnailView,
DISPLAY_IMAGE_OPTIONS);
PicassoHelper.loadPlaylistThumbnail(entry.thumbnailUrl).into(holder.thumbnailView);
} else if (selectedItem instanceof PlaylistRemoteEntity) {
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
holder.titleView.setText(entry.getName());
holder.view.setOnClickListener(view -> clickedItem(position));
imageLoader.displayImage(entry.getThumbnailUrl(), holder.thumbnailView,
DISPLAY_IMAGE_OPTIONS);
PicassoHelper.loadPlaylistThumbnail(entry.getThumbnailUrl())
.into(holder.thumbnailView);
}
}

View File

@@ -13,14 +13,22 @@ import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.util.DeviceUtils;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static org.schabi.newpipe.MainActivity.DEBUG;
/**
* In order to add a migration, follow these steps, given P is the previous version:<br>
* - in the class body add a new {@code MIGRATION_P_P+1 = new Migration(P, P+1) { ... }} and put in
* the {@code migrate()} method the code that need to be run when migrating from P to P+1<br>
* - add {@code MIGRATION_P_P+1} at the end of {@link SettingMigrations#SETTING_MIGRATIONS}<br>
* - increment {@link SettingMigrations#VERSION}'s value by 1 (so it should become P+1)
*/
public final class SettingMigrations {
private static final String TAG = SettingMigrations.class.toString();
/**
* Version number for preferences. Must be incremented every time a migration is necessary.
*/
public static final int VERSION = 3;
private static SharedPreferences sp;
public static final Migration MIGRATION_0_1 = new Migration(0, 1) {
@@ -72,6 +80,35 @@ public final class SettingMigrations {
}
};
public static final Migration MIGRATION_3_4 = new Migration(3, 4) {
@Override
protected void migrate(final Context context) {
// Pull request #3546 added support for choosing the type of search suggestions to
// show, replacing the on-off switch used before, so migrate the previous user choice
final String showSearchSuggestionsKey =
context.getString(R.string.show_search_suggestions_key);
boolean addAllSearchSuggestionTypes;
try {
addAllSearchSuggestionTypes = sp.getBoolean(showSearchSuggestionsKey, true);
} catch (final ClassCastException e) {
// just in case it was not a boolean for some reason, let's consider it a "true"
addAllSearchSuggestionTypes = true;
}
final Set<String> showSearchSuggestionsValueList = new HashSet<>();
if (addAllSearchSuggestionTypes) {
// if the preference was true, all suggestions will be shown, otherwise none
Collections.addAll(showSearchSuggestionsValueList, context.getResources()
.getStringArray(R.array.show_search_suggestions_value_list));
}
sp.edit().putStringSet(
showSearchSuggestionsKey, showSearchSuggestionsValueList).apply();
}
};
/**
* List of all implemented migrations.
* <p>
@@ -81,9 +118,15 @@ public final class SettingMigrations {
private static final Migration[] SETTING_MIGRATIONS = {
MIGRATION_0_1,
MIGRATION_1_2,
MIGRATION_2_3
MIGRATION_2_3,
MIGRATION_3_4,
};
/**
* Version number for preferences. Must be incremented every time a migration is necessary.
*/
public static final int VERSION = 4;
public static void initMigrations(final Context context, final boolean isFirstRun) {
// setup migrations and check if there is something to do

View File

@@ -6,11 +6,21 @@ import androidx.preference.Preference;
import org.schabi.newpipe.R;
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
public class UpdateSettingsFragment extends BasePreferenceFragment {
private final Preference.OnPreferenceChangeListener updatePreferenceChange
= (preference, newValue) -> {
= (preference, checkForUpdates) -> {
defaultPreferences.edit()
.putBoolean(getString(R.string.update_app_key), (boolean) newValue).apply();
.putBoolean(getString(R.string.update_app_key), (boolean) checkForUpdates).apply();
if ((boolean) checkForUpdates) {
// Search for updates immediately when update checks are enabled.
// Reset the expire time. This is necessary to check for an update immediately.
defaultPreferences.edit()
.putLong(getString(R.string.update_expiry_key), 0).apply();
startNewVersionCheckService();
}
return true;
};

View File

@@ -459,11 +459,12 @@ public class StoredFileHelper implements Serializable {
return !str1.equals(str2);
}
public static Intent getPicker(@NonNull final Context ctx) {
public static Intent getPicker(@NonNull final Context ctx,
@NonNull final String mimeType) {
if (NewPipeSettings.useStorageAccessFramework(ctx)) {
return new Intent(Intent.ACTION_OPEN_DOCUMENT)
.putExtra("android.content.extra.SHOW_ADVANCED", true)
.setType("*/*")
.setType(mimeType)
.addCategory(Intent.CATEGORY_OPENABLE)
.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| StoredDirectoryHelper.PERMISSION_FLAGS);
@@ -477,8 +478,10 @@ public class StoredFileHelper implements Serializable {
}
}
public static Intent getPicker(@NonNull final Context ctx, @Nullable final Uri initialPath) {
return applyInitialPathToPickerIntent(ctx, getPicker(ctx), initialPath, null);
public static Intent getPicker(@NonNull final Context ctx,
@NonNull final String mimeType,
@Nullable final Uri initialPath) {
return applyInitialPathToPickerIntent(ctx, getPicker(ctx, mimeType), initialPath, null);
}
public static Intent getNewPicker(@NonNull final Context ctx,

View File

@@ -11,6 +11,7 @@ import android.view.KeyEvent;
import androidx.annotation.Dimension;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
@@ -32,6 +33,9 @@ public final class DeviceUtils {
// Zephir TS43UHD-2
private static final boolean CVT_MT5886_EU_1G = Build.VERSION.SDK_INT == 24
&& Build.DEVICE.equals("cvt_mt5886_eu_1g");
// Hilife TV
private static final boolean REALTEKATV = Build.VERSION.SDK_INT == 25
&& Build.DEVICE.equals("RealtekATV");
private DeviceUtils() {
}
@@ -128,6 +132,16 @@ public final class DeviceUtils {
public static boolean shouldSupportMediaTunneling() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& !HI3798MV200
&& !CVT_MT5886_EU_1G;
&& !CVT_MT5886_EU_1G
&& !REALTEKATV;
}
public static boolean isLandscape(final Context context) {
return context.getResources().getDisplayMetrics().heightPixels < context.getResources()
.getDisplayMetrics().widthPixels;
}
public static boolean isInMultiWindow(final AppCompatActivity activity) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && activity.isInMultiWindowMode();
}
}

View File

@@ -1,65 +0,0 @@
package org.schabi.newpipe.util;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import org.schabi.newpipe.R;
public final class ImageDisplayConstants {
private static final int BITMAP_FADE_IN_DURATION_MILLIS = 250;
/**
* This constant contains the base display options.
*/
private static final DisplayImageOptions BASE_DISPLAY_IMAGE_OPTIONS =
new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(true)
.resetViewBeforeLoading(true)
.bitmapConfig(Bitmap.Config.RGB_565)
.imageScaleType(ImageScaleType.EXACTLY)
.displayer(new FadeInBitmapDisplayer(BITMAP_FADE_IN_DURATION_MILLIS))
.build();
/*//////////////////////////////////////////////////////////////////////////
// DisplayImageOptions default configurations
//////////////////////////////////////////////////////////////////////////*/
public static final DisplayImageOptions DISPLAY_AVATAR_OPTIONS =
new DisplayImageOptions.Builder()
.cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS)
.showImageForEmptyUri(R.drawable.buddy)
.showImageOnFail(R.drawable.buddy)
.build();
public static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS =
new DisplayImageOptions.Builder()
.cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS)
.showImageForEmptyUri(R.drawable.dummy_thumbnail)
.showImageOnFail(R.drawable.dummy_thumbnail)
.build();
public static final DisplayImageOptions DISPLAY_BANNER_OPTIONS =
new DisplayImageOptions.Builder()
.cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS)
.showImageForEmptyUri(R.drawable.channel_banner)
.showImageOnFail(R.drawable.channel_banner)
.build();
public static final DisplayImageOptions DISPLAY_PLAYLIST_OPTIONS =
new DisplayImageOptions.Builder()
.cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS)
.showImageForEmptyUri(R.drawable.dummy_thumbnail_playlist)
.showImageOnFail(R.drawable.dummy_thumbnail_playlist)
.build();
public static final DisplayImageOptions DISPLAY_SEEKBAR_PREVIEW_OPTIONS =
new DisplayImageOptions.Builder()
.cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS)
.build();
private ImageDisplayConstants() { }
}

View File

@@ -226,6 +226,16 @@ public final class Localization {
shortCount(context, subscriberCount));
}
public static String downloadCount(final Context context, final int downloadCount) {
return getQuantity(context, R.plurals.download_finished_notification, 0,
downloadCount, shortCount(context, downloadCount));
}
public static String deletedDownloadCount(final Context context, final int deletedCount) {
return getQuantity(context, R.plurals.deleted_downloads_toast, 0,
deletedCount, shortCount(context, deletedCount));
}
private static String getQuantity(final Context context, @PluralsRes final int pluralId,
@StringRes final int zeroCaseStringId, final long count,
final String formattedCount) {

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