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

Compare commits

..

424 Commits

Author SHA1 Message Date
Christian Schabesberger
568cf9c259 Merge pull request #2067 from TeamNewPipe/release_v0.15.1
release v0.15.1
2019-01-29 13:40:15 +01:00
Christian Schabesberger
b4ef20e785 fix failing tests 2019-01-29 13:38:38 +01:00
Christian Schabesberger
3a56dabf42 fix exception on viewing downloads 2019-01-29 13:23:39 +01:00
Christian Schabesberger
b7b228d9ce merge weblate 2019-01-27 22:00:00 +01:00
Laura Arjona Reina
45339fd6d2 Translated using Weblate (Spanish)
Currently translated at 100.0% (383 of 383 strings)
2019-01-27 17:26:10 +01:00
nautilusx
1856a5a82f Translated using Weblate (German)
Currently translated at 100.0% (383 of 383 strings)
2019-01-27 17:26:02 +01:00
Christian Schabesberger
b2aa703b62 add changelog for version v0.15.1 2019-01-27 16:57:56 +01:00
Christian Schabesberger
b31897d65d Merge pull request #2066 from TeamNewPipe/fix_icons
fix icon messup
2019-01-27 16:48:01 +01:00
Christian Schabesberger
a92f776ebe fix icon messup 2019-01-27 16:46:44 +01:00
Christian Schabesberger
b84ed675f5 Merge pull request #2062 from kapodamy/anoth-buggy-giga-fix
Downloads: add null checks
2019-01-25 22:21:28 +01:00
kapodamy
21eb98a52c add null check
add checks for null. This happens after rotating the screen while is turned off
2019-01-24 23:23:30 -03:00
qazaxtan
75dd8d492b Translated using Weblate (Romanian)
Currently translated at 84.1% (322 of 383 strings)
2019-01-23 18:05:27 +01:00
Christian Schabesberger
338893ded4 fix merge conflict 2019-01-23 16:12:21 +01:00
Christian Schabesberger
b459900040 Merge branch 'fix_download' into dev 2019-01-23 14:14:49 +01:00
Christian Schabesberger
d31300f4f9 Merge pull request #2048 from gkeegan/dev
Add updating section to README
2019-01-23 12:54:52 +01:00
kapodamy
f2285c0b19 MP4 muxer +misc modifications
* allow retry downloads with "post-processing failed" error in the new muxer
* MPEG-4 muxer  ¡¡ no DASH output!!
* keep the progress if download fails
* remove TODO in SecondaryStreamHelper.java
* misc clean-up
* delete TestAlgo.java
* delete ExtSDDownloadFailedActivity.java and remove it from AndroidManifest.xml
* use hardcored version for changing icon colors
2019-01-22 18:53:31 -03:00
kapodamy
684cb81974 Update MissionsFragment.java
work-around for reading the current theme icons
2019-01-22 18:53:31 -03:00
kapodamy
9db272f30e Update MissionsFragment.java
use another way to get the "Context"
2019-01-22 18:53:31 -03:00
kapodamy
c4a5e8dc86 misc fixes
* add null checks before resuming a download
* (MissionAdapter.java) reset percent after resuming a download. prevents the "Error" string get stuck, until the download start
2019-01-22 18:53:30 -03:00
kapodamy
df4dd0122f Update missions_header.xml
make the separator line """"""""simetric""""""""""
2019-01-22 18:53:30 -03:00
kapodamy
8fed18b2ac Update MissionAdapter.java
* check if the iterator is initialized
2019-01-22 18:53:30 -03:00
kapodamy
ecabbb57e6 Update download_menu.xml
* hide clear button by default
2019-01-22 18:53:30 -03:00
kapodamy
8d1d4092aa add missing icons in bright theme
* missing white icons
* update attrs.xml and styles.xml
2019-01-22 18:53:30 -03:00
kapodamy
6185c4ddcf delete list and grid icons
* delete all grid.png files
* delete all list.png files
2019-01-22 18:53:29 -03:00
SiD ViCiO
3ccbbccd10 Translated using Weblate (Spanish)
Currently translated at 91.9% (352 of 383 strings)
2019-01-22 02:53:19 +01:00
YONGLE
30e0ccc77b Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (383 of 383 strings)
2019-01-22 02:53:18 +01:00
thami simo
50f7b72b09 Translated using Weblate (Arabic)
Currently translated at 93.2% (357 of 383 strings)
2019-01-22 02:52:58 +01:00
qazaxtan
a5828c7949 Translated using Weblate (Romanian)
Currently translated at 72.1% (276 of 383 strings)
2019-01-22 02:52:55 +01:00
Tobias Groza
22a5e72470 Translated using Weblate (Romanian)
Currently translated at 72.1% (276 of 383 strings)
2019-01-22 02:52:54 +01:00
Keegan Given
5d14dca818 Add link to #1981 2019-01-21 22:21:24 +00:00
Keegan Given
f97cb821f8 Update README.md
Move Updates underneath Coming Features. Make it linkable.
2019-01-21 21:55:45 +00:00
Keegan Given
d02dce5562 Add updating section to README
This will help cull the number of "how to update?" Issues whenever we need to push out a fix, like how recently NewPipe got completely broken.

This attempts to be an all-inclusive guide to updating NewPipe. If there are any (clean/official) links to additional info, please comment them and I will add it.

Also let me know if this section should be relocated within the README. I figured it would be best to keep it up top, so people see it right away, and then don't make a new Issue.
2019-01-21 02:35:39 +00:00
Christian Schabesberger
c6bd42843b Merge pull request #2040 from TeamNewPipe/fix_livestream
Fix livestream
2019-01-20 14:24:53 +01:00
Christian Schabesberger
43e4fbfcd0 fix livestream issue, and move on to v0.15.1 2019-01-20 14:23:41 +01:00
Igor Nedoboy
ac8430cbba Translated using Weblate (Russian)
Currently translated at 100.0% (383 of 383 strings)
2019-01-20 00:22:24 +01:00
Roberto Palenga
44d2744a8c Translated using Weblate (Italian)
Currently translated at 100.0% (383 of 383 strings)
2019-01-19 01:20:51 +01:00
philSism
22bfbe96c8 Translated using Weblate (Greek)
Currently translated at 100.0% (383 of 383 strings)
2019-01-19 01:20:51 +01:00
Christian Schabesberger
c5d1271894 Merge pull request #2022 from TeamNewPipe/dev
release_v0.15.0
2019-01-18 21:18:10 +01:00
Christian Schabesberger
050d3058f9 Merge pull request #2021 from TeamNewPipe/release_v0.15.0
add changelog for version v0.15.0
2019-01-18 21:16:38 +01:00
Christian Schabesberger
2ae99afa21 add changelog for version v0.15.0 2019-01-18 21:13:04 +01:00
Christian Schabesberger
b094c9190f Merge pull request #2020 from TeamNewPipe/release_v0.15.0
Release v0.15.0
2019-01-18 20:54:52 +01:00
Christian Schabesberger
1a9e1e6f7c Merge branch 'dev' into release_v0.15.0 2019-01-18 20:50:46 +01:00
Christian Schabesberger
8c94926693 move on to version 0.15.0 2019-01-18 20:46:50 +01:00
Christian Schabesberger
880a176e65 move on to version 0.15.0 2019-01-18 20:34:43 +01:00
Christian Schabesberger
8de1b5f3d9 Merge pull request #2014 from TeamNewPipe/fix_decrypt
fix decrypt error once again
2019-01-18 17:23:38 +01:00
Christian Schabesberger
178f4546fc Merge branch 'dev' into fix_decrypt 2019-01-18 17:13:49 +01:00
Christian Schabesberger
7564a49344 fix decrypt error once again 2019-01-18 17:11:31 +01:00
Edwar Tikhonov
6c5c42c2b5 Translated using Weblate (Russian)
Currently translated at 100.0% (383 of 383 strings)
2019-01-15 19:20:55 +01:00
naofum
b1653b359e Translated using Weblate (Japanese)
Currently translated at 84.3% (323 of 383 strings)
2019-01-15 19:20:52 +01:00
DeadMetaler
11098afab5 Translated using Weblate (Belarusian)
Currently translated at 86.7% (332 of 383 strings)
2019-01-13 00:19:37 +01:00
AppSoft4
a64051e0f1 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (383 of 383 strings)
2019-01-12 21:06:58 +01:00
Nathan Follens
db8ac4a9ae Translated using Weblate (Flemish)
Currently translated at 100.0% (383 of 383 strings)
2019-01-12 21:06:57 +01:00
Nathan Follens
890474a635 Translated using Weblate (Dutch)
Currently translated at 100.0% (383 of 383 strings)
2019-01-12 21:06:54 +01:00
Tobias Groza
c7941a85ed Merge pull request #1999 from thisBrian/patch-1
Correct 'description' spelling
2019-01-11 17:15:07 +01:00
Brian
fab5f26e09 Correct 'description' spelling 2019-01-12 04:41:16 +13:00
zmni
a39b10eee9 Translated using Weblate (Indonesian)
Currently translated at 100.0% (383 of 383 strings)
2019-01-08 19:07:15 +01:00
srkrishna
97b933a990 Translated using Weblate (Tamil)
Currently translated at 40.5% (155 of 383 strings)
2019-01-07 15:06:58 +01:00
Sérgio Marques
4926e90514 Translated using Weblate (Portuguese)
Currently translated at 100.0% (383 of 383 strings)
2019-01-07 15:06:58 +01:00
zmni
6dca975844 Translated using Weblate (Indonesian)
Currently translated at 100.0% (383 of 383 strings)
2019-01-04 12:07:00 +01:00
zelos-h
e31743770a Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (383 of 383 strings)
2019-01-04 12:06:59 +01:00
Markel @wakutiteo
d8aab62d75 Translated using Weblate (Spanish)
Currently translated at 90.9% (348 of 383 strings)
2019-01-03 00:48:39 +01:00
Jeff Huang
f90603a18f Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (383 of 383 strings)
2019-01-03 00:48:38 +01:00
Marc Riera
5fce9facbe Translated using Weblate (Catalan)
Currently translated at 99.7% (382 of 383 strings)
2019-01-03 00:48:37 +01:00
zelos-h
32b48d5cdb Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (383 of 383 strings)
2019-01-03 00:48:36 +01:00
Jeff Huang
3e4f0d682b Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (383 of 383 strings)
2019-01-03 00:48:35 +01:00
Yaron Shahrabani
98449ddfe0 Translated using Weblate (Hebrew)
Currently translated at 88.5% (339 of 383 strings)
2019-01-01 10:09:41 +01:00
WaldiS
26040d4e9f Translated using Weblate (Polish)
Currently translated at 100.0% (383 of 383 strings)
2018-12-30 19:03:39 +01:00
srkrishna
83cea5e1ee Translated using Weblate (Tamil)
Currently translated at 39.7% (152 of 383 strings)
2018-12-29 00:09:42 +01:00
postsorino
3e73a5dbd3 Translated using Weblate (Greek)
Currently translated at 99.5% (381 of 383 strings)
2018-12-29 00:09:41 +01:00
Tobias Groza
dd424a4cb3 Translated using Weblate (Greek)
Currently translated at 99.5% (381 of 383 strings)
2018-12-29 00:09:38 +01:00
Éfrit
0c79f5cce3 Translated using Weblate (French)
Currently translated at 100.0% (383 of 383 strings)
2018-12-29 00:09:37 +01:00
krtkush
794c3703e5 Merge branch '1520_app_update_notif' of https://github.com/krtkush/NewPipe into 1520_app_update_notif 2018-12-28 18:09:05 +05:30
krtkush
b674006fcc Conflict resolution. 2018-12-28 18:07:54 +05:30
Yehuda Levy
38641d4edf Translated using Weblate (Hebrew)
Currently translated at 88.5% (339 of 383 strings)
2018-12-23 02:09:26 +01:00
srkrishna
450072bd23 Translated using Weblate (Tamil)
Currently translated at 39.4% (151 of 383 strings)
2018-12-21 22:09:14 +01:00
srkrishna
25eb93fae0 Translated using Weblate (Tamil)
Currently translated at 37.1% (142 of 383 strings)
2018-12-20 14:09:13 +01:00
Ben De Meester
bcf6f60571 Translated using Weblate (Flemish)
Currently translated at 85.4% (327 of 383 strings)
2018-12-20 14:09:11 +01:00
Ben De Meester
2076b8f1d7 Translated using Weblate (Dutch)
Currently translated at 100.0% (383 of 383 strings)
2018-12-20 14:09:10 +01:00
sXp
003855a94c Translated using Weblate (Hindi)
Currently translated at 85.4% (327 of 383 strings)
2018-12-15 19:03:36 +01:00
Christian Schabesberger
3599ab3caf Merge pull request #1759 from kapodamy/giga-postprocessing
Fucking travis
2018-12-11 16:37:28 +01:00
srkrishna
e7a26b436d Translated using Weblate (Tamil)
Currently translated at 32.9% (126 of 383 strings)
2018-12-10 19:42:55 +01:00
AB
ab94bc18dc Translated using Weblate (Ukrainian)
Currently translated at 100.0% (383 of 383 strings)
2018-12-10 13:08:54 +01:00
Eduardo Caron
36e91ea155 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (383 of 383 strings)
2018-12-10 13:08:53 +01:00
Christian Schabesberger
6035be9ce6 Merge branch 'dev' into giga-postprocessing 2018-12-10 12:12:38 +01:00
Kartikey Kushwaha
67499bdc65 Merge branch 'dev' into 1520_app_update_notif 2018-12-09 18:43:20 +05:30
srkrishna
9d648bad51 Translated using Weblate (Tamil)
Currently translated at 30.3% (116 of 383 strings)
2018-12-08 22:08:52 +01:00
Echelon Arpa
33021e8fe0 Translated using Weblate (Turkish)
Currently translated at 100.0% (383 of 383 strings)
2018-12-07 21:09:01 +01:00
Emin Tufan Çetin
5a2ae4c3e3 Translated using Weblate (Turkish)
Currently translated at 100.0% (383 of 383 strings)
2018-12-07 21:09:01 +01:00
srkrishna
9eaef84cc8 Translated using Weblate (Tamil)
Currently translated at 29.8% (114 of 383 strings)
2018-12-07 21:09:00 +01:00
Rintaro matsuo
fcc4d655f5 Translated using Weblate (Japanese)
Currently translated at 81.5% (312 of 383 strings)
2018-12-07 21:08:59 +01:00
ScratchBuild
8d0ac4f5f0 Translated using Weblate (Japanese)
Currently translated at 81.5% (312 of 383 strings)
2018-12-07 21:08:58 +01:00
Tobias Groza
d56cf003f8 Translated using Weblate (German)
Currently translated at 100.0% (383 of 383 strings)
2018-12-07 21:08:58 +01:00
Kristjan Räts
34d0c0b1ba Translated using Weblate (Estonian)
Currently translated at 100.0% (383 of 383 strings)
2018-12-07 21:08:56 +01:00
Vojtěch Šamla
9bbbffbe7a Translated using Weblate (Czech)
Currently translated at 100.0% (383 of 383 strings)
2018-12-07 21:08:55 +01:00
Tobias Groza
ccd0f7d9cc Merge pull request #1878 from shivanju/auto_queue_logic
#1336 Fix for inserting new streams when auto queuing is enabled
2018-12-07 19:43:41 +01:00
Kartikey Kushwaha
bfda8dcc02 Merge branch 'dev' into 1520_app_update_notif 2018-12-05 13:04:24 +05:30
kapodamy
8746e7c9ad Merge branch 'dev' into giga-postprocessing 2018-12-05 01:15:39 -03:00
kapodamy
e2aa36d083 fast download pausing
* fast download pausing
* fix UI thread blocking when calling pause()
* check running threads before start the download
* fix null pointer exception in onDestroy in the download service, without calling onCreate method (android 8)
2018-12-05 01:07:59 -03:00
Shivanju Awasthi
51434a39f8 Merge branch 'dev' into auto_queue_logic 2018-12-04 16:22:18 +00:00
Christian Schabesberger
3e2031be7c Merge pull request #1925 from TeamNewPipe/stale
disable stalebot again
2018-12-03 23:30:58 +01:00
Christian Schabesberger
ea4e8805b7 disable stalebot again 2018-12-03 23:30:28 +01:00
Tobias Groza
f62bfeac08 Merge branch 'dev' into 1520_app_update_notif 2018-12-03 16:33:46 +01:00
Tobias Groza
b9042c37f9 Merge pull request #1915 from agarwalakash06/issue-1505
Issue 1505: Delete on right swipe
2018-12-03 16:06:27 +01:00
srkrishna
49a58dcab1 Translated using Weblate (Tamil)
Currently translated at 29.0% (111 of 383 strings)
2018-12-02 16:08:42 +01:00
notramo
e2acbeddc2 Translated using Weblate (Hungarian)
Currently translated at 84.9% (325 of 383 strings)
2018-12-02 16:08:41 +01:00
Kristjan Räts
449b17d830 Translated using Weblate (Estonian)
Currently translated at 86.4% (331 of 383 strings)
2018-12-02 16:08:39 +01:00
Kartikey Kushwaha
7ed460ce02 Merge branch 'dev' into 1520_app_update_notif 2018-12-02 18:07:08 +05:30
kapodamy
9f4a7e664f more of the same
* misc code clean-up
* fix weird download speed, before switching the list view
* fix CircularFile.java getting stuck on post-processing huge files >2GiB
* keep crashed post-processing downloads visible to the user
2018-12-01 22:14:56 -03:00
Akash Agarwal
38eb75b13f Merge branch 'dev' into issue-1505 2018-12-01 14:12:28 +05:30
Christian Schabesberger
0ad56874b4 Merge pull request #1918 from TeamNewPipe/stale
add stalebot confic
2018-12-01 09:36:45 +01:00
Christian Schabesberger
c1168693fa add stalebot confic 2018-12-01 09:35:42 +01:00
Christian Schabesberger
feb8c27f1f Merge branch 'dev' into giga-postprocessing 2018-12-01 09:30:38 +01:00
Christian Schabesberger
c20ebd66e5 Merge branch 'dev' into auto_queue_logic 2018-12-01 09:29:25 +01:00
Christian Schabesberger
4d78d530dc Merge pull request #1917 from TeamNewPipe/cont
update contribution guidelines
2018-12-01 09:23:58 +01:00
Christian Schabesberger
22b20c15de update contribution guidelines 2018-12-01 09:21:57 +01:00
Akash Agarwal
c69b224c65 Issue 1505: Delete on right swipe 2018-12-01 00:42:56 +05:30
Christian Schabesberger
805d328d6c Merge pull request #1902 from Ping20002015/master
Fix NPE for issue #1901
2018-11-30 12:08:59 +01:00
mahmut özcan
edcb692f78 Translated using Weblate (Turkish)
Currently translated at 97.1% (372 of 383 strings)
2018-11-29 22:08:34 +01:00
ask6155
f33586f062 Translated using Weblate (Hindi)
Currently translated at 81.5% (312 of 383 strings)
2018-11-29 22:08:30 +01:00
kapodamy
b8293f134d Update settings_keys.xml
* remane max_try -> maximum_try
2018-11-29 15:19:53 -03:00
kapodamy
7e9bcff0f3 Merge branch 'dev' into giga-postprocessing 2018-11-28 22:53:29 -03:00
kapodamy
eba3b32708 misc improvements
* don't show notifications while download activity
* proper icon in failed download notifications
* re-write list auto-refresh (MissionAdapter.java)
* improve I/O performance (CircularFile.java)
* fix implementation of "save thread position" on multi-thread downloads
2018-11-28 22:24:52 -03:00
Olexandr Nesterenko
d61bf26e17 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (383 of 383 strings)
2018-11-28 15:17:37 +01:00
AB
2e662b5745 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (383 of 383 strings)
2018-11-28 15:17:36 +01:00
Helios
9714fa369b Translated using Weblate (Tamil)
Currently translated at 28.7% (110 of 383 strings)
2018-11-28 15:17:34 +01:00
Markel @wakutiteo
0e8acba08e Translated using Weblate (Spanish)
Currently translated at 88.3% (338 of 383 strings)
2018-11-28 15:17:28 +01:00
Sérgio Marques
a18e588e55 Translated using Weblate (Portuguese)
Currently translated at 100.0% (383 of 383 strings)
2018-11-28 15:17:27 +01:00
ask6155
bf55e3c0cc Translated using Weblate (Hindi)
Currently translated at 79.6% (305 of 383 strings)
2018-11-28 15:17:20 +01:00
ButterflyOfFire
486d114e64 Translated using Weblate (Arabic)
Currently translated at 85.9% (329 of 383 strings)
2018-11-28 15:17:19 +01:00
Prabjot Singh
ac5c060a98 Translated using Weblate (Hindi)
Currently translated at 79.6% (305 of 383 strings)
2018-11-28 15:17:18 +01:00
ask6155
86a9d197cb Translated using Weblate (Hindi)
Currently translated at 79.6% (305 of 383 strings)
2018-11-28 15:17:17 +01:00
Shivanju Awasthi
e911dbb9d4 Merge branch 'dev' into auto_queue_logic 2018-11-26 22:56:01 +05:30
Ping20002015
e26d123f67 Merge branch 'dev' into master 2018-11-26 18:00:22 +01:00
MadderRagax
70aac81900 Translated using Weblate (Swedish)
Currently translated at 98.4% (377 of 383 strings)
2018-11-26 14:42:04 +01:00
Eduardo Caron
28e78d98f6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (383 of 383 strings)
2018-11-26 14:42:02 +01:00
Mostafa Ahangarha
fabc5ae032 Translated using Weblate (Persian)
Currently translated at 62.4% (239 of 383 strings)
2018-11-26 14:42:01 +01:00
ditokp
e40882455d Translated using Weblate (Indonesian)
Currently translated at 99.7% (382 of 383 strings)
2018-11-26 14:41:54 +01:00
Whod
f1483e8c8e Translated using Weblate (Bulgarian)
Currently translated at 100.0% (383 of 383 strings)
2018-11-26 14:41:51 +01:00
Viktar Vauchkevich
bd40e2c3ff Translated using Weblate (Belarusian)
Currently translated at 86.7% (332 of 383 strings)
2018-11-26 14:41:49 +01:00
Gontzal Manuel Pujana Onaindia
9479498713 Translated using Weblate (Basque)
Currently translated at 100.0% (383 of 383 strings)
2018-11-26 14:41:49 +01:00
ssantos
6bd8523ec2 Translated using Weblate (Portuguese)
Currently translated at 100.0% (383 of 383 strings)
2018-11-26 14:41:46 +01:00
Christian Schabesberger
22e3cddd91 Merge pull request #1884 from TeamNewPipe/remove_4.x
Remove support for android 4.1 - 4.3
2018-11-25 22:49:13 +01:00
krtkush
c864b15c34 Test code revert. 2018-11-26 01:18:33 +05:30
krtkush
069654c5f9 vector -> png 2018-11-26 01:18:02 +05:30
kapodamy
f3d4d4747a and more fixes
* fix content length reading
* use float overflow. Expensive, double is used instead
* fix invalid cast after click the mission body
* use a list for maximum attemps (downloads)
* minor clean up (DownloadManager.java)
* dont pass SharedPreferences instace to DownloadManager
* use a switch instead of checkbox for cross_network_downloads
* notify media scanner after deleting a finished download
2018-11-24 20:13:18 -03:00
shivanju
5bbb0cd666 issue:1336 Remove auto queued stream if a new stream gets appended 2018-11-24 17:35:17 +05:30
Ping20002015
7b5ba3bdc2 Fix NPE for issue #1901 2018-11-23 19:38:01 +01:00
kapodamy
d647555e3a more fixes
* use bold style in status (mission_item_linear.xml)
* fix download attemps not begin updated
* dont stop the queue if a download fails
* implement partial wake-lock & wifi-lock
* show notifications for failed downloads
* ¿proper bitmap dispose? (DownloadManagerService.java)
* improve buffer filling (CircularFile.java)
* [Mp4Dash] increment reserved space from 2MiB to 15MiB. This is expensive but useful for devices with low ram
* [WebM] use 2MiB of reserved space
* fix debug warning if one thread is used
* fix wrong download speed when the activity is suspended
* Fix "Queue" menu item that appears in post-processing errors
* fix mission length dont being updated (missing commit)
2018-11-22 23:33:34 -03:00
krtkush
26e22f97ee Conflict resolution 2018-11-23 01:41:47 +05:30
Shivanju Awasthi
84976a65e0 Merge branch 'dev' into auto_queue_logic 2018-11-22 23:25:13 +05:30
kapodamy
fef9d541ed misc fixes
* use getPreferredLocalization() instead of getLocalization()
* use lastest commit in build.gradle
* fix missing cast in MissionAdapter.java
2018-11-19 15:50:15 -03:00
krtkush
ad5535af81 Code refactoring, PR changes. 2018-11-19 23:27:13 +05:30
Yaron Shahrabani
cdcfb4ffce Translated using Weblate (Hebrew)
Currently translated at 78.9% (302 of 383 strings)
2018-11-18 21:40:36 +01:00
b1a6b64a90
5403ac8893 Translated using Weblate (French)
Currently translated at 99.5% (381 of 383 strings)
2018-11-18 21:40:35 +01:00
Igor Nedoboy
9b26457781 Translated using Weblate (Russian)
Currently translated at 100.0% (383 of 383 strings)
2018-11-18 21:40:33 +01:00
krtkush
939cc56951 Pull request changes v2. 2018-11-18 19:18:16 +05:30
krtkush
23309e6fdf Pull request changes. 2018-11-18 19:15:50 +05:30
MadderRagax
3f60c961d9 Translated using Weblate (Swedish)
Currently translated at 97.9% (375 of 383 strings)
2018-11-18 13:43:46 +01:00
Ariel Shulman
9e7f07e196 Translated using Weblate (Hebrew)
Currently translated at 78.9% (302 of 383 strings)
2018-11-18 13:43:45 +01:00
ssantos
eda4439ee8 Translated using Weblate (German)
Currently translated at 100.0% (383 of 383 strings)
2018-11-18 13:43:43 +01:00
b1a6b64a90
ad02558ade Translated using Weblate (French)
Currently translated at 99.5% (381 of 383 strings)
2018-11-18 13:43:38 +01:00
WaldiS
0d1901cfe5 Translated using Weblate (Polish)
Currently translated at 100.0% (383 of 383 strings)
2018-11-17 14:08:01 +01:00
Emanuele Petriglia
ce493d1ae2 Translated using Weblate (Italian)
Currently translated at 100.0% (383 of 383 strings)
2018-11-17 14:07:59 +01:00
wellinkstein
09312ecd1d Translated using Weblate (French)
Currently translated at 98.2% (376 of 383 strings)
2018-11-17 14:07:58 +01:00
ezjerry liao
2f274d5f52 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (383 of 383 strings)
2018-11-17 14:07:56 +01:00
Marc Riera
6dbf226365 Translated using Weblate (Catalan)
Currently translated at 99.5% (381 of 383 strings)
2018-11-17 14:07:54 +01:00
Viktar Vauchkevich
6401b5f54d Translated using Weblate (Belarusian)
Currently translated at 85.1% (326 of 383 strings)
2018-11-17 14:07:50 +01:00
beriain
ac2dd81d39 Translated using Weblate (Basque)
Currently translated at 85.1% (326 of 383 strings)
2018-11-17 14:07:50 +01:00
kapodamy
6784522195 Merge branch 'dev' into giga-postprocessing 2018-11-15 20:53:30 -03:00
kapodamy
f42d077f30 misc utils
Also this include:
* Mp4 DASH reader/writter
* WebM reader/writter
* a subtitle converter for Timed Text Markup Language v1 and TranScript (v1, v2 and v3)
* SharpStream to wrap IntputStream and OutputStream in one interface
* custom implementation of DataInputStream
2018-11-15 20:17:22 -03:00
Christian Schabesberger
eaca47ebc6 Merge branch 'dev' into remove_4.x 2018-11-15 13:27:27 +01:00
Tobias Groza
4d57223847 Merge pull request #1886 from nicholasfagan/dev
Fix broken screenshots in README.md
2018-11-15 08:20:02 +01:00
Nick Fagan
d9ab96236b Fix broken screenshots in README.md 2018-11-14 15:28:44 -04:00
AB
44b2e62eef Translated using Weblate (Ukrainian)
Currently translated at 84.9% (325 of 383 strings)
2018-11-13 20:43:45 +01:00
Igor Nedoboy
7d5d4df761 Translated using Weblate (Russian)
Currently translated at 100.0% (383 of 383 strings)
2018-11-13 20:43:41 +01:00
Mostafa Ahangarha
da7716db60 Translated using Weblate (Persian)
Currently translated at 35.0% (134 of 383 strings)
2018-11-13 20:43:39 +01:00
postsorino
862c5d342b Translated using Weblate (Greek)
Currently translated at 84.6% (324 of 383 strings)
2018-11-13 20:43:34 +01:00
Igor Nedoboy
6167d5dbfc Translated using Weblate (English)
Currently translated at 100.0% (383 of 383 strings)
2018-11-13 20:43:32 +01:00
Shivanju Awasthi
2856fb029f Merge branch 'dev' into auto_queue_logic 2018-11-14 00:17:54 +05:30
Christian Schabesberger
8fb945312a put autoplay next stream setting into behaviour page 2018-11-13 17:30:04 +01:00
Christian Schabesberger
25d6806ebd set minSdk to 19 and deprecate old player 2018-11-13 17:27:47 +01:00
Christian Schabesberger
51070d9afd Merge pull request #1818 from TeamNewPipe/screenshot-order
Fix fastlane screenshot order
2018-11-13 16:58:37 +01:00
Christian Schabesberger
1048caa496 Merge branch 'dev' into screenshot-order 2018-11-13 16:58:18 +01:00
Christian Schabesberger
0cd7ac05aa Merge pull request #1879 from jludden/play_one_click
downloaded files can now be opened with one click
2018-11-13 13:43:28 +01:00
Christian Schabesberger
2793c42d91 Merge branch 'dev' into play_one_click 2018-11-13 13:18:57 +01:00
Christian Schabesberger
2eaa7288e4 Merge pull request #1876 from bboa/dev
Missed strings
2018-11-13 12:53:01 +01:00
ssantos
f9341bea79 Translated using Weblate (Portuguese)
Currently translated at 100.0% (383 of 383 strings)
2018-11-12 10:08:44 +01:00
jludden
0df8d13020 downloads can now be viewed with one click fixed 2018-11-11 22:25:37 +08:00
jludden
d27622de1e downloaded files can now be opened with one click
For consistency, I removed the view file option from the overflow menu as well
2018-11-11 19:54:35 +08:00
shivanju
47c3da131c issue:1336 Fix for inserting new streams when auto queuing is enabled 2018-11-11 16:24:49 +05:30
Igor Nedoboy
3a608bb582 Translated using Weblate (Russian)
Currently translated at 100.0% (383 of 383 strings)
2018-11-11 00:47:04 +01:00
Igor Nedoboy
64f9228ee3 Update strings.xml 2018-11-11 02:44:45 +03:00
Igor Nedoboy
c926482b3c Translated using Weblate (Russian)
Currently translated at 100.0% (383 of 383 strings)
2018-11-10 23:38:19 +01:00
Igor Nedoboy
9562972c42 Translated using Weblate (Russian)
Currently translated at 100.0% (383 of 383 strings)
2018-11-10 23:02:17 +01:00
Igor Nedoboy
b462b7fcc4 Update strings.xml 2018-11-10 17:08:04 +03:00
Igor Nedoboy
b34f9d7fd3 Update strings.xml 2018-11-10 03:06:55 +03:00
Igor Nedoboy
57732c3e5f Update strings.xml 2018-11-10 02:56:14 +03:00
kapodamy
eb1f56488f resbase (08/11/2018) 2018-11-08 22:00:13 -03:00
kapodamy
5825843f68 main commit
Post-processing infrastructure
* remove interfaces with one implementation
* fix download resources with unknow length
* marquee style for ProgressDrawable
* "view details" option in mission context menu
* notification for finished downloads
* postprocessing infrastructure: sub-missions, circular file, layers for layers of abstractions for Java IO streams
* Mp4 muxing (only DASH brand)
* WebM muxing
* Captions downloading
* alert dialog for overwrite existing downloads finished or not.

Misc changes
* delete SQLiteDownloadDataSource.java
* delete DownloadMissionSQLiteHelper.java
* implement Localization from #114

Misc fixes (this branch)
* restore old mission listeners variables. Prevents registered listeners get de-referenced on low-end devices
* DownloadManagerService.checkForRunningMission() now return false if the mission has a error.
* use Intent.FLAG_ACTIVITY_NEW_TASK when launching an activity from gigaget threads (apparently it is required in old versions of android)

More changes
* proper error handling "infrastructure"
* queue instead of multiple downloads
* move serialized pending downloads (.giga files) to app data
* stop downloads when swicthing to mobile network (never works, see 2nd point)
* save the thread count for next downloads
* a lot of incoherences fixed
* delete DownloadManagerTest.java (too many changes to keep this file updated)
2018-11-08 19:00:44 -03:00
Андрій
dd56a5d869 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (383 of 383 strings)
2018-11-08 17:07:53 +01:00
Nitin Khanna
86f82c0e61 pop-up player crash fixed 2018-11-07 22:01:39 +05:30
rimasx
8ae45240b2 Translated using Weblate (Estonian)
Currently translated at 99.7% (382 of 383 strings)
2018-11-06 15:02:39 +01:00
WaldiS
6d845af7f1 Translated using Weblate (Polish)
Currently translated at 100.0% (383 of 383 strings)
2018-11-02 08:23:47 +01:00
Piotr Kaczmarek
733ebb8caf Translated using Weblate (Polish)
Currently translated at 100.0% (383 of 383 strings)
2018-11-02 08:23:47 +01:00
Max Mathys
695a194467 Translated using Weblate (Finnish)
Currently translated at 100.0% (383 of 383 strings)
2018-11-02 08:23:46 +01:00
simo
8a56257ade Translated using Weblate (Arabic)
Currently translated at 100.0% (383 of 383 strings)
2018-11-02 08:23:45 +01:00
Christian Schabesberger
45fea983b9 Merge pull request #1835 from ritiek/fix-res-crash
Fix crash with default resolution set to best and limited mobile data resolution
2018-11-01 20:22:44 +01:00
Ritiek Malhotra
642b499e70 Fix crash with default resolution best on mobile data 2018-10-31 21:44:12 +05:30
Christian Schabesberger
5c5575bb72 Merge branch 'dev' into screenshot-order 2018-10-31 10:26:31 +01:00
Dual Natan
4d5dc0d39c Translated using Weblate (Macedonian)
Currently translated at 100.0% (383 of 383 strings)
2018-10-29 19:23:30 +01:00
Christian Schabesberger
fba0a8036b Merge pull request #1825 from TeamNewPipe/release_0.14.2
Release 0.14.2
2018-10-27 09:58:56 +02:00
Christian Schabesberger
d82274f5d4 fucking android bullshit 2018-10-27 09:57:11 +02:00
Christian Schabesberger
3cf81230b2 Merge branch 'dev' into release_0.14.2 2018-10-26 15:02:11 +02:00
Christian Schabesberger
17bab456e4 Merge pull request #1845 from AndhikaWB/patch-1
Fix spelling of country names in settings
2018-10-26 15:01:34 +02:00
Christian Schabesberger
e1cc006db7 fix race condition when returning to main player 2018-10-26 14:59:49 +02:00
Tobias Groza
7e95dd3c76 Change from "Côte d'Ivoire" to "Côte d\'Ivoire"
Co-Authored-By: AndhikaWB <karagasnoelan@gmail.com>
2018-10-26 16:22:35 +07:00
AndhikaWB
ff3ce46a26 Update settings_keys.xml 2018-10-26 00:09:04 +07:00
AndhikaWB
4d61c2c5e0 Fix spelling in of country names in settings
- [x] I carefully read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them.

Closes TeamNewPipe/NewPipe#1504
2018-10-26 00:04:36 +07:00
Eduardo Caron
8589ee14ff Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (383 of 383 strings)
2018-10-25 17:26:00 +02:00
Stjepan
1727387c9c Translated using Weblate (Croatian)
Currently translated at 86.9% (333 of 383 strings)
2018-10-25 17:25:58 +02:00
ezjerry liao
e1146e4655 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (383 of 383 strings)
2018-10-25 17:25:54 +02:00
Christian Schabesberger
afaba7ccdf fix broken search result 2018-10-25 15:58:06 +02:00
Christian Schabesberger
ccb3ceae4f add release node 2018-10-25 10:58:37 +02:00
Christian Schabesberger
d0d93f6d2d Merge branch 'dev' into release_0.14.2 2018-10-25 10:57:39 +02:00
Christian Schabesberger
67f091c731 Merge pull request #1830 from boredomdenied/dev
Add foreground service permission for API 28+ devices
2018-10-25 10:54:52 +02:00
krtkush
c29b0645bd Merge branch '1520_app_update_notif' of https://github.com/krtkush/NewPipe into 1520_app_update_notif 2018-10-22 23:12:51 +05:30
krtkush
96dac0f979 Code review suggested changes. 2018-10-22 23:12:25 +05:30
Tobias Groza
fda9b59129 Code review changes.
Co-Authored-By: krtkush <kartikey92@gmail.com>
2018-10-22 23:08:46 +05:30
Christian Schabesberger
fa3aebb7b1 localisation to localization 2018-10-22 12:25:50 +02:00
Christian Schabesberger
988251deb6 fix weblate collision 2018-10-22 12:15:16 +02:00
Christian Schabesberger
d99a389c49 merge weblate 2018-10-22 12:07:12 +02:00
Marian Hanzel
7508f9d3bb Translated using Weblate (Slovak)
Currently translated at 100.0% (383 of 383 strings)
2018-10-21 09:45:21 +02:00
Heimen Stoffels
b2512d7aee Translated using Weblate (Dutch)
Currently translated at 100.0% (383 of 383 strings)
2018-10-21 09:45:18 +02:00
Olexandr Nesterenko
eb74e1e3b2 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (383 of 383 strings)
2018-10-21 09:45:13 +02:00
boredomdenied
bd91e82b17 Update AndroidManifest.xml
remove needless additional line
2018-10-19 21:07:59 -05:00
boredomdenied
bae60acfe7 Update AndroidManifest.xml
Fix foreground playback in android Pie API 28. Tested in emulator & Pixel 3 device.
2018-10-19 20:52:22 -05:00
Emin Tufan Çetin
426cefe8ee Translated using Weblate (Turkish)
Currently translated at 100.0% (383 of 383 strings)
2018-10-20 01:33:46 +02:00
Allan Nordhøy
a36fe3ef21 Translated using Weblate (Swedish)
Currently translated at 100.0% (383 of 383 strings)
2018-10-20 01:33:46 +02:00
Allan Nordhøy
ee0756c7c0 Translated using Weblate (Russian)
Currently translated at 99.7% (382 of 383 strings)
2018-10-20 01:33:45 +02:00
Jaggie
44b96121e4 Translated using Weblate (Punjabi)
Currently translated at 7.3% (28 of 383 strings)
2018-10-20 01:33:44 +02:00
Allan Nordhøy
8add777b34 Translated using Weblate (German)
Currently translated at 100.0% (383 of 383 strings)
2018-10-20 01:33:42 +02:00
Allan Nordhøy
6932b15144 Translated using Weblate (English)
Currently translated at 100.0% (383 of 383 strings)
2018-10-20 01:33:41 +02:00
Allan Nordhøy
c09edda797 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.7% (382 of 383 strings)
2018-10-20 01:33:38 +02:00
Christian Schabesberger
0e86010565 move on to version 0.14.2 2018-10-19 15:53:19 +02:00
Tobias Groza
6fe3fdce11 Merge pull request #1828 from comradekingu/patch-8
Spelling and linguistic updates for readme
2018-10-19 11:00:39 +02:00
Allan Nordhøy
90769a12b1 Spelling and linguistic updates 2018-10-19 02:05:29 +02:00
Kartikey Kushwaha
3c6d27b504 Delete R.java 2018-10-18 23:04:07 +05:30
Kartikey Kushwaha
6ef25eb861 Delete Manifest.java 2018-10-18 23:03:21 +05:30
Kartikey Kushwaha
ec28e972bb Delete BuildConfig.java 2018-10-18 23:02:59 +05:30
Kartikey Kushwaha
d1a9033525 Delete R.java 2018-10-18 23:02:38 +05:30
Kartikey Kushwaha
2d5bc3ada8 Delete Manifest.java 2018-10-18 23:02:30 +05:30
Kartikey Kushwaha
506ffb9885 Delete BuildConfig.java 2018-10-18 23:02:19 +05:30
krtkush
8ef702fa07 Removed updates options from settings in case of non github apk. 2018-10-18 22:59:33 +05:30
Mostafa Ahangarha
1d49b725b6 Translated using Weblate (Persian)
Currently translated at 23.2% (89 of 383 strings)
2018-10-18 11:31:57 +02:00
Stjepan
204feb97ce Translated using Weblate (Croatian)
Currently translated at 86.9% (333 of 383 strings)
2018-10-18 11:31:56 +02:00
maa123
e12389a748 Translated using Weblate (Japanese)
Currently translated at 96.8% (371 of 383 strings)
2018-10-18 11:31:52 +02:00
TobiGr
907e5a10d6 Rename screenshots to display them in correct order in the F-Droid app 2018-10-16 16:33:09 +02:00
Christian Schabesberger
670591e221 Merge pull request #1792 from TeamNewPipe/lang
Add support for Localization
2018-10-16 16:16:05 +02:00
Christian Schabesberger
8006a7d578 Merge branch 'dev' into lang 2018-10-16 16:01:32 +02:00
Christian Schabesberger
8b9078b82e Merge pull request #1801 from TacoTheDank/dev
simply update gradles to 28, AS 3.2.0, and a few dependencies
2018-10-16 16:00:44 +02:00
Christian Schabesberger
56d4edd3ae Merge branch 'dev' into dev 2018-10-16 03:47:29 +02:00
Christian Schabesberger
a7f5ea865e Merge branch 'dev' into lang 2018-10-16 03:47:11 +02:00
Christian Schabesberger
2a62144abd Merge pull request #1800 from TeamNewPipe/history-screenshot
Update screenshots and readme
2018-10-16 03:46:38 +02:00
Christian Schabesberger
c7ea7a4f65 Merge branch 'dev' into dev 2018-10-16 03:45:14 +02:00
TobiGr
7ea090ba8d Update video detail screenshot 2018-10-15 15:16:24 +02:00
TobiGr
9294e353d3 Add link to privacy policy to readme
Closes TeamNewPipe/NewPipe#1789
Part of TeamNewPipe/NewPipe#1817
2018-10-15 15:02:34 +02:00
TobiGr
71c50c58ee Move tablet screenshots for better F-Droid integration 2018-10-15 15:00:41 +02:00
TobiGr
e322299fda Update history screenshot 2018-10-15 14:52:07 +02:00
Ákos Surányi
06602a568a Translated using Weblate (Hungarian)
Currently translated at 90.8% (348 of 383 strings)
2018-10-14 21:31:53 +02:00
krtkush
54ac5e8940 Merge branch '1520_app_update_notif' of https://github.com/krtkush/NewPipe into 1520_app_update_notif 2018-10-14 19:16:59 +05:30
krtkush
e2341363d4 Added check for SHA1 key. 2018-10-14 19:16:28 +05:30
Tobias Groza
a0f0529ad8 Merge pull request #1810 from saderror256/patch-1
Fix broken bold
2018-10-14 15:01:24 +02:00
SadError256
c371d863d9 Fix broken bold
also removed useless backslash in html5
2018-10-13 18:20:47 -04:00
Ákos Surányi
16d8c75953 Translated using Weblate (Hungarian)
Currently translated at 41.5% (159 of 383 strings)
2018-10-13 17:20:53 +02:00
Toldi Balázs
fa48c0e76b Translated using Weblate (Hungarian)
Currently translated at 41.5% (159 of 383 strings)
2018-10-13 17:20:51 +02:00
Tobias Groza
2d17cfaf2b Merge pull request #1644 from ritiek/separate-gesture-options
Separate options for volume and brightness gestures
2018-10-11 17:41:34 +02:00
Matej U
f379de0a3a Translated using Weblate (Slovenian)
Currently translated at 62.6% (240 of 383 strings)
2018-10-11 11:34:26 +02:00
TacoTheDank
d3fcb0aa6a Update build.gradle 2018-10-10 14:29:26 -04:00
Ritiek Malhotra
046740f10b Merge branch 'dev' into separate-gesture-options 2018-10-10 08:41:42 -07:00
TacoTheDank
ffae2eda83 simply update gradles to 28, and some dependencies 2018-10-09 12:22:30 -04:00
Christian Schabesberger
8334b8fe46 Merge pull request #1798 from TeamNewPipe/screenshots
add tablet screenshot
2018-10-08 12:35:57 +02:00
Christian Schabesberger
c6c6e58d28 add tablet screenshot 2018-10-08 12:35:13 +02:00
Christian Schabesberger
2ff0d6665c Merge pull request #1617 from nv95/tablet_ui
Tablet ui and grid list layout
2018-10-08 12:15:02 +02:00
Christian Schabesberger
dc6108c970 Merge branch 'dev' into tablet_ui 2018-10-08 11:56:25 +02:00
Christian Schabesberger
6f52f9b4af Merge pull request #1796 from saderror256/patch-1
Update README.md
2018-10-08 11:39:31 +02:00
zelos-h
b6eb896f0b Translated using Weblate (Chinese (Traditional))
Currently translated at 99.7% (382 of 383 strings)
2018-10-08 10:57:24 +02:00
SadError256
adf861c36d Update README.md 2018-10-07 13:39:02 -04:00
Christian Schabesberger
4e1bcdf209 Merge pull request #1516 from Somethingweirdhere/LongTapInSubs
Long-tap delete and share in subscriptions
2018-10-07 14:08:09 +02:00
Christian Schabesberger
d107fe19f7 Merge branch 'dev' into LongTapInSubs 2018-10-07 13:59:39 +02:00
Christian Schabesberger
fa03ff51d4 Merge pull request #1651 from comradekingu/patch-7
Spelling: Language rework
2018-10-07 13:39:03 +02:00
Christian Schabesberger
f6c6536b48 Merge branch 'dev' into patch-7 2018-10-07 13:26:10 +02:00
Christian Schabesberger
38e4249182 Merge branch 'dev' into lang 2018-10-07 13:25:35 +02:00
Christian Schabesberger
2dd8c0beee Merge pull request #1773 from prg318/clarify-bookmarks
[Closes #1772] change 'tab_bookmarks' string to 'Bookmarked Playlists' for UI clarity
2018-10-06 18:16:38 +02:00
Christian Schabesberger
6a35494ef1 Merge branch 'dev' into patch-7 2018-10-06 18:16:10 +02:00
Christian Schabesberger
bf1c42e085 Merge branch 'dev' into clarify-bookmarks 2018-10-06 18:10:18 +02:00
Christian Schabesberger
f1aa3d8c90 Merge branch 'dev' into 1520_app_update_notif 2018-10-06 18:04:39 +02:00
Christian Schabesberger
0ef6bf8067 Merge pull request #1783 from coin3x/enqueue-autoplay
Enqueuing now triggers video playing if the play queue has already ended
2018-10-06 17:55:22 +02:00
Christian Schabesberger
9d63e2ae97 Merge branch 'dev' into enqueue-autoplay 2018-10-06 17:35:55 +02:00
Christian Schabesberger
fdd8060296 Merge branch 'dev' into lang 2018-10-06 17:29:42 +02:00
Christian Schabesberger
ebbb23ffbb fix bugs in country search 2018-10-06 17:18:19 +02:00
Christian Schabesberger
0e413109bd Merge pull request #1748 from MaX-Lo/store_last_aspect_ratio
store and reload the last used aspect ratio
2018-10-06 12:09:14 +02:00
Christian Schabesberger
f7e29c1e00 revert sdk upgrade 2018-10-06 12:06:03 +02:00
Christian Schabesberger
42f777d49a Merge branch 'dev' into clarify-bookmarks 2018-10-06 12:01:34 +02:00
Christian Schabesberger
efb8ea4c25 make local settings be live updated 2018-10-05 16:31:23 +02:00
Christian Schabesberger
52bf5690c0 add support for content language and content country 2018-10-05 16:20:27 +02:00
Jasmine Vyas
607dff9263 Translated using Weblate (Hindi)
Currently translated at 92.6% (355 of 383 strings)
2018-10-04 22:28:46 +02:00
Coin
0510db75fb Enqueuing now triggers video playing if the play queue has done playing 2018-10-03 00:31:28 +08:00
MaX
859eecabc0 Merge branch 'dev' into store_last_aspect_ratio 2018-10-02 10:44:04 +02:00
MadderRagax
7a0253631b Translated using Weblate (Swedish)
Currently translated at 100.0% (383 of 383 strings)
2018-10-01 18:21:22 +02:00
ezjerry liao
c12a60c1ad Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (383 of 383 strings)
2018-10-01 18:21:21 +02:00
ButterflyOfFire
df9dbd6130 Translated using Weblate (Arabic)
Currently translated at 100.0% (383 of 383 strings)
2018-10-01 18:21:16 +02:00
Tobias Groza
a98e745116 Merge pull request #1771 from jludden/dev
Enable linear layout in Downloads activity with full video names
2018-10-01 17:40:40 +02:00
Lukas Sabota
61f1b10a06 Change 'tab_bookmarks' string to 'Bookmarked Playlists' for UI clarity 2018-09-30 11:34:16 -04:00
jludden
4bcb2a5c9d update linear layout
re-arrange elements and allow the video title to display over multiple lines
2018-09-30 07:42:02 +08:00
MaX
7e48648f9e Merge branch 'dev' into store_last_aspect_ratio 2018-09-29 12:45:10 +02:00
jludden
bcc97d1aa7 Adding switch view button to downloads activity
Can now switch between linear and grid layouts in the downloads activity
2018-09-29 15:13:15 +08:00
Christian Schabesberger
49f9d36718 Merge pull request #1640 from BO41/code-clean
Code clean
2018-09-28 17:17:30 +02:00
O Passante
f9cb258a5e Translated using Weblate (Galician)
Currently translated at 100.0% (383 of 383 strings)
2018-09-28 11:26:41 +02:00
O Passante
97381a4908 Translated using Weblate (Galician)
Currently translated at 100.0% (383 of 383 strings)
2018-09-27 01:28:42 +02:00
RainSlide
7a15acc546 Translated using Weblate (Chinese (Simplified))
Currently translated at 29.2% (112 of 383 strings)
2018-09-25 18:13:16 +02:00
O Passante
0265de2137 Added translation using Weblate (Galician) 2018-09-25 18:13:14 +02:00
Nigel Ticknor
595b9910f5 Translated using Weblate (Japanese)
Currently translated at 93.9% (360 of 383 strings)
2018-09-25 14:22:36 +02:00
Heimen Stoffels
f4d4d2cc35 Translated using Weblate (Dutch)
Currently translated at 100.0% (383 of 383 strings)
2018-09-25 14:22:35 +02:00
zelos-h
b4fb4d7dcf Translated using Weblate (Chinese (Traditional))
Currently translated at 96.0% (368 of 383 strings)
2018-09-25 14:22:30 +02:00
RainSlide
1a375fb523 Translated using Weblate (Chinese (Simplified))
Currently translated at 29.2% (112 of 383 strings)
2018-09-25 14:22:27 +02:00
zelos-h
658c0ff5c4 Translated using Weblate (Chinese (Hong Kong))
Currently translated at 37.8% (145 of 383 strings)
2018-09-25 14:22:23 +02:00
MaX-Lo
6092f06d46 store the last used aspect ratio in SharedPreferences and reload them on
resuming the VideoPlayer Activity (similar to storing/reloading the last used: screen rotation)
2018-09-22 11:32:13 +02:00
ezjerry liao
4671b956b3 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (383 of 383 strings)
2018-09-21 16:22:02 +02:00
postsorino
552ba6999a Translated using Weblate (Greek)
Currently translated at 99.7% (382 of 383 strings)
2018-09-20 15:16:07 +02:00
zelos-h
b86bc4455f Translated using Weblate (Chinese (Traditional))
Currently translated at 94.2% (361 of 383 strings)
2018-09-20 15:16:03 +02:00
Allan Nordhøy
b6d36ee117 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (383 of 383 strings)
2018-09-19 08:30:03 +02:00
Tobias Groza
5d57f864dc Translated using Weblate (Vietnamese)
Currently translated at 97.1% (372 of 383 strings)
2018-09-17 23:21:52 +02:00
naofum
c6ee2816db Translated using Weblate (Japanese)
Currently translated at 93.7% (359 of 383 strings)
2018-09-17 23:21:51 +02:00
Tobias Groza
0aa898b13f Translated using Weblate (Chinese (Hong Kong))
Currently translated at 37.8% (145 of 383 strings)
2018-09-17 23:21:49 +02:00
Stjepan
b22e82c8ce Translated using Weblate (Croatian)
Currently translated at 85.6% (328 of 383 strings)
2018-09-17 23:21:47 +02:00
zelos-h
63dee1e1ac Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (383 of 383 strings)
2018-09-16 13:27:50 +02:00
epsimatt
91c87ac301 Translated using Weblate (Korean)
Currently translated at 96.0% (368 of 383 strings)
2018-09-16 13:27:46 +02:00
Kartikey Kushwaha
7124d9bca5 Removed flvor checks. Added update settings under main settings. 2018-09-15 20:51:17 +05:30
Kartikey Kushwaha
6417bd91ef Pull request changes v1. 2018-09-15 14:08:32 +05:30
Kartikey Kushwaha
cde5f7d12e Merge branch 'dev' of https://github.com/krtkush/NewPipe into 1520_app_update_notif 2018-09-15 13:30:09 +05:30
Kartikey Kushwaha
395c9587b6 Conflict resolution. 2018-09-15 13:22:13 +05:30
Kartikey Kushwaha
17197ad670 Pull request changes begins here. 2018-09-15 12:51:39 +05:30
Shuuji TAKAHASHI (shuuji3)
42371a6e8d Translated using Weblate (Japanese)
Currently translated at 88.7% (340 of 383 strings)
2018-09-14 21:20:02 +02:00
Ritiek Malhotra
d7aae849f6 Merge branch 'dev' into separate-gesture-options 2018-09-12 21:41:47 +05:30
DanieLoche
4fe9413662 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (383 of 383 strings)
2018-09-12 12:20:25 +02:00
CaptainCrumble
37bf51cebf Translated using Weblate (Portuguese)
Currently translated at 91.3% (350 of 383 strings)
2018-09-12 12:20:25 +02:00
DanieLoche
eee9cb8b87 Translated using Weblate (Portuguese)
Currently translated at 91.3% (350 of 383 strings)
2018-09-12 12:20:24 +02:00
DanieLoche
1724bca5f0 Translated using Weblate (French)
Currently translated at 100.0% (383 of 383 strings)
2018-09-12 12:20:21 +02:00
Cipisek Rumcajsu
ae6581fb55 Translated using Weblate (Czech)
Currently translated at 99.7% (382 of 383 strings)
2018-09-12 12:20:20 +02:00
Michael Moroni
0c632307ea Translated using Weblate (Italian)
Currently translated at 100.0% (383 of 383 strings)
2018-09-12 12:20:15 +02:00
BO41
479887eb84 fix broken merge resolve
fixing merge conflicts broke build
2018-09-11 19:37:12 +02:00
BO41
af280a7343 Java language level + javadoc + xml
replace with <>
String builder

BUILD SUCCESSFUL in 4s
39 actionable tasks: 4 executed, 35 up-to-date
2018-09-11 19:20:10 +02:00
BO41
802b26e870 error handling + imports + unboxing 2018-09-11 19:18:50 +02:00
BO41
0ab86937d2 data flow issue + declaration redundancy
make final
unused methods
make final

BUILD SUCCESSFUL in 0s
39 actionable tasks: 39 up-to-date
2018-09-11 19:18:41 +02:00
BO41
3ab06bf383 class structure
BUILD SUCCESSFUL in 17s
39 actionable tasks: 6 executed, 33 up-to-date
2018-09-11 19:18:14 +02:00
BO41
5db0cc5241 performance + usability
obsolete layout params
Ellipsis string can be replaced with ellipsis character
Missing inputType
Usage of showAsAction=always

BUILD SUCCESSFUL in 5s
39 actionable tasks: 4 executed, 35 up-to-date
2018-09-11 19:18:14 +02:00
BO41
a588ec084b correctness
use apply() on SharedPreferences
use dp instead of sp for text sizes

BUILD SUCCESSFUL in 22s
39 actionable tasks: 10 executed, 29 up-to-date
2018-09-11 19:18:02 +02:00
BO41
5660b5ddc6 accessibility
image without contentDescription
Keyboard inaccessible widget

BUILD SUCCESSFUL in 6s
39 actionable tasks: 4 executed, 35 up-to-date
2018-09-11 19:14:42 +02:00
BO41
27fbe69033 code cleanup
mainly removes throw statements

automated using Android Studio, staged by hand

BUILD SUCCESSFUL in 52s
39 actionable tasks: 37 executed, 2 up-to-date
2018-09-11 19:14:21 +02:00
Christian Schabesberger
1672ecb892 Merge branch 'dev' into patch-7 2018-09-11 16:34:58 +02:00
Christian Schabesberger
784e01347c fix wrong subscriptini count 2018-09-11 15:16:39 +02:00
Emin Tufan Çetin
5cc21a831b Translated using Weblate (Turkish)
Currently translated at 100.0% (383 of 383 strings)
2018-09-10 19:13:10 +02:00
Robson Cassiano
dcb9380b50 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (383 of 383 strings)
2018-09-10 19:13:08 +02:00
ButterflyOfFire
b1957773bb Translated using Weblate (Arabic)
Currently translated at 100.0% (383 of 383 strings)
2018-09-10 19:13:05 +02:00
switchtegrax1
c4f4583f20 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.7% (382 of 383 strings)
2018-09-09 18:58:19 +02:00
Dominik Zabicki
440cdbdb23 Translated using Weblate (Polish)
Currently translated at 100.0% (383 of 383 strings)
2018-09-09 18:58:18 +02:00
Duppadaadadii
58bfde33a9 Translated using Weblate (Finnish)
Currently translated at 99.7% (382 of 383 strings)
2018-09-09 18:58:17 +02:00
skil3z
7c33e49ef6 Translated using Weblate (Finnish)
Currently translated at 99.7% (382 of 383 strings)
2018-09-09 18:58:17 +02:00
Rex_sa
3bf318e2b7 Translated using Weblate (Arabic)
Currently translated at 100.0% (383 of 383 strings)
2018-09-09 18:58:10 +02:00
Ritiek Malhotra
f56193ac18 We don't need to check this 2018-09-08 23:13:04 +05:30
ScratchBuild
31efa7a8f8 Translated using Weblate (Japanese)
Currently translated at 74.1% (284 of 383 strings)
2018-09-08 19:15:24 +02:00
skil3z
372932abaf Translated using Weblate (Finnish)
Currently translated at 74.6% (286 of 383 strings)
2018-09-08 19:15:23 +02:00
M Andreev
4c6dd2cdf2 Translated using Weblate (Bulgarian)
Currently translated at 99.4% (381 of 383 strings)
2018-09-08 19:15:18 +02:00
Stjepan
6d04e39151 Translated using Weblate (Croatian)
Currently translated at 65.7% (252 of 383 strings)
2018-09-08 19:15:15 +02:00
Ritiek Malhotra
afa257e79a Merge branch 'dev' into separate-gesture-options 2018-09-08 10:05:51 -07:00
Christian Schabesberger
05f8ee9747 fix channel links in description part 2 2018-09-07 22:23:32 +02:00
Christian Schabesberger
818ae03928 fix decrypt url and move on to v0.14.1 2018-09-07 21:51:14 +02:00
Ritiek Malhotra
97555645f8 Merge branch 'dev' into separate-gesture-options 2018-09-05 09:21:05 -07:00
kaka Thic
e350f43adc Translated using Weblate (Vietnamese)
Currently translated at 95.8% (367 of 383 strings)
2018-09-05 08:26:05 +02:00
Çağdaş Tatar
2257f3484d Translated using Weblate (Turkish)
Currently translated at 100.0% (383 of 383 strings)
2018-09-05 08:25:35 +02:00
Christian Schabesberger
f4ba8df02b Merge branch 'dev' into patch-7 2018-09-04 17:53:39 +02:00
notramo
99ebf03876 Translated using Weblate (Hungarian)
Currently translated at 40.7% (156 of 383 strings)
2018-09-03 19:18:49 +02:00
postsorino
995d79e373 Translated using Weblate (Greek)
Currently translated at 98.1% (376 of 383 strings)
2018-09-03 19:18:46 +02:00
Allan Nordhøy
0cd153ab61 Spelling: Language rework 2018-09-03 13:07:10 +02:00
Vasily
b24baa68ba Tablet UI in player 2018-08-31 17:30:06 +03:00
Vasily
d4c1b8d321 Fix: remove title from PlaylistDialog 2018-08-31 17:12:56 +03:00
Vasily
1e53d6bfab Scroll top related streams when loading 2018-08-31 17:08:13 +03:00
Ritiek Malhotra
5931cd6af7 Separate options for volume and brightness gestures 2018-08-31 19:30:40 +05:30
u1
a1be03543c Grid layout for subscriptions 2018-08-31 16:49:25 +03:00
u1
b1a5547de2 Fix reordering playlist items on grid layout 2018-08-31 16:34:35 +03:00
Vasily
ee4942dfd7 Grid layout for local lists 2018-08-31 14:34:32 +03:00
Vasily
8aef24be1e Merge branch 'tablet_ui' of https://github.com/nv95/NewPipe into tablet_ui 2018-08-29 09:00:15 +03:00
Vasily
fb25f6c7ac Automatic list layout 2018-08-29 08:19:15 +03:00
Vasily
fbd983217d Hide related streams while loading 2018-08-29 08:08:35 +03:00
Vasily
ce21fe2087 Always show description on tablets 2018-08-29 08:01:18 +03:00
Christian Schabesberger
17cd395712 Merge branch 'dev' into tablet_ui 2018-08-28 21:28:10 +02:00
Christian Schabesberger
bfe9de05cd Merge branch 'dev' into LongTapInSubs 2018-08-28 18:39:11 +02:00
Somethingweirdhere
4326354ca6 Code cleanup 2018-08-22 13:59:12 +02:00
Vasily
4208c852e1 Update translations 2018-08-22 10:33:10 +03:00
Vasily
7330b4532e Fix crash on screen rotation 2018-08-22 10:29:37 +03:00
Vasily
1e0f6f9e41 Grid view 2018-08-22 10:14:01 +03:00
Vasily
216e2367c6 Video details tablet layout 2018-08-22 08:32:58 +03:00
Kartikey Kushwaha
910c10f554 Removed debug code 2018-08-17 01:46:33 +05:30
Kartikey Kushwaha
04e974b326 Bug fix. 2018-08-17 01:11:51 +05:30
Kartikey Kushwaha
e7abeb5ad9 Added version code check. 2018-08-17 00:53:42 +05:30
Somethingweirdhere
f4416fe007 Doesn't use getChannelInfo() anymore. 2018-08-16 01:04:37 +02:00
Somethingweirdhere
510591ef0f Removed use of blockingFirst() and scheduleDirect() 2018-08-16 00:45:37 +02:00
Somethingweirdhere
a5e89d1dd1 Merge branch 'dev' into LongTapInSubs 2018-08-15 23:33:59 +02:00
Kartikey Kushwaha
2a18eacf62 More refactoring. 2018-08-12 20:57:30 +05:30
Kartikey Kushwaha
af42e32ae6 Code refactored and added comments. 2018-08-12 18:34:20 +05:30
Kartikey Kushwaha
12b93d6637 Added new icon for update notification. 2018-08-12 17:18:46 +05:30
Kartikey Kushwaha
930c971035 Added version check in the pop-up player 2018-08-12 16:41:21 +05:30
Kartikey Kushwaha
06f20c66f8 Moved the new version check to the application class. 2018-08-12 16:35:53 +05:30
Kartikey Kushwaha
7c875a8541 Merge branch 'dev' of https://github.com/krtkush/NewPipe into 1520_app_update_notif 2018-08-12 15:19:15 +05:30
Kartikey Kushwaha
f85e19c75d Added notification channel and code to show notification. 2018-08-12 15:01:50 +05:30
Kartikey Kushwaha
5e2aa51627 Moved the asynctask to its own class. 2018-08-11 19:36:23 +05:30
Kartikey Kushwaha
75a44fb30a Added HTTPS request to get version data. Added APK flaor for github and fdroid. 2018-08-11 19:13:52 +05:30
Somethingweirdhere
b48c251b36 Added unsubscription toast
Added error handling
Corrected threads
2018-06-30 02:42:47 +02:00
Somethingweirdhere
181a14ce59 Disposable now is cleaned properly 2018-06-30 02:20:01 +02:00
Somethingweirdhere
b9ea7ce066 Code looking nicer 2018-06-29 00:39:44 +02:00
Somethingweirdhere
f2f275512d It looks good now 2018-06-29 00:39:16 +02:00
Somethingweirdhere
5150c2ee62 This thing actually works lol 2018-06-28 23:56:39 +02:00
Somethingweirdhere
0b7593ad28 Delete on long press 2018-06-28 20:43:46 +02:00
Somethingweirdhere
a68823491c Delete on long press 2018-06-28 20:37:05 +02:00
324 changed files with 15634 additions and 5217 deletions

View File

@@ -5,44 +5,64 @@ PLEASE READ THESE GUIDELINES CAREFULLY BEFORE ANY CONTRIBUTION!
## Crash reporting
Do not report crashes in the GitHub issue tracker. NewPipe has an automated crash report system that will ask you to send a report via e-mail when a crash occurs. This contains all the data we need for debugging, and allows you to even add a comment to it. You'll see exactly what is sent, the system is 100% transparent.
Do not report crashes in the GitHub issue tracker. NewPipe has an automated crash report system that will ask you to
send a report via e-mail when a crash occurs. This contains all the data we need for debugging, and allows you to even
add a comment to it. You'll see exactly what is sent, the system is 100% transparent.
## Issue reporting/feature requests
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature hasn't been reported/requested before
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature
hasn't been reported/requested before
* Check whether your issue/feature is already fixed/implemented
* Check if the issue still exists in the latest release/beta version
* If you are an Android/Java developer, you are always welcome to fix/implement an issue/a feature yourself. PRs welcome!
* We use English for development. Issues in other languages will be closed and ignored.
* Please only add *one* issue at a time. Do not put multiple issues into one thread.
* When reporting a bug please give us a context, and a description how to reproduce it.
* Issues that only contain a generated bug report, but no describtion might be closed.
* Issues that only contain a generated bug report, but no description might be closed.
## Bug Fixing
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request, register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information.
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to
tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request,
register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information.
## Translation
* NewPipe can be translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there with your GitHub account.
* NewPipe can be translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there
with your GitHub account.
## Code contribution
* Stick to NewPipe's style conventions (well, just look the other code and then do it the same way :))
* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google libraries.
* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google
libraries.
* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy)
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might not be considered, GitHub is the primary platform. (This only affects you if you are a member of TeamNewPipe)
* When submitting changes, you confirm that your code is licensed under the terms of the [GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR description. Untested code will **not** be merged!
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You
may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might
not be considered, GitHub is the primary platform. (This only affects you if you are a member of TeamNewPipe)
* When submitting changes, you confirm that your code is licensed under the terms of the
[GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR
description. Untested code will **not** be merged!
* Try to figure out yourself why builds on our CI fail.
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job, but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the maintainers' jobs way easier.
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again about submission, or clearly state that in the description of your PR.
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job,
but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the
maintainers' jobs way easier.
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for
the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again
about submission, or clearly state that in the description of your PR.
* Respond yourselves if someone requests changes or otherwise raises issues about your PRs.
* Check if your contributions align with the [fdroid inclusion guidelines](https://f-droid.org/en/docs/Inclusion_Policy/).
* Check if your submission can be build with the current fdroid build server setup.
* Send PR that only cover one specific issue/solution/bug. Do not send PRs that are huge and consists of multiple
independent solutions.
## Communication
* WE DO NOW HAVE A MAILING LIST: [newpipe@list.schabi.org](https://list.schabi.org/cgi-bin/mailman/listinfo/newpipe).
* There is an IRC channel on Freenode which is regularly visited by the core team and other developers: [#newpipe](irc:irc.freenode.net/newpipe). [Click here for Webchat](https://webchat.freenode.net/?channels=newpipe)!
* If you want to get in touch with the core team or one of our other contributors you can send an email to tnp(at)schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue tracker described above!
* There is an IRC channel on Freenode which is regularly visited by the core team and other developers:
[#newpipe](irc:irc.freenode.net/newpipe). [Click here for Webchat](https://webchat.freenode.net/?channels=newpipe)!
* If you want to get in touch with the core team or one of our other contributors you can send an email to
tnp(at)schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue
tracker described above!
* Feel free to post suggestions, changes, ideas etc. on GitHub, IRC or the mailing list!

View File

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

110
README.md
View File

@@ -1,74 +1,91 @@
<p align="center"><a href="https://newpipe.schabi.org"><img src="assets/new_pipe_icon_5.png" width="150"/></a></p>
<p align="center"><a href="https://newpipe.schabi.org"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
<h2 align="center"><b>NewPipe</b></h2>
<h4 align="center">A free lightweight YouTube frontend for 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>
<h4 align="center">A libre lightweight streaming frontend for 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://github.com/TeamNewPipe/NewPipe" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" /></a>
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPL v3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg" /></a>
<a href="https://travis-ci.org/TeamNewPipe/NewPipe" alt="Build Status"><img src="https://travis-ci.org/TeamNewPipe/NewPipe.svg" /></a>
<a href="https://hosted.weblate.org/engage/NewPipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/NewPipe/-/svg-badge.svg" /></a>
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%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" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
<a href="https://travis-ci.org/TeamNewPipe/NewPipe" alt="Build Status"><img src="https://travis-ci.org/TeamNewPipe/NewPipe.svg"></a>
<a href="https://hosted.weblate.org/engage/NewPipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/NewPipe/-/svg-badge.svg"></a>
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%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>
</p>
<hr />
<p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#description">Description</a> &bull; <a href="#features">Features</a> &bull; <a href="#contribution">Contribution</a> &bull; <a href="#donate">Donate</a> &bull; <a href="#license">License</a></p>
<hr>
<p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#description">Description</a> &bull; <a href="#features">Features</a> &bull; <a href="#updates">Updates</a> &bull; <a href="#contribution">Contribution</a> &bull; <a href="#donate">Donate</a> &bull; <a href="#license">License</a></p>
<p align="center"><a href="https://newpipe.schabi.org">Website</a> &bull; <a href="https://newpipe.schabi.org/blog/">Blog</a> &bull; <a href="https://newpipe.schabi.org/press/">Press</a></p>
<hr />
WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.
<hr>
<b>WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.</b>
## Screenshots
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_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)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
## Description
NewPipe does not use any Google framework libraries, or the YouTube API. It only parses the website in order to gain the information it needs. Therefore this app can be used on devices without Google Services installed. Also, you don't need a YouTube account to use NewPipe, and it's FLOSS.
NewPipe does not use any Google framework libraries, nor the YouTube API. Websites are only parsed to fetch required info, so this app can be used on devices without Google services installed. Also, you don't need a YouTube account to use NewPipe, which is copylefted libre software.
### Features
* Search videos
* Display general information about a video
* Display general info about videos
* Watch YouTube videos
* Listen to YouTube videos
* Popup mode (floating player)
* Select the streaming player to watch the video with
* Download videos
* Select streaming player to watch video with
* Download videos
* Download audio only
* Open a video in Kodi
* Show Next/Related videos
* Show next/related videos
* Search YouTube in a specific language
* Watch/Block age restricted material
* Display general information about channels
* Display general info about channels
* Search channels
* Watch videos from a channel
* Orbot/Tor support (not yet directly)
* 1080p/2k/4k support
* 1080p/2K/4K support
* View history
* Subscribe to channels
* Search history
* Search/Watch Playlists
* Watch as queues Playlists
* Queuing videos
* Search/watch playlists
* Watch as enqueued playlists
* Enqueue videos
* Local playlists
* Subtitles
* Multi-service support (eg. SoundCloud in NewPipe Beta)
* Multi-service support (e.g. SoundCloud \[beta\])
* Livestream support
### Coming Features
* Livestream support
* Cast to UPnP and Cast
* Show comments
* ... and many more
* and many more
## Updates
When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can:
* 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.
* Download the APK from [releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it.
* 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.
When you install an APK from one of these options, it will be incompatible with an APK from one of the other options. This is due to different signing keys being used. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app, and are independent. F-Droid and GitHub use different signing keys, and building an APK debug excludes a key. The signing key issue is being discussed in issue [#1981](https://github.com/TeamNewPipe/NewPipe/issues/1981), and may be fixed by setting up our own repository on F-Droid.
In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's core functionality was broken and F-Droid doesn't have the update yet), we recommend following this procedure:
1. Back up your data via "Settings>Content>Export Database" so you keep your history, subscriptions, and playlists
2. Uninstall NewPipe
3. Download the APK from the new source and install it
4. Import the data from step 1 via "Settings>Content>Import Database"
## Contribution
Whether you have ideas, translations, design changes, code cleaning, or real heavy code changes, help is always welcome.
@@ -77,26 +94,31 @@ The more is done the better it gets!
If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md).
## Donate
If you like NewPipe we'd be happy about a donation. You can either donate via Bitcoin, Bountysource or Liberapay. For further information about donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate).
If you like NewPipe we'd be happy about a donation. You can either send bitcoin or donate via Bountysource or Liberapay. For further info on donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate).
<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="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><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="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>
</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="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>
</tr>
</table>
## Privacy Policy
The NewPipe project aims to provide a private, anonymous experience for using media web services.
Therefore, the app does not collect any data without your consent. NewPipe's privacy policy explains in detail what data is sent and stored when you send a crash report, or comment in our blog. You can find the document [here](https://newpipe.schabi.org/legal/privacy/).
## License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)

View File

@@ -1,28 +1,29 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
buildToolsVersion '27.0.3'
compileSdkVersion 28
buildToolsVersion '28.0.3'
defaultConfig {
applicationId "org.schabi.newpipe"
minSdkVersion 15
targetSdkVersion 27
versionCode 67
versionName "0.14.0"
minSdkVersion 19
targetSdkVersion 28
versionCode 71
versionName "0.15.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
multiDexEnabled true
debuggable true
applicationIdSuffix ".debug"
}
@@ -34,6 +35,7 @@ android {
// but continue the build even when errors are found:
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@@ -41,62 +43,61 @@ android {
}
ext {
supportLibVersion = '27.1.1'
exoPlayerLibVersion = '2.8.2'
supportLibVersion = '28.0.0'
exoPlayerLibVersion = '2.8.4' //2.9.0
roomDbLibVersion = '1.1.1'
leakCanaryLibVersion = '1.5.4'
okHttpLibVersion = '3.10.0'
leakCanaryLibVersion = '1.5.4' //1.6.1
okHttpLibVersion = '3.11.0'
icepickLibVersion = '3.2.0'
stethoLibVersion = '1.5.0'
}
dependencies {
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.2', {
exclude module: 'support-annotations'
}
})
implementation 'com.github.TeamNewPipe:NewPipeExtractor:850670917fce'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:f7c7b9df1a'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.8.9'
testImplementation 'org.mockito:mockito-core:2.23.0'
implementation "com.android.support:appcompat-v7:$supportLibVersion"
implementation "com.android.support:support-v4:$supportLibVersion"
implementation "com.android.support:design:$supportLibVersion"
implementation "com.android.support:recyclerview-v7:$supportLibVersion"
implementation "com.android.support:preference-v14:$supportLibVersion"
implementation "com.android.support:appcompat-v7:${supportLibVersion}"
implementation "com.android.support:support-v4:${supportLibVersion}"
implementation "com.android.support:design:${supportLibVersion}"
implementation "com.android.support:recyclerview-v7:${supportLibVersion}"
implementation "com.android.support:preference-v14:${supportLibVersion}"
implementation "com.android.support:cardview-v7:${supportLibVersion}"
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'ch.acra:acra:4.9.2'
implementation 'ch.acra:acra:4.9.2' //4.11
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1'
implementation 'com.nononsenseapps:filepicker:4.2.1'
implementation "com.google.android.exoplayer:exoplayer:$exoPlayerLibVersion"
implementation "com.google.android.exoplayer:extension-mediasession:$exoPlayerLibVersion"
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerLibVersion}"
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerLibVersion}"
debugImplementation "com.facebook.stetho:stetho:$stethoLibVersion"
debugImplementation "com.facebook.stetho:stetho-urlconnection:$stethoLibVersion"
debugImplementation "com.facebook.stetho:stetho:${stethoLibVersion}"
debugImplementation "com.facebook.stetho:stetho-urlconnection:${stethoLibVersion}"
debugImplementation 'com.android.support:multidex:1.0.3'
implementation 'io.reactivex.rxjava2:rxjava:2.1.14'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
implementation "android.arch.persistence.room:runtime:$roomDbLibVersion"
implementation "android.arch.persistence.room:rxjava2:$roomDbLibVersion"
annotationProcessor "android.arch.persistence.room:compiler:$roomDbLibVersion"
implementation "android.arch.persistence.room:runtime:${roomDbLibVersion}"
implementation "android.arch.persistence.room:rxjava2:${roomDbLibVersion}"
annotationProcessor "android.arch.persistence.room:compiler:${roomDbLibVersion}"
implementation "frankiesardo:icepick:$icepickLibVersion"
annotationProcessor "frankiesardo:icepick-processor:$icepickLibVersion"
implementation "frankiesardo:icepick:${icepickLibVersion}"
annotationProcessor "frankiesardo:icepick-processor:${icepickLibVersion}"
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryLibVersion"
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryLibVersion"
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryLibVersion}"
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryLibVersion}"
implementation "com.squareup.okhttp3:okhttp:$okHttpLibVersion"
debugImplementation "com.facebook.stetho:stetho-okhttp3:$stethoLibVersion"
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.android.support:cardview-v7:27.1.1'
implementation "com.squareup.okhttp3:okhttp:${okHttpLibVersion}"
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoLibVersion}"
}

View File

@@ -1,13 +0,0 @@
package org.schabi.newpipe;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:name=".App"
@@ -118,7 +119,6 @@
<activity
android:name=".ReCaptchaActivity"
android:label="@string/reCaptchaActivity"/>
<activity android:name=".download.ExtSDDownloadFailedActivity" />
<provider
android:name="android.support.v4.content.FileProvider"
@@ -238,4 +238,4 @@
android:name=".RouterActivity$FetcherService"
android:exported="false"/>
</application>
</manifest>
</manifest>

View File

@@ -1,10 +1,12 @@
package org.schabi.newpipe;
import android.annotation.TargetApi;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.util.Log;
@@ -21,6 +23,7 @@ import org.acra.config.ConfigurationBuilder;
import org.acra.sender.ReportSenderFactory;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.utils.Localization;
import org.schabi.newpipe.report.AcraReportSenderFactory;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
@@ -63,9 +66,11 @@ import io.reactivex.plugins.RxJavaPlugins;
public class App extends Application {
protected static final String TAG = App.class.toString();
private RefWatcher refWatcher;
private static App app;
@SuppressWarnings("unchecked")
private static final Class<? extends ReportSenderFactory>[] reportSenderFactoryClasses = new Class[]{AcraReportSenderFactory.class};
private static final Class<? extends ReportSenderFactory>[]
reportSenderFactoryClasses = new Class[]{AcraReportSenderFactory.class};
@Override
protected void attachBaseContext(Context base) {
@@ -85,10 +90,13 @@ public class App extends Application {
}
refWatcher = installLeakCanary();
app = this;
// Initialize settings first because others inits can use its values
SettingsActivity.initSettings(this);
NewPipe.init(getDownloader());
NewPipe.init(getDownloader(),
org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(this));
StateSaver.init(this);
initNotificationChannel();
@@ -96,6 +104,9 @@ public class App extends Application {
ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50));
configureRxJavaErrorHandler();
// Check for new version
new CheckForNewAppVersionTask().execute();
}
protected Downloader getDownloader() {
@@ -106,7 +117,7 @@ public class App extends Application {
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
public void accept(@NonNull Throwable throwable) {
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " +
"throwable = [" + throwable.getClass().getName() + "]");
@@ -180,7 +191,11 @@ public class App extends Application {
ACRA.init(this, acraConfig);
} catch (ACRAConfigurationException ace) {
ace.printStackTrace();
ErrorActivity.reportError(this, ace, null, null, ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
ErrorActivity.reportError(this,
ace,
null,
null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not initialize ACRA crash report", R.string.app_ui_crash));
}
}
@@ -200,8 +215,34 @@ public class App extends Application {
NotificationChannel mChannel = new NotificationChannel(id, name, importance);
mChannel.setDescription(description);
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannel(mChannel);
setUpUpdateNotificationChannel(importance);
}
/**
* Set up notification channel for app update.
* @param importance
*/
@TargetApi(Build.VERSION_CODES.O)
private void setUpUpdateNotificationChannel(int importance) {
final String appUpdateId
= getString(R.string.app_update_notification_channel_id);
final CharSequence appUpdateName
= getString(R.string.app_update_notification_channel_name);
final String appUpdateDescription
= getString(R.string.app_update_notification_channel_description);
NotificationChannel appUpdateChannel
= new NotificationChannel(appUpdateId, appUpdateName, importance);
appUpdateChannel.setDescription(appUpdateDescription);
NotificationManager appUpdateNotificationManager
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
}
@Nullable
@@ -217,4 +258,8 @@ public class App extends Application {
protected boolean isDisposedRxExceptionsReported() {
return false;
}
public static App getApp() {
return app;
}
}

View File

@@ -12,14 +12,12 @@ import android.view.View;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.squareup.leakcanary.RefWatcher;
import org.schabi.newpipe.report.UserAction;
import icepick.Icepick;
import icepick.State;
public abstract class BaseFragment extends Fragment {
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
protected boolean DEBUG = MainActivity.DEBUG;
protected final boolean DEBUG = MainActivity.DEBUG;
protected AppCompatActivity activity;
public static final ImageLoader imageLoader = ImageLoader.getInstance();

View File

@@ -0,0 +1,230 @@
package org.schabi.newpipe;
import android.app.Application;
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.Uri;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import org.json.JSONException;
import org.json.JSONObject;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* AsyncTask to check if there is a newer version of the NewPipe github apk available or not.
* If there is a newer version we show a notification, informing the user. On tapping
* the notification, the user will be directed to the download link.
*/
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
private static final Application app = App.getApp();
private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json";
private static final int timeoutPeriod = 30;
private SharedPreferences mPrefs;
private OkHttpClient client;
@Override
protected void onPreExecute() {
mPrefs = PreferenceManager.getDefaultSharedPreferences(app);
// Check if user has enabled/ disabled update checking
// and if the current apk is a github one or not.
if (!mPrefs.getBoolean(app.getString(R.string.update_app_key), true)
|| !isGithubApk()) {
this.cancel(true);
}
}
@Override
protected String doInBackground(Void... voids) {
// Make a network request to get latest NewPipe data.
if (client == null) {
client = new OkHttpClient
.Builder()
.readTimeout(timeoutPeriod, TimeUnit.SECONDS)
.build();
}
Request request = new Request.Builder()
.url(newPipeApiUrl)
.build();
try {
Response response = client.newCall(request).execute();
return response.body().string();
} catch (IOException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"app update API fail", R.string.app_ui_crash));
}
return null;
}
@Override
protected void onPostExecute(String response) {
// Parse the json from the response.
if (response != null) {
try {
JSONObject mainObject = new JSONObject(response);
JSONObject flavoursObject = mainObject.getJSONObject("flavors");
JSONObject githubObject = flavoursObject.getJSONObject("github");
JSONObject githubStableObject = githubObject.getJSONObject("stable");
String versionName = githubStableObject.getString("version");
String versionCode = githubStableObject.getString("version_code");
String apkLocationUrl = githubStableObject.getString("apk");
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
} catch (JSONException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"could not parse app update JSON data", R.string.app_ui_crash));
}
}
}
/**
* Method to compare the current and latest available app version.
* If a newer version is available, we show the update notification.
* @param versionName
* @param apkLocationUrl
*/
private void compareAppVersionAndShowNotification(String versionName,
String apkLocationUrl,
String versionCode) {
int NOTIFICATION_ID = 2000;
if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
// A pending intent to open the apk location url in the browser.
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
PendingIntent pendingIntent
= PendingIntent.getActivity(app, 0, intent, 0);
NotificationCompat.Builder notificationBuilder = new NotificationCompat
.Builder(app, app.getString(R.string.app_update_notification_channel_id))
.setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
.setContentText(app.getString(R.string.app_update_notification_content_text)
+ " " + versionName);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
}
/**
* Method to get the apk's SHA1 key.
* https://stackoverflow.com/questions/9293019/get-certificate-fingerprint-from-android-app#22506133
*/
private static String getCertificateSHA1Fingerprint() {
PackageManager pm = app.getPackageManager();
String packageName = app.getPackageName();
int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;
try {
packageInfo = pm.getPackageInfo(packageName, flags);
} catch (PackageManager.NameNotFoundException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not find package info", R.string.app_ui_crash));
}
Signature[] signatures = packageInfo.signatures;
byte[] cert = signatures[0].toByteArray();
InputStream input = new ByteArrayInputStream(cert);
CertificateFactory cf = null;
X509Certificate c = null;
try {
cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (CertificateException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Certificate error", R.string.app_ui_crash));
}
String hexString = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] publicKey = md.digest(c.getEncoded());
hexString = byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException ex1) {
ErrorActivity.reportError(app, ex1, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
} catch (CertificateEncodingException ex2) {
ErrorActivity.reportError(app, ex2, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
}
return hexString;
}
private static String byte2HexFormatted(byte[] arr) {
StringBuilder str = new StringBuilder(arr.length * 2);
for (int i = 0; i < arr.length; i++) {
String h = Integer.toHexString(arr[i]);
int l = h.length();
if (l == 1) h = "0" + h;
if (l > 2) h = h.substring(l - 2, l);
str.append(h.toUpperCase());
if (i < (arr.length - 1)) str.append(':');
}
return str.toString();
}
public static boolean isGithubApk() {
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
}
}

View File

@@ -4,6 +4,7 @@ import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.utils.Localization;
import java.io.IOException;
import java.io.InputStream;
@@ -43,7 +44,7 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
private static Downloader instance;
private String mCookies;
private OkHttpClient client;
private final OkHttpClient client;
private Downloader(OkHttpClient.Builder builder) {
this.client = builder
@@ -88,7 +89,8 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
.build();
response = client.newCall(request).execute();
return Long.parseLong(response.header("Content-Length"));
String contentLength = response.header("Content-Length");
return contentLength == null ? -1 : Long.parseLong(contentLength);
} catch (NumberFormatException e) {
throw new IOException("Invalid content length", e);
} finally {
@@ -103,13 +105,13 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
* but set the HTTP header field "Accept-Language" to the supplied string.
*
* @param siteUrl the URL of the text file to return the contents of
* @param language the language (usually a 2-character code) to set as the preferred language
* @param localization the language and country (usually a 2-character code) to set
* @return the contents of the specified text file
*/
@Override
public String download(String siteUrl, String language) throws IOException, ReCaptchaException {
public String download(String siteUrl, Localization localization) throws IOException, ReCaptchaException {
Map<String, String> requestProperties = new HashMap<>();
requestProperties.put("Accept-Language", language);
requestProperties.put("Accept-Language", localization.getLanguage());
return download(siteUrl, requestProperties);
}

View File

@@ -23,7 +23,6 @@ package org.schabi.newpipe;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -66,8 +65,6 @@ import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.extractor.InfoItem.InfoType.PLAYLIST;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");

View File

@@ -85,7 +85,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
}
private class ReCaptchaWebViewClient extends WebViewClient {
private Activity context;
private final Activity context;
private String mCookies;
ReCaptchaWebViewClient(Activity ctx) {

View File

@@ -1,7 +1,6 @@
package org.schabi.newpipe;
import android.annotation.SuppressLint;
import android.app.FragmentManager;
import android.app.IntentService;
import android.content.Context;
import android.content.DialogInterface;
@@ -13,7 +12,6 @@ import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
@@ -38,7 +36,6 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
@@ -51,14 +48,12 @@ import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Observer;
import icepick.Icepick;
import icepick.State;
@@ -86,7 +81,7 @@ public class RouterActivity extends AppCompatActivity {
protected int selectedPreviously = -1;
protected String currentUrl;
protected CompositeDisposable disposables = new CompositeDisposable();
protected final CompositeDisposable disposables = new CompositeDisposable();
private boolean selectionIsDownload = false;
@@ -184,12 +179,16 @@ public class RouterActivity extends AppCompatActivity {
if (selectedChoiceKey.equals(alwaysAskKey)) {
final List<AdapterChoiceItem> choices = getChoicesForService(currentService, currentLinkType);
if (choices.size() == 1) {
handleChoice(choices.get(0).key);
} else if (choices.size() == 0) {
handleChoice(showInfoKey);
} else {
showDialog(choices);
switch (choices.size()) {
case 1:
handleChoice(choices.get(0).key);
break;
case 0:
handleChoice(showInfoKey);
break;
default:
showDialog(choices);
break;
}
} else if (selectedChoiceKey.equals(showInfoKey)) {
handleChoice(showInfoKey);
@@ -543,8 +542,7 @@ public class RouterActivity extends AppCompatActivity {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
boolean useOldVideoPlayer = PlayerHelper.isUsingOldPlayer(this);
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);;
PlayQueue playQueue;
String playerChoice = choice.playerChoice;
@@ -556,9 +554,6 @@ public class RouterActivity extends AppCompatActivity {
} else if (playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) {
NavigationHelper.playOnExternalVideoPlayer(this, (StreamInfo) info);
} else if (playerChoice.equals(videoPlayerKey) && useOldVideoPlayer) {
NavigationHelper.playOnOldVideoPlayer(this, (StreamInfo) info);
} else {
playQueue = new SinglePlayQueue((StreamInfo) info);

View File

@@ -4,7 +4,6 @@ import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.webkit.WebView;
@@ -17,7 +16,7 @@ import java.lang.ref.WeakReference;
public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
WeakReference<Activity> weakReference;
final WeakReference<Activity> weakReference;
private License license;
public LicenseFragmentHelper(@Nullable Activity activity) {
@@ -78,18 +77,18 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
throw new NullPointerException("license is null");
}
String licenseContent = "";
StringBuilder licenseContent = new StringBuilder();
String webViewData;
try {
BufferedReader in = new BufferedReader(new InputStreamReader(context.getAssets().open(license.getFilename()), "UTF-8"));
String str;
while ((str = in.readLine()) != null) {
licenseContent += str;
licenseContent.append(str);
}
in.close();
// split the HTML file and insert the stylesheet into the HEAD of the file
String[] insert = licenseContent.split("</head>");
String[] insert = licenseContent.toString().split("</head>");
webViewData = insert[0] + "<style type=\"text/css\">"
+ getLicenseStylesheet(context) + "</style></head>"
+ insert[1];

View File

@@ -30,7 +30,7 @@ public interface BasicDAO<Entity> {
/* Deletes */
@Delete
int delete(final Entity entity);
void delete(final Entity entity);
@Delete
int delete(final Collection<Entity> entities);
@@ -42,5 +42,5 @@ public interface BasicDAO<Entity> {
int update(final Entity entity);
@Update
int update(final Collection<Entity> entities);
void update(final Collection<Entity> entities);
}

View File

@@ -4,7 +4,6 @@ import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Query;
import android.support.annotation.Nullable;
import org.schabi.newpipe.database.BasicDAO;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import java.util.List;

View File

@@ -5,7 +5,6 @@ import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Query;
import android.support.annotation.Nullable;
import org.schabi.newpipe.database.BasicDAO;
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;

View File

@@ -2,7 +2,6 @@ package org.schabi.newpipe.database.playlist.dao;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Transaction;
import org.schabi.newpipe.database.BasicDAO;
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
@@ -12,7 +11,6 @@ import java.util.List;
import io.reactivex.Flowable;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
@Dao

View File

@@ -8,7 +8,6 @@ import org.schabi.newpipe.database.BasicDAO;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import java.util.List;

View File

@@ -5,8 +5,6 @@ import android.arch.persistence.room.Entity;
import android.arch.persistence.room.Index;
import android.arch.persistence.room.PrimaryKey;
import java.util.Date;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;

View File

@@ -6,7 +6,6 @@ import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.Index;
import android.arch.persistence.room.PrimaryKey;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.util.Constants;

View File

@@ -10,7 +10,6 @@ import org.schabi.newpipe.database.BasicDAO;
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import java.util.ArrayList;
import java.util.List;
@@ -23,7 +22,6 @@ import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_SERVI
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_URL;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
@Dao
public abstract class StreamDAO implements BasicDAO<StreamEntity> {

View File

@@ -1,158 +0,0 @@
package org.schabi.newpipe.download;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.BaseTransientBottomBar;
import android.support.design.widget.Snackbar;
import android.view.View;
import org.schabi.newpipe.R;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.get.DownloadMission;
public class DeleteDownloadManager {
private static final String KEY_STATE = "delete_manager_state";
private View mView;
private HashSet<String> mPendingMap;
private List<Disposable> mDisposableList;
private DownloadManager mDownloadManager;
private PublishSubject<DownloadMission> publishSubject = PublishSubject.create();
DeleteDownloadManager(Activity activity) {
mPendingMap = new HashSet<>();
mDisposableList = new ArrayList<>();
mView = activity.findViewById(android.R.id.content);
}
public Observable<DownloadMission> getUndoObservable() {
return publishSubject;
}
public boolean contains(@NonNull DownloadMission mission) {
return mPendingMap.contains(mission.url);
}
public void add(@NonNull DownloadMission mission) {
mPendingMap.add(mission.url);
if (mPendingMap.size() == 1) {
showUndoDeleteSnackbar(mission);
}
}
public void setDownloadManager(@NonNull DownloadManager downloadManager) {
mDownloadManager = downloadManager;
if (mPendingMap.size() < 1) return;
showUndoDeleteSnackbar();
}
public void restoreState(@Nullable Bundle savedInstanceState) {
if (savedInstanceState == null) return;
List<String> list = savedInstanceState.getStringArrayList(KEY_STATE);
if (list != null) {
mPendingMap.addAll(list);
}
}
public void saveState(@Nullable Bundle outState) {
if (outState == null) return;
for (Disposable disposable : mDisposableList) {
disposable.dispose();
}
outState.putStringArrayList(KEY_STATE, new ArrayList<>(mPendingMap));
}
private void showUndoDeleteSnackbar() {
if (mPendingMap.size() < 1) return;
String url = mPendingMap.iterator().next();
for (int i = 0; i < mDownloadManager.getCount(); i++) {
DownloadMission mission = mDownloadManager.getMission(i);
if (url.equals(mission.url)) {
showUndoDeleteSnackbar(mission);
break;
}
}
}
private void showUndoDeleteSnackbar(@NonNull DownloadMission mission) {
final Snackbar snackbar = Snackbar.make(mView, mission.name, Snackbar.LENGTH_INDEFINITE);
final Disposable disposable = Observable.timer(3, TimeUnit.SECONDS)
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(l -> snackbar.dismiss());
mDisposableList.add(disposable);
snackbar.setAction(R.string.undo, v -> {
mPendingMap.remove(mission.url);
publishSubject.onNext(mission);
disposable.dispose();
snackbar.dismiss();
});
snackbar.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
if (!disposable.isDisposed()) {
Completable.fromAction(() -> deletePending(mission))
.subscribeOn(Schedulers.io())
.subscribe();
}
mPendingMap.remove(mission.url);
snackbar.removeCallback(this);
mDisposableList.remove(disposable);
showUndoDeleteSnackbar();
}
});
snackbar.show();
}
public void deletePending() {
if (mPendingMap.size() < 1) return;
HashSet<Integer> idSet = new HashSet<>();
for (int i = 0; i < mDownloadManager.getCount(); i++) {
if (contains(mDownloadManager.getMission(i))) {
idSet.add(i);
}
}
for (Integer id : idSet) {
mDownloadManager.deleteMission(id);
}
mPendingMap.clear();
}
private void deletePending(@NonNull DownloadMission mission) {
for (int i = 0; i < mDownloadManager.getCount(); i++) {
if (mission.url.equals(mDownloadManager.getMission(i).url)) {
mDownloadManager.deleteMission(i);
break;
}
}
}
}

View File

@@ -15,16 +15,12 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.ThemeHelper;
import io.reactivex.Completable;
import io.reactivex.schedulers.Schedulers;
import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.ui.fragment.AllMissionsFragment;
import us.shandian.giga.ui.fragment.MissionsFragment;
public class DownloadActivity extends AppCompatActivity {
private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag";
private DeleteDownloadManager mDeleteDownloadManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -47,32 +43,17 @@ public class DownloadActivity extends AppCompatActivity {
actionBar.setDisplayShowTitleEnabled(true);
}
mDeleteDownloadManager = new DeleteDownloadManager(this);
mDeleteDownloadManager.restoreState(savedInstanceState);
MissionsFragment fragment = (MissionsFragment) getFragmentManager().findFragmentByTag(MISSIONS_FRAGMENT_TAG);
if (fragment != null) {
fragment.setDeleteManager(mDeleteDownloadManager);
} else {
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
updateFragments();
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
mDeleteDownloadManager.saveState(outState);
super.onSaveInstanceState(outState);
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
updateFragments();
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
private void updateFragments() {
MissionsFragment fragment = new AllMissionsFragment();
fragment.setDeleteManager(mDeleteDownloadManager);
MissionsFragment fragment = new MissionsFragment();
getFragmentManager().beginTransaction()
.replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG)
@@ -99,7 +80,6 @@ public class DownloadActivity extends AppCompatActivity {
case R.id.action_settings: {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
deletePending();
return true;
}
default:
@@ -108,14 +88,7 @@ public class DownloadActivity extends AppCompatActivity {
}
@Override
public void onBackPressed() {
super.onBackPressed();
deletePending();
}
private void deletePending() {
Completable.fromAction(mDeleteDownloadManager::deletePending)
.subscribeOn(Schedulers.io())
.subscribe();
public void onRestoreInstanceState(Bundle inState){
super.onRestoreInstanceState(inState);
}
}

View File

@@ -1,13 +1,17 @@
package org.schabi.newpipe.download;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -22,40 +26,57 @@ import android.widget.Toast;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.extractor.utils.Localization;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.FilenameUtils;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.SecondaryStreamHelper;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import icepick.Icepick;
import icepick.State;
import io.reactivex.disposables.CompositeDisposable;
import us.shandian.giga.postprocessing.Postprocessing;
import us.shandian.giga.service.DownloadManagerService;
public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
private static final String TAG = "DialogFragment";
private static final boolean DEBUG = MainActivity.DEBUG;
@State protected StreamInfo currentInfo;
@State protected StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty();
@State protected StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty();
@State protected int selectedVideoIndex = 0;
@State protected int selectedAudioIndex = 0;
@State
protected StreamInfo currentInfo;
@State
protected StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty();
@State
protected StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty();
@State
protected StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams = StreamSizeWrapper.empty();
@State
protected int selectedVideoIndex = 0;
@State
protected int selectedAudioIndex = 0;
@State
protected int selectedSubtitleIndex = 0;
private StreamItemAdapter<AudioStream> audioStreamsAdapter;
private StreamItemAdapter<VideoStream> videoStreamsAdapter;
private StreamItemAdapter<AudioStream, Stream> audioStreamsAdapter;
private StreamItemAdapter<VideoStream, AudioStream> videoStreamsAdapter;
private StreamItemAdapter<SubtitlesStream, Stream> subtitleStreamsAdapter;
private CompositeDisposable disposables = new CompositeDisposable();
private final CompositeDisposable disposables = new CompositeDisposable();
private EditText nameEditText;
private Spinner streamsSpinner;
@@ -63,6 +84,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
private TextView threadsCountTextView;
private SeekBar threadsSeekBar;
private SharedPreferences prefs;
public static DownloadDialog newInstance(StreamInfo info) {
DownloadDialog dialog = new DownloadDialog();
dialog.setInfo(info);
@@ -78,6 +101,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
instance.setVideoStreams(streamsList);
instance.setSelectedVideoStream(selectedStreamIndex);
instance.setAudioStreams(info.getAudioStreams());
instance.setSubtitleStreams(info.getSubtitles());
return instance;
}
@@ -86,7 +111,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
}
public void setAudioStreams(List<AudioStream> audioStreams) {
setAudioStreams(new StreamSizeWrapper<>(audioStreams));
setAudioStreams(new StreamSizeWrapper<>(audioStreams, getContext()));
}
public void setAudioStreams(StreamSizeWrapper<AudioStream> wrappedAudioStreams) {
@@ -94,13 +119,21 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
}
public void setVideoStreams(List<VideoStream> videoStreams) {
setVideoStreams(new StreamSizeWrapper<>(videoStreams));
setVideoStreams(new StreamSizeWrapper<>(videoStreams, getContext()));
}
public void setVideoStreams(StreamSizeWrapper<VideoStream> wrappedVideoStreams) {
this.wrappedVideoStreams = wrappedVideoStreams;
}
public void setSubtitleStreams(List<SubtitlesStream> subtitleStreams) {
setSubtitleStreams(new StreamSizeWrapper<>(subtitleStreams, getContext()));
}
public void setSubtitleStreams(StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams) {
this.wrappedSubtitleStreams = wrappedSubtitleStreams;
}
public void setSelectedVideoStream(int selectedVideoIndex) {
this.selectedVideoIndex = selectedVideoIndex;
}
@@ -109,6 +142,10 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
this.selectedAudioIndex = selectedAudioIndex;
}
public void setSelectedSubtitleStream(int selectedSubtitleIndex) {
this.selectedSubtitleIndex = selectedSubtitleIndex;
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@@ -116,7 +153,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
if (DEBUG)
Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
getDialog().dismiss();
return;
@@ -125,13 +163,29 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(getContext()));
Icepick.restoreInstanceState(this, savedInstanceState);
this.videoStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedVideoStreams, true);
SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams = new SparseArray<>(4);
List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
for (int i = 0; i < videoStreams.size(); i++) {
if (!videoStreams.get(i).isVideoOnly()) continue;
AudioStream audioStream = SecondaryStreamHelper.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
if (audioStream != null) {
secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream));
} else if (DEBUG) {
Log.w(TAG, "No audio stream candidates for video format " + videoStreams.get(i).getFormat().name());
}
}
this.videoStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedVideoStreams, secondaryStreams);
this.audioStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedAudioStreams);
this.subtitleStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedSubtitleStreams);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
if (DEBUG)
Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
return inflater.inflate(R.layout.download_dialog, container);
}
@@ -142,6 +196,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName()));
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams());
selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll());
streamsSpinner = view.findViewById(R.id.quality_spinner);
streamsSpinner.setOnItemSelectedListener(this);
@@ -154,14 +210,18 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
initToolbar(view.findViewById(R.id.toolbar));
setupDownloadOptions();
int def = 3;
threadsCountTextView.setText(String.valueOf(def));
threadsSeekBar.setProgress(def - 1);
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
int threads = prefs.getInt(getString(R.string.default_download_threads), 3);
threadsCountTextView.setText(String.valueOf(threads));
threadsSeekBar.setProgress(threads - 1);
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
threadsCountTextView.setText(String.valueOf(progress + 1));
progress++;
prefs.edit().putInt(getString(R.string.default_download_threads), progress).apply();
threadsCountTextView.setText(String.valueOf(progress));
}
@Override
@@ -189,6 +249,11 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
setupAudioSpinner();
}
}));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams).subscribe(result -> {
if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
setupSubtitleSpinner();
}
}));
}
@Override
@@ -216,7 +281,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
toolbar.setOnMenuItemClickListener(item -> {
if (item.getItemId() == R.id.okay) {
downloadSelected();
prepareSelectedDownload();
return true;
}
return false;
@@ -239,13 +304,24 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
setRadioButtonsState(true);
}
private void setupSubtitleSpinner() {
if (getContext() == null) return;
streamsSpinner.setAdapter(subtitleStreamsAdapter);
streamsSpinner.setSelection(selectedSubtitleIndex);
setRadioButtonsState(true);
}
/*//////////////////////////////////////////////////////////////////////////
// Radio group Video&Audio options - Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
if (DEBUG) Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
if (DEBUG)
Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
boolean flag = true;
switch (checkedId) {
case R.id.audio_button:
setupAudioSpinner();
@@ -253,7 +329,13 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
case R.id.video_button:
setupVideoSpinner();
break;
case R.id.subtitle_button:
setupSubtitleSpinner();
flag = false;
break;
}
threadsSeekBar.setEnabled(flag);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -262,7 +344,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (DEBUG) Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
if (DEBUG)
Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
switch (radioVideoAudioGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
selectedAudioIndex = position;
@@ -270,6 +353,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
case R.id.video_button:
selectedVideoIndex = position;
break;
case R.id.subtitle_button:
selectedSubtitleIndex = position;
break;
}
}
@@ -286,11 +372,14 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
final RadioButton audioButton = radioVideoAudioGroup.findViewById(R.id.audio_button);
final RadioButton videoButton = radioVideoAudioGroup.findViewById(R.id.video_button);
final RadioButton subtitleButton = radioVideoAudioGroup.findViewById(R.id.subtitle_button);
final boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0;
final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0;
final boolean isSubtitleStreamsAvailable = subtitleStreamsAdapter.getCount() > 0;
audioButton.setVisibility(isAudioStreamsAvailable ? View.VISIBLE : View.GONE);
videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE);
subtitleButton.setVisibility(isSubtitleStreamsAvailable ? View.VISIBLE : View.GONE);
if (isVideoStreamsAvailable) {
videoButton.setChecked(true);
@@ -298,6 +387,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
} else if (isAudioStreamsAvailable) {
audioButton.setChecked(true);
setupAudioSpinner();
} else if (isSubtitleStreamsAvailable) {
subtitleButton.setChecked(true);
setupSubtitleSpinner();
} else {
Toast.makeText(getContext(), R.string.no_streams_available_download, Toast.LENGTH_SHORT).show();
getDialog().dismiss();
@@ -307,28 +399,143 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
private void setRadioButtonsState(boolean enabled) {
radioVideoAudioGroup.findViewById(R.id.audio_button).setEnabled(enabled);
radioVideoAudioGroup.findViewById(R.id.video_button).setEnabled(enabled);
radioVideoAudioGroup.findViewById(R.id.subtitle_button).setEnabled(enabled);
}
private void downloadSelected() {
Stream stream;
String location;
private int getSubtitleIndexBy(List<SubtitlesStream> streams) {
Localization loc = NewPipe.getPreferredLocalization();
String fileName = nameEditText.getText().toString().trim();
if (fileName.isEmpty()) fileName = FilenameUtils.createFilename(getContext(), currentInfo.getName());
boolean isAudio = radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button;
if (isAudio) {
stream = audioStreamsAdapter.getItem(selectedAudioIndex);
location = NewPipeSettings.getAudioDownloadPath(getContext());
} else {
stream = videoStreamsAdapter.getItem(selectedVideoIndex);
location = NewPipeSettings.getVideoDownloadPath(getContext());
for (int i = 0; i < streams.size(); i++) {
Locale streamLocale = streams.get(i).getLocale();
String tag = streamLocale.getLanguage().concat("-").concat(streamLocale.getCountry());
if (tag.equalsIgnoreCase(loc.getLanguage())) {
return i;
}
}
String url = stream.getUrl();
fileName += "." + stream.getFormat().getSuffix();
// fallback
// 1st loop match country & language
// 2nd loop match language only
int index = loc.getLanguage().indexOf("-");
String lang = index > 0 ? loc.getLanguage().substring(0, index) : loc.getLanguage();
for (int j = 0; j < 2; j++) {
for (int i = 0; i < streams.size(); i++) {
Locale streamLocale = streams.get(i).getLocale();
if (streamLocale.getLanguage().equalsIgnoreCase(lang)) {
if (j > 0 || streamLocale.getCountry().equalsIgnoreCase(loc.getCountry())) {
return i;
}
}
}
}
return 0;
}
private void prepareSelectedDownload() {
final Context context = getContext();
Stream stream;
String location;
char kind;
String fileName = nameEditText.getText().toString().trim();
if (fileName.isEmpty())
fileName = FilenameUtils.createFilename(context, currentInfo.getName());
switch (radioVideoAudioGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
stream = audioStreamsAdapter.getItem(selectedAudioIndex);
location = NewPipeSettings.getAudioDownloadPath(context);
kind = 'a';
break;
case R.id.video_button:
stream = videoStreamsAdapter.getItem(selectedVideoIndex);
location = NewPipeSettings.getVideoDownloadPath(context);
kind = 'v';
break;
case R.id.subtitle_button:
stream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
location = NewPipeSettings.getVideoDownloadPath(context);// assume that subtitle & video files go together
kind = 's';
break;
default:
return;
}
int threads;
if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
threads = 1;// use unique thread for subtitles due small file size
fileName += ".srt";// final subtitle format
} else {
threads = threadsSeekBar.getProgress() + 1;
fileName += "." + stream.getFormat().getSuffix();
}
final String finalFileName = fileName;
DownloadManagerService.checkForRunningMission(context, location, fileName, (listed, finished) -> {
if (listed) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.download_dialog_title)
.setMessage(finished ? R.string.overwrite_warning : R.string.download_already_running)
.setPositiveButton(
finished ? R.string.overwrite : R.string.generate_unique_name,
(dialog, which) -> downloadSelected(context, stream, location, finalFileName, kind, threads)
)
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
dialog.cancel();
})
.create()
.show();
} else {
downloadSelected(context, stream, location, finalFileName, kind, threads);
}
});
}
private void downloadSelected(Context context, Stream selectedStream, String location, String fileName, char kind, int threads) {
String[] urls;
String psName = null;
String[] psArgs = null;
String secondaryStreamUrl = null;
long nearLength = 0;
if (selectedStream instanceof VideoStream) {
SecondaryStreamHelper<AudioStream> secondaryStream = videoStreamsAdapter
.getAllSecondary()
.get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream));
if (secondaryStream != null) {
secondaryStreamUrl = secondaryStream.getStream().getUrl();
psName = selectedStream.getFormat() == MediaFormat.MPEG_4 ? Postprocessing.ALGORITHM_MP4_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER;
psArgs = null;
long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream);
// set nearLength, only, if both sizes are fetched or known. this probably does not work on slow networks
if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) {
nearLength = secondaryStream.getSizeInBytes() + videoSize;
}
}
} else if ((selectedStream instanceof SubtitlesStream) && selectedStream.getFormat() == MediaFormat.TTML) {
psName = Postprocessing.ALGORITHM_TTML_CONVERTER;
psArgs = new String[]{
selectedStream.getFormat().getSuffix(),
"false",// ignore empty frames
"false",// detect youtube duplicate lines
};
}
if (secondaryStreamUrl == null) {
urls = new String[]{selectedStream.getUrl()};
} else {
urls = new String[]{selectedStream.getUrl(), secondaryStreamUrl};
}
DownloadManagerService.startMission(context, urls, location, fileName, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength);
DownloadManagerService.startMission(getContext(), url, location, fileName, isAudio, threadsSeekBar.getProgress() + 1);
getDialog().dismiss();
}
}

View File

@@ -1,38 +0,0 @@
package org.schabi.newpipe.download;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
public class ExtSDDownloadFailedActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
}
@Override
protected void onStart() {
super.onStart();
new AlertDialog.Builder(this)
.setTitle(R.string.download_to_sdcard_error_title)
.setMessage(R.string.download_to_sdcard_error_message)
.setPositiveButton(R.string.yes, (DialogInterface dialogInterface, int i) -> {
NewPipeSettings.resetDownloadFolders(this);
finish();
})
.setNegativeButton(R.string.cancel, (DialogInterface dialogInterface, int i) -> {
dialogInterface.dismiss();
finish();
})
.create()
.show();
}
}

View File

@@ -32,7 +32,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;
import static org.schabi.newpipe.util.AnimationUtils.animateView;

View File

@@ -2,7 +2,6 @@ package org.schabi.newpipe.fragments;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

View File

@@ -3,9 +3,9 @@ package org.schabi.newpipe.fragments.detail;
import java.io.Serializable;
class StackItem implements Serializable {
private int serviceId;
private final int serviceId;
private String title;
private String url;
private final String url;
StackItem(int serviceId, String url, String title) {
this.serviceId = serviceId;

View File

@@ -33,12 +33,14 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
@@ -61,22 +63,20 @@ import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.old.PlayVideoActivity;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
@@ -87,6 +87,8 @@ import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
@@ -154,6 +156,7 @@ public class VideoDetailFragment
private View videoTitleRoot;
private TextView videoTitleTextView;
@Nullable
private ImageView videoTitleToggleArrow;
private TextView videoCountView;
@@ -368,14 +371,14 @@ public class VideoDetailFragment
Log.w(TAG, "Can't open channel because we got no channel URL");
} else {
try {
NavigationHelper.openChannelFragment(
getFragmentManager(),
currentInfo.getServiceId(),
currentInfo.getUploaderUrl(),
currentInfo.getUploaderName());
NavigationHelper.openChannelFragment(
getFragmentManager(),
currentInfo.getServiceId(),
currentInfo.getUploaderUrl(),
currentInfo.getUploaderName());
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
}
}
break;
case R.id.detail_thumbnail_root_layout:
@@ -415,14 +418,16 @@ public class VideoDetailFragment
}
private void toggleTitleAndDescription() {
if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) {
videoTitleTextView.setMaxLines(1);
videoDescriptionRootLayout.setVisibility(View.GONE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
} else {
videoTitleTextView.setMaxLines(10);
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
if (videoTitleToggleArrow != null) { //it is null for tablets
if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) {
videoTitleTextView.setMaxLines(1);
videoDescriptionRootLayout.setVisibility(View.GONE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
} else {
videoTitleTextView.setMaxLines(10);
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
}
}
}
@@ -622,8 +627,11 @@ public class VideoDetailFragment
relatedStreamsView.addView(
infoItemBuilder.buildView(relatedStreamsView, info.getNextVideo()));
relatedStreamsView.addView(getSeparatorView());
relatedStreamRootLayout.setVisibility(View.VISIBLE);
} else nextStreamTitle.setVisibility(View.GONE);
setRelatedStreamsVisibility(View.VISIBLE);
} else {
nextStreamTitle.setVisibility(View.GONE);
setRelatedStreamsVisibility(View.GONE);
}
if (info.getRelatedStreams() != null
&& !info.getRelatedStreams().isEmpty() && showRelatedStreams) {
@@ -639,13 +647,13 @@ public class VideoDetailFragment
}
//if (DEBUG) Log.d(TAG, "Total time " + ((System.nanoTime() - first) / 1000000L) + "ms");
relatedStreamRootLayout.setVisibility(View.VISIBLE);
setRelatedStreamsVisibility(View.VISIBLE);
relatedStreamExpandButton.setVisibility(View.VISIBLE);
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(
activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand)));
} else {
if (info.getNextVideo() == null) relatedStreamRootLayout.setVisibility(View.GONE);
if (info.getNextVideo() == null) setRelatedStreamsVisibility(View.GONE);
relatedStreamExpandButton.setVisibility(View.GONE);
}
}
@@ -737,7 +745,7 @@ public class VideoDetailFragment
sortedVideoStreams = ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false);
selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(activity, sortedVideoStreams);
final StreamItemAdapter<VideoStream> streamsAdapter = new StreamItemAdapter<>(activity, new StreamSizeWrapper<>(sortedVideoStreams), isExternalPlayerEnabled);
final StreamItemAdapter<VideoStream, Stream> streamsAdapter = new StreamItemAdapter<>(activity, new StreamSizeWrapper<>(sortedVideoStreams, activity), isExternalPlayerEnabled);
spinnerToolbar.setAdapter(streamsAdapter);
spinnerToolbar.setSelection(selectedVideoStreamIndex);
spinnerToolbar.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@@ -760,7 +768,7 @@ public class VideoDetailFragment
* Stack that contains the "navigation history".<br>
* The peek is the current video.
*/
protected LinkedList<StackItem> stack = new LinkedList<>();
protected final LinkedList<StackItem> stack = new LinkedList<>();
public void clearHistory() {
stack.clear();
@@ -913,7 +921,7 @@ public class VideoDetailFragment
.getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
startOnExternalPlayer(activity, currentInfo, selectedVideoStream);
} else {
openNormalPlayer(selectedVideoStream);
openNormalPlayer();
}
}
@@ -926,24 +934,13 @@ public class VideoDetailFragment
}
}
private void openNormalPlayer(VideoStream selectedVideoStream) {
private void openNormalPlayer() {
Intent mIntent;
boolean useOldPlayer = PlayerHelper.isUsingOldPlayer(activity) || (Build.VERSION.SDK_INT < 16);
if (!useOldPlayer) {
// ExoPlayer
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
mIntent = NavigationHelper.getPlayerIntent(activity,
MainVideoPlayer.class,
playQueue,
getSelectedVideoStream().getResolution());
} else {
// Internal Player
mIntent = new Intent(activity, PlayVideoActivity.class)
.putExtra(PlayVideoActivity.VIDEO_TITLE, currentInfo.getName())
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.getUrl())
.putExtra(PlayVideoActivity.VIDEO_URL, currentInfo.getUrl())
.putExtra(PlayVideoActivity.START_POSITION, currentInfo.getStartPosition());
}
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
mIntent = NavigationHelper.getPlayerIntent(activity,
MainVideoPlayer.class,
playQueue,
getSelectedVideoStream().getResolution());
startActivity(mIntent);
}
@@ -1114,8 +1111,16 @@ public class VideoDetailFragment
animateView(videoTitleTextView, true, 0);
videoDescriptionRootLayout.setVisibility(View.GONE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoTitleToggleArrow.setVisibility(View.GONE);
if (videoTitleToggleArrow != null) { //phone
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoTitleToggleArrow.setVisibility(View.GONE);
} else { //tablet
final View related = (View) relatedStreamRootLayout.getParent();
//don`t need to hide it if related streams are disabled
if (related.getVisibility() == View.VISIBLE) {
related.setVisibility(View.INVISIBLE);
}
}
videoTitleRoot.setClickable(false);
imageLoader.cancelDisplayTask(thumbnailImageView);
@@ -1190,11 +1195,15 @@ public class VideoDetailFragment
detailDurationView.setVisibility(View.GONE);
}
videoTitleRoot.setClickable(true);
videoTitleToggleArrow.setVisibility(View.VISIBLE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoDescriptionView.setVisibility(View.GONE);
videoDescriptionRootLayout.setVisibility(View.GONE);
if (videoTitleToggleArrow != null) {
videoTitleRoot.setClickable(true);
videoTitleToggleArrow.setVisibility(View.VISIBLE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoDescriptionRootLayout.setVisibility(View.GONE);
} else {
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
}
if (!TextUtils.isEmpty(info.getUploadDate())) {
videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate()));
}
@@ -1242,6 +1251,11 @@ public class VideoDetailFragment
// Only auto play in the first open
autoPlayEnabled = false;
}
final ViewParent related = relatedStreamRootLayout.getParent();
if (related instanceof ScrollView) {
((ScrollView) related).scrollTo(0, 0);
}
}
@@ -1251,6 +1265,7 @@ public class VideoDetailFragment
downloadDialog.setVideoStreams(sortedVideoStreams);
downloadDialog.setAudioStreams(currentInfo.getAudioStreams());
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.setSubtitleStreams(currentInfo.getSubtitles());
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (Exception e) {
@@ -1299,4 +1314,13 @@ public class VideoDetailFragment
showError(getString(R.string.blocked_by_gema), false, R.drawable.gruese_die_gema);
}
}
private void setRelatedStreamsVisibility(int visibility) {
final ViewParent parent = relatedStreamRootLayout.getParent();
if (parent instanceof ScrollView) {
((ScrollView) parent).setVisibility(visibility);
} else {
relatedStreamRootLayout.setVisibility(visibility);
}
}
}

View File

@@ -3,10 +3,15 @@ package org.schabi.newpipe.fragments.list;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
@@ -21,9 +26,9 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavigationHelper;
@@ -36,7 +41,7 @@ import java.util.Queue;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implements ListViewContract<I, N>, StateSaver.WriteRead {
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implements ListViewContract<I, N>, StateSaver.WriteRead, SharedPreferences.OnSharedPreferenceChangeListener {
/*//////////////////////////////////////////////////////////////////////////
// Views
@@ -44,6 +49,9 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
protected InfoListAdapter infoListAdapter;
protected RecyclerView itemsList;
private int updateFlags = 0;
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
@@ -59,12 +67,31 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
PreferenceManager.getDefaultSharedPreferences(activity)
.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onDestroy() {
super.onDestroy();
StateSaver.onDestroy(savedState);
PreferenceManager.getDefaultSharedPreferences(activity)
.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onResume() {
super.onResume();
if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = isGridLayout();
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setGridItemVariants(useGrid);
infoListAdapter.notifyDataSetChanged();
}
updateFlags = 0;
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -119,13 +146,25 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
return new LinearLayoutManager(activity);
}
protected RecyclerView.LayoutManager getGridLayoutManager() {
final Resources resources = activity.getResources();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
width += (24 * resources.getDisplayMetrics().density);
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width);
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount));
return lm;
}
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
final boolean useGrid = isGridLayout();
itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(getListLayoutManager());
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setGridItemVariants(useGrid);
infoListAdapter.setFooter(getListFooter());
infoListAdapter.setHeader(getListHeader());
@@ -308,4 +347,22 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
public void handleNextItems(N result) {
isLoading.set(false);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(getString(R.string.list_view_mode_key))) {
updateFlags |= LIST_MODE_UPDATE_FLAG;
}
}
protected boolean isGridLayout() {
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value));
if ("auto".equals(list_mode)) {
final Configuration configuration = getResources().getConfiguration();
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
} else {
return "grid".equals(list_mode);
}
}
}

View File

@@ -8,9 +8,6 @@ import android.view.View;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
import org.schabi.newpipe.util.Constants;
import java.util.Queue;
@@ -19,7 +16,6 @@ import icepick.State;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
public abstract class BaseListInfoFragment<I extends ListInfo>

View File

@@ -68,7 +68,7 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private CompositeDisposable disposables = new CompositeDisposable();
private final CompositeDisposable disposables = new CompositeDisposable();
private Disposable subscribeButtonMonitor;
private SubscriptionService subscriptionService;
@@ -90,8 +90,6 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private MenuItem menuRssButton;
private boolean mIsVisibleToUser = false;
public static ChannelFragment getInstance(int serviceId, String url, String name) {
ChannelFragment instance = new ChannelFragment();
instance.setInitialData(serviceId, url, name);
@@ -105,7 +103,6 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
if(activity != null
&& useAsFrontPage
&& isVisibleToUser) {

View File

@@ -5,7 +5,6 @@ import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -19,8 +18,6 @@ import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
@@ -131,26 +128,16 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
@Override
public Single<KioskInfo> loadResult(boolean forceReload) {
String contentCountry = PreferenceManager
.getDefaultSharedPreferences(activity)
.getString(getString(R.string.content_country_key),
getString(R.string.default_country_value));
return ExtractorHelper.getKioskInfo(serviceId,
url,
contentCountry,
forceReload);
}
@Override
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
String contentCountry = PreferenceManager
.getDefaultSharedPreferences(activity)
.getString(getString(R.string.content_country_key),
getString(R.string.default_country_value));
return ExtractorHelper.getMoreKioskItems(serviceId,
url,
currentNextPageUrl,
contentCountry);
currentNextPageUrl);
}
/*//////////////////////////////////////////////////////////////////////////

View File

@@ -20,7 +20,6 @@ import android.widget.TextView;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.schabi.newpipe.App;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
@@ -30,7 +29,6 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;

View File

@@ -122,12 +122,11 @@ public class SearchFragment
private String nextPageUrl;
private String contentCountry;
private boolean isSuggestionsEnabled = true;
private boolean isSearchHistoryEnabled = true;
private PublishSubject<String> suggestionPublisher = PublishSubject.create();
private final PublishSubject<String> suggestionPublisher = PublishSubject.create();
private Disposable searchDisposable;
private Disposable suggestionDisposable;
private CompositeDisposable disposables = new CompositeDisposable();
private final CompositeDisposable disposables = new CompositeDisposable();
private SuggestionListAdapter suggestionListAdapter;
private HistoryRecordManager historyRecordManager;
@@ -173,7 +172,7 @@ public class SearchFragment
suggestionListAdapter = new SuggestionListAdapter(activity);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true);
boolean isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true);
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
historyRecordManager = new HistoryRecordManager(context);
@@ -627,7 +626,7 @@ public class SearchFragment
}
final Observable<List<SuggestionItem>> network = ExtractorHelper
.suggestionsFor(serviceId, query, contentCountry)
.suggestionsFor(serviceId, query)
.toObservable()
.map(strings -> {
List<SuggestionItem> result = new ArrayList<>();
@@ -727,8 +726,7 @@ public class SearchFragment
searchDisposable = ExtractorHelper.searchFor(serviceId,
searchString,
Arrays.asList(contentFilter),
sortFilter,
contentCountry)
sortFilter)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
@@ -746,8 +744,7 @@ public class SearchFragment
searchString,
asList(contentFilter),
sortFilter,
nextPageUrl,
contentCountry)
nextPageUrl)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))

View File

@@ -45,7 +45,7 @@ public class InfoItemBuilder {
private static final String TAG = InfoItemBuilder.class.toString();
private final Context context;
private ImageLoader imageLoader = ImageLoader.getInstance();
private final ImageLoader imageLoader = ImageLoader.getInstance();
private OnClickGesture<StreamInfoItem> onStreamSelectedListener;
private OnClickGesture<ChannelInfoItem> onChannelSelectedListener;

View File

@@ -5,7 +5,6 @@ import android.app.AlertDialog;
import android.content.DialogInterface;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

View File

@@ -1,6 +1,7 @@
package org.schabi.newpipe.info_list;
import android.app.Activity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
@@ -12,9 +13,12 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder;
import org.schabi.newpipe.util.FallbackViewHolder;
@@ -52,14 +56,18 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
private static final int MINI_STREAM_HOLDER_TYPE = 0x100;
private static final int STREAM_HOLDER_TYPE = 0x101;
private static final int GRID_STREAM_HOLDER_TYPE = 0x102;
private static final int MINI_CHANNEL_HOLDER_TYPE = 0x200;
private static final int CHANNEL_HOLDER_TYPE = 0x201;
private static final int GRID_CHANNEL_HOLDER_TYPE = 0x202;
private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300;
private static final int PLAYLIST_HOLDER_TYPE = 0x301;
private static final int GRID_PLAYLIST_HOLDER_TYPE = 0x302;
private final InfoItemBuilder infoItemBuilder;
private final ArrayList<InfoItem> infoItemList;
private boolean useMiniVariant = false;
private boolean useGridVariant = false;
private boolean showFooter = false;
private View header = null;
private View footer = null;
@@ -94,6 +102,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
this.useMiniVariant = useMiniVariant;
}
public void setGridItemVariants(boolean useGridVariant) {
this.useGridVariant = useGridVariant;
}
public void addInfoItemList(List<InfoItem> data) {
if (data != null) {
if (DEBUG) {
@@ -206,11 +218,11 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
final InfoItem item = infoItemList.get(position);
switch (item.getInfoType()) {
case STREAM:
return useMiniVariant ? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE;
return useGridVariant ? GRID_STREAM_HOLDER_TYPE : useMiniVariant ? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE;
case CHANNEL:
return useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
case PLAYLIST:
return useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
default:
Log.e(TAG, "Trollolo");
return -1;
@@ -229,14 +241,20 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
return new StreamMiniInfoItemHolder(infoItemBuilder, parent);
case STREAM_HOLDER_TYPE:
return new StreamInfoItemHolder(infoItemBuilder, parent);
case GRID_STREAM_HOLDER_TYPE:
return new StreamGridInfoItemHolder(infoItemBuilder, parent);
case MINI_CHANNEL_HOLDER_TYPE:
return new ChannelMiniInfoItemHolder(infoItemBuilder, parent);
case CHANNEL_HOLDER_TYPE:
return new ChannelInfoItemHolder(infoItemBuilder, parent);
case GRID_CHANNEL_HOLDER_TYPE:
return new ChannelGridInfoItemHolder(infoItemBuilder, parent);
case MINI_PLAYLIST_HOLDER_TYPE:
return new PlaylistMiniInfoItemHolder(infoItemBuilder, parent);
case PLAYLIST_HOLDER_TYPE:
return new PlaylistInfoItemHolder(infoItemBuilder, parent);
case GRID_PLAYLIST_HOLDER_TYPE:
return new PlaylistGridInfoItemHolder(infoItemBuilder, parent);
default:
Log.e(TAG, "Trollolo");
return new FallbackViewHolder(new View(parent.getContext()));
@@ -257,4 +275,14 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
((HFHolder) holder).view = footer;
}
}
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
return new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
final int type = getItemViewType(position);
return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1;
}
};
}
}

View File

@@ -0,0 +1,13 @@
package org.schabi.newpipe.info_list.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.info_list.InfoItemBuilder;
public class ChannelGridInfoItemHolder extends ChannelMiniInfoItemHolder {
public ChannelGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_channel_grid_item, parent);
}
}

View File

@@ -47,6 +47,13 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
itemBuilder.getOnChannelSelectedListener().selected(item);
}
});
itemView.setOnLongClickListener(view -> {
if (itemBuilder.getOnChannelSelectedListener() != null) {
itemBuilder.getOnChannelSelectedListener().held(item);
}
return true;
});
}
protected String getDetailLine(final ChannelInfoItem item) {

View File

@@ -0,0 +1,13 @@
package org.schabi.newpipe.info_list.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.info_list.InfoItemBuilder;
public class PlaylistGridInfoItemHolder extends PlaylistMiniInfoItemHolder {
public PlaylistGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
}
}

View File

@@ -0,0 +1,13 @@
package org.schabi.newpipe.info_list.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.info_list.InfoItemBuilder;
public class StreamGridInfoItemHolder extends StreamMiniInfoItemHolder {
public StreamGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_stream_grid_item, parent);
}
}

View File

@@ -1,8 +1,13 @@
package org.schabi.newpipe.local;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
@@ -25,7 +30,7 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
* called and is memory efficient when in backstack.
* */
public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
implements ListViewContract<I, N> {
implements ListViewContract<I, N>, SharedPreferences.OnSharedPreferenceChangeListener {
/*//////////////////////////////////////////////////////////////////////////
// Views
@@ -36,6 +41,9 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
protected LocalItemListAdapter itemListAdapter;
protected RecyclerView itemsList;
private int updateFlags = 0;
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
/*//////////////////////////////////////////////////////////////////////////
// Lifecycle - Creation
@@ -45,6 +53,29 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
PreferenceManager.getDefaultSharedPreferences(activity)
.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onDestroy() {
super.onDestroy();
PreferenceManager.getDefaultSharedPreferences(activity)
.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onResume() {
super.onResume();
if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = isGridLayout();
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setGridItemVariants(useGrid);
itemListAdapter.notifyDataSetChanged();
}
updateFlags = 0;
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -59,6 +90,16 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
return activity.getLayoutInflater().inflate(R.layout.pignate_footer, itemsList, false);
}
protected RecyclerView.LayoutManager getGridLayoutManager() {
final Resources resources = activity.getResources();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
width += (24 * resources.getDisplayMetrics().density);
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width);
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount));
return lm;
}
protected RecyclerView.LayoutManager getListLayoutManager() {
return new LinearLayoutManager(activity);
}
@@ -67,10 +108,13 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
protected void initViews(View rootView, Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(getListLayoutManager());
itemListAdapter = new LocalItemListAdapter(activity);
final boolean useGrid = isGridLayout();
itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setGridItemVariants(useGrid);
itemListAdapter.setHeader(headerRootView = getListHeader());
itemListAdapter.setFooter(footerRootView = getListFooter());
@@ -174,4 +218,22 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
resetFragment();
return super.onError(exception);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(getString(R.string.list_view_mode_key))) {
updateFlags |= LIST_MODE_UPDATE_FLAG;
}
}
protected boolean isGridLayout() {
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value));
if ("auto".equals(list_mode)) {
final Configuration configuration = getResources().getConfiguration();
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
} else {
return "grid".equals(list_mode);
}
}
}

View File

@@ -33,7 +33,7 @@ public class LocalItemBuilder {
private static final String TAG = LocalItemBuilder.class.toString();
private final Context context;
private ImageLoader imageLoader = ImageLoader.getInstance();
private final ImageLoader imageLoader = ImageLoader.getInstance();
private OnClickGesture<LocalItem> onSelectedListener;

View File

@@ -1,18 +1,21 @@
package org.schabi.newpipe.local;
import android.app.Activity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.local.HeaderFooterHolder;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.holder.LocalItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistStreamGridItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistStreamItemHolder;
import org.schabi.newpipe.local.holder.LocalStatisticStreamGridItemHolder;
import org.schabi.newpipe.local.holder.LocalStatisticStreamItemHolder;
import org.schabi.newpipe.local.holder.RemotePlaylistGridItemHolder;
import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder;
import org.schabi.newpipe.util.FallbackViewHolder;
import org.schabi.newpipe.util.Localization;
@@ -52,14 +55,19 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
private static final int STREAM_STATISTICS_HOLDER_TYPE = 0x1000;
private static final int STREAM_PLAYLIST_HOLDER_TYPE = 0x1001;
private static final int STREAM_STATISTICS_GRID_HOLDER_TYPE = 0x1002;
private static final int STREAM_PLAYLIST_GRID_HOLDER_TYPE = 0x1004;
private static final int LOCAL_PLAYLIST_HOLDER_TYPE = 0x2000;
private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001;
private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002;
private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004;
private final LocalItemBuilder localItemBuilder;
private final ArrayList<LocalItem> localItems;
private final DateFormat dateFormat;
private boolean showFooter = false;
private boolean useGridVariant = false;
private View header = null;
private View footer = null;
@@ -134,6 +142,10 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
notifyDataSetChanged();
}
public void setGridItemVariants(boolean useGridVariant) {
this.useGridVariant = useGridVariant;
}
public void setHeader(View header) {
boolean changed = header != this.header;
this.header = header;
@@ -195,11 +207,11 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
final LocalItem item = localItems.get(position);
switch (item.getLocalItemType()) {
case PLAYLIST_LOCAL_ITEM: return LOCAL_PLAYLIST_HOLDER_TYPE;
case PLAYLIST_REMOTE_ITEM: return REMOTE_PLAYLIST_HOLDER_TYPE;
case PLAYLIST_LOCAL_ITEM: return useGridVariant ? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE;
case PLAYLIST_REMOTE_ITEM: return useGridVariant ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE;
case PLAYLIST_STREAM_ITEM: return STREAM_PLAYLIST_HOLDER_TYPE;
case STATISTIC_STREAM_ITEM: return STREAM_STATISTICS_HOLDER_TYPE;
case PLAYLIST_STREAM_ITEM: return useGridVariant ? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE;
case STATISTIC_STREAM_ITEM: return useGridVariant ? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE;
default:
Log.e(TAG, "No holder type has been considered for item: [" +
item.getLocalItemType() + "]");
@@ -218,12 +230,20 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
return new HeaderFooterHolder(footer);
case LOCAL_PLAYLIST_HOLDER_TYPE:
return new LocalPlaylistItemHolder(localItemBuilder, parent);
case LOCAL_PLAYLIST_GRID_HOLDER_TYPE:
return new LocalPlaylistGridItemHolder(localItemBuilder, parent);
case REMOTE_PLAYLIST_HOLDER_TYPE:
return new RemotePlaylistItemHolder(localItemBuilder, parent);
case REMOTE_PLAYLIST_GRID_HOLDER_TYPE:
return new RemotePlaylistGridItemHolder(localItemBuilder, parent);
case STREAM_PLAYLIST_HOLDER_TYPE:
return new LocalPlaylistStreamItemHolder(localItemBuilder, parent);
case STREAM_PLAYLIST_GRID_HOLDER_TYPE:
return new LocalPlaylistStreamGridItemHolder(localItemBuilder, parent);
case STREAM_STATISTICS_HOLDER_TYPE:
return new LocalStatisticStreamItemHolder(localItemBuilder, parent);
case STREAM_STATISTICS_GRID_HOLDER_TYPE:
return new LocalStatisticStreamGridItemHolder(localItemBuilder, parent);
default:
Log.e(TAG, "No view type has been considered for holder: [" + type + "]");
return new FallbackViewHolder(new View(parent.getContext()));
@@ -247,4 +267,14 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
((HeaderFooterHolder) holder).view = footer;
}
}
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
return new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
final int type = getItemViewType(position);
return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1;
}
};
}
}

View File

@@ -1,9 +1,11 @@
package org.schabi.newpipe.local.dialog;
import android.app.Dialog;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.view.Window;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.util.StateSaver;
@@ -41,6 +43,18 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
StateSaver.onDestroy(savedState);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Dialog dialog = super.onCreateDialog(savedInstanceState);
//remove title
final Window window = dialog.getWindow();
if (window != null) {
window.requestFeature(Window.FEATURE_NO_TITLE);
}
return dialog;
}
/*//////////////////////////////////////////////////////////////////////////
// State Saving
//////////////////////////////////////////////////////////////////////////*/
@@ -58,7 +72,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
@Override
@SuppressWarnings("unchecked")
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
public void readFrom(@NonNull Queue<Object> savedObjects) {
streamEntities = (List<StreamEntity>) savedObjects.poll();
}

View File

@@ -36,8 +36,6 @@ import io.reactivex.MaybeObserver;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Predicate;
public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Void> {

View File

@@ -1,7 +1,6 @@
package org.schabi.newpipe.local.history;
import android.content.Context;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;

View File

@@ -45,7 +45,6 @@ import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Scheduler;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;

View File

@@ -21,7 +21,6 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.player.playqueue.PlayQueue;
@@ -57,7 +56,7 @@ public class StatisticsPlaylistFragment
/* Used for independent events */
private Subscription databaseSubscription;
private HistoryRecordManager recordManager;
private CompositeDisposable disposables = new CompositeDisposable();
private final CompositeDisposable disposables = new CompositeDisposable();
private enum StatisticSortMode {
LAST_PLAYED,

View File

@@ -0,0 +1,13 @@
package org.schabi.newpipe.local.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.local.LocalItemBuilder;
public class LocalPlaylistGridItemHolder extends LocalPlaylistItemHolder {
public LocalPlaylistGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
}
}

View File

@@ -16,6 +16,10 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
super(infoItemBuilder, parent);
}
LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
}
@Override
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
if (!(localItem instanceof PlaylistMetadataEntry)) return;

View File

@@ -0,0 +1,13 @@
package org.schabi.newpipe.local.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.local.LocalItemBuilder;
public class LocalPlaylistStreamGridItemHolder extends LocalPlaylistStreamItemHolder {
public LocalPlaylistStreamGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_stream_playlist_grid_item, parent); //TODO
}
}

View File

@@ -0,0 +1,13 @@
package org.schabi.newpipe.local.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.local.LocalItemBuilder;
public class LocalStatisticStreamGridItemHolder extends LocalStatisticStreamItemHolder {
public LocalStatisticStreamGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_stream_grid_item, parent);
}
}

View File

@@ -1,5 +1,6 @@
package org.schabi.newpipe.local.holder;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.view.ViewGroup;
@@ -42,10 +43,15 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
public final TextView itemVideoTitleView;
public final TextView itemUploaderView;
public final TextView itemDurationView;
@Nullable
public final TextView itemAdditionalDetails;
public LocalStatisticStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_stream_item, parent);
public LocalStatisticStreamItemHolder(LocalItemBuilder itemBuilder, ViewGroup parent) {
this(itemBuilder, R.layout.list_stream_item, parent);
}
LocalStatisticStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView);
@@ -80,7 +86,9 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
itemDurationView.setVisibility(View.GONE);
}
itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateFormat));
if (itemAdditionalDetails != null) {
itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateFormat));
}
// Default thumbnail is shown on error, while loading and if the url is empty
itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView,

View File

@@ -0,0 +1,13 @@
package org.schabi.newpipe.local.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.local.LocalItemBuilder;
public class RemotePlaylistGridItemHolder extends RemotePlaylistItemHolder {
public RemotePlaylistGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
}
}

View File

@@ -16,6 +16,10 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
super(infoItemBuilder, parent);
}
RemotePlaylistItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
}
@Override
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
if (!(localItem instanceof PlaylistRemoteEntity)) return;

View File

@@ -459,7 +459,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
int directions = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
if (isGridLayout()) {
directions |= ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
}
return new ItemTouchHelper.SimpleCallback(directions,
ItemTouchHelper.ACTION_STATE_IDLE) {
@Override
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize,

View File

@@ -1,15 +1,22 @@
package org.schabi.newpipe.local.subscription;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -17,6 +24,7 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@@ -40,10 +48,11 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService;
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
@@ -72,7 +81,7 @@ import static org.schabi.newpipe.local.subscription.services.SubscriptionsImport
import static org.schabi.newpipe.util.AnimationUtils.animateRotation;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEntity>> {
public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEntity>> implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final int REQUEST_EXPORT_CODE = 666;
private static final int REQUEST_IMPORT_CODE = 667;
@@ -80,8 +89,10 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
@State
protected Parcelable itemsListState;
private InfoListAdapter infoListAdapter;
private int updateFlags = 0;
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
private View headerRootLayout;
private View whatsNewItemListHeader;
private View importExportListHeader;
@@ -100,6 +111,8 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
PreferenceManager.getDefaultSharedPreferences(activity)
.registerOnSharedPreferenceChangeListener(this);
}
@Override
@@ -127,6 +140,15 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
public void onResume() {
super.onResume();
setupBroadcastReceiver();
if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = isGridLayout();
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setGridItemVariants(useGrid);
infoListAdapter.notifyDataSetChanged();
}
updateFlags = 0;
}
}
@Override
@@ -153,9 +175,25 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
disposables = null;
subscriptionService = null;
PreferenceManager.getDefaultSharedPreferences(activity)
.unregisterOnSharedPreferenceChangeListener(this);
super.onDestroy();
}
protected RecyclerView.LayoutManager getListLayoutManager() {
return new LinearLayoutManager(activity);
}
protected RecyclerView.LayoutManager getGridLayoutManager() {
final Resources resources = activity.getResources();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
width += (24 * resources.getDisplayMetrics().density);
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width);
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount));
return lm;
}
/*/////////////////////////////////////////////////////////////////////////
// Menu
/////////////////////////////////////////////////////////////////////////*/
@@ -287,16 +325,19 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
protected void initViews(View rootView, Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
final boolean useGrid = isGridLayout();
infoListAdapter = new InfoListAdapter(getActivity());
itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(new LinearLayoutManager(activity));
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
View headerRootLayout;
infoListAdapter.setHeader(headerRootLayout = activity.getLayoutInflater().inflate(R.layout.subscription_header, itemsList, false));
whatsNewItemListHeader = headerRootLayout.findViewById(R.id.whats_new);
importExportListHeader = headerRootLayout.findViewById(R.id.import_export);
importExportOptions = headerRootLayout.findViewById(R.id.import_export_options);
infoListAdapter.useMiniItemVariants(true);
infoListAdapter.setGridItemVariants(useGrid);
itemsList.setAdapter(infoListAdapter);
setupImportFromItems(headerRootLayout.findViewById(R.id.import_from_options));
@@ -320,7 +361,7 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
super.initListeners();
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() {
@Override
public void selected(ChannelInfoItem selectedItem) {
final FragmentManager fragmentManager = getFM();
NavigationHelper.openChannelFragment(fragmentManager,
@@ -328,6 +369,11 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
selectedItem.getUrl(),
selectedItem.getName());
}
public void held(ChannelInfoItem selectedItem) {
showLongTapDialog(selectedItem);
}
});
//noinspection ConstantConditions
@@ -338,6 +384,85 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
importExportListHeader.setOnClickListener(v -> importExportOptions.switchState());
}
private void showLongTapDialog(ChannelInfoItem selectedItem) {
final Context context = getContext();
final Activity activity = getActivity();
if (context == null || context.getResources() == null || getActivity() == null) return;
final String[] commands = new String[]{
context.getResources().getString(R.string.share),
context.getResources().getString(R.string.unsubscribe)
};
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
switch (i) {
case 0:
shareChannel(selectedItem);
break;
case 1:
deleteChannel(selectedItem);
break;
default:
break;
}
};
final View bannerView = View.inflate(activity, R.layout.dialog_title, null);
bannerView.setSelected(true);
TextView titleView = bannerView.findViewById(R.id.itemTitleView);
titleView.setText(selectedItem.getName());
TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
detailsView.setVisibility(View.GONE);
new AlertDialog.Builder(activity)
.setCustomTitle(bannerView)
.setItems(commands, actions)
.create()
.show();
}
private void shareChannel (ChannelInfoItem selectedItem) {
shareUrl(selectedItem.getName(), selectedItem.getUrl());
}
@SuppressLint("CheckResult")
private void deleteChannel (ChannelInfoItem selectedItem) {
subscriptionService.subscriptionTable()
.getSubscription(selectedItem.getServiceId(), selectedItem.getUrl())
.toObservable()
.observeOn(Schedulers.io())
.subscribe(getDeleteObserver());
Toast.makeText(activity, getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show();
}
private Observer<List<SubscriptionEntity>> getDeleteObserver(){
return new Observer<List<SubscriptionEntity>>() {
@Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
@Override
public void onNext(List<SubscriptionEntity> subscriptionEntities) {
subscriptionService.subscriptionTable().delete(subscriptionEntities);
}
@Override
public void onError(Throwable exception) {
SubscriptionFragment.this.onError(exception);
}
@Override
public void onComplete() { }
};
}
private void resetFragment() {
if (disposables != null) disposables.clear();
if (infoListAdapter != null) infoListAdapter.clearStreamItemList();
@@ -447,4 +572,22 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
R.string.general_error);
return true;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(getString(R.string.list_view_mode_key))) {
updateFlags |= LIST_MODE_UPDATE_FLAG;
}
}
protected boolean isGridLayout() {
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value));
if ("auto".equals(list_mode)) {
final Configuration configuration = getResources().getConfiguration();
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
} else {
return "grid".equals(list_mode);
}
}
}

View File

@@ -55,10 +55,10 @@ public class SubscriptionService {
private static final int SUBSCRIPTION_DEBOUNCE_INTERVAL = 500;
private static final int SUBSCRIPTION_THREAD_POOL_SIZE = 4;
private AppDatabase db;
private Flowable<List<SubscriptionEntity>> subscription;
private final AppDatabase db;
private final Flowable<List<SubscriptionEntity>> subscription;
private Scheduler subscriptionScheduler;
private final Scheduler subscriptionScheduler;
private SubscriptionService(Context context) {
db = NewPipeDatabase.getInstance(context.getApplicationContext());
@@ -116,7 +116,7 @@ public class SubscriptionService {
public Completable updateChannelInfo(final ChannelInfo info) {
final Function<List<SubscriptionEntity>, CompletableSource> update = new Function<List<SubscriptionEntity>, CompletableSource>() {
@Override
public CompletableSource apply(@NonNull List<SubscriptionEntity> subscriptionEntities) throws Exception {
public CompletableSource apply(@NonNull List<SubscriptionEntity> subscriptionEntities) {
if (DEBUG) Log.d(TAG, "updateChannelInfo() called with: subscriptionEntities = [" + subscriptionEntities + "]");
if (subscriptionEntities.size() == 1) {
SubscriptionEntity subscription = subscriptionEntities.get(0);

View File

@@ -58,8 +58,8 @@ public abstract class BaseImportExportService extends Service {
protected NotificationCompat.Builder notificationBuilder;
protected SubscriptionService subscriptionService;
protected CompositeDisposable disposables = new CompositeDisposable();
protected PublishProcessor<String> notificationUpdater = PublishProcessor.create();
protected final CompositeDisposable disposables = new CompositeDisposable();
protected final PublishProcessor<String> notificationUpdater = PublishProcessor.create();
@Nullable
@Override
@@ -90,9 +90,9 @@ public abstract class BaseImportExportService extends Service {
private static final int NOTIFICATION_SAMPLING_PERIOD = 2500;
protected AtomicInteger currentProgress = new AtomicInteger(-1);
protected AtomicInteger maxProgress = new AtomicInteger(-1);
protected ImportExportEventListener eventListener = new ImportExportEventListener() {
protected final AtomicInteger currentProgress = new AtomicInteger(-1);
protected final AtomicInteger maxProgress = new AtomicInteger(-1);
protected final ImportExportEventListener eventListener = new ImportExportEventListener() {
@Override
public void onSizeReceived(int size) {
maxProgress.set(size);
@@ -187,13 +187,13 @@ public abstract class BaseImportExportService extends Service {
protected Toast toast;
protected void showToast(@StringRes int message) {
showToast(getString(message), Toast.LENGTH_SHORT);
showToast(getString(message));
}
protected void showToast(String message, int duration) {
protected void showToast(String message) {
if (toast != null) toast.cancel();
toast = Toast.makeText(this, message, duration);
toast = Toast.makeText(this, message, Toast.LENGTH_SHORT);
toast.show();
}

View File

@@ -144,12 +144,16 @@ public class SubscriptionsImportService extends BaseImportExportService {
showToast(R.string.import_ongoing);
Flowable<List<SubscriptionItem>> flowable = null;
if (currentMode == CHANNEL_URL_MODE) {
flowable = importFromChannelUrl();
} else if (currentMode == INPUT_STREAM_MODE) {
flowable = importFromInputStream();
} else if (currentMode == PREVIOUS_EXPORT_MODE) {
flowable = importFromPreviousExport();
switch (currentMode) {
case CHANNEL_URL_MODE:
flowable = importFromChannelUrl();
break;
case INPUT_STREAM_MODE:
flowable = importFromInputStream();
break;
case PREVIOUS_EXPORT_MODE:
flowable = importFromPreviousExport();
break;
}
if (flowable == null) {

View File

@@ -98,35 +98,54 @@ public abstract class BasePlayer implements
Player.EventListener, PlaybackListener, ImageLoadingListener {
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
@NonNull public static final String TAG = "BasePlayer";
@NonNull
public static final String TAG = "BasePlayer";
@NonNull final protected Context context;
@NonNull
final protected Context context;
@NonNull final protected BroadcastReceiver broadcastReceiver;
@NonNull final protected IntentFilter intentFilter;
@NonNull
final protected BroadcastReceiver broadcastReceiver;
@NonNull
final protected IntentFilter intentFilter;
@NonNull final protected HistoryRecordManager recordManager;
@NonNull
final protected HistoryRecordManager recordManager;
@NonNull final protected CustomTrackSelector trackSelector;
@NonNull final protected PlayerDataSource dataSource;
@NonNull
final protected CustomTrackSelector trackSelector;
@NonNull
final protected PlayerDataSource dataSource;
@NonNull final private LoadControl loadControl;
@NonNull final private RenderersFactory renderFactory;
@NonNull
final private LoadControl loadControl;
@NonNull
final private RenderersFactory renderFactory;
@NonNull final private SerialDisposable progressUpdateReactor;
@NonNull final private CompositeDisposable databaseUpdateReactor;
@NonNull
final private SerialDisposable progressUpdateReactor;
@NonNull
final private CompositeDisposable databaseUpdateReactor;
/*//////////////////////////////////////////////////////////////////////////
// Intent
//////////////////////////////////////////////////////////////////////////*/
@NonNull public static final String REPEAT_MODE = "repeat_mode";
@NonNull public static final String PLAYBACK_PITCH = "playback_pitch";
@NonNull public static final String PLAYBACK_SPEED = "playback_speed";
@NonNull public static final String PLAYBACK_SKIP_SILENCE = "playback_skip_silence";
@NonNull public static final String PLAYBACK_QUALITY = "playback_quality";
@NonNull public static final String PLAY_QUEUE_KEY = "play_queue_key";
@NonNull public static final String APPEND_ONLY = "append_only";
@NonNull public static final String SELECT_ON_APPEND = "select_on_append";
@NonNull
public static final String REPEAT_MODE = "repeat_mode";
@NonNull
public static final String PLAYBACK_PITCH = "playback_pitch";
@NonNull
public static final String PLAYBACK_SPEED = "playback_speed";
@NonNull
public static final String PLAYBACK_SKIP_SILENCE = "playback_skip_silence";
@NonNull
public static final String PLAYBACK_QUALITY = "playback_quality";
@NonNull
public static final String PLAY_QUEUE_KEY = "play_queue_key";
@NonNull
public static final String APPEND_ONLY = "append_only";
@NonNull
public static final String SELECT_ON_APPEND = "select_on_append";
/*//////////////////////////////////////////////////////////////////////////
// Playback
@@ -137,13 +156,18 @@ public abstract class BasePlayer implements
protected PlayQueue playQueue;
protected PlayQueueAdapter playQueueAdapter;
@Nullable protected MediaSourceManager playbackManager;
@Nullable
protected MediaSourceManager playbackManager;
@Nullable private PlayQueueItem currentItem;
@Nullable private MediaSourceTag currentMetadata;
@Nullable private Bitmap currentThumbnail;
@Nullable
private PlayQueueItem currentItem;
@Nullable
private MediaSourceTag currentMetadata;
@Nullable
private Bitmap currentThumbnail;
@Nullable protected Toast errorToast;
@Nullable
protected Toast errorToast;
/*//////////////////////////////////////////////////////////////////////////
// Player
@@ -213,7 +237,8 @@ public abstract class BasePlayer implements
registerBroadcastReceiver();
}
public void initListeners() {}
public void initListeners() {
}
public void handleIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
@@ -230,7 +255,8 @@ public abstract class BasePlayer implements
int sizeBeforeAppend = playQueue.size();
playQueue.append(queue.getStreams());
if (intent.getBooleanExtra(SELECT_ON_APPEND, false) &&
if ((intent.getBooleanExtra(SELECT_ON_APPEND, false) ||
getCurrentState() == STATE_COMPLETED) &&
queue.getStreams().size() > 0) {
playQueue.setIndex(sizeBeforeAppend);
}
@@ -296,7 +322,6 @@ public abstract class BasePlayer implements
databaseUpdateReactor.clear();
progressUpdateReactor.set(null);
simpleExoPlayer = null;
}
/*//////////////////////////////////////////////////////////////////////////
@@ -424,13 +449,15 @@ public abstract class BasePlayer implements
if (!isProgressLoopRunning()) startProgressLoop();
}
public void onBuffering() {}
public void onBuffering() {
}
public void onPaused() {
if (isProgressLoopRunning()) stopProgressLoop();
}
public void onPausedSeek() {}
public void onPausedSeek() {
}
public void onCompleted() {
if (DEBUG) Log.d(TAG, "onCompleted() called");
@@ -601,19 +628,19 @@ public abstract class BasePlayer implements
/**
* Processes the exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}.
* There are multiple types of errors: <br><br>
*
* <p>
* {@link ExoPlaybackException#TYPE_SOURCE TYPE_SOURCE}: <br><br>
*
* <p>
* {@link ExoPlaybackException#TYPE_UNEXPECTED TYPE_UNEXPECTED}: <br><br>
* If a runtime error occurred, then we can try to recover it by restarting the playback
* after setting the timestamp recovery. <br><br>
*
* <p>
* {@link ExoPlaybackException#TYPE_RENDERER TYPE_RENDERER}: <br><br>
* If the renderer failed, treat the error as unrecoverable.
*
* @see #processSourceError(IOException)
* @see Player.EventListener#onPlayerError(ExoPlaybackException)
* */
*/
@Override
public void onPlayerError(ExoPlaybackException error) {
if (DEBUG) Log.d(TAG, "ExoPlayer - onPlayerError() called with: " +
@@ -899,8 +926,8 @@ public abstract class BasePlayer implements
if (DEBUG) Log.d(TAG, "onPlayPrevious() called");
/* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT_MILLIS milliseconds,
* restart current track. Also restart the track if the current track
* is the first in a queue.*/
* restart current track. Also restart the track if the current track
* is the first in a queue.*/
if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT_MILLIS ||
playQueue.getIndex() == 0) {
seekToDefault();
@@ -1009,8 +1036,8 @@ public abstract class BasePlayer implements
try {
metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag();
} catch (IndexOutOfBoundsException | ClassCastException error) {
if(DEBUG) Log.d(TAG, "Could not update metadata: " + error.getMessage());
if(DEBUG) error.printStackTrace();
if (DEBUG) Log.d(TAG, "Could not update metadata: " + error.getMessage());
if (DEBUG) error.printStackTrace();
return;
}
@@ -1074,7 +1101,9 @@ public abstract class BasePlayer implements
currentThumbnail;
}
/** Checks if the current playback is a livestream AND is playing at or beyond the live edge */
/**
* Checks if the current playback is a livestream AND is playing at or beyond the live edge
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isLiveEdge() {
if (simpleExoPlayer == null || !isLive()) return false;
@@ -1098,13 +1127,14 @@ public abstract class BasePlayer implements
} catch (@NonNull IndexOutOfBoundsException ignored) {
// Why would this even happen =(
// But lets log it anyway. Save is save
if(DEBUG) Log.d(TAG, "Could not update metadata: " + ignored.getMessage());
if(DEBUG) ignored.printStackTrace();
if (DEBUG) Log.d(TAG, "Could not update metadata: " + ignored.getMessage());
if (DEBUG) ignored.printStackTrace();
return false;
}
}
public boolean isPlaying() {
if (simpleExoPlayer == null) return false;
final int state = simpleExoPlayer.getPlaybackState();
return (state == Player.STATE_READY || state == Player.STATE_BUFFERING)
&& simpleExoPlayer.getPlayWhenReady();
@@ -1112,7 +1142,9 @@ public abstract class BasePlayer implements
@Player.RepeatMode
public int getRepeatMode() {
return simpleExoPlayer == null ? Player.REPEAT_MODE_OFF : simpleExoPlayer.getRepeatMode();
return simpleExoPlayer == null
? Player.REPEAT_MODE_OFF
: simpleExoPlayer.getRepeatMode();
}
public void setRepeatMode(@Player.RepeatMode final int repeatMode) {
@@ -1178,4 +1210,8 @@ public abstract class BasePlayer implements
if (DEBUG) Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos);
playQueue.setRecovery(queuePos, windowPos);
}
public boolean gotDestroyed() {
return simpleExoPlayer == null;
}
}

View File

@@ -175,6 +175,10 @@ public final class MainVideoPlayer extends AppCompatActivity
setLandscape(lastOrientationWasLandscape);
}
final int lastResizeMode = defaultPreferences.getInt(
getString(R.string.last_resize_mode), AspectRatioFrameLayout.RESIZE_MODE_FIT);
playerImpl.setResizeMode(lastResizeMode);
// Upon going in or out of multiwindow mode, isInMultiWindow will always be false,
// since the first onResume needs to restore the player.
// Subsequent onResume calls while multiwindow mode remains the same and the player is
@@ -213,10 +217,9 @@ public final class MainVideoPlayer extends AppCompatActivity
if (playerImpl == null) return;
playerImpl.setRecovery();
playerState = new PlayerState(playerImpl.getPlayQueue(), playerImpl.getRepeatMode(),
playerImpl.getPlaybackSpeed(), playerImpl.getPlaybackPitch(),
playerImpl.getPlaybackQuality(), playerImpl.getPlaybackSkipSilence(),
playerImpl.isPlaying());
if(!playerImpl.gotDestroyed()) {
playerState = createPlayerState();
}
StateSaver.tryToSave(isChangingConfigurations(), null, outState, this);
}
@@ -231,6 +234,7 @@ public final class MainVideoPlayer extends AppCompatActivity
if (!isBackPressed) {
playerImpl.minimize();
}
playerState = createPlayerState();
playerImpl.destroy();
isInMultiWindow = false;
@@ -241,6 +245,13 @@ public final class MainVideoPlayer extends AppCompatActivity
// State Saving
//////////////////////////////////////////////////////////////////////////*/
private PlayerState createPlayerState() {
return new PlayerState(playerImpl.getPlayQueue(), playerImpl.getRepeatMode(),
playerImpl.getPlaybackSpeed(), playerImpl.getPlaybackPitch(),
playerImpl.getPlaybackQuality(), playerImpl.getPlaybackSkipSilence(),
playerImpl.isPlaying());
}
@Override
public String generateSuffix() {
return "." + UUID.randomUUID().toString() + ".player";
@@ -705,14 +716,27 @@ public final class MainVideoPlayer extends AppCompatActivity
@Override
protected int nextResizeMode(int currentResizeMode) {
final int newResizeMode;
switch (currentResizeMode) {
case AspectRatioFrameLayout.RESIZE_MODE_FIT:
return AspectRatioFrameLayout.RESIZE_MODE_FILL;
newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL;
break;
case AspectRatioFrameLayout.RESIZE_MODE_FILL:
return AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
break;
default:
return AspectRatioFrameLayout.RESIZE_MODE_FIT;
newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
break;
}
storeResizeMode(newResizeMode);
return newResizeMode;
}
private void storeResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode) {
defaultPreferences.edit()
.putInt(getString(R.string.last_resize_mode), resizeMode)
.apply();
}
@Override
@@ -876,6 +900,11 @@ public final class MainVideoPlayer extends AppCompatActivity
public void onMove(int sourceIndex, int targetIndex) {
if (playQueue != null) playQueue.move(sourceIndex, targetIndex);
}
@Override
public void onSwiped(int index) {
if(index != -1) playQueue.remove(index);
}
};
}
@@ -989,12 +1018,14 @@ public final class MainVideoPlayer extends AppCompatActivity
private static final int MOVEMENT_THRESHOLD = 40;
private final boolean isPlayerGestureEnabled = PlayerHelper.isPlayerGestureEnabled(getApplicationContext());
private final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(getApplicationContext());
private final boolean isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(getApplicationContext());
private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume();
@Override
public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) {
if (!isPlayerGestureEnabled) return false;
if (!isVolumeGestureEnabled && !isBrightnessGestureEnabled) return false;
//noinspection PointlessBooleanExpression
if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " +
@@ -1010,7 +1041,11 @@ public final class MainVideoPlayer extends AppCompatActivity
isMoving = true;
if (initialEvent.getX() > playerImpl.getRootView().getWidth() / 2) {
boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
boolean acceptVolumeArea = acceptAnyArea || initialEvent.getX() > playerImpl.getRootView().getWidth() / 2;
boolean acceptBrightnessArea = acceptAnyArea || !acceptVolumeArea;
if (isVolumeGestureEnabled && acceptVolumeArea) {
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
float currentProgressPercent =
(float) playerImpl.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
@@ -1035,7 +1070,7 @@ public final class MainVideoPlayer extends AppCompatActivity
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
}
} else {
} else if (isBrightnessGestureEnabled && acceptBrightnessArea) {
playerImpl.getBrightnessProgressBar().incrementProgressBy((int) distanceY);
float currentProgressPercent =
(float) playerImpl.getBrightnessProgressBar().getProgress() / playerImpl.getMaxGestureLength();

View File

@@ -68,7 +68,6 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.old.PlayVideoActivity;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.ListHelper;
@@ -80,7 +79,6 @@ import java.util.List;
import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING;
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION;
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
import static org.schabi.newpipe.player.helper.PlayerHelper.isUsingOldPlayer;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
/**
@@ -114,7 +112,6 @@ public final class PopupVideoPlayer extends Service {
private View closeOverlayView;
private FloatingActionButton closeOverlayButton;
private WindowManager.LayoutParams closeOverlayLayoutParams;
private int tossFlingVelocity;
@@ -248,7 +245,7 @@ public final class PopupVideoPlayer extends Service {
final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
closeOverlayLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
layoutParamType,
flags,
@@ -555,27 +552,17 @@ public final class PopupVideoPlayer extends Service {
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
setRecovery();
Intent intent;
if (!isUsingOldPlayer(getApplicationContext())) {
intent = NavigationHelper.getPlayerIntent(
context,
MainVideoPlayer.class,
this.getPlayQueue(),
this.getRepeatMode(),
this.getPlaybackSpeed(),
this.getPlaybackPitch(),
this.getPlaybackSkipSilence(),
this.getPlaybackQuality()
);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else {
intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
.putExtra(PlayVideoActivity.VIDEO_TITLE, getVideoTitle())
.putExtra(PlayVideoActivity.STREAM_URL, getSelectedVideoStream().getUrl())
.putExtra(PlayVideoActivity.VIDEO_URL, getVideoUrl())
.putExtra(PlayVideoActivity.START_POSITION, Math.round(getPlayer().getCurrentPosition() / 1000f));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
final Intent intent = NavigationHelper.getPlayerIntent(
context,
MainVideoPlayer.class,
this.getPlayQueue(),
this.getRepeatMode(),
this.getPlaybackSpeed(),
this.getPlaybackPitch(),
this.getPlaybackSkipSilence(),
this.getPlaybackQuality()
);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
closePopup();
}

View File

@@ -375,6 +375,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
public void onMove(int sourceIndex, int targetIndex) {
if (player != null) player.getPlayQueue().move(sourceIndex, targetIndex);
}
@Override
public void onSwiped(int index) {
if (index != -1) player.getPlayQueue().remove(index);
}
};
}

View File

@@ -137,16 +137,16 @@ public abstract class VideoPlayer extends BasePlayer
private TextView captionTextView;
private ValueAnimator controlViewAnimator;
private Handler controlsVisibilityHandler = new Handler();
private final Handler controlsVisibilityHandler = new Handler();
boolean isSomePopupMenuVisible = false;
private int qualityPopupMenuGroupId = 69;
private final int qualityPopupMenuGroupId = 69;
private PopupMenu qualityPopupMenu;
private int playbackSpeedPopupMenuGroupId = 79;
private final int playbackSpeedPopupMenuGroupId = 79;
private PopupMenu playbackSpeedPopupMenu;
private int captionPopupMenuGroupId = 89;
private final int captionPopupMenuGroupId = 89;
private PopupMenu captionPopupMenu;
///////////////////////////////////////////////////////////////////////////
@@ -683,12 +683,17 @@ public abstract class VideoPlayer extends BasePlayer
if (getAspectRatioFrameLayout() != null) {
final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode();
final int newResizeMode = nextResizeMode(currentResizeMode);
getAspectRatioFrameLayout().setResizeMode(newResizeMode);
getResizeView().setText(PlayerHelper.resizeTypeOf(context, newResizeMode));
setResizeMode(newResizeMode);
}
}
protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
getAspectRatioFrameLayout().setResizeMode(resizeMode);
getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode));
}
protected abstract int nextResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode);
/*//////////////////////////////////////////////////////////////////////////
// SeekBar Listener
//////////////////////////////////////////////////////////////////////////*/

View File

@@ -116,7 +116,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
private void onAudioFocusGain() {
Log.d(TAG, "onAudioFocusGain() called");
player.setVolume(DUCK_AUDIO_TO);
animateAudio(DUCK_AUDIO_TO, 1f, DUCK_DURATION);
animateAudio(DUCK_AUDIO_TO, 1f);
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
player.setPlayWhenReady(true);
@@ -131,13 +131,13 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
private void onAudioFocusLossCanDuck() {
Log.d(TAG, "onAudioFocusLossCanDuck() called");
// Set the volume to 1/10 on ducking
animateAudio(player.getVolume(), DUCK_AUDIO_TO, DUCK_DURATION);
animateAudio(player.getVolume(), DUCK_AUDIO_TO);
}
private void animateAudio(final float from, final float to, int duration) {
private void animateAudio(final float from, final float to) {
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setFloatValues(from, to);
valueAnimator.setDuration(duration);
valueAnimator.setDuration(AudioReactor.DUCK_DURATION);
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {

View File

@@ -4,12 +4,9 @@ import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.upstream.cache.CacheDataSink;
@@ -17,8 +14,6 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import org.schabi.newpipe.Downloader;
import java.io.File;
/* package-private */ class CacheFactory implements DataSource.Factory {

View File

@@ -66,25 +66,15 @@ public class PlaybackParameterDialog extends DialogFragment {
private double stepSize = DEFAULT_STEP;
@Nullable private SeekBar tempoSlider;
@Nullable private TextView tempoMinimumText;
@Nullable private TextView tempoMaximumText;
@Nullable private TextView tempoCurrentText;
@Nullable private TextView tempoStepDownText;
@Nullable private TextView tempoStepUpText;
@Nullable private SeekBar pitchSlider;
@Nullable private TextView pitchMinimumText;
@Nullable private TextView pitchMaximumText;
@Nullable private TextView pitchCurrentText;
@Nullable private TextView pitchStepDownText;
@Nullable private TextView pitchStepUpText;
@Nullable private TextView stepSizeOnePercentText;
@Nullable private TextView stepSizeFivePercentText;
@Nullable private TextView stepSizeTenPercentText;
@Nullable private TextView stepSizeTwentyFivePercentText;
@Nullable private TextView stepSizeOneHundredPercentText;
@Nullable private CheckBox unhookingCheckbox;
@Nullable private CheckBox skipSilenceCheckbox;
@@ -181,8 +171,8 @@ public class PlaybackParameterDialog extends DialogFragment {
private void setupTempoControl(@NonNull View rootView) {
tempoSlider = rootView.findViewById(R.id.tempoSeekbar);
tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText);
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
@@ -203,8 +193,8 @@ public class PlaybackParameterDialog extends DialogFragment {
private void setupPitchControl(@NonNull View rootView) {
pitchSlider = rootView.findViewById(R.id.pitchSeekbar);
pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText);
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
@@ -247,11 +237,11 @@ public class PlaybackParameterDialog extends DialogFragment {
}
private void setupStepSizeSelector(@NonNull final View rootView) {
stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
stepSizeTwentyFivePercentText = rootView.findViewById(R.id.stepSizeTwentyFivePercent);
stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent);
TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
TextView stepSizeTwentyFivePercentText = rootView.findViewById(R.id.stepSizeTwentyFivePercent);
TextView stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent);
if (stepSizeOnePercentText != null) {
stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE));

View File

@@ -19,11 +19,11 @@ import com.google.android.exoplayer2.util.MimeTypes;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.Subtitles;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.SubtitlesFormat;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
@@ -87,7 +87,7 @@ public class PlayerHelper {
return pitchFormatter.format(pitch);
}
public static String mimeTypesOf(final SubtitlesFormat format) {
public static String subtitleMimeTypesOf(final MediaFormat format) {
switch (format) {
case VTT: return MimeTypes.TEXT_VTT;
case TTML: return MimeTypes.APPLICATION_TTML;
@@ -97,8 +97,8 @@ public class PlayerHelper {
@NonNull
public static String captionLanguageOf(@NonNull final Context context,
@NonNull final Subtitles subtitles) {
final String displayName = subtitles.getLocale().getDisplayName(subtitles.getLocale());
@NonNull final SubtitlesStream subtitles) {
final String displayName = subtitles.getDisplayLanguageName();
return displayName + (subtitles.isAutoGenerated() ? " (" + context.getString(R.string.caption_auto_generated)+ ")" : "");
}
@@ -145,7 +145,7 @@ public class PlayerHelper {
final StreamInfoItem nextVideo = info.getNextVideo();
if (nextVideo != null && !urls.contains(nextVideo.getUrl())) {
return new SinglePlayQueue(nextVideo);
return getAutoQueuedSinglePlayQueue(nextVideo);
}
final List<InfoItem> relatedItems = info.getRelatedStreams();
@@ -158,7 +158,7 @@ public class PlayerHelper {
}
}
Collections.shuffle(autoQueueItems);
return autoQueueItems.isEmpty() ? null : new SinglePlayQueue(autoQueueItems.get(0));
return autoQueueItems.isEmpty() ? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
}
////////////////////////////////////////////////////////////////////////////
@@ -169,12 +169,12 @@ public class PlayerHelper {
return isResumeAfterAudioFocusGain(context, false);
}
public static boolean isPlayerGestureEnabled(@NonNull final Context context) {
return isPlayerGestureEnabled(context, true);
public static boolean isVolumeGestureEnabled(@NonNull final Context context) {
return isVolumeGestureEnabled(context, true);
}
public static boolean isUsingOldPlayer(@NonNull final Context context) {
return isUsingOldPlayer(context, false);
public static boolean isBrightnessGestureEnabled(@NonNull final Context context) {
return isBrightnessGestureEnabled(context, true);
}
public static boolean isRememberingPopupDimensions(@NonNull final Context context) {
@@ -203,7 +203,7 @@ public class PlayerHelper {
@NonNull
public static SeekParameters getSeekParameters(@NonNull final Context context) {
return isUsingInexactSeek(context, false) ?
return isUsingInexactSeek(context) ?
SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
}
@@ -306,20 +306,20 @@ public class PlayerHelper {
return getPreferences(context).getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), b);
}
private static boolean isPlayerGestureEnabled(@NonNull final Context context, final boolean b) {
return getPreferences(context).getBoolean(context.getString(R.string.player_gesture_controls_key), b);
private static boolean isVolumeGestureEnabled(@NonNull final Context context, final boolean b) {
return getPreferences(context).getBoolean(context.getString(R.string.volume_gesture_control_key), b);
}
private static boolean isUsingOldPlayer(@NonNull final Context context, final boolean b) {
return getPreferences(context).getBoolean(context.getString(R.string.use_old_player_key), b);
private static boolean isBrightnessGestureEnabled(@NonNull final Context context, final boolean b) {
return getPreferences(context).getBoolean(context.getString(R.string.brightness_gesture_control_key), b);
}
private static boolean isRememberingPopupDimensions(@NonNull final Context context, final boolean b) {
return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
}
private static boolean isUsingInexactSeek(@NonNull final Context context, final boolean b) {
return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), b);
private static boolean isUsingInexactSeek(@NonNull final Context context) {
return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), false);
}
private static boolean isAutoQueueEnabled(@NonNull final Context context, final boolean b) {
@@ -350,4 +350,10 @@ public class PlayerHelper {
return getPreferences(context).getString(context.getString(R.string.minimize_on_exit_key),
key);
}
private static SinglePlayQueue getAutoQueuedSinglePlayQueue(StreamInfoItem streamInfoItem) {
SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);
singlePlayQueue.getItem().setAutoQueued(true);
return singlePlayQueue;
}
}

View File

@@ -79,7 +79,7 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator
private void publishFloatingQueueWindow() {
if (callback.getQueueSize() == 0) {
mediaSession.setQueue(Collections.<MediaSessionCompat.QueueItem>emptyList());
mediaSession.setQueue(Collections.emptyList());
activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID;
return;
}

View File

@@ -1,369 +0,0 @@
package org.schabi.newpipe.player.old;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.MediaController;
import android.widget.ProgressBar;
import android.widget.VideoView;
import org.schabi.newpipe.R;
/*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* PlayVideoActivity.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class PlayVideoActivity extends AppCompatActivity {
//// TODO: 11.09.15 add "choose stream" menu
private static final String TAG = PlayVideoActivity.class.toString();
public static final String VIDEO_URL = "video_url";
public static final String STREAM_URL = "stream_url";
public static final String VIDEO_TITLE = "video_title";
private static final String POSITION = "position";
public static final String START_POSITION = "start_position";
private static final long HIDING_DELAY = 3000;
private String videoUrl = "";
private ActionBar actionBar;
private VideoView videoView;
private int position;
private MediaController mediaController;
private ProgressBar progressBar;
private View decorView;
private boolean uiIsHidden;
private static long lastUiShowTime;
private boolean isLandscape = true;
private boolean hasSoftKeys;
private SharedPreferences prefs;
private static final String PREF_IS_LANDSCAPE = "is_landscape";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play_video);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
//set background arrow style
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_back_white_24dp);
isLandscape = checkIfLandscape();
hasSoftKeys = checkIfHasSoftKeys();
actionBar = getSupportActionBar();
assert actionBar != null;
actionBar.setDisplayHomeAsUpEnabled(true);
Intent intent = getIntent();
if(mediaController == null) {
//prevents back button hiding media controller controls (after showing them)
//instead of exiting video
//see http://stackoverflow.com/questions/6051825
//also solves https://github.com/theScrabi/NewPipe/issues/99
mediaController = new MediaController(this) {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
final boolean uniqueDown = event.getRepeatCount() == 0
&& event.getAction() == KeyEvent.ACTION_DOWN;
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (uniqueDown)
{
if (isShowing()) {
finish();
} else {
hide();
}
}
return true;
}
return super.dispatchKeyEvent(event);
}
};
}
position = intent.getIntExtra(START_POSITION, 0)*1000;//convert from seconds to milliseconds
videoView = findViewById(R.id.video_view);
progressBar = findViewById(R.id.play_video_progress_bar);
try {
videoView.setMediaController(mediaController);
videoView.setVideoURI(Uri.parse(intent.getStringExtra(STREAM_URL)));
} catch (Exception e) {
e.printStackTrace();
}
videoView.requestFocus();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
progressBar.setVisibility(View.GONE);
videoView.seekTo(position);
if (position <= 0) {
videoView.start();
showUi();
} else {
videoView.pause();
}
}
});
videoUrl = intent.getStringExtra(VIDEO_URL);
Button button = findViewById(R.id.content_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(uiIsHidden) {
showUi();
} else {
hideUi();
}
}
});
decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
if (visibility == View.VISIBLE && uiIsHidden) {
showUi();
}
}
});
if (android.os.Build.VERSION.SDK_INT >= 17) {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
prefs = getPreferences(Context.MODE_PRIVATE);
if(prefs.getBoolean(PREF_IS_LANDSCAPE, false) && !isLandscape) {
toggleOrientation();
}
}
@Override
public boolean onCreatePanelMenu(int featured, Menu menu) {
super.onCreatePanelMenu(featured, menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.video_player, menu);
return true;
}
@Override
public void onPause() {
super.onPause();
videoView.pause();
}
@Override
public void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
prefs = getPreferences(Context.MODE_PRIVATE);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id) {
case android.R.id.home:
finish();
break;
case R.id.menu_item_share:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, videoUrl);
intent.setType("text/plain");
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
break;
case R.id.menu_item_screen_rotation:
toggleOrientation();
break;
default:
Log.e(TAG, "Error: MenuItem not known");
return false;
}
return true;
}
@Override
public void onConfigurationChanged(Configuration config) {
super.onConfigurationChanged(config);
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
isLandscape = true;
adjustMediaControlMetrics();
} else if (config.orientation == Configuration.ORIENTATION_PORTRAIT){
isLandscape = false;
adjustMediaControlMetrics();
}
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
//savedInstanceState.putInt(POSITION, videoView.getCurrentPosition());
//videoView.pause();
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
position = savedInstanceState.getInt(POSITION);
//videoView.seekTo(position);
}
private void showUi() {
try {
uiIsHidden = false;
mediaController.show(100000);
actionBar.show();
adjustMediaControlMetrics();
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
if ((System.currentTimeMillis() - lastUiShowTime) >= HIDING_DELAY) {
hideUi();
}
}
}, HIDING_DELAY);
lastUiShowTime = System.currentTimeMillis();
}catch(Exception e) {
e.printStackTrace();
}
}
private void hideUi() {
uiIsHidden = true;
actionBar.hide();
mediaController.hide();
if (android.os.Build.VERSION.SDK_INT >= 17) {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
private void adjustMediaControlMetrics() {
MediaController.LayoutParams mediaControllerLayout
= new MediaController.LayoutParams(MediaController.LayoutParams.MATCH_PARENT,
MediaController.LayoutParams.WRAP_CONTENT);
if(!hasSoftKeys) {
mediaControllerLayout.setMargins(20, 0, 20, 20);
} else {
int width = getNavigationBarWidth();
int height = getNavigationBarHeight();
mediaControllerLayout.setMargins(width + 20, 0, width + 20, height + 20);
}
mediaController.setLayoutParams(mediaControllerLayout);
}
private boolean checkIfHasSoftKeys(){
return Build.VERSION.SDK_INT >= 17 ||
getNavigationBarHeight() != 0 ||
getNavigationBarWidth() != 0;
}
private int getNavigationBarHeight() {
if(Build.VERSION.SDK_INT >= 17) {
Display d = getWindowManager().getDefaultDisplay();
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
d.getRealMetrics(realDisplayMetrics);
DisplayMetrics displayMetrics = new DisplayMetrics();
d.getMetrics(displayMetrics);
int realHeight = realDisplayMetrics.heightPixels;
int displayHeight = displayMetrics.heightPixels;
return realHeight - displayHeight;
} else {
return 50;
}
}
private int getNavigationBarWidth() {
if(Build.VERSION.SDK_INT >= 17) {
Display d = getWindowManager().getDefaultDisplay();
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
d.getRealMetrics(realDisplayMetrics);
DisplayMetrics displayMetrics = new DisplayMetrics();
d.getMetrics(displayMetrics);
int realWidth = realDisplayMetrics.widthPixels;
int displayWidth = displayMetrics.widthPixels;
return realWidth - displayWidth;
} else {
return 50;
}
}
private boolean checkIfLandscape() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.heightPixels < displayMetrics.widthPixels;
}
private void toggleOrientation() {
if(isLandscape) {
isLandscape = false;
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else {
isLandscape = true;
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
}
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(PREF_IS_LANDSCAPE, isLandscape);
editor.apply();
}
}

View File

@@ -8,7 +8,7 @@ import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
public class BasePlayerMediaSession implements MediaSessionCallback {
private BasePlayer player;
private final BasePlayer player;
public BasePlayerMediaSession(final BasePlayer player) {
this.player = player;

View File

@@ -4,7 +4,6 @@ import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
@@ -12,7 +11,6 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
/**
* This class allows irregular text language labels for use when selecting text captions and
@@ -55,7 +53,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
/** @see DefaultTrackSelector#selectTextTrack(TrackGroupArray, int[][], Parameters) */
@Override
protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport,
Parameters params) throws ExoPlaybackException {
Parameters params) {
TrackGroup selectedGroup = null;
int selectedTrackIndex = 0;
int selectedTrackScore = 0;

View File

@@ -335,7 +335,7 @@ public class MediaSourceManager {
private void loadImmediate() {
if (DEBUG) Log.d(TAG, "MediaSource - loadImmediate() called");
final ItemsToLoad itemsToLoad = getItemsToLoad(playQueue, WINDOW_SIZE);
final ItemsToLoad itemsToLoad = getItemsToLoad(playQueue);
if (itemsToLoad == null) return;
// Evict the previous items being loaded to free up memory, before start loading new ones
@@ -472,8 +472,7 @@ public class MediaSourceManager {
// Manager Helpers
//////////////////////////////////////////////////////////////////////////*/
@Nullable
private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue,
final int windowSize) {
private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue) {
// The current item has higher priority
final int currentIndex = playQueue.getIndex();
final PlayQueueItem currentItem = playQueue.getItem(currentIndex);
@@ -482,8 +481,8 @@ public class MediaSourceManager {
// The rest are just for seamless playback
// Although timeline is not updated prior to the current index, these sources are still
// loaded into the cache for faster retrieval at a potentially later time.
final int leftBound = Math.max(0, currentIndex - windowSize);
final int rightLimit = currentIndex + windowSize + 1;
final int leftBound = Math.max(0, currentIndex - MediaSourceManager.WINDOW_SIZE);
final int rightLimit = currentIndex + MediaSourceManager.WINDOW_SIZE + 1;
final int rightBound = Math.min(playQueue.size(), rightLimit);
final Set<PlayQueueItem> neighbors = new ArraySet<>(
playQueue.getStreams().subList(leftBound,rightBound));

View File

@@ -8,8 +8,6 @@ import com.google.android.exoplayer2.source.MediaSource;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import java.util.List;
public interface PlaybackListener {
/**

View File

@@ -19,14 +19,14 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
boolean isInitial;
boolean isComplete;
int serviceId;
String baseUrl;
final int serviceId;
final String baseUrl;
String nextUrl;
transient Disposable fetchReactor;
AbstractInfoPlayQueue(final U item) {
this(item.getServiceId(), item.getUrl(), null, Collections.<StreamInfoItem>emptyList(), 0);
this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0);
}
AbstractInfoPlayQueue(final int serviceId,

View File

@@ -233,6 +233,9 @@ public abstract class PlayQueue implements Serializable {
backup.addAll(itemList);
Collections.shuffle(itemList);
}
if (!streams.isEmpty() && streams.get(streams.size() - 1).isAutoQueued() && !itemList.get(0).isAutoQueued()) {
streams.remove(streams.size() - 1);
}
streams.addAll(itemList);
broadcast(new AppendEvent(itemList.size()));
@@ -314,7 +317,9 @@ public abstract class PlayQueue implements Serializable {
queueIndex.incrementAndGet();
}
streams.add(target, streams.remove(source));
PlayQueueItem playQueueItem = streams.remove(source);
playQueueItem.setAutoQueued(false);
streams.add(target, playQueueItem);
broadcast(new MoveEvent(source, target));
}

View File

@@ -25,9 +25,10 @@ public class PlayQueueItem implements Serializable {
@NonNull final private String uploader;
@NonNull final private StreamType streamType;
private boolean isAutoQueued;
private long recoveryPosition;
private Throwable error;
PlayQueueItem(@NonNull final StreamInfo info) {
this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(),
info.getThumbnailUrl(), info.getUploaderName(), info.getStreamType());
@@ -105,6 +106,14 @@ public class PlayQueueItem implements Serializable {
.doOnError(throwable -> error = throwable);
}
public boolean isAutoQueued() {
return isAutoQueued;
}
public void setAutoQueued(boolean autoQueued) {
isAutoQueued = autoQueued;
}
////////////////////////////////////////////////////////////////////////////
// Item States, keep external access out
////////////////////////////////////////////////////////////////////////////

View File

@@ -5,10 +5,8 @@ import android.text.TextUtils;
import android.view.MotionEvent;
import android.view.View;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;

View File

@@ -6,8 +6,6 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
/**
* Created by Christian Schabesberger on 01.08.16.

View File

@@ -8,11 +8,13 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC
private static final int MAXIMUM_INITIAL_DRAG_VELOCITY = 25;
public PlayQueueItemTouchCallback() {
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.RIGHT);
}
public abstract void onMove(final int sourceIndex, final int targetIndex);
public abstract void onSwiped(int index);
@Override
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize,
int viewSizeOutOfBounds, int totalSize,
@@ -44,9 +46,11 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC
@Override
public boolean isItemViewSwipeEnabled() {
return false;
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {}
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
onSwiped(viewHolder.getAdapterPosition());
}
}

View File

@@ -10,7 +10,7 @@ import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.Subtitles;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
@@ -93,8 +93,8 @@ public class VideoPlaybackResolver implements PlaybackResolver {
// Below are auxiliary media sources
// Create subtitle sources
for (final Subtitles subtitle : info.getSubtitles()) {
final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType());
for (final SubtitlesStream subtitle : info.getSubtitles()) {
final String mimeType = PlayerHelper.subtitleMimeTypesOf(subtitle.getFormat());
if (mimeType == null) continue;
final Format textFormat = Format.createTextSampleFormat(null, mimeType,

View File

@@ -5,7 +5,6 @@ import android.support.annotation.NonNull;
import org.acra.collector.CrashReportData;
import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderException;
import org.schabi.newpipe.R;
/*
@@ -31,7 +30,7 @@ import org.schabi.newpipe.R;
public class AcraReportSender implements ReportSender {
@Override
public void send(@NonNull Context context, @NonNull CrashReportData report) throws ReportSenderException {
public void send(@NonNull Context context, @NonNull CrashReportData report) {
ErrorActivity.reportError(context, report,
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,"none",
"App crash, UI failure", R.string.app_ui_crash));

View File

@@ -3,7 +3,6 @@ package org.schabi.newpipe.report;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
@@ -46,7 +45,6 @@ import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicBoolean;
/*
* Created by Christian Schabesberger on 24.10.15.
@@ -81,12 +79,7 @@ public class ErrorActivity extends AppCompatActivity {
private ErrorInfo errorInfo;
private Class returnActivity;
private String currentTimeStamp;
// views
private TextView errorView;
private EditText userCommentBox;
private Button reportButton;
private TextView infoView;
private TextView errorMessageView;
public static void reportUiError(final AppCompatActivity activity, final Throwable el) {
reportError(activity, el, activity.getClass(), null,
@@ -194,11 +187,11 @@ public class ErrorActivity extends AppCompatActivity {
actionBar.setDisplayShowTitleEnabled(true);
}
reportButton = findViewById(R.id.errorReportButton);
Button reportButton = findViewById(R.id.errorReportButton);
userCommentBox = findViewById(R.id.errorCommentBox);
errorView = findViewById(R.id.errorView);
infoView = findViewById(R.id.errorInfosView);
errorMessageView = findViewById(R.id.errorMessageView);
TextView errorView = findViewById(R.id.errorView);
TextView infoView = findViewById(R.id.errorInfosView);
TextView errorMessageView = findViewById(R.id.errorMessageView);
ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
returnActivity = ac.returnActivity;
@@ -281,15 +274,14 @@ public class ErrorActivity extends AppCompatActivity {
}
private String formErrorText(String[] el) {
String text = "";
StringBuilder text = new StringBuilder();
if (el != null) {
for (String e : el) {
text += "-------------------------------------\n"
+ e;
text.append("-------------------------------------\n").append(e);
}
}
text += "-------------------------------------";
return text;
text.append("-------------------------------------");
return text.toString();
}
/**

View File

@@ -6,7 +6,6 @@ import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.v7.preference.Preference;
import android.util.Log;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.Constants;
@@ -49,7 +48,7 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
return super.onPreferenceTreeClick(preference);
}
private Preference.OnPreferenceChangeListener themePreferenceChange = new Preference.OnPreferenceChangeListener() {
private final Preference.OnPreferenceChangeListener themePreferenceChange = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply();

View File

@@ -13,7 +13,7 @@ import org.schabi.newpipe.MainActivity;
public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
protected boolean DEBUG = MainActivity.DEBUG;
protected final boolean DEBUG = MainActivity.DEBUG;
protected SharedPreferences defaultPreferences;

View File

@@ -9,8 +9,6 @@ import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.util.Log;
import android.widget.Toast;
@@ -20,11 +18,10 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.utils.Localization;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.ZipHelper;
import java.io.BufferedOutputStream;
@@ -47,7 +44,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
private static final int REQUEST_IMPORT_PATH = 8945;
private static final int REQUEST_EXPORT_PATH = 30945;
private String homeDir;
private File databasesDir;
private File newpipe_db;
private File newpipe_db_journal;
@@ -81,7 +77,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
homeDir = getActivity().getApplicationInfo().dataDir;
String homeDir = getActivity().getApplicationInfo().dataDir;
databasesDir = new File(homeDir + "/databases");
newpipe_db = new File(homeDir + "/databases/newpipe.db");
newpipe_db_journal = new File(homeDir + "/databases/newpipe.db-journal");
@@ -112,6 +108,20 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
startActivityForResult(i, REQUEST_EXPORT_PATH);
return true;
});
Preference setPreferredLanguage = findPreference(getString(R.string.content_language_key));
setPreferredLanguage.setOnPreferenceChangeListener((Preference p, Object newLanguage) -> {
Localization oldLocal = org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(getActivity());
NewPipe.setLocalization(new Localization(oldLocal.getCountry(), (String) newLanguage));
return true;
});
Preference setPreferredCountry = findPreference(getString(R.string.content_country_key));
setPreferredCountry.setOnPreferenceChangeListener((Preference p, Object newCountry) -> {
Localization oldLocal = org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(getActivity());
NewPipe.setLocalization(new Localization((String) newCountry, oldLocal.getLanguage()));
return true;
});
}
@Override
@@ -193,7 +203,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
} finally {
try {
zipFile.close();
} catch (Exception e){}
} catch (Exception ignored){}
}
try {
@@ -254,17 +264,17 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
String key = entry.getKey();
if (v instanceof Boolean)
prefEdit.putBoolean(key, ((Boolean) v).booleanValue());
prefEdit.putBoolean(key, (Boolean) v);
else if (v instanceof Float)
prefEdit.putFloat(key, ((Float) v).floatValue());
prefEdit.putFloat(key, (Float) v);
else if (v instanceof Integer)
prefEdit.putInt(key, ((Integer) v).intValue());
prefEdit.putInt(key, (Integer) v);
else if (v instanceof Long)
prefEdit.putLong(key, ((Long) v).longValue());
prefEdit.putLong(key, (Long) v);
else if (v instanceof String)
prefEdit.putString(key, ((String) v));
}
prefEdit.commit();
prefEdit.apply();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
@@ -286,13 +296,12 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
// Error
//////////////////////////////////////////////////////////////////////////*/
protected boolean onError(Throwable e) {
protected void onError(Throwable e) {
final Activity activity = getActivity();
ErrorActivity.reportError(activity, e,
activity.getClass(),
null,
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
"none", "", R.string.app_ui_crash));
return true;
}
}

View File

@@ -1,29 +1,20 @@
package org.schabi.newpipe.settings;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.support.v7.preference.Preference;
import android.util.Log;
import android.widget.Toast;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.InfoCache;
import java.util.ArrayList;
import java.util.Collection;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
public class HistorySettingsFragment extends BasePreferenceFragment {
private String cacheWipeKey;

View File

@@ -4,6 +4,7 @@ import android.os.Bundle;
import android.support.v7.preference.Preference;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.CheckForNewAppVersionTask;
import org.schabi.newpipe.R;
public class MainSettingsFragment extends BasePreferenceFragment {
@@ -13,6 +14,13 @@ public class MainSettingsFragment extends BasePreferenceFragment {
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.main_settings);
if (!CheckForNewAppVersionTask.isGithubApk()) {
final Preference update = findPreference(getString(R.string.update_pref_screen_key));
getPreferenceScreen().removePreference(update);
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply();
}
if (!DEBUG) {
final Preference debug = findPreference(getString(R.string.debug_pref_screen_key));
getPreferenceScreen().removePreference(debug);

View File

@@ -51,9 +51,7 @@ import io.reactivex.schedulers.Schedulers;
*/
public class SelectChannelFragment extends DialogFragment {
private SelectChannelAdapter channelAdapter;
private SubscriptionService subscriptionService;
private ImageLoader imageLoader = ImageLoader.getInstance();
private final ImageLoader imageLoader = ImageLoader.getInstance();
private ProgressBar progressBar;
private TextView emptyView;
@@ -89,9 +87,9 @@ public class SelectChannelFragment extends DialogFragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.select_channel_fragment, container, false);
recyclerView = (RecyclerView) v.findViewById(R.id.items_list);
recyclerView = v.findViewById(R.id.items_list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
channelAdapter = new SelectChannelAdapter();
SelectChannelAdapter channelAdapter = new SelectChannelAdapter();
recyclerView.setAdapter(channelAdapter);
progressBar = v.findViewById(R.id.progressBar);
@@ -101,7 +99,7 @@ public class SelectChannelFragment extends DialogFragment {
emptyView.setVisibility(View.GONE);
subscriptionService = SubscriptionService.getInstance(getContext());
SubscriptionService subscriptionService = SubscriptionService.getInstance(getContext());
subscriptionService.getSubscription().toObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -203,9 +201,9 @@ public class SelectChannelFragment extends DialogFragment {
thumbnailView = v.findViewById(R.id.itemThumbnailView);
titleView = v.findViewById(R.id.itemTitleView);
}
public View view;
public CircleImageView thumbnailView;
public TextView titleView;
public final View view;
public final CircleImageView thumbnailView;
public final TextView titleView;
}
}
@@ -213,14 +211,13 @@ public class SelectChannelFragment extends DialogFragment {
// Error
//////////////////////////////////////////////////////////////////////////*/
protected boolean onError(Throwable e) {
protected void onError(Throwable e) {
final Activity activity = getActivity();
ErrorActivity.reportError(activity, e,
activity.getClass(),
null,
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
"none", "", R.string.app_ui_crash));
return true;
}

View File

@@ -75,7 +75,7 @@ public class SelectKioskFragment extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.select_kiosk_fragment, container, false);
recyclerView = (RecyclerView) v.findViewById(R.id.items_list);
recyclerView = v.findViewById(R.id.items_list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
try {
selectKioskAdapter = new SelectKioskAdapter();
@@ -112,13 +112,13 @@ public class SelectKioskFragment extends DialogFragment {
public Entry (int i, int si, String ki, String kn){
icon = i; serviceId=si; kioskId=ki; kioskName = kn;
}
int icon;
int serviceId;
String kioskId;
String kioskName;
final int icon;
final int serviceId;
final String kioskId;
final String kioskName;
}
private List<Entry> kioskList = new Vector<>();
private final List<Entry> kioskList = new Vector<>();
public SelectKioskAdapter()
throws Exception {
@@ -157,9 +157,9 @@ public class SelectKioskFragment extends DialogFragment {
thumbnailView = v.findViewById(R.id.itemThumbnailView);
titleView = v.findViewById(R.id.itemTitleView);
}
public View view;
public ImageView thumbnailView;
public TextView titleView;
public final View view;
public final ImageView thumbnailView;
public final TextView titleView;
}
public void onBindViewHolder(SelectKioskItemHolder holder, final int position) {
@@ -179,13 +179,12 @@ public class SelectKioskFragment extends DialogFragment {
// Error
//////////////////////////////////////////////////////////////////////////*/
protected boolean onError(Throwable e) {
protected void onError(Throwable e) {
final Activity activity = getActivity();
ErrorActivity.reportError(activity, e,
activity.getClass(),
null,
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
"none", "", R.string.app_ui_crash));
return true;
}
}

View File

@@ -0,0 +1,33 @@
package org.schabi.newpipe.settings;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.preference.Preference;
import org.schabi.newpipe.CheckForNewAppVersionTask;
import org.schabi.newpipe.R;
public class UpdateSettingsFragment extends BasePreferenceFragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String updateToggleKey = getString(R.string.update_app_key);
findPreference(updateToggleKey).setOnPreferenceChangeListener(updatePreferenceChange);
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.update_settings);
}
private Preference.OnPreferenceChangeListener updatePreferenceChange
= (preference, newValue) -> {
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key),
(boolean) newValue).apply();
return true;
};
}

View File

@@ -0,0 +1,103 @@
package org.schabi.newpipe.streams;
import java.io.EOFException;
import java.io.IOException;
import org.schabi.newpipe.streams.io.SharpStream;
/**
* @author kapodamy
*/
public class DataReader {
public final static int SHORT_SIZE = 2;
public final static int LONG_SIZE = 8;
public final static int INTEGER_SIZE = 4;
public final static int FLOAT_SIZE = 4;
private long pos;
public final SharpStream stream;
private final boolean rewind;
public DataReader(SharpStream stream) {
this.rewind = stream.canRewind();
this.stream = stream;
this.pos = 0L;
}
public long position() {
return pos;
}
public final int readInt() throws IOException {
primitiveRead(INTEGER_SIZE);
return primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3];
}
public final int read() throws IOException {
int value = stream.read();
if (value == -1) {
throw new EOFException();
}
pos++;
return value;
}
public final long skipBytes(long amount) throws IOException {
amount = stream.skip(amount);
pos += amount;
return amount;
}
public final long readLong() throws IOException {
primitiveRead(LONG_SIZE);
long high = primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3];
long low = primitive[4] << 24 | primitive[5] << 16 | primitive[6] << 8 | primitive[7];
return high << 32 | low;
}
public final short readShort() throws IOException {
primitiveRead(SHORT_SIZE);
return (short) (primitive[0] << 8 | primitive[1]);
}
public final int read(byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length);
}
public final int read(byte[] buffer, int offset, int count) throws IOException {
int res = stream.read(buffer, offset, count);
pos += res;
return res;
}
public final boolean available() {
return stream.available() > 0;
}
public void rewind() throws IOException {
stream.rewind();
pos = 0;
}
public boolean canRewind() {
return rewind;
}
private short[] primitive = new short[LONG_SIZE];
private void primitiveRead(int amount) throws IOException {
byte[] buffer = new byte[amount];
int read = stream.read(buffer, 0, amount);
pos += read;
if (read != amount) {
throw new EOFException("Truncated data, missing " + String.valueOf(amount - read) + " bytes");
}
for (int i = 0; i < buffer.length; i++) {
primitive[i] = (short) (buffer[i] & 0xFF);// the "byte" datatype is signed and is very annoying
}
}
}

View File

@@ -0,0 +1,817 @@
package org.schabi.newpipe.streams;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import org.schabi.newpipe.streams.io.SharpStream;
/**
* @author kapodamy
*/
public class Mp4DashReader {
// <editor-fold defaultState="collapsed" desc="Constants">
private static final int ATOM_MOOF = 0x6D6F6F66;
private static final int ATOM_MFHD = 0x6D666864;
private static final int ATOM_TRAF = 0x74726166;
private static final int ATOM_TFHD = 0x74666864;
private static final int ATOM_TFDT = 0x74666474;
private static final int ATOM_TRUN = 0x7472756E;
private static final int ATOM_MDIA = 0x6D646961;
private static final int ATOM_FTYP = 0x66747970;
private static final int ATOM_SIDX = 0x73696478;
private static final int ATOM_MOOV = 0x6D6F6F76;
private static final int ATOM_MDAT = 0x6D646174;
private static final int ATOM_MVHD = 0x6D766864;
private static final int ATOM_TRAK = 0x7472616B;
private static final int ATOM_MVEX = 0x6D766578;
private static final int ATOM_TREX = 0x74726578;
private static final int ATOM_TKHD = 0x746B6864;
private static final int ATOM_MFRA = 0x6D667261;
private static final int ATOM_TFRA = 0x74667261;
private static final int ATOM_MDHD = 0x6D646864;
private static final int BRAND_DASH = 0x64617368;
// </editor-fold>
private final DataReader stream;
private Mp4Track[] tracks = null;
private Box box;
private Moof moof;
private boolean chunkZero = false;
private int selectedTrack = -1;
public enum TrackKind {
Audio, Video, Other
}
public Mp4DashReader(SharpStream source) {
this.stream = new DataReader(source);
}
public void parse() throws IOException, NoSuchElementException {
if (selectedTrack > -1) {
return;
}
box = readBox(ATOM_FTYP);
if (parse_ftyp() != BRAND_DASH) {
throw new NoSuchElementException("Main Brand is not dash");
}
Moov moov = null;
int i;
while (box.type != ATOM_MOOF) {
ensure(box);
box = readBox();
switch (box.type) {
case ATOM_MOOV:
moov = parse_moov(box);
break;
case ATOM_SIDX:
break;
case ATOM_MFRA:
break;
case ATOM_MDAT:
throw new IOException("Expected moof, found mdat");
}
}
if (moov == null) {
throw new IOException("The provided Mp4 doesn't have the 'moov' box");
}
tracks = new Mp4Track[moov.trak.length];
for (i = 0; i < tracks.length; i++) {
tracks[i] = new Mp4Track();
tracks[i].trak = moov.trak[i];
if (moov.mvex_trex != null) {
for (Trex mvex_trex : moov.mvex_trex) {
if (tracks[i].trak.tkhd.trackId == mvex_trex.trackId) {
tracks[i].trex = mvex_trex;
}
}
}
if (moov.trak[i].tkhd.bHeight == 0 && moov.trak[i].tkhd.bWidth == 0) {
tracks[i].kind = moov.trak[i].tkhd.bVolume == 0 ? TrackKind.Other : TrackKind.Audio;
} else {
tracks[i].kind = TrackKind.Video;
}
}
}
public Mp4Track selectTrack(int index) {
selectedTrack = index;
return tracks[index];
}
/**
* Count all fragments present. This operation requires a seekable stream
*
* @return list with a basic info
* @throws IOException if the source stream is not seekeable
*/
public int getFragmentsCount() throws IOException {
if (selectedTrack < 0) {
throw new IllegalStateException("track no selected");
}
if (!stream.canRewind()) {
throw new IOException("The provided stream doesn't allow seek");
}
Box tmp;
int count = 0;
long orig_offset = stream.position();
if (box.type == ATOM_MOOF) {
tmp = box;
} else {
ensure(box);
tmp = readBox();
}
do {
if (tmp.type == ATOM_MOOF) {
ensure(readBox(ATOM_MFHD));
Box traf;
while ((traf = untilBox(tmp, ATOM_TRAF)) != null) {
Box tfhd = readBox(ATOM_TFHD);
if (parse_tfhd(tracks[selectedTrack].trak.tkhd.trackId) != null) {
count++;
break;
}
ensure(tfhd);
ensure(traf);
}
}
ensure(tmp);
} while (stream.available() && (tmp = readBox()) != null);
stream.rewind();
stream.skipBytes((int) orig_offset);
return count;
}
public Mp4Track[] getAvailableTracks() {
return tracks;
}
public Mp4TrackChunk getNextChunk() throws IOException {
Mp4Track track = tracks[selectedTrack];
while (stream.available()) {
if (chunkZero) {
ensure(box);
if (!stream.available()) {
break;
}
box = readBox();
} else {
chunkZero = true;
}
switch (box.type) {
case ATOM_MOOF:
if (moof != null) {
throw new IOException("moof found without mdat");
}
moof = parse_moof(box, track.trak.tkhd.trackId);
if (moof.traf != null) {
if (hasFlag(moof.traf.trun.bFlags, 0x0001)) {
moof.traf.trun.dataOffset -= box.size + 8;
if (moof.traf.trun.dataOffset < 0) {
throw new IOException("trun box has wrong data offset, points outside of concurrent mdat box");
}
}
if (moof.traf.trun.chunkSize < 1) {
if (hasFlag(moof.traf.tfhd.bFlags, 0x10)) {
moof.traf.trun.chunkSize = moof.traf.tfhd.defaultSampleSize * moof.traf.trun.entryCount;
} else {
moof.traf.trun.chunkSize = box.size - 8;
}
}
if (!hasFlag(moof.traf.trun.bFlags, 0x900) && moof.traf.trun.chunkDuration == 0) {
if (hasFlag(moof.traf.tfhd.bFlags, 0x20)) {
moof.traf.trun.chunkDuration = moof.traf.tfhd.defaultSampleDuration * moof.traf.trun.entryCount;
}
}
}
break;
case ATOM_MDAT:
if (moof == null) {
throw new IOException("mdat found without moof");
}
if (moof.traf == null) {
moof = null;
continue;// find another chunk
}
Mp4TrackChunk chunk = new Mp4TrackChunk();
chunk.moof = moof;
chunk.data = new TrackDataChunk(stream, moof.traf.trun.chunkSize);
moof = null;
stream.skipBytes(chunk.moof.traf.trun.dataOffset);
return chunk;
default:
}
}
return null;
}
// <editor-fold defaultState="collapsed" desc="Utils">
private long readUint() throws IOException {
return stream.readInt() & 0xffffffffL;
}
public static boolean hasFlag(int flags, int mask) {
return (flags & mask) == mask;
}
private String boxName(Box ref) {
return boxName(ref.type);
}
private String boxName(int type) {
try {
return new String(ByteBuffer.allocate(4).putInt(type).array(), "UTF-8");
} catch (UnsupportedEncodingException e) {
return "0x" + Integer.toHexString(type);
}
}
private Box readBox() throws IOException {
Box b = new Box();
b.offset = stream.position();
b.size = stream.readInt();
b.type = stream.readInt();
return b;
}
private Box readBox(int expected) throws IOException {
Box b = readBox();
if (b.type != expected) {
throw new NoSuchElementException("expected " + boxName(expected) + " found " + boxName(b));
}
return b;
}
private void ensure(Box ref) throws IOException {
long skip = ref.offset + ref.size - stream.position();
if (skip == 0) {
return;
} else if (skip < 0) {
throw new EOFException(String.format(
"parser go beyond limits of the box. type=%s offset=%s size=%s position=%s",
boxName(ref), ref.offset, ref.size, stream.position()
));
}
stream.skipBytes((int) skip);
}
private Box untilBox(Box ref, int... expected) throws IOException {
Box b;
while (stream.position() < (ref.offset + ref.size)) {
b = readBox();
for (int type : expected) {
if (b.type == type) {
return b;
}
}
ensure(b);
}
return null;
}
// </editor-fold>
// <editor-fold defaultState="collapsed" desc="Box readers">
private Moof parse_moof(Box ref, int trackId) throws IOException {
Moof obj = new Moof();
Box b = readBox(ATOM_MFHD);
obj.mfhd_SequenceNumber = parse_mfhd();
ensure(b);
while ((b = untilBox(ref, ATOM_TRAF)) != null) {
obj.traf = parse_traf(b, trackId);
ensure(b);
if (obj.traf != null) {
return obj;
}
}
return obj;
}
private int parse_mfhd() throws IOException {
// version
// flags
stream.skipBytes(4);
return stream.readInt();
}
private Traf parse_traf(Box ref, int trackId) throws IOException {
Traf traf = new Traf();
Box b = readBox(ATOM_TFHD);
traf.tfhd = parse_tfhd(trackId);
ensure(b);
if (traf.tfhd == null) {
return null;
}
b = untilBox(ref, ATOM_TRUN, ATOM_TFDT);
if (b.type == ATOM_TFDT) {
traf.tfdt = parse_tfdt();
ensure(b);
b = readBox(ATOM_TRUN);
}
traf.trun = parse_trun();
ensure(b);
return traf;
}
private Tfhd parse_tfhd(int trackId) throws IOException {
Tfhd obj = new Tfhd();
obj.bFlags = stream.readInt();
obj.trackId = stream.readInt();
if (trackId != -1 && obj.trackId != trackId) {
return null;
}
if (hasFlag(obj.bFlags, 0x01)) {
stream.skipBytes(8);
}
if (hasFlag(obj.bFlags, 0x02)) {
stream.skipBytes(4);
}
if (hasFlag(obj.bFlags, 0x08)) {
obj.defaultSampleDuration = stream.readInt();
}
if (hasFlag(obj.bFlags, 0x10)) {
obj.defaultSampleSize = stream.readInt();
}
if (hasFlag(obj.bFlags, 0x20)) {
obj.defaultSampleFlags = stream.readInt();
}
return obj;
}
private long parse_tfdt() throws IOException {
int version = stream.read();
stream.skipBytes(3);// flags
return version == 0 ? readUint() : stream.readLong();
}
private Trun parse_trun() throws IOException {
Trun obj = new Trun();
obj.bFlags = stream.readInt();
obj.entryCount = stream.readInt();// unsigned int
obj.entries_rowSize = 0;
if (hasFlag(obj.bFlags, 0x0100)) {
obj.entries_rowSize += 4;
}
if (hasFlag(obj.bFlags, 0x0200)) {
obj.entries_rowSize += 4;
}
if (hasFlag(obj.bFlags, 0x0400)) {
obj.entries_rowSize += 4;
}
if (hasFlag(obj.bFlags, 0x0800)) {
obj.entries_rowSize += 4;
}
obj.bEntries = new byte[obj.entries_rowSize * obj.entryCount];
if (hasFlag(obj.bFlags, 0x0001)) {
obj.dataOffset = stream.readInt();
}
if (hasFlag(obj.bFlags, 0x0004)) {
obj.bFirstSampleFlags = stream.readInt();
}
stream.read(obj.bEntries);
for (int i = 0; i < obj.entryCount; i++) {
TrunEntry entry = obj.getEntry(i);
if (hasFlag(obj.bFlags, 0x0100)) {
obj.chunkDuration += entry.sampleDuration;
}
if (hasFlag(obj.bFlags, 0x0200)) {
obj.chunkSize += entry.sampleSize;
}
if (hasFlag(obj.bFlags, 0x0800)) {
if (!hasFlag(obj.bFlags, 0x0100)) {
obj.chunkDuration += entry.sampleCompositionTimeOffset;
}
}
}
return obj;
}
private int parse_ftyp() throws IOException {
int brand = stream.readInt();
stream.skipBytes(4);// minor version
return brand;
}
private Mvhd parse_mvhd() throws IOException {
int version = stream.read();
stream.skipBytes(3);// flags
// creation entries_time
// modification entries_time
stream.skipBytes(2 * (version == 0 ? 4 : 8));
Mvhd obj = new Mvhd();
obj.timeScale = readUint();
// chunkDuration
stream.skipBytes(version == 0 ? 4 : 8);
// rate
// volume
// reserved
// matrix array
// predefined
stream.skipBytes(76);
obj.nextTrackId = readUint();
return obj;
}
private Tkhd parse_tkhd() throws IOException {
int version = stream.read();
Tkhd obj = new Tkhd();
// flags
// creation entries_time
// modification entries_time
stream.skipBytes(3 + (2 * (version == 0 ? 4 : 8)));
obj.trackId = stream.readInt();
stream.skipBytes(4);// reserved
obj.duration = version == 0 ? readUint() : stream.readLong();
stream.skipBytes(2 * 4);// reserved
obj.bLayer = stream.readShort();
obj.bAlternateGroup = stream.readShort();
obj.bVolume = stream.readShort();
stream.skipBytes(2);// reserved
obj.matrix = new byte[9 * 4];
stream.read(obj.matrix);
obj.bWidth = stream.readInt();
obj.bHeight = stream.readInt();
return obj;
}
private Trak parse_trak(Box ref) throws IOException {
Trak trak = new Trak();
Box b = readBox(ATOM_TKHD);
trak.tkhd = parse_tkhd();
ensure(b);
b = untilBox(ref, ATOM_MDIA);
trak.mdia = new byte[b.size];
ByteBuffer buffer = ByteBuffer.wrap(trak.mdia);
buffer.putInt(b.size);
buffer.putInt(ATOM_MDIA);
stream.read(trak.mdia, 8, b.size - 8);
trak.mdia_mdhd_timeScale = parse_mdia(buffer);
return trak;
}
private int parse_mdia(ByteBuffer data) {
while (data.hasRemaining()) {
int end = data.position() + data.getInt();
if (data.getInt() == ATOM_MDHD) {
byte version = data.get();
data.position(data.position() + 3 + ((version == 0 ? 4 : 8) * 2));
return data.getInt();
}
data.position(end);
}
return 0;// this NEVER should happen
}
private Moov parse_moov(Box ref) throws IOException {
Box b = readBox(ATOM_MVHD);
Moov moov = new Moov();
moov.mvhd = parse_mvhd();
ensure(b);
ArrayList<Trak> tmp = new ArrayList<>((int) moov.mvhd.nextTrackId);
while ((b = untilBox(ref, ATOM_TRAK, ATOM_MVEX)) != null) {
switch (b.type) {
case ATOM_TRAK:
tmp.add(parse_trak(b));
break;
case ATOM_MVEX:
moov.mvex_trex = parse_mvex(b, (int) moov.mvhd.nextTrackId);
break;
}
ensure(b);
}
moov.trak = tmp.toArray(new Trak[tmp.size()]);
return moov;
}
private Trex[] parse_mvex(Box ref, int possibleTrackCount) throws IOException {
ArrayList<Trex> tmp = new ArrayList<>(possibleTrackCount);
Box b;
while ((b = untilBox(ref, ATOM_TREX)) != null) {
tmp.add(parse_trex());
ensure(b);
}
return tmp.toArray(new Trex[tmp.size()]);
}
private Trex parse_trex() throws IOException {
// version
// flags
stream.skipBytes(4);
Trex obj = new Trex();
obj.trackId = stream.readInt();
obj.defaultSampleDescriptionIndex = stream.readInt();
obj.defaultSampleDuration = stream.readInt();
obj.defaultSampleSize = stream.readInt();
obj.defaultSampleFlags = stream.readInt();
return obj;
}
private Tfra parse_tfra() throws IOException {
int version = stream.read();
stream.skipBytes(3);// flags
Tfra tfra = new Tfra();
tfra.trackId = stream.readInt();
stream.skipBytes(3);// reserved
int bFlags = stream.read();
int size_tts = ((bFlags >> 4) & 3) + ((bFlags >> 2) & 3) + (bFlags & 3);
tfra.entries_time = new int[stream.readInt()];
for (int i = 0; i < tfra.entries_time.length; i++) {
tfra.entries_time[i] = version == 0 ? stream.readInt() : (int) stream.readLong();
stream.skipBytes(size_tts + (version == 0 ? 4 : 8));
}
return tfra;
}
private Sidx parse_sidx() throws IOException {
int version = stream.read();
stream.skipBytes(3);// flags
Sidx obj = new Sidx();
obj.referenceId = stream.readInt();
obj.timescale = stream.readInt();
// earliest presentation entries_time
// first offset
// reserved
stream.skipBytes((2 * (version == 0 ? 4 : 8)) + 2);
obj.entries_subsegmentDuration = new int[stream.readShort()];
for (int i = 0; i < obj.entries_subsegmentDuration.length; i++) {
// reference type
// referenced size
stream.skipBytes(4);
obj.entries_subsegmentDuration[i] = stream.readInt();// unsigned int
// starts with SAP
// SAP type
// SAP delta entries_time
stream.skipBytes(4);
}
return obj;
}
private Tfra[] parse_mfra(Box ref, int trackCount) throws IOException {
ArrayList<Tfra> tmp = new ArrayList<>(trackCount);
long limit = ref.offset + ref.size;
while (stream.position() < limit) {
box = readBox();
if (box.type == ATOM_TFRA) {
tmp.add(parse_tfra());
}
ensure(box);
}
return tmp.toArray(new Tfra[tmp.size()]);
}
// </editor-fold>
// <editor-fold defaultState="collapsed" desc="Helper classes">
class Box {
int type;
long offset;
int size;
}
class Sidx {
int timescale;
int referenceId;
int[] entries_subsegmentDuration;
}
public class Moof {
int mfhd_SequenceNumber;
public Traf traf;
}
public class Traf {
public Tfhd tfhd;
long tfdt;
public Trun trun;
}
public class Tfhd {
int bFlags;
public int trackId;
int defaultSampleDuration;
int defaultSampleSize;
int defaultSampleFlags;
}
public class TrunEntry {
public int sampleDuration;
public int sampleSize;
public int sampleFlags;
public int sampleCompositionTimeOffset;
}
public class Trun {
public int chunkDuration;
public int chunkSize;
public int bFlags;
int bFirstSampleFlags;
int dataOffset;
public int entryCount;
byte[] bEntries;
int entries_rowSize;
public TrunEntry getEntry(int i) {
ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entries_rowSize, entries_rowSize);
TrunEntry entry = new TrunEntry();
if (hasFlag(bFlags, 0x0100)) {
entry.sampleDuration = buffer.getInt();
}
if (hasFlag(bFlags, 0x0200)) {
entry.sampleSize = buffer.getInt();
}
if (hasFlag(bFlags, 0x0400)) {
entry.sampleFlags = buffer.getInt();
}
if (hasFlag(bFlags, 0x0800)) {
entry.sampleCompositionTimeOffset = buffer.getInt();
}
return entry;
}
}
public class Tkhd {
int trackId;
long duration;
short bVolume;
int bWidth;
int bHeight;
byte[] matrix;
short bLayer;
short bAlternateGroup;
}
public class Trak {
public Tkhd tkhd;
public int mdia_mdhd_timeScale;
byte[] mdia;
}
class Mvhd {
long timeScale;
long nextTrackId;
}
class Moov {
Mvhd mvhd;
Trak[] trak;
Trex[] mvex_trex;
}
class Tfra {
int trackId;
int[] entries_time;
}
public class Trex {
private int trackId;
int defaultSampleDescriptionIndex;
int defaultSampleDuration;
int defaultSampleSize;
int defaultSampleFlags;
}
public class Mp4Track {
public TrackKind kind;
public Trak trak;
public Trex trex;
}
public class Mp4TrackChunk {
public InputStream data;
public Moof moof;
}
//</editor-fold>
}

View File

@@ -0,0 +1,623 @@
package org.schabi.newpipe.streams;
import org.schabi.newpipe.streams.io.SharpStream;
import org.schabi.newpipe.streams.Mp4DashReader.Mp4Track;
import org.schabi.newpipe.streams.Mp4DashReader.Mp4TrackChunk;
import org.schabi.newpipe.streams.Mp4DashReader.Trak;
import org.schabi.newpipe.streams.Mp4DashReader.Trex;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import static org.schabi.newpipe.streams.Mp4DashReader.hasFlag;
/**
*
* @author kapodamy
*/
public class Mp4DashWriter {
private final static byte DIMENSIONAL_FIVE = 5;
private final static byte DIMENSIONAL_TWO = 2;
private final static short DEFAULT_TIMESCALE = 1000;
private final static int BUFFER_SIZE = 8 * 1024;
private final static byte DEFAULT_TREX_SIZE = 32;
private final static byte[] TFRA_TTS_DEFAULT = new byte[]{0x01, 0x01, 0x01};
private final static int EPOCH_OFFSET = 2082844800;
private Mp4Track[] infoTracks;
private SharpStream[] sourceTracks;
private Mp4DashReader[] readers;
private final long time;
private boolean done = false;
private boolean parsed = false;
private long written = 0;
private ArrayList<ArrayList<Integer>> chunkTimes;
private ArrayList<Long> moofOffsets;
private ArrayList<Integer> fragSizes;
public Mp4DashWriter(SharpStream... source) {
sourceTracks = source;
readers = new Mp4DashReader[sourceTracks.length];
infoTracks = new Mp4Track[sourceTracks.length];
time = (System.currentTimeMillis() / 1000L) + EPOCH_OFFSET;
}
public Mp4Track[] getTracksFromSource(int sourceIndex) throws IllegalStateException {
if (!parsed) {
throw new IllegalStateException("All sources must be parsed first");
}
return readers[sourceIndex].getAvailableTracks();
}
public void parseSources() throws IOException, IllegalStateException {
if (done) {
throw new IllegalStateException("already done");
}
if (parsed) {
throw new IllegalStateException("already parsed");
}
try {
for (int i = 0; i < readers.length; i++) {
readers[i] = new Mp4DashReader(sourceTracks[i]);
readers[i].parse();
}
} finally {
parsed = true;
}
}
public void selectTracks(int... trackIndex) throws IOException {
if (done) {
throw new IOException("already done");
}
if (chunkTimes != null) {
throw new IOException("tracks already selected");
}
try {
chunkTimes = new ArrayList<>(readers.length);
moofOffsets = new ArrayList<>(32);
fragSizes = new ArrayList<>(32);
for (int i = 0; i < readers.length; i++) {
infoTracks[i] = readers[i].selectTrack(trackIndex[i]);
chunkTimes.add(new ArrayList<Integer>(32));
}
} finally {
parsed = true;
}
}
public long getBytesWritten() {
return written;
}
public void build(SharpStream out) throws IOException, RuntimeException {
if (done) {
throw new RuntimeException("already done");
}
if (!out.canWrite()) {
throw new IOException("the provided output is not writable");
}
long sidxOffsets = -1;
int maxFrags = 0;
for (SharpStream stream : sourceTracks) {
if (!stream.canRewind()) {
sidxOffsets = -2;// sidx not available
}
}
try {
dump(make_ftyp(), out);
dump(make_moov(), out);
if (sidxOffsets == -1 && out.canRewind()) {
//<editor-fold defaultstate="collapsed" desc="calculate sidx">
int reserved = 0;
for (Mp4DashReader reader : readers) {
int count = reader.getFragmentsCount();
if (count > maxFrags) {
maxFrags = count;
}
reserved += 12 + calcSidxBodySize(count);
}
if (maxFrags > 0xFFFF) {
sidxOffsets = -3;// TODO: to many fragments, needs a multi-sidx implementation
} else {
sidxOffsets = written;
dump(make_free(reserved), out);
}
//</editor-fold>
}
ArrayList<Mp4TrackChunk> chunks = new ArrayList<>(readers.length);
chunks.add(null);
int read;
byte[] buffer = new byte[BUFFER_SIZE];
int sequenceNumber = 1;
while (true) {
chunks.clear();
for (int i = 0; i < readers.length; i++) {
Mp4TrackChunk chunk = readers[i].getNextChunk();
if (chunk == null || chunk.moof.traf.trun.chunkSize < 1) {
continue;
}
chunk.moof.traf.tfhd.trackId = i + 1;
chunks.add(chunk);
if (sequenceNumber == 1) {
if (chunk.moof.traf.trun.entryCount > 0 && hasFlag(chunk.moof.traf.trun.bFlags, 0x0800)) {
chunkTimes.get(i).add(chunk.moof.traf.trun.getEntry(0).sampleCompositionTimeOffset);
} else {
chunkTimes.get(i).add(0);
}
}
chunkTimes.get(i).add(chunk.moof.traf.trun.chunkDuration);
}
if (chunks.size() < 1) {
break;
}
long offset = written;
moofOffsets.add(offset);
dump(make_moof(sequenceNumber++, chunks, offset), out);
dump(make_mdat(chunks), out);
for (Mp4TrackChunk chunk : chunks) {
while ((read = chunk.data.read(buffer)) > 0) {
out.write(buffer, 0, read);
written += read;
}
}
fragSizes.add((int) (written - offset));
}
dump(make_mfra(), out);
if (sidxOffsets > 0 && moofOffsets.size() == maxFrags) {
long len = written;
out.rewind();
out.skip(sidxOffsets);
written = sidxOffsets;
sidxOffsets = moofOffsets.get(0);
for (int i = 0; i < readers.length; i++) {
dump(make_sidx(i, sidxOffsets - written), out);
}
written = len;
}
} finally {
done = true;
}
}
public boolean isDone() {
return done;
}
public boolean isParsed() {
return parsed;
}
public void close() {
done = true;
parsed = true;
for (SharpStream src : sourceTracks) {
src.dispose();
}
sourceTracks = null;
readers = null;
infoTracks = null;
moofOffsets = null;
chunkTimes = null;
}
// <editor-fold defaultstate="collapsed" desc="Utils">
private void dump(byte[][] buffer, SharpStream stream) throws IOException {
for (byte[] buff : buffer) {
stream.write(buff);
written += buff.length;
}
}
private byte[][] lengthFor(byte[][] buffer) {
int length = 0;
for (byte[] buff : buffer) {
length += buff.length;
}
ByteBuffer.wrap(buffer[0]).putInt(length);
return buffer;
}
private int calcSidxBodySize(int entryCount) {
return 4 + 4 + 8 + 8 + 4 + (entryCount * 12);
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Box makers">
private byte[][] make_moof(int sequence, ArrayList<Mp4TrackChunk> chunks, long referenceOffset) {
int pos = 2;
TrunExtra[] extra = new TrunExtra[chunks.size()];
byte[][] buffer = new byte[pos + (extra.length * DIMENSIONAL_FIVE)][];
buffer[0] = new byte[]{
0x00, 0x00, 0x00, 0x00, 0x6D, 0x6F, 0x6F, 0x66,// info header
0x00, 0x00, 0x00, 0x10, 0x6D, 0x66, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00//mfhd
};
buffer[1] = new byte[4];
ByteBuffer.wrap(buffer[1]).putInt(sequence);
for (int i = 0; i < extra.length; i++) {
extra[i] = new TrunExtra();
for (byte[] buff : make_traf(chunks.get(i), extra[i], referenceOffset)) {
buffer[pos++] = buff;
}
}
lengthFor(buffer);
int offset = 8 + ByteBuffer.wrap(buffer[0]).getInt();
for (int i = 0; i < extra.length; i++) {
extra[i].byteBuffer.putInt(offset);
offset += chunks.get(i).moof.traf.trun.chunkSize;
}
return buffer;
}
private byte[][] make_traf(Mp4TrackChunk chunk, TrunExtra extra, long moofOffset) {
byte[][] buffer = new byte[DIMENSIONAL_FIVE][];
buffer[0] = new byte[]{
0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x66,
0x00, 0x00, 0x00, 0x00, 0x74, 0x66, 0x68, 0x64
};
int flags = (chunk.moof.traf.tfhd.bFlags & 0x38) | 0x01;
byte tfhdBodySize = 8 + 8;
if (hasFlag(flags, 0x08)) {
tfhdBodySize += 4;
}
if (hasFlag(flags, 0x10)) {
tfhdBodySize += 4;
}
if (hasFlag(flags, 0x20)) {
tfhdBodySize += 4;
}
buffer[1] = new byte[tfhdBodySize];
ByteBuffer set = ByteBuffer.wrap(buffer[1]);
set.position(4);
set.putInt(chunk.moof.traf.tfhd.trackId);
set.putLong(moofOffset);
if (hasFlag(flags, 0x08)) {
set.putInt(chunk.moof.traf.tfhd.defaultSampleDuration);
}
if (hasFlag(flags, 0x10)) {
set.putInt(chunk.moof.traf.tfhd.defaultSampleSize);
}
if (hasFlag(flags, 0x20)) {
set.putInt(chunk.moof.traf.tfhd.defaultSampleFlags);
}
set.putInt(0, flags);
ByteBuffer.wrap(buffer[0]).putInt(8, 8 + tfhdBodySize);
buffer[2] = new byte[]{
0x00, 0x00, 0x00, 0x14,
0x74, 0x66, 0x64, 0x74,
0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
ByteBuffer.wrap(buffer[2]).putLong(12, chunk.moof.traf.tfdt);
buffer[3] = new byte[]{
0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x75, 0x6E,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
buffer[4] = chunk.moof.traf.trun.bEntries;
lengthFor(buffer);
set = ByteBuffer.wrap(buffer[3]);
set.putInt(buffer[3].length + buffer[4].length);
set.position(8);
set.putInt((chunk.moof.traf.trun.bFlags | 0x01) & 0x0F01);
set.putInt(chunk.moof.traf.trun.entryCount);
extra.byteBuffer = set;
return buffer;
}
private byte[][] make_mdat(ArrayList<Mp4TrackChunk> chunks) {
byte[][] buffer = new byte[][]{
{
0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x61, 0x74
}
};
int length = 0;
for (Mp4TrackChunk chunk : chunks) {
length += chunk.moof.traf.trun.chunkSize;
}
ByteBuffer.wrap(buffer[0]).putInt(length + 8);
return buffer;
}
private byte[][] make_ftyp() {
return new byte[][]{
{
0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x64, 0x61, 0x73, 0x68, 0x00, 0x00, 0x00, 0x00,
0x6D, 0x70, 0x34, 0x31, 0x69, 0x73, 0x6F, 0x6D, 0x69, 0x73, 0x6F, 0x36, 0x69, 0x73, 0x6F, 0x32
}
};
}
private byte[][] make_mvhd() {
byte[][] buffer = new byte[DIMENSIONAL_FIVE][];
buffer[0] = new byte[]{
0x00, 0x00, 0x00, 0x78, 0x6D, 0x76, 0x68, 0x64, 0x01, 0x00, 0x00, 0x00
};
buffer[1] = new byte[28];
buffer[2] = new byte[]{
0x00, 0x01, 0x00, 0x00, 0x01, 0x00,// default volume and rate
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// reserved values
// default matrix
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00
};
buffer[3] = new byte[24];// predefined
buffer[4] = ByteBuffer.allocate(4).putInt(infoTracks.length + 1).array();
long longestTrack = 0;
for (Mp4Track track : infoTracks) {
long tmp = (long) ((track.trak.tkhd.duration / (double) track.trak.mdia_mdhd_timeScale) * DEFAULT_TIMESCALE);
if (tmp > longestTrack) {
longestTrack = tmp;
}
}
ByteBuffer.wrap(buffer[1])
.putLong(time)
.putLong(time)
.putInt(DEFAULT_TIMESCALE)
.putLong(longestTrack);
return buffer;
}
private byte[][] make_trak(int trackId, Trak trak) throws RuntimeException {
if (trak.tkhd.matrix.length != 36) {
throw new RuntimeException("bad track matrix length (expected 36)");
}
byte[][] buffer = new byte[DIMENSIONAL_FIVE][];
buffer[0] = new byte[]{
0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x6B,// trak header
0x00, 0x00, 0x00, 0x68, 0x74, 0x6B, 0x68, 0x64, 0x01, 0x00, 0x00, 0x03 // tkhd header
};
buffer[1] = new byte[48];
buffer[2] = trak.tkhd.matrix;
buffer[3] = new byte[8];
buffer[4] = trak.mdia;
ByteBuffer set = ByteBuffer.wrap(buffer[1]);
set.putLong(time);
set.putLong(time);
set.putInt(trackId);
set.position(24);
set.putLong(trak.tkhd.duration);
set.position(40);
set.putShort(trak.tkhd.bLayer);
set.putShort(trak.tkhd.bAlternateGroup);
set.putShort(trak.tkhd.bVolume);
ByteBuffer.wrap(buffer[3])
.putInt(trak.tkhd.bWidth)
.putInt(trak.tkhd.bHeight);
return lengthFor(buffer);
}
private byte[][] make_moov() throws RuntimeException {
int pos = 1;
byte[][] buffer = new byte[2 + (DIMENSIONAL_TWO * infoTracks.length) + (DIMENSIONAL_FIVE * infoTracks.length) + DIMENSIONAL_FIVE + 1][];
buffer[0] = new byte[]{
0x00, 0x00, 0x00, 0x00, 0x6D, 0x6F, 0x6F, 0x76
};
for (byte[] buff : make_mvhd()) {
buffer[pos++] = buff;
}
for (int i = 0; i < infoTracks.length; i++) {
for (byte[] buff : make_trak(i + 1, infoTracks[i].trak)) {
buffer[pos++] = buff;
}
}
buffer[pos] = new byte[]{
0x00, 0x00, 0x00, 0x00, 0x6D, 0x76, 0x65, 0x78
};
ByteBuffer.wrap(buffer[pos++]).putInt((infoTracks.length * DEFAULT_TREX_SIZE) + 8);
for (int i = 0; i < infoTracks.length; i++) {
for (byte[] buff : make_trex(i + 1, infoTracks[i].trex)) {
buffer[pos++] = buff;
}
}
// default udta
buffer[pos] = new byte[]{
0x00, 0x00, 0x00, 0x5C, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x54, 0x6D, 0x65, 0x74, 0x61,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x69, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00,
0x1F, (byte) 0xA9, 0x63, 0x6D, 0x74, 0x00, 0x00, 0x00, 0x17, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00,
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65// "NewPipe" binary string
};
return lengthFor(buffer);
}
private byte[][] make_trex(int trackId, Trex trex) {
byte[][] buffer = new byte[][]{
{
0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00
},
new byte[20]
};
ByteBuffer.wrap(buffer[1])
.putInt(trackId)
.putInt(trex.defaultSampleDescriptionIndex)
.putInt(trex.defaultSampleDuration)
.putInt(trex.defaultSampleSize)
.putInt(trex.defaultSampleFlags);
return buffer;
}
private byte[][] make_tfra(int trackId, List<Integer> times, List<Long> moofOffsets) {
int entryCount = times.size() - 1;
byte[][] buffer = new byte[DIMENSIONAL_TWO][];
buffer[0] = new byte[]{
0x00, 0x00, 0x00, 0x00, 0x74, 0x66, 0x72, 0x61, 0x01, 0x00, 0x00, 0x00
};
buffer[1] = new byte[12 + ((16 + TFRA_TTS_DEFAULT.length) * entryCount)];
ByteBuffer set = ByteBuffer.wrap(buffer[1]);
set.putInt(trackId);
set.position(8);
set.putInt(entryCount);
long decodeTime = 0;
for (int i = 0; i < entryCount; i++) {
decodeTime += times.get(i);
set.putLong(decodeTime);
set.putLong(moofOffsets.get(i));
set.put(TFRA_TTS_DEFAULT);// default values: traf number/trun number/sample number
}
return lengthFor(buffer);
}
private byte[][] make_mfra() {
byte[][] buffer = new byte[2 + (DIMENSIONAL_TWO * infoTracks.length)][];
buffer[0] = new byte[]{
0x00, 0x00, 0x00, 0x00, 0x6D, 0x66, 0x72, 0x61
};
int pos = 1;
for (int i = 0; i < infoTracks.length; i++) {
for (byte[] buff : make_tfra(i + 1, chunkTimes.get(i), moofOffsets)) {
buffer[pos++] = buff;
}
}
buffer[pos] = new byte[]{// mfro
0x00, 0x00, 0x00, 0x10, 0x6D, 0x66, 0x72, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
lengthFor(buffer);
ByteBuffer set = ByteBuffer.wrap(buffer[pos]);
set.position(12);
set.put(buffer[0], 0, 4);
return buffer;
}
private byte[][] make_sidx(int internalTrackId, long firstOffset) {
List<Integer> times = chunkTimes.get(internalTrackId);
int count = times.size() - 1;// the first item is ignored (composition time)
if (count > 65535) {
throw new OutOfMemoryError("to many fragments. sidx limit is 65535, found " + String.valueOf(count));
}
byte[][] buffer = new byte[][]{
new byte[]{
0x00, 0x00, 0x00, 0x00, 0x73, 0x69, 0x64, 0x78, 0x01, 0x00, 0x00, 0x00
},
new byte[calcSidxBodySize(count)]
};
lengthFor(buffer);
ByteBuffer set = ByteBuffer.wrap(buffer[1]);
set.putInt(internalTrackId + 1);
set.putInt(infoTracks[internalTrackId].trak.mdia_mdhd_timeScale);
set.putLong(0);
set.putLong(firstOffset - ByteBuffer.wrap(buffer[0]).getInt());
set.putInt(0xFFFF & count);// unsigned
int i = 0;
while (i < count) {
set.putInt(fragSizes.get(i) & 0x7fffffff);// default reference type is 0
set.putInt(times.get(i + 1));
set.putInt(0x90000000);// default SAP settings
i++;
}
return buffer;
}
private byte[][] make_free(int totalSize) {
return lengthFor(new byte[][]{
new byte[]{0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x65, 0x65},
new byte[totalSize - 8]// this is waste of RAM
});
}
//</editor-fold>
class TrunExtra {
ByteBuffer byteBuffer;
}
}

View File

@@ -0,0 +1,370 @@
package org.schabi.newpipe.streams;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.Locale;
import org.schabi.newpipe.streams.io.SharpStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
/**
* @author kapodamy
*/
public class SubtitleConverter {
private static final String NEW_LINE = "\r\n";
public void dumpTTML(SharpStream in, final SharpStream out, final boolean ignoreEmptyFrames, final boolean detectYoutubeDuplicateLines
) throws IOException, ParseException, SAXException, ParserConfigurationException, XPathExpressionException {
final FrameWriter callback = new FrameWriter() {
int frameIndex = 0;
final Charset charset = Charset.forName("utf-8");
@Override
public void yield(SubtitleFrame frame) throws IOException {
if (ignoreEmptyFrames && frame.isEmptyText()) {
return;
}
out.write(String.valueOf(frameIndex++).getBytes(charset));
out.write(NEW_LINE.getBytes(charset));
out.write(getTime(frame.start, true).getBytes(charset));
out.write(" --> ".getBytes(charset));
out.write(getTime(frame.end, true).getBytes(charset));
out.write(NEW_LINE.getBytes(charset));
out.write(frame.text.getBytes(charset));
out.write(NEW_LINE.getBytes(charset));
out.write(NEW_LINE.getBytes(charset));
}
};
read_xml_based(in, callback, detectYoutubeDuplicateLines,
"tt", "xmlns", "http://www.w3.org/ns/ttml",
new String[]{"timedtext", "head", "wp"},
new String[]{"body", "div", "p"},
"begin", "end", true
);
}
private void read_xml_based(SharpStream source, FrameWriter callback, boolean detectYoutubeDuplicateLines,
String root, String formatAttr, String formatVersion, String[] cuePath, String[] framePath,
String timeAttr, String durationAttr, boolean hasTimestamp
) throws IOException, ParseException, SAXException, ParserConfigurationException, XPathExpressionException {
/*
* XML based subtitles parser with BASIC support
* multiple CUE is not supported
* styling is not supported
* tag timestamps (in auto-generated subtitles) are not supported, maybe in the future
* also TimestampTagOption enum is not applicable
* Language parsing is not supported
*/
byte[] buffer = new byte[source.available()];
source.read(buffer);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document xml = builder.parse(new ByteArrayInputStream(buffer));
String attr;
// get the format version or namespace
Element node = xml.getDocumentElement();
if (node == null) {
throw new ParseException("Can't get the format version. ¿wrong namespace?", -1);
} else if (!node.getNodeName().equals(root)) {
throw new ParseException("Invalid root", -1);
}
if (formatAttr.equals("xmlns")) {
if (!node.getNamespaceURI().equals(formatVersion)) {
throw new UnsupportedOperationException("Expected xml namespace: " + formatVersion);
}
} else {
attr = node.getAttributeNS(formatVersion, formatAttr);
if (attr == null) {
throw new ParseException("Can't get the format attribute", -1);
}
if (!attr.equals(formatVersion)) {
throw new ParseException("Invalid format version : " + attr, -1);
}
}
NodeList node_list;
int line_break = 0;// Maximum characters per line if present (valid for TranScript v3)
if (!hasTimestamp) {
node_list = selectNodes(xml, cuePath, formatVersion);
if (node_list != null) {
// if the subtitle has multiple CUEs, use the highest value
for (int i = 0; i < node_list.getLength(); i++) {
try {
int tmp = Integer.parseInt(((Element) node_list.item(i)).getAttributeNS(formatVersion, "ah"));
if (tmp > line_break) {
line_break = tmp;
}
} catch (Exception err) {
}
}
}
}
// parse every frame
node_list = selectNodes(xml, framePath, formatVersion);
if (node_list == null) {
return;// no frames detected
}
int fs_ff = -1;// first timestamp of first frame
boolean limit_lines = false;
for (int i = 0; i < node_list.getLength(); i++) {
Element elem = (Element) node_list.item(i);
SubtitleFrame obj = new SubtitleFrame();
obj.text = elem.getTextContent();
attr = elem.getAttribute(timeAttr);// ¡this cant be null!
obj.start = hasTimestamp ? parseTimestamp(attr) : Integer.parseInt(attr);
attr = elem.getAttribute(durationAttr);
if (obj.text == null || attr == null) {
continue;// normally is a blank line (on auto-generated subtitles) ignore
}
if (hasTimestamp) {
obj.end = parseTimestamp(attr);
if (detectYoutubeDuplicateLines) {
if (limit_lines) {
int swap = obj.end;
obj.end = fs_ff;
fs_ff = swap;
} else {
if (fs_ff < 0) {
fs_ff = obj.end;
} else {
if (fs_ff < obj.start) {
limit_lines = true;// the subtitles has duplicated lines
} else {
detectYoutubeDuplicateLines = false;
}
}
}
}
} else {
obj.end = obj.start + Integer.parseInt(attr);
}
if (/*node.getAttribute("w").equals("1") &&*/line_break > 1 && obj.text.length() > line_break) {
// implement auto line breaking (once)
StringBuilder text = new StringBuilder(obj.text);
obj.text = null;
switch (text.charAt(line_break)) {
case ' ':
case '\t':
putBreakAt(line_break, text);
break;
default:// find the word start position
for (int j = line_break - 1; j > 0; j--) {
switch (text.charAt(j)) {
case ' ':
case '\t':
putBreakAt(j, text);
j = -1;
break;
case '\r':
case '\n':
j = -1;// long word, just ignore
break;
}
}
break;
}
obj.text = text.toString();// set the processed text
}
callback.yield(obj);
}
}
private static NodeList selectNodes(Document xml, String[] path, String namespaceUri) throws XPathExpressionException {
Element ref = xml.getDocumentElement();
for (int i = 0; i < path.length - 1; i++) {
NodeList nodes = ref.getChildNodes();
if (nodes.getLength() < 1) {
return null;
}
Element elem;
for (int j = 0; j < nodes.getLength(); j++) {
if (nodes.item(j).getNodeType() == Node.ELEMENT_NODE) {
elem = (Element) nodes.item(j);
if (elem.getNodeName().equals(path[i]) && elem.getNamespaceURI().equals(namespaceUri)) {
ref = elem;
break;
}
}
}
}
return ref.getElementsByTagNameNS(namespaceUri, path[path.length - 1]);
}
private static int parseTimestamp(String multiImpl) throws NumberFormatException, ParseException {
if (multiImpl.length() < 1) {
return 0;
} else if (multiImpl.length() == 1) {
return Integer.parseInt(multiImpl) * 1000;// ¡this must be a number in seconds!
}
// detect wallclock-time
if (multiImpl.startsWith("wallclock(")) {
throw new UnsupportedOperationException("Parsing wallclock timestamp is not implemented");
}
// detect offset-time
if (multiImpl.indexOf(':') < 0) {
int multiplier = 1000;
char metric = multiImpl.charAt(multiImpl.length() - 1);
switch (metric) {
case 'h':
multiplier *= 3600000;
break;
case 'm':
multiplier *= 60000;
break;
case 's':
if (multiImpl.charAt(multiImpl.length() - 2) == 'm') {
multiplier = 1;// ms
}
break;
default:
if (!Character.isDigit(metric)) {
throw new NumberFormatException("Invalid metric suffix found on : " + multiImpl);
}
metric = '\0';
break;
}
try {
String offset_time = multiImpl;
if (multiplier == 1) {
offset_time = offset_time.substring(0, offset_time.length() - 2);
} else if (metric != '\0') {
offset_time = offset_time.substring(0, offset_time.length() - 1);
}
double time_metric_based = Double.parseDouble(offset_time);
if (Math.abs(time_metric_based) <= Double.MAX_VALUE) {
return (int) (time_metric_based * multiplier);
}
} catch (Exception err) {
throw new UnsupportedOperationException("Invalid or not implemented timestamp on: " + multiImpl);
}
}
// detect clock-time
int time = 0;
String[] units = multiImpl.split(":");
if (units.length < 3) {
throw new ParseException("Invalid clock-time timestamp", -1);
}
time += Integer.parseInt(units[0]) * 3600000;// hours
time += Integer.parseInt(units[1]) * 60000;//minutes
time += Float.parseFloat(units[2]) * 1000f;// seconds and milliseconds (if present)
// frames and sub-frames are ignored (not implemented)
// time += units[3] * fps;
return time;
}
private static void putBreakAt(int idx, StringBuilder str) {
// this should be optimized at compile time
if (NEW_LINE.length() > 1) {
str.delete(idx, idx + 1);// remove after replace
str.insert(idx, NEW_LINE);
} else {
str.setCharAt(idx, NEW_LINE.charAt(0));
}
}
private static String getTime(int time, boolean comma) {
// cast every value to integer to avoid auto-round in ToString("00").
StringBuilder str = new StringBuilder(12);
str.append(numberToString(time / 1000 / 3600, 2));// hours
str.append(':');
str.append(numberToString(time / 1000 / 60 % 60, 2));// minutes
str.append(':');
str.append(numberToString(time / 1000 % 60, 2));// seconds
str.append(comma ? ',' : '.');
str.append(numberToString(time % 1000, 3));// miliseconds
return str.toString();
}
private static String numberToString(int nro, int pad) {
return String.format(Locale.ENGLISH, "%0".concat(String.valueOf(pad)).concat("d"), nro);
}
/******************
* helper classes *
******************/
private interface FrameWriter {
void yield(SubtitleFrame frame) throws IOException;
}
private static class SubtitleFrame {
//Java no support unsigned int
public int end;
public int start;
public String text = "";
private boolean isEmptyText() {
if (text == null) {
return true;
}
for (int i = 0; i < text.length(); i++) {
switch (text.charAt(i)) {
case ' ':
case '\t':
case '\r':
case '\n':
break;
default:
return false;
}
}
return true;
}
}
}

View File

@@ -0,0 +1,65 @@
package org.schabi.newpipe.streams;
import java.io.InputStream;
import java.io.IOException;
public class TrackDataChunk extends InputStream {
private final DataReader base;
private int size;
public TrackDataChunk(DataReader base, int size) {
this.base = base;
this.size = size;
}
@Override
public int read() throws IOException {
if (size < 1) {
return -1;
}
int res = base.read();
if (res >= 0) {
size--;
}
return res;
}
@Override
public int read(byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length);
}
@Override
public int read(byte[] buffer, int offset, int count) throws IOException {
count = Math.min(size, count);
int read = base.read(buffer, offset, count);
size -= count;
return read;
}
@Override
public long skip(long amount) throws IOException {
long res = base.skipBytes(Math.min(amount, size));
size -= res;
return res;
}
@Override
public int available() {
return size;
}
@Override
public void close() {
size = 0;
}
@Override
public boolean markSupported() {
return false;
}
}

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