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

Compare commits

..

1317 Commits

Author SHA1 Message Date
Tobias Groza
1670751c94 Merge pull request #2312 from TeamNewPipe/release_v0.16.2
Release v0.16.2
2019-05-31 23:30:39 +02:00
Tobias Groza
0c354c4fdb Remove old strings
Remove strings which have been deleted from the English strings file during development, but were translated via Weblate, which failed to pull and push our upstream repo.
2019-05-27 00:11:37 +02:00
Tobias Groza
bd7413119a Merge branch 'weblate' into release_v0.16.2
Update translations
2019-05-26 22:58:53 +02:00
Davide Palma
f26915aab6 Translated using Weblate (Italian)
Currently translated at 99.8% (442 of 443 strings)
2019-05-25 04:49:13 +02:00
Tobias Groza
f6068dc69d Translated using Weblate (German)
Currently translated at 100.0% (443 of 443 strings)
2019-05-25 04:49:11 +02:00
Marc Riera
bd2c65cd94 Translated using Weblate (Catalan)
Currently translated at 96.8% (429 of 443 strings)
2019-05-15 11:49:29 +02:00
Tobias Groza
4777910644 Update changelog for 0.16.2 2019-05-14 23:17:04 +02:00
Tobias Groza
66b26e52ce Update extractor version 2019-05-14 23:16:43 +02:00
Tolstovka
a758267d72 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (443 of 443 strings)
2019-05-13 10:53:20 +02:00
Tolstovka
72eae64698 Translated using Weblate (Russian)
Currently translated at 100.0% (443 of 443 strings)
2019-05-13 10:53:18 +02:00
84436
d63c18f7f0 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (443 of 443 strings)
2019-05-10 10:19:35 +02:00
Eduardo Caron
b05e3ca8d8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (443 of 443 strings)
2019-05-10 10:17:50 +02:00
gabriellluz
0a88100b67 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (443 of 443 strings)
2019-05-08 16:49:07 +02:00
abvgeej
55d6825f63 Translated using Weblate (Russian)
Currently translated at 99.5% (441 of 443 strings)
2019-05-06 14:48:53 +02:00
Jonathan Tavares
e4f9af7076 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.6% (437 of 443 strings)
2019-05-06 14:48:49 +02:00
Tobias Groza
ff52fe4884 Add changelog for NewPipe 0.16.2 2019-05-03 07:58:46 +02:00
Snow K1ng
0ab29b7c1f Translated using Weblate (Romanian)
Currently translated at 75.8% (336 of 443 strings)
2019-04-29 13:48:19 +02:00
yunna
d742ed7b65 Translated using Weblate (Japanese)
Currently translated at 100.0% (443 of 443 strings)
2019-04-29 13:46:50 +02:00
Jesper Hertel
a684e380b7 Translated using Weblate (Danish)
Currently translated at 98.9% (438 of 443 strings)
2019-04-29 13:45:08 +02:00
AB
c90feaf3db Translated using Weblate (Ukrainian)
Currently translated at 100.0% (443 of 443 strings)
2019-04-28 21:49:00 +02:00
Chandra Mohan Jha
460610f672 Translated using Weblate (Hindi)
Currently translated at 77.2% (342 of 443 strings)
2019-04-28 21:49:00 +02:00
Jesper Hertel
46511533aa Translated using Weblate (Danish)
Currently translated at 88.7% (393 of 443 strings)
2019-04-28 21:48:59 +02:00
Tobias Groza
40efed6580 Update version code and name to NewPipe 0.16.2 2019-04-28 03:34:47 +02:00
Tobias Groza
78b1c1eb4a Merge remote-tracking branch 'TeamNewPipe/master' into dev 2019-04-28 03:34:38 +02:00
Tobias Groza
5a59a3dd50 Merge pull request #2311 from Redirion/FixSubs
Readd CustomTrackSelector
2019-04-28 02:29:20 +02:00
Robin
0763280196 Readd CustomTrackSelector 2019-04-28 01:45:19 +02:00
Chandra Mohan Jha
e7d0685ebc Translated using Weblate (Hindi)
Currently translated at 74.5% (330 of 443 strings)
2019-04-27 09:58:33 +02:00
sXp
aadbfe1eed Translated using Weblate (Hindi)
Currently translated at 74.5% (330 of 443 strings)
2019-04-27 09:58:31 +02:00
Chandra Mohan Jha
4a54fbb872 Translated using Weblate (Hindi)
Currently translated at 74.5% (330 of 443 strings)
2019-04-27 09:58:09 +02:00
sXp
8b1836d699 Translated using Weblate (Hindi)
Currently translated at 74.5% (330 of 443 strings)
2019-04-27 09:58:08 +02:00
Chandra Mohan Jha
c47d4fd35a Translated using Weblate (Hindi)
Currently translated at 74.5% (330 of 443 strings)
2019-04-27 09:58:08 +02:00
pjammo
a58af1275c Translated using Weblate (Italian)
Currently translated at 99.5% (441 of 443 strings)
2019-04-26 19:48:45 +02:00
Marc Riera
1d63b39553 Translated using Weblate (Catalan)
Currently translated at 91.6% (406 of 443 strings)
2019-04-26 19:48:44 +02:00
Jesper Hertel
c300d52b29 Translated using Weblate (Danish)
Currently translated at 87.1% (386 of 443 strings)
2019-04-25 14:48:56 +02:00
Sören Strecke
5314e275bc Translated using Weblate (German)
Currently translated at 100.0% (443 of 443 strings)
2019-04-24 11:37:07 +02:00
yunna
08f8b9770a Translated using Weblate (Japanese)
Currently translated at 100.0% (443 of 443 strings)
2019-04-23 22:48:54 +02:00
YONGLE
a9c64b2fec Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (443 of 443 strings)
2019-04-23 22:48:50 +02:00
Kristjan Räts
1df8af35d4 Translated using Weblate (Estonian)
Currently translated at 100.0% (443 of 443 strings)
2019-04-16 21:05:04 +02:00
Tobias Groza
049cd2d236 Merge pull request #1969 from nv95/close_button
Show close button when playing completed
2019-04-14 11:47:33 +02:00
Tobias Groza
e99714eba6 Merge remote-tracking branch 'TeamNewPipe/dev' into close_button 2019-04-13 18:18:17 +02:00
Lambda Monad
4751075e87 Translated using Weblate (Indonesian)
Currently translated at 99.3% (440 of 443 strings)
2019-04-12 21:04:47 +02:00
Tobias Groza
416e0fb609 Merge pull request #2271 from Stypox/dev
Add "share at current time" button to the main video player
2019-04-11 19:18:52 +02:00
minsk21
f96a371464 Translated using Weblate (Belarusian)
Currently translated at 74.5% (330 of 443 strings)
2019-04-10 04:04:47 +02:00
___
e423192265 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (443 of 443 strings)
2019-04-09 03:16:34 +02:00
minsk21
e9444e058c Translated using Weblate (Belarusian)
Currently translated at 74.3% (329 of 443 strings)
2019-04-09 03:16:28 +02:00
Yaron Shahrabani
037632fbf0 Translated using Weblate (Hebrew)
Currently translated at 100.0% (443 of 443 strings)
2019-04-07 15:59:53 +02:00
Yoav
6cdea85a49 Translated using Weblate (Hebrew)
Currently translated at 100.0% (443 of 443 strings)
2019-04-07 15:59:49 +02:00
Yaron Shahrabani
f86d755890 Translated using Weblate (Hebrew)
Currently translated at 100.0% (443 of 443 strings)
2019-04-07 15:59:49 +02:00
Stypox
98cc97251a Merge branch 'dev' into dev 2019-04-07 08:18:39 +02:00
Tobias Groza
562f414e3e Merge pull request #2237 from epitron/dev
Fixed English translations for Play/Enqueue commands
2019-04-06 23:32:05 +02:00
Tobias Groza
1afc301432 Merge branch 'dev' into dev 2019-04-06 23:23:15 +02:00
Yaron Shahrabani
115b44585b Translated using Weblate (Hebrew)
Currently translated at 100.0% (443 of 443 strings)
2019-04-06 21:10:05 +02:00
artik banana
3ff47623d5 Translated using Weblate (Hebrew)
Currently translated at 100.0% (443 of 443 strings)
2019-04-06 21:10:03 +02:00
Yaron Shahrabani
943e03f9d8 Translated using Weblate (Hebrew)
Currently translated at 100.0% (443 of 443 strings)
2019-04-06 21:10:03 +02:00
AB
f7a534a0d0 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (443 of 443 strings)
2019-04-06 20:59:53 +02:00
Yaron Shahrabani
72e30d8e40 Translated using Weblate (Hebrew)
Currently translated at 100.0% (443 of 443 strings)
2019-04-06 20:59:50 +02:00
Yehuda Levy
eb265300fc Translated using Weblate (Hebrew)
Currently translated at 100.0% (443 of 443 strings)
2019-04-06 20:59:48 +02:00
artik banana
704b8f61dd Translated using Weblate (Hebrew)
Currently translated at 100.0% (443 of 443 strings)
2019-04-06 20:59:47 +02:00
Stypox
40957c445f Implemented share button in MainVideoPlayer
Android Studio also decided to change the indentation of some lines
2019-04-06 20:27:13 +02:00
Stypox
8eead9fda2 Add share button to main player layout
Placed under "more options"
2019-04-06 20:21:32 +02:00
Stypox
aadc8168be Remove share utilities from BaseStateFragment
Replaced by ShareUtils
2019-04-06 20:17:04 +02:00
Stypox
cb33f04bfc Add ShareUtils class to share videos or open urls in browser. 2019-04-06 20:11:23 +02:00
Florian
82cb71bf3f Translated using Weblate (French)
Currently translated at 99.8% (442 of 443 strings)
2019-04-06 09:53:58 +02:00
Stjepan
c37b88a239 Translated using Weblate (Croatian)
Currently translated at 99.3% (440 of 443 strings)
2019-04-06 09:53:57 +02:00
___
de59bf695d Translated using Weblate (Ukrainian)
Currently translated at 99.8% (442 of 443 strings)
2019-04-06 09:53:55 +02:00
Tobias Groza
592627b013 Merge pull request #2247 from yausername/preferredTabState
preferred tab based on selected state, fixes #2238
2019-04-04 16:16:51 +02:00
Tobias Groza
9ed1fb2588 Merge branch 'dev' into preferredTabState 2019-04-04 15:22:57 +02:00
Tobias Groza
8232a92653 Translated using Weblate (Romanian)
Currently translated at 75.6% (335 of 443 strings)
2019-04-03 17:04:43 +02:00
Tobias Groza
09eeaa92d1 Merge pull request #2207 from yausername/timestampClickFix
seek on clicking timestamp links in comments
2019-04-03 16:04:31 +02:00
Igor Nedoboy
1e4b1a2c70 Translated using Weblate (Russian)
Currently translated at 100.0% (443 of 443 strings)
2019-03-31 22:58:10 +02:00
Marco Ieni
0a1e7a7c86 Translated using Weblate (Italian)
Currently translated at 99.3% (440 of 443 strings)
2019-03-31 22:16:38 +02:00
nautilusx
e5f3b2bf6e Translated using Weblate (German)
Currently translated at 100.0% (443 of 443 strings)
2019-03-31 22:16:37 +02:00
Florian
29183c10ff Translated using Weblate (French)
Currently translated at 99.1% (439 of 443 strings)
2019-03-31 22:16:37 +02:00
Stjepan
46b8bdace7 Translated using Weblate (Croatian)
Currently translated at 99.3% (440 of 443 strings)
2019-03-31 22:16:36 +02:00
Igor Nedoboy
9b6924ec9f Translated using Weblate (Russian)
Currently translated at 100.0% (443 of 443 strings)
2019-03-31 22:16:19 +02:00
Stjepan
c69de107e5 Translated using Weblate (Croatian)
Currently translated at 68.8% (305 of 443 strings)
2019-03-29 00:36:30 +01:00
Igor Nedoboy
c4d451e420 Translated using Weblate (Russian)
Currently translated at 100.0% (443 of 443 strings)
2019-03-29 00:36:25 +01:00
sherlockbeard
be6bce5771 Translated using Weblate (Hindi)
Currently translated at 74.3% (329 of 443 strings)
2019-03-27 21:04:26 +01:00
Tobias Groza
bf845be727 Merge branch 'dev' into timestampClickFix 2019-03-26 23:20:55 +01:00
Ahanaf Taskin Ar-Rafee
e92a5414d1 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 30.5% (135 of 443 strings)
2019-03-25 06:38:50 +01:00
yausername
657125f43c save selected tab sate in stream detail fragment, fixes #2238 2019-03-24 06:31:28 +05:30
Christian Schabesberger
e98f68e93b Merge pull request #2242 from yausername/fixMissingAuthorName
fix empty author endpoint
2019-03-23 23:43:05 +01:00
Florian
bef84e9eec Translated using Weblate (French)
Currently translated at 97.3% (431 of 443 strings)
2019-03-23 20:38:52 +01:00
Le Poisson Libre
6a5f2402c7 Translated using Weblate (French)
Currently translated at 97.3% (431 of 443 strings)
2019-03-23 20:38:49 +01:00
yausername
d6cc6ba144 fix empty author endpoint 2019-03-23 00:22:59 +05:30
yausername
07f8dcb3ca use ellipsis character 2019-03-22 05:56:56 +05:30
yausername
a026143a84 linkify optimizations 2019-03-22 04:57:33 +05:30
Andrea Gelmini
73a5b6738d Translated using Weblate (Italian)
Currently translated at 99.1% (439 of 443 strings)
2019-03-21 18:03:59 +01:00
epitron
3e2b12ae4a Fixed English translations for Play/Enqueue commands 2019-03-21 11:24:52 -04:00
naofum
8073364b7a Translated using Weblate (Japanese)
Currently translated at 100.0% (443 of 443 strings)
2019-03-20 03:14:37 +01:00
Osoitz
b141d96e7c Translated using Weblate (Basque)
Currently translated at 100.0% (443 of 443 strings)
2019-03-20 03:14:36 +01:00
Serdar Sağlam
a86e8b98fe Translated using Weblate (Turkish)
Currently translated at 100.0% (443 of 443 strings)
2019-03-20 03:14:34 +01:00
WaldiS
a7d77716f3 Translated using Weblate (Polish)
Currently translated at 100.0% (443 of 443 strings)
2019-03-18 09:39:23 +01:00
Igor Nedoboy
b017e439b1 Translated using Weblate (Russian)
Currently translated at 100.0% (443 of 443 strings)
2019-03-17 21:06:17 +01:00
ssantos
a7156f665a Translated using Weblate (Portuguese)
Currently translated at 100.0% (443 of 443 strings)
2019-03-16 12:54:18 +01:00
WaldiS
44e34d084e Translated using Weblate (Polish)
Currently translated at 99.8% (442 of 443 strings)
2019-03-16 12:54:18 +01:00
C. Rüdinger
065820ffa4 Translated using Weblate (German)
Currently translated at 100.0% (443 of 443 strings)
2019-03-16 12:54:17 +01:00
Jeff Huang
26991928ae Translated using Weblate (Chinese (Traditional))
Currently translated at 99.8% (442 of 443 strings)
2019-03-16 12:54:16 +01:00
ssantos
f4fa68c390 Translated using Weblate (German)
Currently translated at 100.0% (443 of 443 strings)
2019-03-16 12:54:09 +01:00
Gontzal Manuel Pujana Onaindia
8b86f9ea6d Translated using Weblate (Basque)
Currently translated at 100.0% (443 of 443 strings)
2019-03-14 19:00:35 +01:00
Christian Schabesberger
48f966e7db add changelog for 730 2019-03-14 09:24:02 +01:00
Christian Schabesberger
d8746dc592 hotfix decrypt function and move on to version v0.16.1 2019-03-14 09:20:37 +01:00
Ritvik Saraf
6421d3017e seek on clicking timestamp links in comments 2019-03-13 07:01:24 +05:30
Tobias Groza
f9e771f8f7 Merge pull request #2198 from Redirion/directOnBackground
Directplay on Background
2019-03-12 13:42:43 +01:00
Tobias Groza
09456ce421 Merge branch 'dev' into directOnBackground 2019-03-12 13:36:14 +01:00
dextro67
716f7e722b Translated using Weblate (Hindi)
Currently translated at 73.6% (326 of 443 strings)
2019-03-12 04:03:54 +01:00
Der_Floh
ffa4b1483f Translated using Weblate (German)
Currently translated at 100.0% (443 of 443 strings)
2019-03-12 04:03:53 +01:00
Christian Schabesberger
6651aa924f Merge pull request #2184 from Redirion/exoplayerupdate
Update ExoPlayer to 2.9.6
2019-03-11 20:43:28 +01:00
Olexandr Nesterenko
70238fd773 Translated using Weblate (Ukrainian)
Currently translated at 98.4% (436 of 443 strings)
2019-03-10 20:09:04 +01:00
WaldiS
fc4e007cc4 Translated using Weblate (Polish)
Currently translated at 100.0% (443 of 443 strings)
2019-03-10 20:09:00 +01:00
Nathan Follens
b7667ce97a Translated using Weblate (Flemish)
Currently translated at 100.0% (443 of 443 strings)
2019-03-10 20:08:59 +01:00
ssantos
1315da0da7 Translated using Weblate (German)
Currently translated at 100.0% (443 of 443 strings)
2019-03-10 20:08:57 +01:00
84436
c4d8eae547 Translated using Weblate (Vietnamese)
Currently translated at 99.3% (440 of 443 strings)
2019-03-09 17:04:15 +01:00
Pj Pj
b01fc1be62 Translated using Weblate (Telugu)
Currently translated at 28.7% (127 of 443 strings)
2019-03-09 17:04:13 +01:00
marciozomb13
c46e3cf5cb Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.5% (441 of 443 strings)
2019-03-09 17:04:12 +01:00
WaldiS
1a6b915112 Translated using Weblate (Polish)
Currently translated at 100.0% (443 of 443 strings)
2019-03-09 17:04:11 +01:00
Terry Louwers
2f38943488 Translated using Weblate (Dutch)
Currently translated at 100.0% (443 of 443 strings)
2019-03-09 17:04:09 +01:00
Marco Szeto
06711dc6c3 Translated using Weblate (Chinese (Hong Kong))
Currently translated at 31.8% (141 of 443 strings)
2019-03-09 17:04:08 +01:00
Robin
cc7e342ab7 Merge remote-tracking branch 'upstream/dev' into directOnBackground 2019-03-08 23:02:47 +01:00
Robin
5b64743987 Directplay on Background 2019-03-08 22:52:17 +01:00
Robin
a84ad031d9 Merge remote-tracking branch 'upstream/dev' into exoplayerupdate 2019-03-07 16:06:02 +01:00
Tobias Groza
c4c2fe2a9c Merge pull request #1963 from Klearchos-K/dev
Issue #1951 - Search shared text
2019-03-07 15:50:51 +01:00
Tobias Groza
8ccaef454c Merge branch 'dev' into dev 2019-03-07 15:20:42 +01:00
Marian Hanzel
27579dff37 Translated using Weblate (Slovak)
Currently translated at 88.3% (391 of 443 strings)
2019-03-06 22:13:04 +01:00
Nikola Perović
389d08c233 Translated using Weblate (Serbian)
Currently translated at 47.4% (210 of 443 strings)
2019-03-06 22:12:59 +01:00
Michalis Nikolaidis
5412a087fe Translated using Weblate (Greek)
Currently translated at 100.0% (443 of 443 strings)
2019-03-06 22:12:59 +01:00
Konstantinos Giannopoulos
dd0f3ac651 Translated using Weblate (Greek)
Currently translated at 100.0% (443 of 443 strings)
2019-03-06 22:12:58 +01:00
YONGLE
4d4d776e4d Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (443 of 443 strings)
2019-03-06 22:12:51 +01:00
Robin
7877b107c1 Merge branch 'exoplayerupdate' of https://github.com/Redirion/NewPipe into exoplayerupdate 2019-03-06 09:38:17 +01:00
Robin
a2aa0aa9a8 Fix for wrong case after language normalization 2019-03-06 09:37:55 +01:00
Redirion
b3475d30c0 Merge branch 'dev' into exoplayerupdate 2019-03-05 21:44:27 +01:00
Tobias Groza
6484c8d636 Merge pull request #2183 from Redirion/Cleanup
delete unused files
2019-03-05 21:38:19 +01:00
Tobias Groza
587cf554f2 Merge branch 'dev' into Cleanup 2019-03-05 21:30:48 +01:00
Tobias Groza
1d4e4eb6b3 Merge pull request #2178 from Redirion/patch-1
Cache duration String to improve performance of background player
2019-03-05 21:06:22 +01:00
Tobias Groza
31c4ed7d0e Update app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
Co-Authored-By: Redirion <redirion@web.de>
2019-03-05 20:57:05 +01:00
Robin
7f246b2d3d Removed unused import 2019-03-05 19:54:37 +01:00
Robin
7d68cff700 NOTE for legacy version: Removed Lint markers and completely dropped Jelly Bean workarounds 2019-03-05 19:48:39 +01:00
Robin
4d80bdcc9f Update ExoPlayer to 2.9.6, including httook dependency and deprecations 2019-03-05 18:05:44 +01:00
Robin
8b4a94e5aa delete unused files 2019-03-05 18:01:11 +01:00
Redirion
111ad14ad3 Merge branch 'dev' into patch-1 2019-03-05 17:57:52 +01:00
Tobias Groza
aec3f19d23 Merge pull request #2179 from Redirion/patch-2
Improve performance of getTimeString
2019-03-04 19:16:43 +01:00
Redirion
d8b80f961a Improved performance of getTimeString
This pull requests complements pull request  #2178 by reducing general computational time for the method getTimeString.

On my local machine (Desktop PC with Java) my tests with a sample size of 10000 calls to the method with param 86400001 showed a performance improvement of about 50%.

See sample code below to reproduce:

    private static final StringBuilder stringBuilder = new StringBuilder();
    private static final Formatter stringFormatter = new Formatter(stringBuilder, Locale.getDefault());
    
    public static String getTimeString(int milliSeconds) {
        int seconds = (milliSeconds % 60000) / 1000;
        int minutes = (milliSeconds % 3600000) / 60000;
        int hours = (milliSeconds % 86400000) / 3600000;
        int days = (milliSeconds % (86400000 * 7)) / 86400000;

        stringBuilder.setLength(0);
        return days > 0 ? stringFormatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString()
                : hours > 0 ? stringFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
                : stringFormatter.format("%02d:%02d", minutes, seconds).toString();
    }
    
    public static String getTimeStringL(int milliSeconds) {
        long seconds = (milliSeconds % 60000L) / 1000L;
        long minutes = (milliSeconds % 3600000L) / 60000L;
        long hours = (milliSeconds % 86400000L) / 3600000L;
        long days = (milliSeconds % (86400000L * 7L)) / 86400000L;

        stringBuilder.setLength(0);
        return days > 0 ? stringFormatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString()
                : hours > 0 ? stringFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
                : stringFormatter.format("%02d:%02d", minutes, seconds).toString();
    }
    
	public static void main(String[] args) throws Exception {
		final int SAMPLE_SIZE = 25000;
		long[] results = new long[SAMPLE_SIZE];
		for(int i = 0; i < SAMPLE_SIZE; i++) {
			long now = System.nanoTime();
			getTimeString(86400001);
			results[i] = System.nanoTime() - now;
		}
		long sum = 0;
		for(int i = 0; i < SAMPLE_SIZE; i++) {
			sum += results[i];
		}
		System.out.println("Average execution time: " + (sum/SAMPLE_SIZE));
		results = new long[SAMPLE_SIZE];
		for(int i = 0; i < SAMPLE_SIZE; i++) {
			long now = System.nanoTime();
			getTimeStringL(86400001);
			results[i] = System.nanoTime() - now;
		}
		sum = 0;
		for(int i = 0; i < SAMPLE_SIZE; i++) {
			sum += results[i];
		}
		System.out.println("Average execution time: " + (sum/SAMPLE_SIZE));
2019-03-04 15:45:59 +01:00
rimasx
891bb7fa40 Translated using Weblate (Estonian)
Currently translated at 100.0% (443 of 443 strings)
2019-03-04 11:58:31 +01:00
Rex_sa
0e3af45466 Translated using Weblate (Arabic)
Currently translated at 100.0% (443 of 443 strings)
2019-03-04 11:58:24 +01:00
Redirion
6aebbc3109 Cache duration String to improve performance
In VideoPlayer the Duration String is cached effectively by setting it to the playbackSeekBar. As the playbackSeekBar doesn't exist in BackgroundPlayer, using two addition variables will reduce performance impact of notification updates by almost 50% and thus perform similar to VideoPlayer.

This addresses issue #2170
2019-03-04 10:24:08 +01:00
Christian Schabesberger
15eb7f3186 Merge pull request #2023 from dotvirus/enqueue-playlist
Add playlist to queue when long clicking on 'Background'
2019-03-03 20:59:15 +01:00
Christian Schabesberger
fb4cd98014 Merge branch 'dev' into enqueue-playlist 2019-03-03 20:53:17 +01:00
Christian Schabesberger
eb692dea59 Merge pull request #2168 from yausername/commentSizeAndLinks
make links in comments clickable, increase text size
2019-03-03 20:52:11 +01:00
Christian Schabesberger
d8039fb542 Merge branch 'dev' into enqueue-playlist 2019-03-03 20:50:00 +01:00
Christian Schabesberger
5e06d19d77 Merge branch 'dev' into commentSizeAndLinks 2019-03-03 20:46:03 +01:00
Christian Schabesberger
963f390336 Merge pull request #2176 from yausername/fixScrollOnBlank
fixed scroll w/ comments and related streams disabled
2019-03-03 20:41:07 +01:00
Ritvik Saraf
2309e15261 fixed scroll w/ comments and related streams disabled 2019-03-03 18:20:15 +05:30
Ritvik Saraf
8491035b2f updated extractor 2019-03-03 04:34:50 +05:30
Ritvik Saraf
4d4107aefc Merge remote-tracking branch 'upstream/dev' into commentSizeAndLinks 2019-03-03 04:32:19 +05:30
Ritvik Saraf
67d2b9131e handling timestamp links in comments 2019-03-02 05:12:06 +05:30
Christian Schabesberger
da8644168c Merge branch 'master' into dev 2019-03-01 09:53:43 +01:00
Ritvik Saraf
c0004e988a make links in comments clickable, increase text size 2019-03-01 13:28:32 +05:30
Javi
b9187445e0 Translated using Weblate (Spanish)
Currently translated at 100.0% (443 of 443 strings)
2019-02-27 22:28:31 +01:00
Christian Schabesberger
554ebf7ab9 Merge pull request #2161 from Redirion/patch-1
Fixed CheckForNewAppVersionTask being executed when it shouldn't
2019-02-27 22:14:53 +01:00
Redirion
3e54cd7284 Update CheckForNewAppVersionTask.java 2019-02-26 19:33:01 +01:00
Redirion
a7afc23a9a Fixed Asynctask being executed when it shouldn't
#1 check if cancel was called in onPrepare
#2 if we currently don't have a Connection, don't show crash report dialogue to user
2019-02-26 19:23:54 +01:00
abvgeej
fa3a047519 Translated using Weblate (Russian)
Currently translated at 100.0% (443 of 443 strings)
2019-02-25 23:18:20 +01:00
Christian Schabesberger
f24fab0fa2 fix brake when selecting a mediaccc channel form subscription page 2019-02-25 12:24:48 +01:00
Christian Schabesberger
261620fd13 Merge branch 'master' into release_v0.16.0 2019-02-24 23:06:06 +01:00
Christian Schabesberger
9af6effad9 move on to version 0.16.0 2019-02-24 22:52:07 +01:00
Christian Schabesberger
92602916dd merge weblate 2019-02-24 22:51:08 +01:00
Christian Schabesberger
f4d215664e Merge pull request #2117 from nv95/patch1_ui
Small ux improve
2019-02-24 22:34:12 +01:00
Christian Schabesberger
84894a557a Merge branch 'dev' into patch1_ui 2019-02-24 22:27:06 +01:00
Christian Schabesberger
ffad6e4c61 Merge pull request #2148 from yausername/commentsAndRelatedUx
added top padding for comments and related videos
2019-02-24 22:26:46 +01:00
Christian Schabesberger
93b266e6be Merge branch 'dev' into commentsAndRelatedUx 2019-02-24 22:19:47 +01:00
Christian Schabesberger
f92ea28581 Merge pull request #2151 from nv95/fix_leak
Fix AudioManager memory leak
2019-02-24 22:19:27 +01:00
Christian Schabesberger
85213e4b6c Merge branch 'dev' into patch1_ui 2019-02-24 22:18:06 +01:00
Christian Schabesberger
4e46119e18 Merge branch 'dev' into commentsAndRelatedUx 2019-02-24 22:17:24 +01:00
Christian Schabesberger
fdc6e9f1c3 Merge branch 'dev' into fix_leak 2019-02-24 22:12:46 +01:00
Christian Schabesberger
cb20f1e212 Merge pull request #2153 from TeamNewPipe/fix_url
fix invalid channel url for commends
2019-02-24 22:10:40 +01:00
Christian Schabesberger
e3fccd7125 fix invalid channel url for commends 2019-02-24 22:10:16 +01:00
Vasiliy
15142c1ec3 Fix AudioManager memory leak 2019-02-24 10:51:30 +02:00
Nathan Follens
6c7b90e1c3 Translated using Weblate (Flemish)
Currently translated at 100.0% (443 of 443 strings)
2019-02-24 06:18:27 +01:00
Nathan Follens
d8e57144f7 Translated using Weblate (Dutch)
Currently translated at 100.0% (443 of 443 strings)
2019-02-24 06:18:27 +01:00
Ritvik Saraf
49fe8a427a added top padding for comments and related videos 2019-02-24 02:21:11 +05:30
Vasiliy
4587428d13 Merge branch 'dev' into close_button 2019-02-23 13:19:09 +02:00
Vasiliy
5318e77035 Merge branch 'dev' into patch1_ui 2019-02-23 13:18:14 +02:00
Christian Schabesberger
e69c45a246 Merge pull request #1650 from yausername/dev
comments support
2019-02-21 15:50:21 +01:00
Ritvik Saraf
3f87a6d714 Merge remote-tracking branch 'origin/dev' into dev 2019-02-20 05:29:34 +05:30
Ritvik Saraf
841124b1f3 updated extractor, fixed settings padding 2019-02-20 05:24:33 +05:30
Hosted Weblate
257a878ef4 Merge branch 'origin/dev' into Weblate. 2019-02-19 19:18:29 +01:00
84436
07d3f82912 Translated using Weblate (Vietnamese)
Currently translated at 99.3% (440 of 443 strings)
2019-02-19 19:18:26 +01:00
Ali Demirtas
7d3eb4f5a6 Translated using Weblate (Turkish)
Currently translated at 100.0% (443 of 443 strings)
2019-02-19 19:18:12 +01:00
srkrishna
0efcc55373 Translated using Weblate (Tamil)
Currently translated at 34.5% (153 of 443 strings)
2019-02-19 19:18:12 +01:00
Christian Schabesberger
eafceb8a6c Merge branch 'dev' into dev 2019-02-19 17:35:49 +01:00
Christian Schabesberger
5ac8318e71 Merge pull request #2136 from TeamNewPipe/db_doc
add database doc in dia
2019-02-19 16:06:11 +01:00
Christian Schabesberger
7981f9dc91 add database doc in dia 2019-02-19 16:03:10 +01:00
Christian Schabesberger
a9bf04cbc6 Merge pull request #2135 from TeamNewPipe/firetv
add support for search in amazon firetv
2019-02-19 15:00:22 +01:00
Christian Schabesberger
4b5591d884 move firetv utils into utils package 2019-02-19 14:57:49 +01:00
Christian Schabesberger
c08197f025 Merge branch 'dev' into feature/amazonfiretv-search-support 2019-02-19 14:54:48 +01:00
Christian Schabesberger
5d22eabfa3 Merge pull request #2092 from connectety/updated-urls
support for youtube-nocookie.com
2019-02-19 14:30:56 +01:00
Christian Schabesberger
c3a38e384a Merge branch 'dev' into updated-urls 2019-02-19 14:30:34 +01:00
Christian Schabesberger
0d3dd94559 Merge pull request #2120 from Redirion/patch-1
Fix delayed ducking of Audio
2019-02-19 14:30:11 +01:00
Christian Schabesberger
9cdaa37519 Merge branch 'dev' into patch-1 2019-02-19 14:29:34 +01:00
Christian Schabesberger
202a319d69 Merge pull request #2129 from TeamNewPipe/remove-old-player
Remove old player from the manifest
2019-02-19 14:29:02 +01:00
Christian Schabesberger
198384b2ed Merge branch 'dev' into remove-old-player 2019-02-19 14:28:53 +01:00
Christian Schabesberger
9d8b070f1e Merge pull request #2133 from kapodamy/popup-player-fixup
Fix random Popup player crash
2019-02-19 14:28:04 +01:00
dimqua
28ba2d5008 Translated using Weblate (Russian)
Currently translated at 100.0% (443 of 443 strings)
2019-02-19 14:07:27 +01:00
Arthur
37ddd63d27 Translated using Weblate (Russian)
Currently translated at 100.0% (443 of 443 strings)
2019-02-19 14:07:26 +01:00
dimqua
d7f8b8c1e0 Translated using Weblate (Russian)
Currently translated at 97.5% (432 of 443 strings)
2019-02-18 13:28:20 +01:00
Arthur
9e70c5bbea Translated using Weblate (Russian)
Currently translated at 97.5% (432 of 443 strings)
2019-02-18 13:28:19 +01:00
kapodamy
4dd572063e fix crash while switching from popup to fullscreen player, or closing the popup player. 2019-02-17 16:59:35 -03:00
TobiGr
db9cf95648 Remove old player from the manifest
See https://github.com/TeamNewPipe/NewPipe/pull/1884 for more info
2019-02-17 09:52:05 +01:00
Ritvik Saraf
df6bae4712 merged upstream/dev 2019-02-16 02:06:18 +05:30
Ritvik Saraf
56cb8209b8 refactored comments capability 2019-02-16 01:23:26 +05:30
WaldiS
aba9c2e113 Translated using Weblate (Polish)
Currently translated at 100.0% (443 of 443 strings)
2019-02-15 17:24:20 +01:00
Ali Demirtas
3d25008739 Translated using Weblate (Turkish)
Currently translated at 97.5% (432 of 443 strings)
2019-02-14 10:11:03 +01:00
Redirion
9437f057d0 Fix delayed ducking of Audio
Scenario: listening to a video on NewPipe over Bluetooth and a Notification Sound causes audio focus event AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK.

Problem: With the current implementation animateAudio would cause the audio to reach the target volume AFTER the notification sound is played, which is irritating and annoying.

Solution: animateAudio should just be used on focusGain where it is sensible to increase audio gradually. On ducking event the reaction should be immediate.

This very simple fix does this. Please approve.
2019-02-14 09:52:46 +01:00
Emanuele Petriglia
917d6089a7 Translated using Weblate (Italian)
Currently translated at 97.5% (432 of 443 strings)
2019-02-13 19:10:46 +01:00
toanpv
f19e99e3ad Translated using Weblate (Vietnamese)
Currently translated at 74.9% (332 of 443 strings)
2019-02-13 12:11:48 +01:00
Allan Nordhøy
2d0fb05fa6 Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.8% (429 of 443 strings)
2019-02-13 12:11:46 +01:00
Chris
c617f2dfad Translated using Weblate (Malay)
Currently translated at 100.0% (443 of 443 strings)
2019-02-13 12:11:46 +01:00
naofum
747c5fc89a Translated using Weblate (Japanese)
Currently translated at 96.6% (428 of 443 strings)
2019-02-13 12:11:14 +01:00
Chris
4f662ef203 Translated using Weblate (Indonesian)
Currently translated at 100.0% (443 of 443 strings)
2019-02-13 12:11:13 +01:00
Le Poisson Libre
eb274ad88f Translated using Weblate (French)
Currently translated at 91.9% (407 of 443 strings)
2019-02-13 12:11:09 +01:00
Chris
bd7b41be9b Translated using Weblate (English)
Currently translated at 99.1% (439 of 443 strings)
2019-02-13 12:11:07 +01:00
Chris
076720bfff Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (443 of 443 strings)
2019-02-13 12:11:06 +01:00
Osoitz
2aadae407e Translated using Weblate (Basque)
Currently translated at 100.0% (443 of 443 strings)
2019-02-13 12:11:05 +01:00
Rex_sa
b16bb07774 Translated using Weblate (Arabic)
Currently translated at 100.0% (443 of 443 strings)
2019-02-13 12:11:00 +01:00
naofum
81dd083388 Translated using Weblate (Japanese)
Currently translated at 98.4% (436 of 443 strings)
2019-02-12 16:20:23 +01:00
Chris
92d4cef1e2 Translated using Weblate (Japanese)
Currently translated at 98.4% (436 of 443 strings)
2019-02-12 16:20:22 +01:00
Vasiliy
0cb5197ccf Merge remote-tracking branch 'upstream/dev' into patch1_ui 2019-02-12 10:21:03 +02:00
C. Rüdinger
d2cd79dcf5 Translated using Weblate (German)
Currently translated at 99.8% (442 of 443 strings)
2019-02-10 23:34:08 +01:00
Hosted Weblate
0ef8b391e4 Merge branch 'origin/dev' into Weblate. 2019-02-09 13:57:02 +01:00
Eduardo Caron
0fbd105977 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (443 of 443 strings)
2019-02-09 13:57:02 +01:00
ssantos
5c23af541e Translated using Weblate (Portuguese)
Currently translated at 100.0% (443 of 443 strings)
2019-02-09 13:56:57 +01:00
WaldiS
db021ff78b Translated using Weblate (Polish)
Currently translated at 100.0% (443 of 443 strings)
2019-02-09 13:56:57 +01:00
Allan Nordhøy
31294c6f54 Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.8% (429 of 443 strings)
2019-02-09 13:56:56 +01:00
JS Ahn
03d3495759 Translated using Weblate (Korean)
Currently translated at 78.6% (348 of 443 strings)
2019-02-09 13:56:56 +01:00
ssantos
4644e105a8 Translated using Weblate (German)
Currently translated at 100.0% (443 of 443 strings)
2019-02-09 13:56:53 +01:00
Tobias Groza
af5002508d Translated using Weblate (German)
Currently translated at 100.0% (443 of 443 strings)
2019-02-09 13:56:52 +01:00
Nathan Follens
9416d2b9c7 Translated using Weblate (Flemish)
Currently translated at 100.0% (443 of 443 strings)
2019-02-09 13:56:52 +01:00
Nathan Follens
f60f5d5a6c Translated using Weblate (Dutch)
Currently translated at 100.0% (443 of 443 strings)
2019-02-09 13:56:46 +01:00
ezjerry liao
26d00f87a8 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (443 of 443 strings)
2019-02-09 13:56:44 +01:00
Nathan Follens
6c33ea423c Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.6% (428 of 443 strings)
2019-02-09 13:56:40 +01:00
Connectety-W
ad354aca4e added support for youtube-nocookie.com 2019-02-09 12:30:14 +01:00
Christian Schabesberger
ce6a5ebf0a Merge pull request #2107 from chin123/fix_padding_settings
Fix padding in settings layout (Fixes #1866)
2019-02-09 09:08:20 +01:00
Yaron Shahrabani
d66ef38e8d Translated using Weblate (Hebrew)
Currently translated at 100.0% (443 of 443 strings)
2019-02-09 03:38:29 +01:00
Chinmaya Krishnan Mahesh
1b6c49f621 Fix padding in settings layout (Fixes #1866) 2019-02-08 17:33:35 -06:00
Allan Nordhøy
0262c83815 Translated using Weblate (English)
Currently translated at 98.9% (438 of 443 strings)
2019-02-08 21:38:01 +01:00
Allan Nordhøy
c212934130 Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.8% (429 of 443 strings)
2019-02-07 15:12:26 +01:00
Yaron Shahrabani
ac30e47e59 Translated using Weblate (Hebrew)
Currently translated at 98.2% (435 of 443 strings)
2019-02-06 20:36:39 +01:00
Yehuda Levy
d640057453 Translated using Weblate (Hebrew)
Currently translated at 90.1% (399 of 443 strings)
2019-02-06 09:17:45 +01:00
Yaron Shahrabani
0cea136d4d Translated using Weblate (Hebrew)
Currently translated at 90.1% (399 of 443 strings)
2019-02-06 09:17:44 +01:00
Hosted Weblate
9b3d93e734 Merge branch 'origin/dev' into Weblate. 2019-02-06 08:40:24 +01:00
Emin Tufan Çetin
ca3782ac62 Translated using Weblate (Turkish)
Currently translated at 100.0% (441 of 441 strings)
2019-02-06 08:40:24 +01:00
dimqua
dc9ffb7fd5 Translated using Weblate (Russian)
Currently translated at 96.8% (427 of 441 strings)
2019-02-06 08:40:23 +01:00
WaldiS
b21b3a55fe Translated using Weblate (Polish)
Currently translated at 100.0% (441 of 441 strings)
2019-02-06 08:40:21 +01:00
Mostafa Ahangarha
e62bd85a7a Translated using Weblate (Persian)
Currently translated at 57.8% (255 of 441 strings)
2019-02-06 08:40:19 +01:00
Michael Moroni
fed221b008 Translated using Weblate (Italian)
Currently translated at 100.0% (441 of 441 strings)
2019-02-06 08:40:18 +01:00
Terry Louwers
935298d159 Translated using Weblate (Dutch)
Currently translated at 100.0% (441 of 441 strings)
2019-02-06 08:40:17 +01:00
Nathan Follens
909919250c Translated using Weblate (Dutch)
Currently translated at 100.0% (441 of 441 strings)
2019-02-06 08:40:14 +01:00
Emil Tang Kristensen
d24f278f7c Translated using Weblate (Danish)
Currently translated at 85.7% (378 of 441 strings)
2019-02-06 08:40:14 +01:00
赖文胜
ca408a495c Translated using Weblate (Chinese (Simplified))
Currently translated at 97.1% (428 of 441 strings)
2019-02-06 08:39:42 +01:00
Sérgio Marques
ee99719137 Translated using Weblate (Portuguese)
Currently translated at 100.0% (441 of 441 strings)
2019-02-06 08:39:36 +01:00
Christian Schabesberger
5fb0729eae Merge pull request #2083 from TeamNewPipe/ccc
Add MediaCCC support
2019-02-05 18:28:22 +01:00
Christian Schabesberger
a1db3187cd Merge branch 'dev' into ccc 2019-02-05 18:28:17 +01:00
Christian Schabesberger
488a3cf1ff Merge pull request #2084 from TeamNewPipe/splash
add splash screen
2019-02-05 18:27:15 +01:00
Christian Schabesberger
1253a620a4 Merge pull request #2094 from def14nt/add-instable-warning
Added a warning about instability
2019-02-05 18:27:02 +01:00
Christian Schabesberger
64547fc4a7 remove splash for older android 2019-02-05 18:24:34 +01:00
Emil Tang Kristensen
d643d140cf Added translation using Weblate (Danish) 2019-02-04 20:57:57 +01:00
Ali Demirtas
222e2e3242 Translated using Weblate (Turkish)
Currently translated at 100.0% (441 of 441 strings)
2019-02-04 12:37:19 +01:00
Ali Demirtas
ea7b6daf26 Translated using Weblate (Turkish)
Currently translated at 100.0% (441 of 441 strings)
2019-02-04 02:10:01 +01:00
DefiantDog66
14cfab495d Added a warning about instability
This is a follow up to TeamNewPipe#2069. I've removed the "WARNING" from the second one as it looks a bit silly to have the same thing twice, when the user is already attracted to the first one, which means they will most probably read the second warning.

I can revert this if you don't like it.
2019-02-03 19:47:51 +00:00
Alec Holmes
ed4b4a1a3c Updated search fragment to be amazon fire tv friendly 2019-02-01 13:02:28 +00:00
Hosted Weblate
511c552188 Merge branch 'origin/dev' into Weblate. 2019-02-01 09:37:22 +01:00
ssantos
8c650219eb Translated using Weblate (Portuguese)
Currently translated at 100.0% (441 of 441 strings)
2019-02-01 09:37:22 +01:00
WaldiS
189b779abe Translated using Weblate (Polish)
Currently translated at 100.0% (441 of 441 strings)
2019-02-01 09:37:18 +01:00
Michael Moroni
c26074b184 Translated using Weblate (Italian)
Currently translated at 100.0% (441 of 441 strings)
2019-02-01 09:37:16 +01:00
Emanuele Petriglia
6c70779ff1 Translated using Weblate (Italian)
Currently translated at 100.0% (441 of 441 strings)
2019-02-01 09:37:15 +01:00
Yaron Shahrabani
73897481ac Translated using Weblate (Hebrew)
Currently translated at 83.7% (369 of 441 strings)
2019-02-01 09:37:11 +01:00
C. Rüdinger
a62c523ed5 Translated using Weblate (German)
Currently translated at 100.0% (441 of 441 strings)
2019-02-01 09:37:08 +01:00
Florian
0f46f57b52 Translated using Weblate (French)
Currently translated at 89.8% (396 of 441 strings)
2019-02-01 09:37:08 +01:00
Nathan Follens
bc4d65a20c Translated using Weblate (Dutch)
Currently translated at 86.4% (381 of 441 strings)
2019-02-01 09:37:07 +01:00
Vojtěch Šamla
f1e9a44ad9 Translated using Weblate (Czech)
Currently translated at 88.4% (390 of 441 strings)
2019-02-01 09:37:06 +01:00
Marc Riera
61972c4719 Translated using Weblate (Catalan)
Currently translated at 92.1% (406 of 441 strings)
2019-02-01 09:37:05 +01:00
Almin Agic
9183c9c220 Translated using Weblate (Bosnian (latin))
Currently translated at 4.3% (19 of 441 strings)
2019-02-01 09:37:03 +01:00
Rex_sa
1ce59f13ff Translated using Weblate (Arabic)
Currently translated at 87.8% (387 of 441 strings)
2019-02-01 09:37:00 +01:00
Christian Schabesberger
1bac5db6d5 add splash 2019-01-31 18:13:00 +01:00
Christian Schabesberger
58f2c4d8e6 Merge branch 'dev' into ccc 2019-01-31 16:48:22 +01:00
Robin
2c2c61b2fc Add Artist and Duration to MediaDescription 2019-01-31 16:47:33 +01:00
Christian Schabesberger
98b04a4a83 Merge branch 'dev' into ccc 2019-01-31 14:28:10 +01:00
Christian Schabesberger
3ac64c44f4 Merge pull request #1900 from anoymouserver/patch-1
support for invidio.us links
2019-01-31 14:10:10 +01:00
Christian Schabesberger
3375847302 Merge branch 'dev' into patch-1 2019-01-31 14:02:24 +01:00
Christian Schabesberger
14043c86f5 fix backstack issue with mediaccc 2019-01-31 13:24:02 +01:00
Jeff Huang
030117780a Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (441 of 441 strings)
2019-01-31 08:57:07 +01:00
Benedikt Freisen
971c9fe5a1 Translated using Weblate (German)
Currently translated at 100.0% (441 of 441 strings)
2019-01-30 08:49:53 +01:00
Ritvik Saraf
77c6d3d576 merged upstream/dev 2019-01-29 22:32:58 +05:30
Christian Schabesberger
6edbfe2a6f add content filter to mediaccc 2019-01-29 17:20:30 +01:00
Christian Schabesberger
d0a3125df4 fox ogg 2019-01-29 16:13:46 +01:00
Christian Schabesberger
d8c76d4c21 add conferences 2019-01-29 15:39:18 +01:00
Christian Schabesberger
a1cc0897df make frontend not crash on scrolling on ccc search 2019-01-29 15:39:18 +01:00
Christian Schabesberger
e88a90f242 add theming to mediaccc 2019-01-29 15:39:18 +01:00
Christian Schabesberger
1ae54f6f8c further compatiblity fix for meadic.ccc 2019-01-29 15:39:18 +01:00
Hosted Weblate
8a4cb484fa Merge branch 'origin/dev' into Weblate. 2019-01-29 14:17:45 +01:00
Licaon Kter
93defbf341 Translated using Weblate (Romanian)
Currently translated at 84.1% (322 of 383 strings)
2019-01-29 14:17:45 +01:00
Allan Nordhøy
ca1089da9a Translated using Weblate (Norwegian Bokmål)
Currently translated at 97.7% (374 of 383 strings)
2019-01-29 14:17:44 +01:00
Rex_sa
dd07c7db0e Translated using Weblate (Arabic)
Currently translated at 100.0% (383 of 383 strings)
2019-01-29 14:17:42 +01:00
Almin Agic
dfe932e37a Added translation using Weblate (Bosnian (latin)) 2019-01-29 14:17:38 +01:00
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
anoy
3ac4899b96 Merge branch 'dev' into patch-1 2019-01-28 21:19:21 +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
Ritvik Saraf
86d619be30 merged upstream/dev 2019-01-19 15:48:05 +05:30
Ritvik Saraf
fec7672598 updated extractor for url decryption fix 2019-01-19 15:26:31 +05:30
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
dotvirus
c0aca723da Update PlaylistFragment.java 2019-01-18 23:58:24 +01:00
dotvirus
28e5ee51ec Add playlist to queue when long click on 'Background' 2019-01-18 23:16:02 +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
Vasily
7ab10aeb80 Remove search history items using swipe 2018-12-29 20:55:24 +02:00
Vasily
9316962e47 Clear history option menu item 2018-12-29 20:55:10 +02: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
Vasily
505c528194 Show close button when playing completed 2018-12-27 16:51:48 +02:00
Ritvik Saraf
559c397b2f more NPE fix 2018-12-25 20:17:56 +05:30
Ritvik Saraf
646698f1ed fixed NPE in soundcloud 2018-12-25 19:59:03 +05:30
Ritvik Saraf
c9b938ae55 readded animations 2018-12-25 15:36:15 +05:30
Klearchos-K
e4409e8ea4 Update RouterActivity.java
Add dunction handleText() for #1951 issue
2018-12-24 20:11:21 +02:00
Ritvik Saraf
990c220fa0 updated extractor 2018-12-23 09:44:42 +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
Ritvik Saraf
f19cfb75e6 merged upstream/dev 2018-12-20 08:51:44 +05:30
Ritvik Saraf
ceaacc771d removed jerky animations 2018-12-19 10:58:59 +05:30
Ritvik Saraf
48067e3285 up next text alignment 2018-12-17 10:03:04 +05:30
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
Ritvik Saraf
222c8fdb62 tablet ui support for comments 2018-12-09 03:21:55 +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
Ritvik Saraf
1a62b9a161 removed dislike button, added comment published time 2018-12-08 20:32:28 +05:30
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
Ritvik Saraf
ccb9bceecc removed unused imports 2018-12-07 08:42:05 +05:30
Ritvik Saraf
c1a67ff1f8 minor scrolling fix and ellipsize fix 2018-12-07 06:45:33 +05:30
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
Ritvik Saraf
ff90f257cc removed useless log statement 2018-12-04 23:37:02 +05:30
Ritvik Saraf
9b84046865 merged upstream/dev 2018-12-04 23:19:57 +05:30
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
anoy
5ac17ee6b8 added support for invidio.us links 2018-11-23 19:18:34 +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
Ritvik Saraf
fa5896ee5b fixed screen rotation for viewpager 2018-10-19 18:44:03 +05:30
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
Ritvik Saraf
9fc38b5bb8 improved fling behavior, added tab indicator dots, added next video in related videos 2018-10-17 00:23:02 +05:30
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
Ritvik Saraf
cf3e53eb71 update notify on dataset change 2018-10-02 21:30:11 +05:30
Ritvik Saraf
b8865e925d added content setting to disable comments 2018-10-02 20:56:14 +05:30
Ritvik Saraf
2e9a860aaa added viewpager. changed from parallaxscrollview to coordinate layout 2018-10-02 20:39:16 +05:30
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
Ritvik Saraf
e4bef056e6 merged upstream/dev 2018-09-29 15:46:47 +05:30
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
Ritvik Saraf
803b8eab28 updated extractor version 2018-09-28 05:04:36 +05:30
Ritvik Saraf
08bbfc50ee updated extractor 2018-09-27 23:27:52 +05:30
O Passante
97381a4908 Translated using Weblate (Galician)
Currently translated at 100.0% (383 of 383 strings)
2018-09-27 01:28:42 +02:00
Ritvik Saraf
4e6722f201 updated extractor and downloader 2018-09-27 04:20:57 +05:30
Ritvik Saraf
a29e2116a7 handling error while loading comments 2018-09-27 00:53:36 +05:30
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
Ritvik Saraf
515be677a9 no comments 2018-09-24 14:53:43 +05:30
Ritvik Saraf
d694c5f511 smoother transition to comments fragment 2018-09-23 19:45:26 +05:30
Ritvik Saraf
66c753f3a3 changed comments fragment loading animation 2018-09-23 15:22:45 +05:30
Ritvik Saraf
7047b62442 added comments fragment 2018-09-23 07:02:19 +05:30
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
Ritvik Saraf
5cc73555cd updated extractor version 2018-09-19 05:41:01 +05:30
Ritvik Saraf
219922cd82 added commentsInfo in streamInfo 2018-09-19 05:13:55 +05:30
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
Ritvik Saraf
08127e5806 added basic/crappy comments support 2018-09-15 17:15:44 +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
Christian Schabesberger
97ad3c1e6d Merge pull request #1654 from mauriciocolli/misc-fixes
Improve "selected tabs" and misc fixes
2018-09-05 20:52:29 +02:00
Ritiek Malhotra
97555645f8 Merge branch 'dev' into separate-gesture-options 2018-09-05 09:21:05 -07:00
Mauricio Colli
612228bb73 Update extractor version
- Handle case where subscribers count is not available
- Fix NPE when a YouTube playlist is empty
- Quick fix for the kiosks in SoundCloud
2018-09-05 07:29:15 -03: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
Mauricio Colli
6e75d41956 Use current volume as the start value in the volume gesture
- Renamed some variables/classes to increase readability
2018-09-04 23:54:17 -03:00
Mauricio Colli
9883a38698 Fix registering of broadcast receiver 2018-09-04 23:54:17 -03:00
Mauricio Colli
07256e2e34 Handle case where subscribers count is not available 2018-09-04 23:54:17 -03:00
Mauricio Colli
43674ae80a Improve tabs UX and saving/loading
- Show icons in the tabs list and dialog chooser
- Add a "restore to defaults" button
- Make removing gesture more user intuitive
2018-09-04 23:54:17 -03:00
Christian Schabesberger
f4ba8df02b Merge branch 'dev' into patch-7 2018-09-04 17:53:39 +02:00
Christian Schabesberger
c066ebd76f merge extractor fix for empty subscriptioin count 2018-09-04 14:31:08 +02:00
Christian Schabesberger
6e382c64a4 Reciever not registered 2018-09-04 13:07: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
Christian Schabesberger
81e76f260c fix drawer header font color for white theme 2018-09-01 12:33:08 +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
Christian Schabesberger
93571961ee merge weblate changes 2018-08-31 14:11:16 +02:00
Vasily
ee4942dfd7 Grid layout for local lists 2018-08-31 14:34:32 +03:00
MadderRagax
cb24347b23 Translated using Weblate (Swedish)
Currently translated at 100.0% (383 of 383 strings)
2018-08-30 23:23:27 +02:00
Vincent Tam
146b7be825 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (383 of 383 strings)
2018-08-29 12:35:17 +02:00
Vincent Tam
50203c5f87 Translated using Weblate (Chinese (Simplified))
Currently translated at 27.4% (105 of 383 strings)
2018-08-29 12:35:16 +02:00
Vincent Tam
b188073fb0 Translated using Weblate (Chinese (Hong Kong))
Currently translated at 36.2% (139 of 383 strings)
2018-08-29 12:35:14 +02: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
Christian Schabesberger
91c60df0e9 move on to v0.14.0 2018-08-28 18:04:45 +02:00
Christian Schabesberger
ad065e9281 Merge pull request #1623 from TeamNewPipe/refactor_and_bugfix
Refactor and bugfix
2018-08-28 16:30:09 +02:00
Christian Schabesberger
b1429366da fixes acording to code review
fixes moreacording to code review

fixed link handling once more
2018-08-28 12:19:07 +02:00
ButterflyOfFire
8bf7af2e74 Translated using Weblate (Arabic)
Currently translated at 100.0% (383 of 383 strings)
2018-08-28 02:34:24 +02:00
Christian Schabesberger
2003f51d49 fix thumbnail not shown in background player 2018-08-27 16:37:21 +02:00
Christian Schabesberger
ce83fd9a10 make dash parser ignore segmented streams 2018-08-27 16:37:21 +02:00
Christian Schabesberger
eacbaa3680 fix exception on nothing found 2018-08-27 16:37:21 +02:00
Christian Schabesberger
98c65fb9b7 add more debug statements to BasePlayer 2018-08-27 16:37:21 +02:00
Christian Schabesberger
44a71d8565 add reset extSD card folder dialog 2018-08-27 16:37:21 +02:00
Christian Schabesberger
badd4d3207 fix linkhandling in description
bla
2018-08-27 16:37:21 +02:00
Christian Schabesberger
0f517b803b fix layout width of currentPlayTime 2018-08-27 16:37:21 +02:00
Christian Schabesberger
c2d11e786f rename Search Query handler 2018-08-27 16:37:21 +02:00
Christian Schabesberger
b0efe49e29 fix cycling search results 2018-08-27 16:37:21 +02:00
Christian Schabesberger
2d029b9f76 fix exception when loading premium videos 2018-08-27 16:37:21 +02:00
Dual Natan
8ad917cff0 Translated using Weblate (Macedonian)
Currently translated at 100.0% (383 of 383 strings)
2018-08-25 15:56:40 +02:00
Mauricio Colli
4e478c65d3 Merge pull request #1604 from TheMatten/player_controls
Gesture controls
2018-08-24 13:36:58 -03:00
TheMatten
a469086915 Add background to fast rewind icon, change android:src to tools:src
-White icon was barely visible on bright backgrounds
-Secondly, drawable is set programmatically anyway and so it's setting in
 XML is good just for a confusion
2018-08-24 13:24:35 -03:00
TheMatten
bf05ff6048 Use animated circular design for gesture control (brightness and volume)
-Previous version used emojis for brightness and volume icons, which may
 be inconsistent across devices and do not fit well with other parts of UI
 (Frankly, previous version was more informative than eye-candy)

-This commit replaces old version with circular progress bar that shows
 current value (before conversion). Gesture mode (volume/brightness) is
 indicated by icon that changes between (4/3) modes according to current
 value

-Text information about current value was removed, because with progress
 bar present it does not add any real value to UI.
2018-08-24 13:24:35 -03:00
Christian Schabesberger
a817d8cbf9 git replace getFragmentManager() with getFM() 2018-08-24 12:30:23 +02:00
Christian Schabesberger
4a19c78fa5 despaget certain parts of the new design 2018-08-24 12:27:02 +02:00
Somethingweirdhere
e8bb7da906 Put listener initialization into onCreate 2018-08-24 12:26:16 +02:00
Somethingweirdhere
523477fc2b Added swiping to remove, which is enabled by long-pressing 2018-08-24 12:26:16 +02:00
Somethingweirdhere
c730426be0 Fixed dragging 2018-08-24 12:26:15 +02:00
Somethingweirdhere
57d6c97203 Fixed revert 2018-08-24 12:26:15 +02:00
Somethingweirdhere
fce17aa1d4 Revert "Revert "Changed the default preferences to show trending.""
This reverts commit b441665
2018-08-24 12:26:15 +02:00
Somethingweirdhere
01abc244b1 Fixed revert 2018-08-24 12:26:15 +02:00
Somethingweirdhere
7bedacf5ad Revert "Revert "Changed the way how kiosks are handled""
This reverts commit b020567
2018-08-24 12:26:15 +02:00
Somethingweirdhere
552a1d0464 Options here again 2018-08-24 12:26:15 +02:00
Somethingweirdhere
8dde25532a Code reviewed 2018-08-24 12:26:15 +02:00
Somethingweirdhere
f29fa939ab Removing by long pressing no longer removes a random tab, but the pressed one. 2018-08-24 12:23:26 +02:00
Somethingweirdhere
614bdb33b4 Added dragging 2018-08-24 12:23:26 +02:00
Somethingweirdhere
71761675cf Fixes problems 1-3 2018-08-24 12:23:26 +02:00
Somethingweirdhere
d9194aa859 Revert "Changed the way how kiosks are handled"
This reverts commit f3da712
2018-08-24 12:23:26 +02:00
Somethingweirdhere
f15081a474 Revert "Changed the default preferences to show trending."
This reverts commit 25481d0
2018-08-24 12:23:26 +02:00
Somethingweirdhere
2f99ff4a0c Changed the default preferences to show trending. 2018-08-24 12:23:26 +02:00
Somethingweirdhere
3a7d26aa46 Changed the way how kiosks are handled 2018-08-24 12:23:26 +02:00
Somethingweirdhere
3f35bc593c Ever more UI tweaks 2018-08-24 12:23:26 +02:00
Somethingweirdhere
e5e708d781 UI tweaks 2018-08-24 12:23:26 +02:00
Somethingweirdhere
d694561980 Added fab and handles, made cards cardier 2018-08-24 12:23:26 +02:00
Somethingweirdhere
8d6d18e875 UI redisign 2018-08-24 12:23:26 +02:00
Somethingweirdhere
072e27ed27 Code cleanup 2018-08-24 12:23:26 +02:00
Somethingweirdhere
6d64215614 + New Tab is now on the bottom
Made dialog more beautiful
2018-08-24 12:17:42 +02:00
Somethingweirdhere
33f5ed5b14 Reduced Font size, fixed bugs that were created when moving the setting 2018-08-24 12:17:42 +02:00
Somethingweirdhere
27f509c8e0 Fixed 2. Use CardView to reprecent each tab. 2018-08-24 12:17:42 +02:00
Somethingweirdhere
890b3e13c9 Fixed 1. Put the tab settings into Aperence settings 2018-08-24 12:16:41 +02:00
Somethingweirdhere
b730cb099f Fixed 4. buggy behavior when adding a new tab. 2018-08-24 12:16:41 +02:00
Somethingweirdhere
fc94f184d2 Reduced lag and increased button size for older devices&users. 2018-08-24 12:16:41 +02:00
Somethingweirdhere
cbf6540889 New selection menu 2018-08-24 12:16:41 +02:00
Somethingweirdhere
40804a7fb3 Navigation drawer has services in a new menu! 2018-08-24 12:16:41 +02:00
Somethingweirdhere
d4101c4f43 Nav drawer now moves behind the status bar and the colors also work correctly. 2018-08-24 12:14:53 +02:00
Somethingweirdhere
409bebd5bc Nav drawer now moves behind the status bar 2018-08-24 12:14:53 +02:00
Somethingweirdhere
8e3ad69adb Videos now also open from the History Tab. 2018-08-24 12:14:53 +02:00
Somethingweirdhere
c8e46d9e21 PopUp now looks better on hell theme 2018-08-24 12:14:53 +02:00
Somethingweirdhere
c56241ffc1 Tab icons now work correctly in bright theme 2018-08-24 12:14:53 +02:00
Somethingweirdhere
be62a2bfc5 Fixed icons and tab titles 2018-08-24 12:14:53 +02:00
Somethingweirdhere
5cb7771484 Fixed bugs&crashes 2018-08-24 12:14:53 +02:00
Somethingweirdhere
6675d3e2cd Set up custom Main Page tabs 2018-08-24 12:14:53 +02:00
Somethingweirdhere
8ecbe4c8ad Created a dialog for the main page content 2018-08-24 12:13:44 +02:00
Somethingweirdhere
edb75c4bab Fixed crash in Subscriptions section 2018-08-24 12:12:08 +02:00
Somethingweirdhere
54b21c716a Added drawer menu 2018-08-24 12:04:35 +02:00
Somethingweirdhere
4704274b87 New Branch 2018-08-24 11:54:59 +02:00
Mauricio Colli
78547aa119 Merge pull request #1597 from mauriciocolli/close-popup-overlay
New way to close the popup player
2018-08-23 23:56:41 -03:00
Mauricio Colli
3887231c73 Fix popup position when draggable area is resized
A common case where this happens is when the soft input is visible.
2018-08-22 23:58:12 -03:00
Mauricio Colli
8a29cfbb7e Remove popup shutdown gesture in favor of the new close overlay 2018-08-22 23:58:12 -03:00
Mauricio Colli
a01d6eaf72 Don't make controls visible when moving popup 2018-08-22 23:58:12 -03:00
Mauricio Colli
69fc571b56 Add overlay to close popup 2018-08-22 23:57:57 -03:00
DPap
4cfd9c322b Translated using Weblate (Greek)
Currently translated at 96.6% (370 of 383 strings)
2018-08-22 22:38:46 +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
DPap
d2dce8801b Translated using Weblate (Greek)
Currently translated at 91.9% (352 of 383 strings)
2018-08-21 19:38:17 +02:00
DPap
5b8bb9f678 Translated using Weblate (Greek)
Currently translated at 79.6% (305 of 383 strings)
2018-08-20 16:38:19 +02:00
AB
2076f146cf Translated using Weblate (Ukrainian)
Currently translated at 100.0% (383 of 383 strings)
2018-08-18 22:43:37 +02: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
Haris Subandie Md. Suhaimin
5d4f2b7862 Translated using Weblate (Malay)
Currently translated at 7.8% (30 of 383 strings)
2018-08-15 21:43:39 +02:00
Igor Nedoboy
e3815e40d2 Translated using Weblate (Russian)
Currently translated at 100.0% (383 of 383 strings)
2018-08-15 21:43:35 +02:00
Ivan Dekovets
6dccfb4774 Translated using Weblate (Belarusian)
Currently translated at 100.0% (383 of 383 strings)
2018-08-15 13:57:25 +02:00
Haris Subandie Md. Suhaimin
1ccc1f4c1a Added translation using Weblate (Malay) 2018-08-15 13:57:23 +02:00
Ivan Dekovets
042809620a Translated using Weblate (Belarusian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-15 08:48:29 +02:00
Tobias Groza
cb4c8abd94 Translated using Weblate (Telugu)
Currently translated at 34.2% (131 of 383 strings)
2018-08-15 03:30:48 +02:00
Mauricio Colli
e86302f5b9 Added translation using Weblate (Belarusian) 2018-08-15 03:30:43 +02:00
AB
de080d5811 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (383 of 383 strings)
2018-08-14 16:36:58 +02:00
Ale-Ma
21b7045f93 Translated using Weblate (Italian)
Currently translated at 100.0% (383 of 383 strings)
2018-08-14 16:36:55 +02:00
Telugu Speaker
627301f83d Translated using Weblate (Telugu)
Currently translated at 34.2% (131 of 383 strings)
2018-08-14 03:44:47 +02:00
Mauricio Colli
607ca84fcc Merge pull request #1545 from MadderRagax/dev
Fixing #1543 - Removed incorrect explanations of the M4A and WebM audio formats
2018-08-12 23:53:00 -03:00
oscar
a7f36248d0 Removed incorrect explanations of the M4A and WebM audio formats 2018-08-12 23:46:21 -03:00
Mauricio Colli
d008d15167 Merge pull request #1560 from kapodamy/wifi-check-fix
Additional checks to obtain WiFi status
2018-08-12 23:27:00 -03:00
Mauricio Colli
607dc436bd Merge branch 'dev' into wifi-check-fix 2018-08-12 23:20:21 -03:00
Praveen0899
4384948f6c Translated using Weblate (Telugu)
Currently translated at 33.9% (130 of 383 strings)
2018-08-13 03:35:37 +02:00
rimasx
5e05e9ec93 Translated using Weblate (Estonian)
Currently translated at 99,7% (382 of 383 strings)
2018-08-12 22:08:40 +02:00
Igor Nedoboy
ac1fe66cf9 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-12 22:08:34 +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
Dharmendra
86732b6ae4 Translated using Weblate (Hindi)
Currently translated at 91.3% (350 of 383 strings)
2018-08-09 20:45:17 +02:00
AB
0713f55e9c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (383 of 383 strings)
2018-08-09 20:45:08 +02:00
Igor Nedoboy
5c32d73409 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-08 13:38:05 +02:00
Igor Nedoboy
5e13a1735d Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-08 01:54:07 +02:00
Igor Nedoboy
6a1fbb00d9 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-08 00:04:07 +02:00
Nathan Follens
20c3badfac Translated using Weblate (Flemish)
Currently translated at 100,0% (383 of 383 strings)
2018-08-07 23:16:56 +02:00
Igor Nedoboy
7817cfe0c1 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-07 23:16:53 +02:00
Nathan Follens
9ed823b5a5 Translated using Weblate (Flemish)
Currently translated at 100,0% (383 of 383 strings)
2018-08-07 10:46:08 +02:00
Nathan Follens
c6a5dedf0a Translated using Weblate (Dutch)
Currently translated at 100,0% (383 of 383 strings)
2018-08-07 10:45:30 +02:00
Igor Nedoboy
01c9ab36b7 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-07 02:11:09 +02:00
Igor Nedoboy
27f5bdeef1 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-07 01:54:23 +02:00
Igor Nedoboy
723898f87d Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-07 00:24:15 +02:00
Igor Nedoboy
bc05cc1445 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-07 00:09:20 +02:00
Igor Nedoboy
dcf4e43e28 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-06 22:23:11 +02:00
Igor Nedoboy
3868c53908 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-06 21:59:23 +02:00
Igor Nedoboy
a71c693ca3 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-06 17:24:30 +02:00
Igor Nedoboy
691f93f01c Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-06 16:52:59 +02:00
Igor Nedoboy
4cff749186 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-06 14:23:44 +02:00
AB
e1ac1547fd Translated using Weblate (Ukrainian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-06 11:58:58 +02:00
Igor Nedoboy
e53bd505fb Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-06 11:58:55 +02:00
mesnevi
cfa926542e Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-06 11:58:53 +02:00
Igor Nedoboy
79097eca47 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-05 22:52:07 +02:00
Igor Nedoboy
d1741e40e3 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-05 20:20:01 +02:00
Igor Nedoboy
5d0528d195 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-05 15:32:32 +02:00
Igor Nedoboy
a9ea06f753 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-05 13:30:04 +02:00
Igor Nedoboy
62e121c12c Translated using Weblate (Ukrainian)
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:41:09 +02:00
Igor Nedoboy
02ef05160f Translated using Weblate (Turkish)
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:41:09 +02:00
Igor Nedoboy
6e66c013c0 Translated using Weblate (Swedish)
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:41:08 +02:00
Igor Nedoboy
dcb11f01e1 Translated using Weblate (Spanish)
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:41:08 +02:00
Igor Nedoboy
8a0e4b577c Translated using Weblate (Slovak)
Currently translated at 93,7% (359 of 383 strings)
2018-08-05 12:41:07 +02:00
Igor Nedoboy
b57f420261 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:41:06 +02:00
Igor Nedoboy
7d1790abe3 Translated using Weblate (Polish)
Currently translated at 96,3% (369 of 383 strings)
2018-08-05 12:41:06 +02:00
Igor Nedoboy
1fc494571b Translated using Weblate (Norwegian Bokmål)
Currently translated at 98,6% (378 of 383 strings)
2018-08-05 12:41:05 +02:00
Igor Nedoboy
e6d97bc773 Translated using Weblate (Macedonian)
Currently translated at 96,3% (369 of 383 strings)
2018-08-05 12:41:05 +02:00
Igor Nedoboy
0e53323fb7 Translated using Weblate (Italian)
Currently translated at 98,6% (378 of 383 strings)
2018-08-05 12:41:04 +02:00
Igor Nedoboy
eb4764d2b2 Translated using Weblate (German)
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:41:04 +02:00
Igor Nedoboy
298a91adbf Translated using Weblate (French)
Currently translated at 98,1% (376 of 383 strings)
2018-08-05 12:41:03 +02:00
Igor Nedoboy
2cb9912039 Translated using Weblate (Flemish)
Currently translated at 98,6% (378 of 383 strings)
2018-08-05 12:41:03 +02:00
Igor Nedoboy
e52bfe4335 Translated using Weblate (Estonian)
Currently translated at 91,6% (351 of 383 strings)
2018-08-05 12:41:02 +02:00
Igor Nedoboy
761a249e05 Translated using Weblate (Dutch)
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:41:02 +02:00
Igor Nedoboy
c42df3a0c2 Translated using Weblate (Czech)
Currently translated at 93,9% (360 of 383 strings)
2018-08-05 12:41:01 +02:00
Igor Nedoboy
3d359b7a98 Translated using Weblate (Chinese (Traditional))
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:41:01 +02:00
Igor Nedoboy
deef6417ad Translated using Weblate (Chinese (Simplified))
Currently translated at 97,1% (372 of 383 strings)
2018-08-05 12:41:00 +02:00
Igor Nedoboy
f55a8deb97 Translated using Weblate (Catalan)
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:40:58 +02:00
Igor Nedoboy
333506e00b Translated using Weblate (Bulgarian)
Currently translated at 98,4% (377 of 383 strings)
2018-08-05 12:40:58 +02:00
Igor Nedoboy
bbc1642b90 Translated using Weblate (Basque)
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:40:57 +02:00
Igor Nedoboy
8209eda27a Translated using Weblate (Arabic)
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:40:56 +02:00
Igor Nedoboy
b13f7a599b Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-05 12:40:54 +02:00
Igor Nedoboy
cb0f700be1 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-05 12:16:07 +02:00
Igor Nedoboy
7b6d6b466a Translated using Weblate (English)
Currently translated at 99,7% (382 of 383 strings)
2018-08-05 12:13:00 +02:00
Igor Nedoboy
76f97e5c2e Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-05 12:12:59 +02:00
Igor Nedoboy
4669a1ab57 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 23:46:04 +02:00
mesnevi
b1ad0edbe1 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 23:46:03 +02:00
Igor Nedoboy
51a695d047 Translated using Weblate (Slovak)
Currently translated at 93.7% (359 of 383 strings)
2018-08-04 20:36:34 +02:00
Igor Nedoboy
396e2d14f3 Translated using Weblate (Polish)
Currently translated at 96.3% (369 of 383 strings)
2018-08-04 20:36:34 +02:00
Igor Nedoboy
245479c339 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.7% (382 of 383 strings)
2018-08-04 20:36:33 +02:00
Igor Nedoboy
092215f47a Translated using Weblate (Macedonian)
Currently translated at 96.3% (369 of 383 strings)
2018-08-04 20:36:32 +02:00
Igor Nedoboy
fcb46db718 Translated using Weblate (Italian)
Currently translated at 98.6% (378 of 383 strings)
2018-08-04 20:36:32 +02:00
Igor Nedoboy
60c58c8b9c Translated using Weblate (Indonesian)
Currently translated at 93.7% (359 of 383 strings)
2018-08-04 20:36:31 +02:00
Igor Nedoboy
124a2839b5 Translated using Weblate (French)
Currently translated at 98.4% (377 of 383 strings)
2018-08-04 20:36:31 +02:00
Igor Nedoboy
ededfe10ab Translated using Weblate (Flemish)
Currently translated at 98.6% (378 of 383 strings)
2018-08-04 20:36:30 +02:00
Igor Nedoboy
81895c20d6 Translated using Weblate (English)
Currently translated at 99.7% (382 of 383 strings)
2018-08-04 20:36:30 +02:00
Igor Nedoboy
46fabe065c Translated using Weblate (Czech)
Currently translated at 93.9% (360 of 383 strings)
2018-08-04 20:36:29 +02:00
Igor Nedoboy
7ac338756a Translated using Weblate (Chinese (Simplified))
Currently translated at 97.1% (372 of 383 strings)
2018-08-04 20:36:28 +02:00
Igor Nedoboy
640b8edd78 Translated using Weblate (Bulgarian)
Currently translated at 98.6% (378 of 383 strings)
2018-08-04 20:36:28 +02:00
Igor Nedoboy
c5d98752fa Translated using Weblate (Esperanto)
Currently translated at 25.0% (96 of 383 strings)
2018-08-04 20:36:25 +02:00
MadderRagax
a6a5bef447 Update translation via weblate
Translated using Weblate (Swedish)

Currently translated at 100.0% (383 of 383 strings)

Translated using Weblate (Chinese (Mandarin))

Currently translated at 27.4% (105 of 383 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 97.1% (372 of 383 strings)

Translated using Weblate (German)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Arabic)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Basque)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Dutch)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Spanish)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Catalan)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (German)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Turkish)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Swedish)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (French)

Currently translated at 97,1% (372 of 383 strings)

Translated using Weblate (French)

Currently translated at 97,1% (372 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Arabic)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Basque)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Dutch)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Spanish)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Catalan)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (German)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Swedish)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Turkish)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Estonian)

Currently translated at 91.6% (351 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)

Translated using Weblate (Russian)

Currently translated at 100,0% (383 of 383 strings)
2018-08-04 17:58:57 +02:00
Igor Nedoboy
042885c155 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 16:17:24 +02:00
Igor Nedoboy
cbb9dcf7d0 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 14:43:27 +02:00
Igor Nedoboy
9b080800e1 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 14:23:07 +02:00
Igor Nedoboy
6effbf50a8 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 14:10:52 +02:00
Igor Nedoboy
398f9aa19a Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 13:53:37 +02:00
Igor Nedoboy
935d89747f Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 13:41:41 +02:00
Igor Nedoboy
21c2fbfd39 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 12:54:43 +02:00
Igor Nedoboy
bd337f3aac Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 12:30:54 +02:00
Igor Nedoboy
4a673eee81 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 12:10:15 +02:00
Igor Nedoboy
cebf349f9a Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 11:58:15 +02:00
Igor Nedoboy
3683deb51c Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 11:35:35 +02:00
Igor Nedoboy
99ee076db9 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 11:13:01 +02:00
Igor Nedoboy
04f759041f Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 10:52:31 +02:00
Igor Nedoboy
75f89059e7 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 10:51:04 +02:00
Igor Nedoboy
a81f31156d Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-04 02:51:46 +02:00
Igor Nedoboy
f706452e67 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 23:19:08 +02:00
Igor Nedoboy
b0126afbcf Translated using Weblate (Estonian)
Currently translated at 91.6% (351 of 383 strings)
2018-08-03 23:18:13 +02:00
Igor Nedoboy
5b8393ff89 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:51:52 +02:00
Igor Nedoboy
d2235da06a Translated using Weblate (Turkish)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:49:53 +02:00
Igor Nedoboy
a87f6a0791 Translated using Weblate (Swedish)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:46:13 +02:00
Igor Nedoboy
7e7cfb79a4 Translated using Weblate (Ukrainian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:45:49 +02:00
Igor Nedoboy
fb43a5265c Translated using Weblate (German)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:44:09 +02:00
Igor Nedoboy
439a814133 Translated using Weblate (Chinese (Traditional))
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:42:37 +02:00
Igor Nedoboy
d9dfcc04bf Translated using Weblate (Catalan)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:42:13 +02:00
Igor Nedoboy
0cfac137b7 Translated using Weblate (Spanish)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:41:16 +02:00
Igor Nedoboy
4575ee805a Translated using Weblate (Dutch)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:40:02 +02:00
Igor Nedoboy
67f70ce2cc Translated using Weblate (Basque)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:39:06 +02:00
Igor Nedoboy
2f641ffb13 Translated using Weblate (Arabic)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:38:43 +02:00
Igor Nedoboy
50f92269c2 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 21:37:23 +02:00
Igor Nedoboy
f220f397ae Translated using Weblate (French)
Currently translated at 97,1% (372 of 383 strings)
2018-08-03 19:32:56 +02:00
PiR
cdb4096124 Translated using Weblate (French)
Currently translated at 97,1% (372 of 383 strings)
2018-08-03 19:32:51 +02:00
Igor Nedoboy
01938af65b Translated using Weblate (Swedish)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 19:32:12 +02:00
Igor Nedoboy
47a1fca32f Translated using Weblate (Ukrainian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 19:31:23 +02:00
Igor Nedoboy
321342cf6d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 19:30:30 +02:00
Igor Nedoboy
086e9beb59 Translated using Weblate (Turkish)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 19:29:40 +02:00
Igor Nedoboy
3519d4b219 Translated using Weblate (German)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 19:27:39 +02:00
Igor Nedoboy
9034b9a9ae Translated using Weblate (Chinese (Traditional))
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 19:26:01 +02:00
Igor Nedoboy
90ba8440a0 Translated using Weblate (Catalan)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 19:24:45 +02:00
Igor Nedoboy
4988b37d6f Translated using Weblate (Spanish)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 19:23:04 +02:00
Igor Nedoboy
ad4799ee60 Translated using Weblate (Dutch)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 19:22:17 +02:00
Igor Nedoboy
35229f8ae5 Translated using Weblate (Basque)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 19:19:46 +02:00
Igor Nedoboy
7a011d9e75 Translated using Weblate (Arabic)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 19:18:48 +02:00
Igor Nedoboy
3d86835979 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 14:06:07 +02:00
Igor Nedoboy
4073306538 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 14:04:42 +02:00
Igor Nedoboy
b86aa28d6a Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 14:03:46 +02:00
Igor Nedoboy
d43cee29f2 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 14:03:05 +02:00
Igor Nedoboy
acad468b4a Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 14:02:07 +02:00
Igor Nedoboy
4cbe842cfa Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 14:01:13 +02:00
Igor Nedoboy
d93e227190 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 13:59:48 +02:00
Igor Nedoboy
a9cf424998 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-08-03 13:10:41 +02:00
ssantos
6d8fdf46d5 Translated using Weblate (German)
Currently translated at 100,0% (383 of 383 strings)
2018-08-02 22:53:59 +02:00
Hosted Weblate
781f98ef91 Merge branch 'origin/dev' into Weblate 2018-08-02 12:43:38 +02:00
YeeVonAngg
1313f685da Translated using Weblate (Chinese (Simplified))
Currently translated at 97.1% (372 of 383 strings)
2018-08-02 12:43:38 +02:00
YeeVonAngg
4779a993d3 Translated using Weblate (Chinese (Mandarin))
Currently translated at 27.4% (105 of 383 strings)
2018-08-02 12:43:37 +02:00
MadderRagax
342b2ae5dc Translated using Weblate (Swedish)
Currently translated at 100.0% (383 of 383 strings)
2018-08-02 12:43:28 +02:00
Christian Schabesberger
ce8ae40206 Merge pull request #1573 from cpba/patch-1
Fix typo in v0.13.7 changelog
2018-08-02 10:20:15 +02:00
Maxime Burlandy
d0704f621f Translated using Weblate (French)
Currently translated at 97.1% (372 of 383 strings)
2018-08-02 09:38:18 +02:00
D D
c6da4043ed Translated using Weblate (Bulgarian)
Currently translated at 98.4% (377 of 383 strings)
2018-08-02 08:34:57 +02:00
Carles Pastor Badosa
1aa3761d1a Fix typo in v0.13.7 changelog 2018-08-02 03:22:26 +02:00
Víctor Manuel Tapia Ramírez
ff769caf82 Translated using Weblate (Spanish)
Currently translated at 100,0% (383 of 383 strings)
2018-08-01 03:25:41 +02:00
MadderRagax
be6bc68b56 Translated using Weblate (Swedish)
Currently translated at 100.0% (383 of 383 strings)
2018-08-01 00:47:30 +02:00
Dual Natan
feb3d11f63 Translated using Weblate (Swedish)
Currently translated at 100.0% (383 of 383 strings)
2018-08-01 00:47:18 +02:00
Igor Nedoboy
d2f9b063b2 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-07-30 22:19:48 +02:00
Emin Tufan Çetin
315089c361 Translated using Weblate (Turkish)
Currently translated at 100.0% (383 of 383 strings)
2018-07-30 17:43:06 +02:00
Igor Nedoboy
e95df0dbd5 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-07-30 08:05:57 +02:00
Igor Nedoboy
8fed029ee3 Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-07-30 06:06:16 +02:00
Igor Nedoboy
522daf5aff Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-07-30 05:56:58 +02:00
Igor Nedoboy
e948eebe84 Translated using Weblate (Russian)
Currently translated at 99,4% (381 of 383 strings)
2018-07-30 05:31:23 +02:00
Igor Nedoboy
783d4e7e8a Translated using Weblate (Russian)
Currently translated at 100,0% (383 of 383 strings)
2018-07-30 05:13:13 +02:00
Eduardo Caron
1ce2198621 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (383 of 383 strings)
2018-07-30 02:43:29 +02:00
Freddy Morán Jr
f521def4a5 Translated using Weblate (Spanish)
Currently translated at 99.4% (381 of 383 strings)
2018-07-29 21:43:48 +02:00
Eduardo Caron
75202921a1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (383 of 383 strings)
2018-07-29 02:12:45 +02:00
kapodamy
4ef8b93344 patch for ListHelper.java
double check for null
2018-07-28 12:07:10 -03:00
Andrey mm
881b191b8d Translated using Weblate (Russian)
Currently translated at 100.0% (383 of 383 strings)
2018-07-26 12:41:57 +02:00
mesnevi
b39e071d1e Translated using Weblate (Russian)
Currently translated at 100.0% (383 of 383 strings)
2018-07-26 12:41:52 +02:00
mesnevi
84cb3a1060 Translated using Weblate (Russian)
Currently translated at 100.0% (383 of 383 strings)
2018-07-25 12:19:47 +02:00
AB
f4ea3980c2 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (383 of 383 strings)
2018-07-25 10:43:30 +02:00
Hosted Weblate
ef73720a5e Merge branch 'origin/dev' into Weblate 2018-07-24 04:51:05 +02:00
Rex_sa
d8cdc57702 Translated using Weblate (Arabic)
Currently translated at 100,0% (383 of 383 strings)
2018-07-24 04:51:02 +02:00
Christian Schabesberger
b52dee37f4 move on to version v0.13.7 2018-07-23 18:54:40 +02:00
Ali Demirtas
21bd9f09da Translated using Weblate (Turkish)
Currently translated at 100,0% (383 of 383 strings)
2018-07-22 18:53:19 +02:00
Weblate
44f24e58f6 Merge branch 'origin/dev' into Weblate 2018-07-22 15:03:59 +02:00
Allan Nordhøy
860c4d045a Translated using Weblate (Norwegian Bokmål)
Currently translated at 95.8% (367 of 383 strings)
2018-07-22 15:03:58 +02:00
Marc Riera
3b0c96f654 Translated using Weblate (Catalan)
Currently translated at 100.0% (383 of 383 strings)
2018-07-22 15:03:56 +02:00
Jeff Huang
65b744472b Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (383 of 383 strings)
2018-07-22 15:03:53 +02:00
Christian Schabesberger
26489b0f00 fix filters 2018-07-22 13:55:17 +02:00
Marc Riera
414abad05f Translated using Weblate (Catalan)
Currently translated at 100,0% (383 of 383 strings)
2018-07-22 12:41:26 +02:00
Heimen Stoffels
735d9a5391 Translated using Weblate (Dutch)
Currently translated at 100.0% (383 of 383 strings)
2018-07-22 11:40:53 +02:00
Osoitz
50571449cb Translated using Weblate (Basque)
Currently translated at 100.0% (383 of 383 strings)
2018-07-22 11:01:08 +02:00
Weblate
a31aacd115 Merge branch 'origin/dev' into Weblate 2018-07-22 10:57:42 +02:00
Rex_sa
f5b57cc0da Translated using Weblate (Arabic)
Currently translated at 100.0% (379 of 379 strings)
2018-07-22 10:57:42 +02:00
Osoitz
b7006a8f2c Translated using Weblate (Basque)
Currently translated at 100.0% (379 of 379 strings)
2018-07-22 10:57:35 +02:00
Christian Schabesberger
82bb467a2a move on to version v0.13.6 2018-07-21 14:35:03 +02:00
Christian Schabesberger
3a44e92432 Merge branch 'search' into dev 2018-07-19 16:19:33 +02:00
Christian Schabesberger
e60db5f928 make new filtersystem translatable 2018-07-19 16:18:57 +02:00
Ariel Shulman
78485287a4 Translated using Weblate (Hebrew)
Currently translated at 82.3% (312 of 379 strings)
2018-07-18 20:37:59 +02:00
D D
48b6f01b13 Translated using Weblate (Bulgarian)
Currently translated at 97.8% (371 of 379 strings)
2018-07-18 17:35:33 +02:00
Christian Schabesberger
39e04de208 show radio pin for content filter again 2018-07-18 15:05:49 +02:00
Next Hubs
68d5b59693 Translated using Weblate (Urdu)
Currently translated at 5.0% (19 of 379 strings)
2018-07-18 12:43:31 +02:00
Andrea Troiano
4ef01ef745 Translated using Weblate (Italian)
Currently translated at 100,0% (379 of 379 strings)
2018-07-18 10:28:06 +02:00
Rex_sa
573fa8870c Translated using Weblate (Arabic)
Currently translated at 100.0% (379 of 379 strings)
2018-07-17 14:34:21 +02:00
Rex_sa
88d354b08b Translated using Weblate (Arabic)
Currently translated at 100,0% (379 of 379 strings)
2018-07-16 13:17:38 +02:00
Rex_sa
0ff65b5496 Translated using Weblate (Arabic)
Currently translated at 100.0% (379 of 379 strings)
2018-07-15 21:34:22 +02:00
Christian Schabesberger
14e0dcb085 fix names from UIH to LinkHandler 2018-07-15 21:21:09 +02:00
Rex_sa
e008fd21a4 Translated using Weblate (Arabic)
Currently translated at 100.0% (379 of 379 strings)
2018-07-14 21:00:07 +02:00
Nathan Follens
4638149ad0 Translated using Weblate (Flemish)
Currently translated at 100,0% (379 of 379 strings)
2018-07-11 20:52:24 +02:00
Tobias Groza
e386bdd6b3 Translated using Weblate (Greek)
Currently translated at 19.7% (75 of 379 strings)
2018-07-11 18:38:04 +02:00
Christian Schabesberger
decb167ba9 make the new extractor refactorings work with SoundCloud 2018-07-10 16:26:42 +02:00
Tobias Groza
dd9557c13e Translated using Weblate (Greek)
Currently translated at 19.5% (74 of 379 strings)
2018-07-10 02:38:34 +02:00
Lee Hoe Mun
708d7162fb Translated using Weblate (Chinese (Mandarin))
Currently translated at 16.6% (63 of 379 strings)
2018-07-09 20:35:11 +02:00
Michalis Nikolaidis
1ee1b522f1 Translated using Weblate (Greek)
Currently translated at 19,5% (74 of 379 strings)
2018-07-09 02:11:31 +02:00
Christian Schabesberger
d5a500c037 bring everything to compile and run 2018-07-08 17:46:21 +02:00
Eduardo Caron
3e02c65bc0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (379 of 379 strings)
2018-07-08 15:22:38 +02:00
Christian Schabesberger
d10f9a5f25 add getMoreInfo to SearchInfo 2018-07-08 14:45:00 +02:00
AB
17e7214d25 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (379 of 379 strings)
2018-07-08 09:28:02 +02:00
Víctor Manuel Tapia Ramírez
22774592db Translated using Weblate (Spanish)
Currently translated at 100,0% (379 of 379 strings)
2018-07-07 21:07:00 +02:00
Freddy Morán Jr
7c2aa6e69d Translated using Weblate (Spanish)
Currently translated at 100,0% (379 of 379 strings)
2018-07-07 21:06:55 +02:00
Ali Demirtas
d1dbcda88e Translated using Weblate (Turkish)
Currently translated at 100,0% (379 of 379 strings)
2018-07-07 19:03:18 +02:00
Jeff Huang
3e05508cf9 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (379 of 379 strings)
2018-07-07 15:10:47 +02:00
Heimen Stoffels
1acdefd358 Translated using Weblate (Dutch)
Currently translated at 100.0% (379 of 379 strings)
2018-07-07 11:06:57 +02:00
Marc Riera
23143797f9 Translated using Weblate (Catalan)
Currently translated at 100,0% (379 of 379 strings)
2018-07-07 10:27:11 +02:00
ssantos
3364bf268b Translated using Weblate (German)
Currently translated at 100,0% (379 of 379 strings)
2018-07-07 07:51:52 +02:00
Weblate
2025d6305e Merge branch 'origin/dev' into Weblate 2018-07-07 07:48:38 +02:00
cxj
639ad7698d Translated using Weblate (Chinese (Mandarin))
Currently translated at 15,6% (59 of 378 strings)
2018-07-07 07:48:37 +02:00
D D
4eaff51ba2 Translated using Weblate (Bulgarian)
Currently translated at 61,9% (234 of 378 strings)
2018-07-07 07:48:35 +02:00
ssantos
1fb30bc3d9 Translated using Weblate (German)
Currently translated at 100,0% (378 of 378 strings)
2018-07-07 07:48:33 +02:00
Christian Schabesberger
6b66f40bcb Merge pull request #1392 from karyogamy/exoplayer-2.8.0-update
ExoPlayer 2.8.2 Update
2018-07-05 13:02:21 +02:00
Allan Nordhøy
a3cd531cc8 Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.0% (363 of 378 strings)
2018-07-04 00:40:31 +02:00
Nathan Follens
55c84192be Translated using Weblate (Flemish)
Currently translated at 100.0% (378 of 378 strings)
2018-07-03 09:36:55 +02:00
Víctor Manuel Tapia Ramírez
aa9018b97b Translated using Weblate (Spanish)
Currently translated at 100.0% (378 of 378 strings)
2018-07-03 00:43:30 +02:00
monolifed
1e79c146a7 Translated using Weblate (Turkish)
Currently translated at 100.0% (378 of 378 strings)
2018-07-02 15:43:34 +02:00
Marian Hanzel
cb8545e33d Translated using Weblate (Slovak)
Currently translated at 96.0% (363 of 378 strings)
2018-07-02 09:44:01 +02:00
Nathan Follens
4a484c535b Translated using Weblate (Flemish)
Currently translated at 100,0% (378 of 378 strings)
2018-07-02 09:27:09 +02:00
Víctor Manuel Tapia Ramírez
ee417e41ea Translated using Weblate (Spanish)
Currently translated at 100,0% (378 of 378 strings)
2018-07-02 00:16:04 +02:00
Eduardo Caron
3cdc1fcaee Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (378 of 378 strings)
2018-07-01 22:45:25 +02:00
Jeff Huang
0a023a61f9 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (378 of 378 strings)
2018-07-01 15:21:02 +02:00
monolifed
73f81c5b52 Translated using Weblate (Turkish)
Currently translated at 100.0% (378 of 378 strings)
2018-07-01 14:48:03 +02:00
Marc Riera
bc4acbb7e1 Translated using Weblate (Catalan)
Currently translated at 100,0% (378 of 378 strings)
2018-07-01 12:39:58 +02:00
Heimen Stoffels
26d07ea2c6 Translated using Weblate (Dutch)
Currently translated at 100.0% (378 of 378 strings)
2018-07-01 11:40:26 +02:00
Weblate
5380c8352d Merge branch 'origin/dev' into Weblate 2018-07-01 08:42:10 +02:00
Anton Shestakov
e85e91183e Translated using Weblate (Russian)
Currently translated at 99.4% (371 of 373 strings)
2018-07-01 08:42:07 +02:00
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
Christian Schabesberger
a208a22bc2 add link to the incredible bugreport converter 2018-06-29 14:03:15 +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
John Zhen Mo
644766b5a6 -Updated exoplayer lib to 2.8.2. 2018-06-28 12:21:03 -07:00
John Zhen Mo
ca679f5932 -Fixed potential NPE when updating thumbnail in background player. 2018-06-28 12:18:02 -07:00
John Zhen Mo
7f7145e8de -Fixed playback parameter dialog settings not persisting through rotation.
-Moved playback parameter dialog step size selector to below pitch slider.
2018-06-28 12:04:30 -07:00
John Zhen Mo
aa1878c15a -Changed baseplayer metadata getters to use media tag as source.
-Changed background player notification to no longer update bitmap on progress time change.
-Changed popup player to move above soft keyboard when it is opened.
2018-06-28 12:04:30 -07:00
John Zhen Mo
e7d23176b7 -Fixed database backup failing due to journal file name change after Room DB version update. 2018-06-28 12:04:30 -07:00
John Zhen Mo
31218c2a8c -Fixed popup player notification metadata not updated on stream change.
-Fixed popup player window not clipped to above soft input keyboard upon expansion.
2018-06-28 12:03:20 -07:00
John Zhen Mo
06374c82fd -Fixed video players end screen not cleared on restarting playback after single stream play queue is completed. 2018-06-28 12:03:20 -07:00
John Zhen Mo
8efcc8f80f -Fixed main video player end screen thumbnail not fitting screen aspect ratio. 2018-06-28 12:03:20 -07:00
John Zhen Mo
2d6317bd24 -Fixed audio-only streams thumbnail not displaying on video players.
-Fixed potential play queue desynchronization due to fast forwarding on silence.
-Added current thumbnail storing in base player to allow immediate retrieval for notification building.
-Removed video player buffer spinner during interim buffering but not initial buffering.
-Reverted foreground notification stopping on pause and on complete.
2018-06-28 12:03:20 -07:00
John Zhen Mo
157b064214 -Fixed player database and progress disposable disposed when destroying exoplayer.
-Fixed livestream not reloading on behind live window exception.
-Added nonnull annotation to player intent strings.
2018-06-28 12:01:34 -07:00
John Zhen Mo
0ece4851d2 -Updated ExoPlayer to 2.8.1, fixing livestream with long duration not loading.
-Updated OkHttp to 3.10.0 and RxJava to 2.1.14.
-Changed player recovery seek to use ExoPlayer built-in window seeking instead of seeking after stream window starts playing.
-Changed playback speed changer default step size to 25%.
-Changed player notification to reset on all state changes.
-Fixed gradle dependency version incorrect variable names.
-Fixed video player double tap not working during pause.
-[#1412] Fixed NPE when sharing video to main video activity when it was playing but is out of focus: Reset main player state when new intent is received.
-[#1410] Fixed fast forwarding and rewinding not working within 10 seconds from beginning or end of a stream window.
2018-06-28 12:01:34 -07:00
John Zhen Mo
f1f5996975 -Refactored playback resolvers and other persistent player objects to instantiate once only during player creation to enforce non-nullity.
-Fixed background and popup player service staying in foreground when playback is paused or completed.
-Fixed player metadata not updating on new stream.
-Fixed player intent playback quality not applied.
-Fixed player auto-queue not applied after stream transition or swapping.
2018-06-28 12:00:00 -07:00
John Zhen Mo
0a2dbc4688 -Fixed playlist fragment infinite update cycle.
-Updated Room DB version to 1.1.0.
2018-06-28 11:59:59 -07:00
John Zhen Mo
13587d7ab3 -Fixed some typos. 2018-06-28 11:58:33 -07:00
John Zhen Mo
0fcef064fb -Reduced fling speed required to close popup by 40%. 2018-06-28 11:58:33 -07:00
John Zhen Mo
19b8796cbc -Fixed statistics fragment button not animating when pressed.
-Removed background player notification button opacity change.
2018-06-28 11:58:33 -07:00
John Zhen Mo
15fb60a845 -Fixed bookmarked playlist not updating metadata when changed. 2018-06-28 11:58:32 -07:00
John Zhen Mo
5c202f04e7 -[#1383]Fixed popup player caption selector not populating due to full width aspect ratio selector.
-Fixed potential memory leak in media session connector containing player instance.
2018-06-28 11:58:32 -07:00
John Zhen Mo
bc6fdf81d2 -Refactored player media source resolution into external helpers.
-Baked resolved media metadata into media source for one-way data passing.
2018-06-28 11:58:32 -07:00
John Zhen Mo
3194a2bf2c -Fixed skip silence state not maintained by player on new queue.
-Fixed TrackSelector deprecations.
2018-06-28 11:58:32 -07:00
John Zhen Mo
72d1e5131f -Added skip silence toggle to playback speed control.
-Added step size selector to playback speed control.
-Added skip silence flag to player intents.
-Moved default preset in playback speed control to neutral dialog button, renamed as reset.
-Removed nightcore preset from playback speed control.
2018-06-28 11:58:32 -07:00
John Zhen Mo
7721098551 -Updated ExoPlayer to 2.8.0
-Updated MediaSource contracts in ManagedMediaSource.
-Changed PlaceholderMediaSource and FailedMediaSource to use built-in BaseMediaSource implementation.
-Changed deprecated DynamicConcatenatingMediaSource to ConcatenatingMediaSource.
-Removed manual playlist media source disposal in favor of player built-in disposal.
2018-06-28 11:58:32 -07: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
Christian Schabesberger
f563bc4210 Merge pull request #1510 from karyogamy/lib-update
Library version update and database backup fix
2018-06-28 12:16:04 +02:00
Christian Schabesberger
43e7be9b86 Merge pull request #1509 from karyogamy/main-video-player-fix
Main video player fix
2018-06-28 12:00:54 +02:00
John Zhen Mo
27131d15dd -Updated room db to 1.1.1.
-Fixed database import/export to no longer include accessory db files to ensure backward compatibility.
2018-06-26 12:26:01 -07:00
John Zhen Mo
fb1a290bd9 -Updated okHttp to 3.10.0.
-Updated mockito to 2.8.9.
-Updated rxJava to 2.1.14.
-Fixed stetho to use correct lib version.
2018-06-26 12:02:26 -07:00
John Zhen Mo
ef16145695 -Fixed player new share intent causing main player crash due to player activity in background. 2018-06-26 10:21:43 -07:00
John Zhen Mo
4fbd1182c2 -Fixed minimizing to popup player does not destroying existing player when drawing over app permission is not granted. 2018-06-26 10:19:16 -07:00
Christian Schabesberger
2d39e65b5c Merge branch '640-screen-off' of https://github.com/krtkush/NewPipe into test 2018-06-26 11:23:45 +02:00
Kartikey Kushwaha
8e96b675fa Removed unwanted files. 2018-06-26 01:13:21 +05:30
Christian Schabesberger
adb6943420 Merge pull request #1354 from karyogamy/minimize-on-exit
Minimize main player on exit
2018-06-25 19:46:13 +02:00
Christian Schabesberger
eae7babf93 Merge pull request #1454 from Somethingweirdhere/dev
Added share option to long tap menu
2018-06-25 15:28:37 +02:00
Christian Schabesberger
7d5e18c05b Merge pull request #1498 from Somethingweirdhere/download
Download option in share menu
2018-06-25 12:35:34 +02:00
Somethingweirdhere
cbe001efd6 Added option to menu 2018-06-25 12:04:11 +02:00
Christian Schabesberger
86b783fb0f Merge pull request #1472 from acrosca/undo_delete
undo delete
2018-06-25 10:04:17 +02:00
Christian Schabesberger
ccc27b48df Merge pull request #1497 from karyogamy/view-history-fix
View history fix
2018-06-25 09:26:08 +02:00
Tobias Groza
a32391f560 Translated using Weblate (German)
Currently translated at 100.0% (373 of 373 strings)
2018-06-24 17:38:37 +02:00
nautilusx
aed0348802 Translated using Weblate (German)
Currently translated at 100.0% (373 of 373 strings)
2018-06-24 17:38:32 +02:00
Rossinière Vaud
1b66446c0d Translated using Weblate (Polish)
Currently translated at 100.0% (373 of 373 strings)
2018-06-24 12:42:40 +02:00
Tobias Groza
2bc0c8a483 Translated using Weblate (German)
Currently translated at 100,0% (373 of 373 strings)
2018-06-23 17:09:58 +02:00
DafabHoid
3f7e02e305 Translated using Weblate (German)
Currently translated at 100,0% (373 of 373 strings)
2018-06-23 17:09:53 +02:00
nautilusx
dd0d666003 Translated using Weblate (German)
Currently translated at 100,0% (373 of 373 strings)
2018-06-23 17:09:47 +02:00
Tobias Groza
81859a37de Translated using Weblate (German)
Currently translated at 100,0% (373 of 373 strings)
2018-06-23 17:05:55 +02:00
rimasx
180bb581a3 Translated using Weblate (Estonian)
Currently translated at 94.6% (353 of 373 strings)
2018-06-23 16:37:11 +02:00
yuriqdev
2f31779af4 Translated using Weblate (Russian)
Currently translated at 99.7% (372 of 373 strings)
2018-06-23 13:42:42 +02:00
Rossinière Vaud
ee6d512165 Translated using Weblate (Polish)
Currently translated at 100,0% (373 of 373 strings)
2018-06-23 12:08:23 +02:00
Weblate
1470fdc057 Merge branch 'origin/dev' into Weblate 2018-06-22 09:35:50 +02:00
rimasx
93bbaf187e Translated using Weblate (Estonian)
Currently translated at 94.6% (353 of 373 strings)
2018-06-22 09:35:46 +02:00
Víctor Manuel Tapia Ramírez
f1e43007f1 Translated using Weblate (Chinese (Mandarin))
Currently translated at 6.4% (24 of 373 strings)
2018-06-22 09:35:21 +02:00
D D
155436b85d Translated using Weblate (Bulgarian)
Currently translated at 62.7% (234 of 373 strings)
2018-06-22 09:35:18 +02:00
Somethingweirdhere
f3e029c3f6 Cleaned code, downloaddialog now also appears after giving storage permission. 2018-06-20 14:46:57 +02:00
Christian Schabesberger
90d6416f55 Merge pull request #1491 from annoyatron255/video-info-fix
Fix #1440 Broken Video Info Layout
2018-06-19 09:50:02 +02:00
Ale-Ma
af2a2e45af Translated using Weblate (Italian)
Currently translated at 100.0% (373 of 373 strings)
2018-06-19 09:40:03 +02:00
Andrea Troiano
e79eda9c5c Translated using Weblate (Italian)
Currently translated at 100.0% (373 of 373 strings)
2018-06-19 09:39:58 +02:00
John Zhen Mo
b338d9dbcf -Fixed view not registered when playback is started on external players. 2018-06-18 18:27:37 -07:00
John Zhen Mo
7fb9345344 -Fixed remote playlist metadata not updated when remote source data has changed. 2018-06-18 18:22:52 -07:00
Weblate
77b488568b Merge branch 'origin/dev' into Weblate 2018-06-17 15:56:31 +02:00
Edwar Tikhonov
fcf650c6eb Translated using Weblate (Russian)
Currently translated at 100.0% (373 of 373 strings)
2018-06-17 15:56:30 +02:00
rimasx
0c21023ad8 Added translation using Weblate (Estonian) 2018-06-17 15:56:28 +02:00
Somethingweirdhere
8f35a56ec8 Added download to share menu 2018-06-17 13:55:43 +02:00
annoyatron255
95ba1873e4 Fix #1440 Broken Video Info Layout 2018-06-16 18:12:56 -05:00
Andrei.Rosca
8b8652d44c undo delete - code format 2018-06-15 16:15:55 +02:00
Issam Maghni
2515b8167f Disable animation (hidden anyway by navigation) 2018-06-15 01:21:30 -04:00
Andrei.Rosca
09dd044f3d undo delete 2018-06-13 09:07:57 +02:00
Dual Natan
ace0ed9667 Translated using Weblate (Swedish)
Currently translated at 68.9% (257 of 373 strings)
2018-06-12 22:43:59 +02:00
Dual Natan
cf03708da2 Translated using Weblate (Macedonian)
Currently translated at 100.0% (373 of 373 strings)
2018-06-11 21:21:57 +02:00
Edwar Tikhonov
d4670bf6fa Translated using Weblate (Russe)
Currently translated at 100,0% (373 of 373 strings)
2018-06-11 16:04:29 +02:00
Edwar Tikhonov
feea448a24 Translated using Weblate (Русский)
Currently translated at 100,0% (373 of 373 strings)
2018-06-10 15:17:20 +02:00
aiddroid
71ad54652b Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (373 of 373 strings)
2018-06-10 10:35:41 +02:00
aiddroid
8e1deda7b0 Translated using Weblate (Chinese (Mandarin))
Currently translated at 0.0% (0 of 373 strings)
2018-06-10 09:35:42 +02:00
aiddroid
752f985e13 Translated using Weblate (简体中文(Chinese Simplified))
Currently translated at 100.0% (373 of 373 strings)
2018-06-09 10:09:35 +02:00
Kartikey Kushwaha
89e3219e06 Further fixes wrt FLAG_NOT_FOCUSABLE. 2018-06-09 01:38:57 +05:30
Kartikey Kushwaha
429dddc6c9 reintroduced hideControls method. 2018-06-09 01:13:37 +05:30
Kartikey Kushwaha
981174a490 Fixed bug #640. 2018-06-09 01:01:13 +05:30
Somethingweirdhere
201f7e9848 Added share option to Popup and Background queues 2018-06-08 15:59:05 +02:00
kapodamy
bb0d8ad58a Translated using Weblate (Spanish)
Currently translated at 99.7% (372 of 373 strings)
2018-06-08 01:44:11 +02:00
Weblate
29e64f7c1a Merge branch 'origin/dev' into Weblate 2018-06-06 20:37:29 +02:00
JAPP
4819ebd56e Translated using Weblate (French)
Currently translated at 99.1% (370 of 373 strings)
2018-06-06 20:37:23 +02:00
John Zhen Mo
3b603b0637 -Added back button press check to destroy rather than minimize main video player. 2018-06-05 23:37:20 -07:00
Somethingweirdhere
baa63249d1 Added share option to long tap menu 2018-06-05 19:48:31 +02:00
Somethingweirdhere
ee347e3081 Merge pull request #1 from TeamNewPipe/dev
Up to date
2018-06-05 19:35:47 +02:00
Christian Schabesberger
f96bc95053 Update CONTRIBUTING.md 2018-06-05 11:03:48 +02:00
John Zhen Mo
e1df4757e4 -Expanded minimize to exit to allow resuming on background player.
-Modified minimize to exit toggle to selection dialog.
2018-06-03 14:09:16 -07:00
John Zhen Mo
4fc37a7321 -Added toggle to allow main video player to switch to popup player when onstop is called.
-Fixed player state not recovering when player is stopped during multiwindow mode.
-Updated gradle to 3.1.2.
2018-06-03 13:20:39 -07:00
Christian Schabesberger
2a45a13f73 move on to version v0.13.5 2018-06-03 12:36:35 +02:00
Christian Schabesberger
067b15c300 Merge branch 'dev' of https://github.com/DafabHoid/NewPipe into test 2018-06-03 12:20:43 +02:00
Christian Schabesberger
8a1c283542 Merge branch 'media-session-fix' of https://github.com/karyogamy/NewPipe into test 2018-06-03 12:12:54 +02:00
Christian Schabesberger
93d1e8b2ff Merge pull request #1441 from DafabHoid/fixdownloadercrashrelease
Downloader: Fix crash on loading unfinished downloads from .giga files (Fixup for release builds)
2018-06-03 12:08:20 +02:00
Christian Schabesberger
c60d5b54fa Merge branch 'remeber_brightness' of https://github.com/acrosca/NewPipe into test 2018-06-03 11:02:49 +02:00
Andrei.Rosca
ef180f082e Remember brightness for the session 2018-06-02 09:06:40 +02:00
Rex_sa
f52741cc37 Translated using Weblate (Arabic)
Currently translated at 100.0% (373 of 373 strings)
2018-06-02 07:34:24 +02:00
DafabHoid
2a2661f066 Downloader: Fix crash on loading unfinished downloads from .giga files
This is a fixup, which fixes the crash in release builds, too. It keeps proguard from removing the new method "private void readObject(ObjectInputStream)", which is only called by the VM, but not from the code.
2018-06-01 14:35:03 +02:00
Rex_sa
b521903138 Translated using Weblate (Arabic)
Currently translated at 100.0% (373 of 373 strings)
2018-06-01 07:02:51 +02:00
Ali Toor
acbd699d95 Translated using Weblate (Urdu)
Currently translated at 2.9% (11 of 373 strings)
2018-05-31 06:43:02 +02:00
Andrea Troiano
6b8928becb Translated using Weblate (Italiano)
Currently translated at 100,0% (373 of 373 strings)
2018-05-30 11:14:38 +02:00
Víctor Manuel Tapia Ramírez
e393bdb1e5 Translated using Weblate (Spanish)
Currently translated at 99.7% (372 of 373 strings)
2018-05-30 10:43:28 +02:00
Osoitz
bba6b96765 Translated using Weblate (Basque)
Currently translated at 100.0% (373 of 373 strings)
2018-05-29 16:58:04 +02:00
John Zhen Mo
740116356c -Fixed media session activation.
-Removed redundant setShuffle call in media session callback and its user.
-Removed unused dummy playback preparer.
2018-05-28 20:02:02 -07:00
monolifed
2f6e4fa4a3 Translated using Weblate (Turkish)
Currently translated at 100.0% (373 of 373 strings)
2018-05-28 20:44:52 +02:00
Allan Nordhøy
fb3f6721b2 Translated using Weblate (Norwegian Bokmål)
Currently translated at 95.9% (358 of 373 strings)
2018-05-28 17:41:18 +02:00
AB
4e7bd21e5c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (373 of 373 strings)
2018-05-28 17:07:13 +02:00
Jeff Huang
219c2030b9 Translated using Weblate (漢語(正體字))
Currently translated at 100.0% (373 of 373 strings)
2018-05-28 08:56:18 +02:00
Marc Riera
b75fdb4566 Translated using Weblate (català)
Currently translated at 100,0% (373 of 373 strings)
2018-05-28 08:41:52 +02:00
Nathan Follens
4584b14a31 Translated using Weblate (Vlaams)
Currently translated at 100,0% (373 of 373 strings)
2018-05-28 07:53:32 +02:00
thami simo
814ddb5932 Translated using Weblate (Arabic)
Currently translated at 100.0% (373 of 373 strings)
2018-05-28 07:45:58 +02:00
DafabHoid
6ea0f6290a Downloader: Notify the progress every 64K instead of every 512 Bytes
This improves downloading performance dramatically when cpu bound:
Before, even a high-end cpu from 2013 can't download faster than around 1MB/s.
The bigger read buffer size removes the need for a dedicated BufferedInputStream.
2018-05-28 01:07:30 +02:00
monolifed
c796fe1fe6 Translated using Weblate (Turkish)
Currently translated at 100.0% (373 of 373 strings)
2018-05-27 19:49:08 +02:00
Heimen Stoffels
a452a164e6 Translated using Weblate (Dutch)
Currently translated at 100.0% (373 of 373 strings)
2018-05-27 18:00:16 +02:00
ssantos
0d9dd69b19 Translated using Weblate (Deutsch)
Currently translated at 100,0% (373 of 373 strings)
2018-05-27 17:51:41 +02:00
Eduardo Caron
d7b73c18f1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (373 of 373 strings)
2018-05-27 15:14:19 +02:00
Weblate
07f66c0e45 Merge branch 'dev' into weblate-merge-tmp 2018-05-27 15:11:40 +02:00
monolifed
d449acbf86 Translated using Weblate (Turkish)
Currently translated at 99.4% (369 of 371 strings)
2018-05-27 15:11:40 +02:00
Allan Nordhøy
fce416ba76 Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.2% (357 of 371 strings)
2018-05-27 15:11:35 +02:00
dadosch
cb6bfe8556 Translated using Weblate (German)
Currently translated at 100.0% (371 of 371 strings)
2018-05-27 15:11:35 +02:00
Florian
d7b31e1d25 Translated using Weblate (French)
Currently translated at 98.6% (366 of 371 strings)
2018-05-27 15:11:34 +02:00
Eduardo Caron
85057376d6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (371 of 371 strings)
2018-05-27 15:11:31 +02:00
Christian Schabesberger
c43ac7c869 fix conflict 2018-05-27 13:30:23 +02:00
Heimen Stoffels
b2657315f1 Translated using Weblate (Dutch)
Currently translated at 100.0% (371 of 371 strings)
2018-05-27 12:36:08 +02:00
ezjerry liao
de5ed9717c Translated using Weblate (漢語(正體字))
Currently translated at 100.0% (371 of 371 strings)
2018-05-27 01:18:13 +02:00
Ali Demirtas
e17a6cbb9f Translated using Weblate (Turkish)
Currently translated at 100.0% (371 of 371 strings)
2018-05-27 00:07:08 +02:00
Marc Riera
8e783b774b Translated using Weblate (català)
Currently translated at 100,0% (371 of 371 strings)
2018-05-27 00:03:34 +02:00
AB
733663f40d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (371 of 371 strings)
2018-05-26 22:34:26 +02:00
Ali Demirtas
4b2a792a62 Translated using Weblate (Türkçe)
Currently translated at 100,0% (371 of 371 strings)
2018-05-26 21:01:30 +02:00
dadosch
f7aa171d01 Translated using Weblate (Deutsch)
Currently translated at 100,0% (371 of 371 strings)

informal "du"
2018-05-26 15:14:27 +02:00
dadosch
5eafefb683 Translated using Weblate (Deutsch)
Currently translated at 100,0% (371 of 371 strings)
2018-05-26 15:12:02 +02:00
dadosch
5d1b02a856 Translated using Weblate (Deutsch)
Currently translated at 100,0% (371 of 371 strings)

informal "du"
2018-05-26 15:08:29 +02:00
dadosch
4acda3d9ae Translated using Weblate (Deutsch)
Currently translated at 100,0% (371 of 371 strings)
2018-05-26 15:07:50 +02:00
dadosch
643e10ace2 Translated using Weblate (Deutsch)
Currently translated at 100,0% (371 of 371 strings)

informal "du"
2018-05-26 15:04:45 +02:00
dadosch
7b64a232de Translated using Weblate (Deutsch)
Currently translated at 100,0% (371 of 371 strings)
2018-05-26 15:00:23 +02:00
dadosch
d7472d837d Translated using Weblate (Deutsch)
Currently translated at 100,0% (371 of 371 strings)

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

Peut-être existe des traductions existantes mais je ne les aies pas trouvées.
2018-05-07 18:17:34 +02:00
nailyk
8c43674fa4 Translated using Weblate (French)
Currently translated at 95.8% (349 of 364 strings)
2018-05-07 18:14:23 +02:00
Florent Peterschmitt
f162316a6b Translated using Weblate (French)
Currently translated at 94.5% (344 of 364 strings)
2018-05-07 14:37:09 +02:00
Andrea Troiano
670596ed88 Translated using Weblate (Italian)
Currently translated at 100.0% (364 of 364 strings)
2018-05-07 10:08:51 +02:00
Prabjot Singh
6b3eb716c4 Added translation using Weblate (Punjabi) 2018-05-07 06:39:45 +02:00
Emin Tufan Çetin
6a5180d94c Translated using Weblate (Turkish)
Currently translated at 100.0% (364 of 364 strings)
2018-05-06 17:16:30 +02:00
Florent Peterschmitt
83faaedfcc Translated using Weblate (French)
Currently translated at 93.4% (340 of 364 strings)
2018-05-06 13:54:09 +02:00
Florian
d98d790a7a Translated using Weblate (French)
Currently translated at 93.4% (340 of 364 strings)
2018-05-06 13:54:02 +02:00
Florian
36b5833a3a Translated using Weblate (French)
Currently translated at 93.4% (340 of 364 strings)
2018-05-06 13:51:12 +02:00
5e86781a79 Translated using Weblate (French)
Currently translated at 89.5% (326 of 364 strings)
2018-05-06 13:27:25 +02:00
Florian
6a780504b4 Translated using Weblate (French)
Currently translated at 89.5% (326 of 364 strings)
2018-05-06 13:27:20 +02:00
Weblate
a0d8212136 Merge remote-tracking branch 'origin/dev' into dev 2018-05-06 13:01:58 +02:00
Marian Hanzel
0e1e6a9d62 Translated using Weblate (Slovak)
Currently translated at 95.8% (349 of 364 strings)
2018-05-06 13:01:58 +02:00
thami simo
812282a332 Translated using Weblate (Arabic)
Currently translated at 89.5% (326 of 364 strings)
2018-05-06 13:01:56 +02:00
r2308145
a8a4c9e97f Translated using Weblate (Czech)
Currently translated at 100.0% (364 of 364 strings)
2018-05-06 13:01:53 +02:00
Andrei.Rosca
24c293e335 fix context leaks 2018-05-06 10:50:02 +02:00
Andrei.Rosca
0a596df497 default ViewHolder 2018-05-06 10:14:24 +02:00
Andrei.Rosca
3d66c6572b prevent infinite loop 2018-05-06 10:08:56 +02:00
Andrei.Rosca
f45769cbb2 Reduce overdraw 2018-05-05 10:26:35 +02:00
Weblate
ef51f93c6f Merge remote-tracking branch 'origin/dev' into dev 2018-05-05 09:02:21 +02:00
ssantos
35af68f148 Translated using Weblate (German)
Currently translated at 100.0% (364 of 364 strings)
2018-05-05 09:02:19 +02:00
James Straub
646fa877ba Update to mobile data limiting
- Moved non-key strings from string_keys.xml to strings.xml
- Code style changes
- Replaced a hard coded key string with resource constant
2018-04-22 10:20:19 -04:00
James Straub
d1b0cd74be Added the ability to limit video quality if using mobile data.
* Added a dropdown to video & audio settings
* Changes to ListHelper:
** Limits resolution when code requests the default video resolution
** Limits audio bitrate when code requests the default audio bitrate
** Removed some dead code and did some cleanup
** Make methods private/protected to help understand what was in use
** The code now chooses one format over an other using a simple raking system defined in array constants. I realized I needed to do this in order to choose the most efficient video stream. I did my best to evaluate the video and audio formats based on quality and efficiency. It's not an exact science.
** Made changes to the tests to support my changes
2018-04-21 12:35:04 -04:00
Somethingweirdhere
dcdb2c323e Added settings export 2018-04-19 01:31:25 +02:00
Somethingweirdhere
d9e616beee Fixed crash when trying to open a downloaded file without a player 2018-04-17 22:26:24 +02:00
463 changed files with 30244 additions and 10792 deletions

View File

@@ -5,42 +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 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.
* 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

@@ -1,2 +1,3 @@
- [ ] I carefully read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them.
- [ ] I checked if the issue/feature exists in the latest version.
- [ ] I did use the [incredible bugreport to markdown converter](https://teamnewpipe.github.io/CrashReportToMarkdown/) to paste bug reports.

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:

112
README.md
View File

@@ -1,74 +1,93 @@
<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: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>
<b>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 +96,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 62
versionName "0.13.3"
minSdkVersion 19
targetSdkVersion 28
versionCode 740
versionName "0.16.2"
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,58 +43,61 @@ android {
}
ext {
supportLibVersion = '27.1.1'
exoPlayerLibVersion = '2.7.3'
roomDbLibVersion = '1.0.0'
leakCanaryLibVersion = '1.5.4'
okHttpLibVersion = '1.5.0'
supportLibVersion = '28.0.0'
exoPlayerLibVersion = '2.9.6'
roomDbLibVersion = '1.1.1'
leakCanaryLibVersion = '1.5.4' //1.6.1
okHttpLibVersion = '3.12.1'
icepickLibVersion = '3.2.0'
stethoLibVersion = '1.5.0'
}
dependencies {
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
exclude module: 'support-annotations'
}
implementation 'com.github.TeamNewPipe:NewPipeExtractor:bf1c771'
dependencies {
androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.2', {
exclude module: 'support-annotations'
})
implementation 'com.github.TeamNewPipe:NewPipeExtractor:2ac713e'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:1.10.19'
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.10'
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:3.9.1'
debugImplementation "com.facebook.stetho:stetho-okhttp3:$okHttpLibVersion"
implementation "com.squareup.okhttp3:okhttp:${okHttpLibVersion}"
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoLibVersion}"
}

View File

@@ -42,3 +42,9 @@
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
}

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"
@@ -34,12 +35,6 @@
</intent-filter>
</receiver>
<activity
android:name=".player.old.PlayVideoActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/OldVideoPlayerTheme"
tools:ignore="UnusedAttribute"/>
<service
android:name=".player.BackgroundPlayer"
android:exported="false">
@@ -76,10 +71,6 @@
android:name=".about.AboutActivity"
android:label="@string/title_activity_about"/>
<activity
android:name=".history.HistoryActivity"
android:label="@string/title_activity_history"/>
<service android:name=".local.subscription.services.SubscriptionsImportService"/>
<service android:name=".local.subscription.services.SubscriptionsExportService"/>
@@ -186,6 +177,19 @@
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="www.youtube-nocookie.com"/>
<data android:pathPrefix="/embed/"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="vnd.youtube"/>
<data android:scheme="vnd.youtube.launch"/>
</intent-filter>
@@ -212,6 +216,29 @@
<data android:pathPrefix="/user/"/>
</intent-filter>
<!-- Invidious filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="invidio.us"/>
<data android:host="www.invidio.us"/>
<!-- video prefix -->
<data android:pathPrefix="/embed/"/>
<data android:pathPrefix="/watch"/>
<!-- channel prefix -->
<data android:pathPrefix="/channel/"/>
<data android:pathPrefix="/user/"/>
<!-- playlist prefix -->
<data android:pathPrefix="/playlist"/>
</intent-filter>
<!-- Soundcloud filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
@@ -241,4 +268,4 @@
android:name=".RouterActivity$FetcherService"
android:exported="false"/>
</application>
</manifest>
</manifest>

View File

@@ -0,0 +1,116 @@
package android.support.design.widget;
import android.animation.ValueAnimator;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.design.animation.AnimationUtils;
import android.util.AttributeSet;
import android.view.View;
// check this https://github.com/ToDou/appbarlayout-spring-behavior/blob/master/appbarspring/src/main/java/android/support/design/widget/AppBarFlingFixBehavior.java
public final class FlingBehavior extends AppBarLayout.Behavior {
private ValueAnimator mOffsetAnimator;
private static final int MAX_OFFSET_ANIMATION_DURATION = 600; // ms
public FlingBehavior() {
}
public FlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) {
if (dy != 0) {
int val = child.getBottom();
if (val != 0) {
int min, max;
if (dy < 0) {
// We're scrolling down
} else {
// We're scrolling up
if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) {
mOffsetAnimator.cancel();
}
min = -child.getUpNestedPreScrollRange();
max = 0;
consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
}
}
}
}
@Override
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull AppBarLayout child, @NonNull View target, float velocityX, float velocityY) {
if (velocityY != 0) {
if (velocityY < 0) {
// We're flinging down
int val = child.getBottom();
if (val != 0) {
final int targetScroll =
+child.getDownNestedPreScrollRange();
animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
}
} else {
// We're flinging up
int val = child.getBottom();
if (val != 0) {
final int targetScroll = -child.getUpNestedPreScrollRange();
if (getTopBottomOffsetForScrollingSibling() > targetScroll) {
animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
}
}
}
}
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
private void animateOffsetTo(final CoordinatorLayout coordinatorLayout,
final AppBarLayout child, final int offset, float velocity) {
final int distance = Math.abs(getTopBottomOffsetForScrollingSibling() - offset);
final int duration;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 3 * Math.round(1000 * (distance / velocity));
} else {
final float distanceRatio = (float) distance / child.getHeight();
duration = (int) ((distanceRatio + 1) * 150);
}
animateOffsetWithDuration(coordinatorLayout, child, offset, duration);
}
private void animateOffsetWithDuration(final CoordinatorLayout coordinatorLayout,
final AppBarLayout child, final int offset, final int duration) {
final int currentOffset = getTopBottomOffsetForScrollingSibling();
if (currentOffset == offset) {
if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) {
mOffsetAnimator.cancel();
}
return;
}
if (mOffsetAnimator == null) {
mOffsetAnimator = new ValueAnimator();
mOffsetAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
mOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
setHeaderTopBottomOffset(coordinatorLayout, child,
(Integer) animator.getAnimatedValue());
}
});
} else {
mOffsetAnimator.cancel();
}
mOffsetAnimator.setDuration(Math.min(duration, MAX_OFFSET_ANIMATION_DURATION));
mOffsetAnimator.setIntValues(currentOffset, offset);
mOffsetAnimator.start();
}
}

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

@@ -4,6 +4,7 @@ import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
@@ -12,14 +13,24 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import com.squareup.leakcanary.RefWatcher;
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();
//These values are used for controlling framgents when they are part of the frontpage
@State
protected boolean useAsFrontPage = false;
protected boolean mIsVisibleToUser = false;
public void useAsFrontPage(boolean value) {
useAsFrontPage = value;
}
/*//////////////////////////////////////////////////////////////////////////
// Fragment's Lifecycle
//////////////////////////////////////////////////////////////////////////*/
@@ -72,6 +83,12 @@ public abstract class BaseFragment extends Fragment {
if (refWatcher != null) refWatcher.watch(this);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
@@ -88,8 +105,15 @@ public abstract class BaseFragment extends Fragment {
public void setTitle(String title) {
if (DEBUG) Log.d(TAG, "setTitle() called with: title = [" + title + "]");
if (activity != null && activity.getSupportActionBar() != null) {
if((!useAsFrontPage || mIsVisibleToUser)
&& (activity != null && activity.getSupportActionBar() != null)) {
activity.getSupportActionBar().setTitle(title);
}
}
protected FragmentManager getFM() {
return getParentFragment() == null
? getFragmentManager()
: getParentFragment().getFragmentManager();
}
}

View File

@@ -0,0 +1,242 @@
package org.schabi.newpipe;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.ConnectivityManager;
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) {
if(isCancelled() || !isConnected()) return null;
// 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);
}
private boolean isConnected() {
ConnectivityManager cm =
(ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isConnected();
}
}

View File

@@ -3,17 +3,24 @@ package org.schabi.newpipe;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.schabi.newpipe.extractor.DownloadRequest;
import org.schabi.newpipe.extractor.DownloadResponse;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.utils.Localization;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
@@ -43,7 +50,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 +95,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 +111,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);
}
@@ -137,13 +145,16 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
private ResponseBody getBody(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException {
final Request.Builder requestBuilder = new Request.Builder()
.method("GET", null).url(siteUrl)
.addHeader("User-Agent", USER_AGENT);
.method("GET", null).url(siteUrl);
for (Map.Entry<String, String> header : customProperties.entrySet()) {
requestBuilder.addHeader(header.getKey(), header.getValue());
}
if (!customProperties.containsKey("User-Agent")) {
requestBuilder.header("User-Agent", USER_AGENT);
}
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
}
@@ -175,4 +186,96 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
public String download(String siteUrl) throws IOException, ReCaptchaException {
return download(siteUrl, Collections.emptyMap());
}
}
@Override
public DownloadResponse get(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
final Request.Builder requestBuilder = new Request.Builder()
.method("GET", null).url(siteUrl);
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
// set custom headers in request
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
for(String value : pair.getValue()){
requestBuilder.addHeader(pair.getKey(), value);
}
}
if (!requestHeaders.containsKey("User-Agent")) {
requestBuilder.header("User-Agent", USER_AGENT);
}
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
}
final Request okRequest = requestBuilder.build();
final Response response = client.newCall(okRequest).execute();
final ResponseBody body = response.body();
if (response.code() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested");
}
if (body == null) {
response.close();
return null;
}
return new DownloadResponse(body.string(), response.headers().toMultimap());
}
@Override
public DownloadResponse get(String siteUrl) throws IOException, ReCaptchaException {
return get(siteUrl, DownloadRequest.emptyRequest);
}
@Override
public DownloadResponse post(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
if(null == requestHeaders.get("Content-Type") || requestHeaders.get("Content-Type").isEmpty()){
// content type header is required. maybe throw an exception here
return null;
}
String contentType = requestHeaders.get("Content-Type").get(0);
RequestBody okRequestBody = null;
if(null != request.getRequestBody()){
okRequestBody = RequestBody.create(MediaType.parse(contentType), request.getRequestBody());
}
final Request.Builder requestBuilder = new Request.Builder()
.method("POST", okRequestBody).url(siteUrl);
// set custom headers in request
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
for(String value : pair.getValue()){
requestBuilder.addHeader(pair.getKey(), value);
}
}
if (!requestHeaders.containsKey("User-Agent")) {
requestBuilder.header("User-Agent", USER_AGENT);
}
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
}
final Request okRequest = requestBuilder.build();
final Response response = client.newCall(okRequest).execute();
final ResponseBody body = response.body();
if (response.code() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested");
}
if (body == null) {
response.close();
return null;
}
return new DownloadResponse(body.string(), response.headers().toMultimap());
}
}

View File

@@ -23,7 +23,7 @@ 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;
import android.os.Looper;
@@ -43,18 +43,22 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ServiceHelper;
@@ -70,6 +74,19 @@ public class MainActivity extends AppCompatActivity {
private NavigationView drawerItems = null;
private TextView headerServiceView = null;
private boolean servicesShown = false;
private ImageView serviceArrow;
private static final int ITEM_ID_SUBSCRIPTIONS = - 1;
private static final int ITEM_ID_FEED = - 2;
private static final int ITEM_ID_BOOKMARKS = - 3;
private static final int ITEM_ID_DOWNLOADS = - 4;
private static final int ITEM_ID_HISTORY = - 5;
private static final int ITEM_ID_SETTINGS = 0;
private static final int ITEM_ID_ABOUT = 1;
private static final int ORDER = 0;
/*//////////////////////////////////////////////////////////////////////////
// Activity's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@@ -83,28 +100,64 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window w = getWindow();
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments();
}
setSupportActionBar(findViewById(R.id.toolbar));
setupDrawer();
try {
setupDrawer();
} catch (Exception e) {
ErrorActivity.reportUiError(this, e);
}
}
private void setupDrawer() {
private void setupDrawer() throws Exception {
final Toolbar toolbar = findViewById(R.id.toolbar);
drawer = findViewById(R.id.drawer_layout);
drawerItems = findViewById(R.id.navigation);
for(StreamingService s : NewPipe.getServices()) {
final String title = s.getServiceInfo().getName() +
(ServiceHelper.isBeta(s) ? " (beta)" : "");
final MenuItem item = drawerItems.getMenu()
.add(R.id.menu_services_group, s.getServiceId(), 0, title);
item.setIcon(ServiceHelper.getIcon(s.getServiceId()));
//Tabs
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
StreamingService service = NewPipe.getService(currentServiceId);
int kioskId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
drawerItems.getMenu()
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcons(ks, this));
kioskId ++;
}
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_whats_new)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
//Settings and About
drawerItems.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
drawerItems.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close);
toggle.syncState();
@@ -119,53 +172,179 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onDrawerClosed(View drawerView) {
if(servicesShown) {
toggleServices();
}
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
}
}
});
drawerItems.setNavigationItemSelectedListener(this::changeService);
setupDrawerFooter();
drawerItems.setNavigationItemSelectedListener(this::drawerItemSelected);
setupDrawerHeader();
}
private boolean changeService(MenuItem item) {
if (item.getGroupId() == R.id.menu_services_group) {
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false);
ServiceHelper.setSelectedServiceId(this, item.getItemId());
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
} else {
return false;
private boolean drawerItemSelected(MenuItem item) {
switch (item.getGroupId()) {
case R.id.menu_services_group:
changeService(item);
break;
case R.id.menu_tabs_group:
try {
tabSelected(item);
} catch (Exception e) {
ErrorActivity.reportUiError(this, e);
}
break;
case R.id.menu_options_about_group:
optionsAboutSelected(item);
break;
default:
return false;
}
drawer.closeDrawers();
return true;
}
private void setupDrawerFooter() {
ImageButton settings = findViewById(R.id.drawer_settings);
ImageButton downloads = findViewById(R.id.drawer_downloads);
ImageButton history = findViewById(R.id.drawer_history);
private void changeService(MenuItem item) {
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false);
ServiceHelper.setSelectedServiceId(this, item.getItemId());
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
}
settings.setOnClickListener(view -> NavigationHelper.openSettings(this));
downloads.setOnClickListener(view ->NavigationHelper.openDownloads(this));
history.setOnClickListener(view ->
NavigationHelper.openStatisticFragment(getSupportFragmentManager()));
private void tabSelected(MenuItem item) throws ExtractionException {
switch(item.getItemId()) {
case ITEM_ID_SUBSCRIPTIONS:
NavigationHelper.openSubscriptionFragment(getSupportFragmentManager());
break;
case ITEM_ID_FEED:
NavigationHelper.openWhatsNewFragment(getSupportFragmentManager());
break;
case ITEM_ID_BOOKMARKS:
NavigationHelper.openBookmarksFragment(getSupportFragmentManager());
break;
case ITEM_ID_DOWNLOADS:
NavigationHelper.openDownloads(this);
break;
case ITEM_ID_HISTORY:
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
break;
default:
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
StreamingService service = NewPipe.getService(currentServiceId);
String serviceName = "";
int kioskId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
if(kioskId == item.getItemId()) {
serviceName = ks;
}
kioskId ++;
}
NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId, serviceName);
break;
}
}
private void optionsAboutSelected(MenuItem item) {
switch(item.getItemId()) {
case ITEM_ID_SETTINGS:
NavigationHelper.openSettings(this);
break;
case ITEM_ID_ABOUT:
NavigationHelper.openAbout(this);
break;
}
}
private void setupDrawerHeader() {
headerServiceView = findViewById(R.id.drawer_header_service_view);
Button action = findViewById(R.id.drawer_header_action_button);
NavigationView navigationView = findViewById(R.id.navigation);
View hView = navigationView.getHeaderView(0);
serviceArrow = hView.findViewById(R.id.drawer_arrow);
headerServiceView = hView.findViewById(R.id.drawer_header_service_view);
Button action = hView.findViewById(R.id.drawer_header_action_button);
action.setOnClickListener(view -> {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://newpipe.schabi.org/blog/"));
startActivity(intent);
drawer.closeDrawers();
toggleServices();
});
}
private void toggleServices() {
servicesShown = !servicesShown;
drawerItems.getMenu().removeGroup(R.id.menu_services_group);
drawerItems.getMenu().removeGroup(R.id.menu_tabs_group);
drawerItems.getMenu().removeGroup(R.id.menu_options_about_group);
if(servicesShown) {
showServices();
} else {
try {
showTabs();
} catch (Exception e) {
ErrorActivity.reportUiError(this, e);
}
}
}
private void showServices() {
serviceArrow.setImageResource(R.drawable.ic_arrow_up_white);
for(StreamingService s : NewPipe.getServices()) {
final String title = s.getServiceInfo().getName() +
(ServiceHelper.isBeta(s) ? " (beta)" : "");
drawerItems.getMenu()
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
}
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
}
private void showTabs() throws ExtractionException {
serviceArrow.setImageResource(R.drawable.ic_arrow_down_white);
//Tabs
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
StreamingService service = NewPipe.getService(currentServiceId);
int kioskId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
drawerItems.getMenu()
.add(R.id.menu_tabs_group, kioskId, ORDER, KioskTranslator.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcons(ks, this));
kioskId ++;
}
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_whats_new)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
//Settings and About
drawerItems.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
drawerItems.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
}
@Override
protected void onDestroy() {
super.onDestroy();
@@ -329,16 +508,13 @@ public class MainActivity extends AppCompatActivity {
onHomeButtonPressed();
return true;
case R.id.action_show_downloads:
return NavigationHelper.openDownloads(this);
return NavigationHelper.openDownloads(this);
case R.id.action_history:
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
return true;
case R.id.action_about:
NavigationHelper.openAbout(this);
return true;
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
return true;
case R.id.action_settings:
NavigationHelper.openSettings(this);
return true;
NavigationHelper.openSettings(this);
return true;
default:
return super.onOptionsItemSelected(item);
}
@@ -382,31 +558,45 @@ public class MainActivity extends AppCompatActivity {
}
private void handleIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
try {
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
String url = intent.getStringExtra(Constants.KEY_URL);
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
String title = intent.getStringExtra(Constants.KEY_TITLE);
switch (((StreamingService.LinkType) intent.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
case STREAM:
boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(), serviceId, url, title, autoPlay);
break;
case CHANNEL:
NavigationHelper.openChannelFragment(getSupportFragmentManager(), serviceId, url, title);
break;
case PLAYLIST:
NavigationHelper.openPlaylistFragment(getSupportFragmentManager(), serviceId, url, title);
break;
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
String url = intent.getStringExtra(Constants.KEY_URL);
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
String title = intent.getStringExtra(Constants.KEY_TITLE);
switch (((StreamingService.LinkType) intent.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
case STREAM:
boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(), serviceId, url, title, autoPlay);
break;
case CHANNEL:
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
serviceId,
url,
title);
break;
case PLAYLIST:
NavigationHelper.openPlaylistFragment(getSupportFragmentManager(),
serviceId,
url,
title);
break;
}
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
String searchString = intent.getStringExtra(Constants.KEY_SEARCH_STRING);
if (searchString == null) searchString = "";
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
NavigationHelper.openSearchFragment(
getSupportFragmentManager(),
serviceId,
searchString);
} else {
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
}
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
String searchQuery = intent.getStringExtra(Constants.KEY_QUERY);
if (searchQuery == null) searchQuery = "";
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
NavigationHelper.openSearchFragment(getSupportFragmentManager(), serviceId, searchQuery);
} else {
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
} catch (Exception e) {
ErrorActivity.reportUiError(this, e);
}
}
}

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,13 +1,16 @@
package org.schabi.newpipe;
import android.annotation.SuppressLint;
import android.app.IntentService;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.AlertDialog;
@@ -23,6 +26,7 @@ import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Toast;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
@@ -31,13 +35,15 @@ import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -75,7 +81,12 @@ public class RouterActivity extends AppCompatActivity {
protected int selectedPreviously = -1;
protected String currentUrl;
protected CompositeDisposable disposables = new CompositeDisposable();
protected boolean internalRoute = false;
protected final CompositeDisposable disposables = new CompositeDisposable();
private boolean selectionIsDownload = false;
public static final String internalRouteKey = "internalRoute";
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -86,11 +97,13 @@ public class RouterActivity extends AppCompatActivity {
currentUrl = getUrl(getIntent());
if (TextUtils.isEmpty(currentUrl)) {
Toast.makeText(this, R.string.invalid_url_toast, Toast.LENGTH_LONG).show();
handleText();
finish();
}
}
internalRoute = getIntent().getBooleanExtra(internalRouteKey, false);
setTheme(ThemeHelper.isLightThemeSelected(this)
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
}
@@ -165,20 +178,27 @@ public class RouterActivity extends AppCompatActivity {
final String videoPlayerKey = getString(R.string.video_player_key);
final String backgroundPlayerKey = getString(R.string.background_player_key);
final String popupPlayerKey = getString(R.string.popup_player_key);
final String downloadKey = getString(R.string.download_key);
final String alwaysAskKey = getString(R.string.always_ask_open_action_key);
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);
} else if (selectedChoiceKey.equals(downloadKey)) {
handleChoice(downloadKey);
} else {
final boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
final boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
@@ -236,7 +256,9 @@ public class RouterActivity extends AppCompatActivity {
.setCancelable(true)
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
.setPositiveButton(R.string.always, dialogButtonsClickListener)
.setOnDismissListener((dialog) -> finish())
.setOnDismissListener((dialog) -> {
if(!selectionIsDownload) finish();
})
.create();
//noinspection CodeBlock2Expr
@@ -316,6 +338,9 @@ public class RouterActivity extends AppCompatActivity {
resolveResourceIdFromAttr(context, R.attr.audio)));
}
returnList.add(new AdapterChoiceItem(getString(R.string.download_key), getString(R.string.download),
resolveResourceIdFromAttr(context, R.attr.download)));
return returnList;
}
@@ -333,6 +358,15 @@ public class RouterActivity extends AppCompatActivity {
positiveButton.setEnabled(state);
}
private void handleText(){
String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
NavigationHelper.openSearch(getThemeWrapperContext(),serviceId,searchString);
}
private void handleChoice(final String selectedChoiceKey) {
final List<String> validChoicesList = Arrays.asList(getResources().getStringArray(R.array.preferred_open_action_values_list));
if (validChoicesList.contains(selectedChoiceKey)) {
@@ -347,6 +381,14 @@ public class RouterActivity extends AppCompatActivity {
return;
}
if (selectedChoiceKey.equals(getString(R.string.download_key))) {
if (PermissionHelper.checkStoragePermissions(this, PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
selectionIsDownload = true;
openDownloadDialog();
}
return;
}
// stop and bypass FetcherService if InfoScreen was selected since
// StreamDetailFragment can fetch data itself
if (selectedChoiceKey.equals(getString(R.string.show_info_key))) {
@@ -355,8 +397,10 @@ public class RouterActivity extends AppCompatActivity {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
if(!internalRoute){
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
}
startActivity(intent);
finish();
@@ -373,6 +417,47 @@ public class RouterActivity extends AppCompatActivity {
finish();
}
@SuppressLint("CheckResult")
private void openDownloadDialog() {
ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull StreamInfo result) -> {
List<VideoStream> sortedVideoStreams = ListHelper.getSortedStreamVideosList(this,
result.getVideoStreams(),
result.getVideoOnlyStreams(),
false);
int selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(this,
sortedVideoStreams);
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
downloadDialog.setVideoStreams(sortedVideoStreams);
downloadDialog.setAudioStreams(result.getAudioStreams());
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.show(fm, "downloadDialog");
fm.executePendingTransactions();
downloadDialog.getDialog().setOnDismissListener(dialog -> {
finish();
});
}, (@NonNull Throwable throwable) -> {
onError();
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
for (int i: grantResults){
if (i == PackageManager.PERMISSION_DENIED){
finish();
return;
}
}
if (requestCode == PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE) {
openDownloadDialog();
}
}
private static class AdapterChoiceItem {
final String description, key;
@DrawableRes final int icon;
@@ -473,8 +558,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;
@@ -486,9 +570,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

@@ -128,47 +128,31 @@ public class AboutActivity extends AppCompatActivity {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
Context context = this.getContext();
TextView version = rootView.findViewById(R.id.app_version);
version.setText(BuildConfig.VERSION_NAME);
View githubLink = rootView.findViewById(R.id.github_link);
githubLink.setOnClickListener(new OnGithubLinkClickListener());
githubLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.github_url), context));
View donationLink = rootView.findViewById(R.id.donation_link);
donationLink.setOnClickListener(new OnDonationLinkClickListener());
donationLink.setOnClickListener(v -> openWebsite(context.getString(R.string.donation_url), context));
View websiteLink = rootView.findViewById(R.id.website_link);
websiteLink.setOnClickListener(new OnWebsiteLinkClickListener());
websiteLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.website_url), context));
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
privacyPolicyLink.setOnClickListener(v -> openWebsite(context.getString(R.string.privacy_policy_url), context));
return rootView;
}
private static class OnGithubLinkClickListener implements View.OnClickListener {
@Override
public void onClick(final View view) {
final Context context = view.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.github_url)));
context.startActivity(intent);
}
private void openWebsite(String url, Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(intent);
}
private static class OnDonationLinkClickListener implements View.OnClickListener {
@Override
public void onClick(final View view) {
final Context context = view.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.donation_url)));
context.startActivity(intent);
}
}
private static class OnWebsiteLinkClickListener implements View.OnClickListener {
@Override
public void onClick(final View view) {
final Context context = view.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.website_url)));
context.startActivity(intent);
}
}
}

View File

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

View File

@@ -1,8 +1,10 @@
package org.schabi.newpipe.about;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.webkit.WebView;
import org.schabi.newpipe.R;
@@ -10,26 +12,46 @@ import org.schabi.newpipe.util.ThemeHelper;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
private Context context;
final WeakReference<Activity> weakReference;
private License license;
public LicenseFragmentHelper(@Nullable Activity activity) {
weakReference = new WeakReference<>(activity);
}
@Nullable
private Activity getActivity() {
Activity activity = weakReference.get();
if (activity != null && activity.isFinishing()) {
return null;
} else {
return activity;
}
}
@Override
protected Integer doInBackground(Object... objects) {
context = (Context) objects[0];
license = (License) objects[1];
license = (License) objects[0];
return 1;
}
@Override
protected void onPostExecute(Integer result){
String webViewData = getFormattedLicense(context, license);
AlertDialog.Builder alert = new AlertDialog.Builder(context);
protected void onPostExecute(Integer result) {
Activity activity = getActivity();
if (activity == null) {
return;
}
String webViewData = getFormattedLicense(activity, license);
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
alert.setTitle(license.getName());
WebView wv = new WebView(context);
WebView wv = new WebView(activity);
wv.loadData(webViewData, "text/html; charset=UTF-8", null);
alert.setView(wv);
@@ -55,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;
@@ -71,6 +70,14 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
info.getUploaderName(), info.getStreamCount());
}
@Ignore
public boolean isIdenticalTo(final PlaylistInfo info) {
return getServiceId() == info.getServiceId() && getName().equals(info.getName()) &&
getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) &&
getThumbnailUrl().equals(info.getThumbnailUrl()) &&
getUploader().equals(info.getUploaderName());
}
public long getUid() {
return uid;
}

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

@@ -16,11 +16,12 @@ import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.ThemeHelper;
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";
@Override
protected void onCreate(Bundle savedInstanceState) {
// Service
@@ -42,21 +43,20 @@ public class DownloadActivity extends AppCompatActivity {
actionBar.setDisplayShowTitleEnabled(true);
}
// Fragment
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
updateFragments();
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
private void updateFragments() {
MissionsFragment fragment = new MissionsFragment();
MissionsFragment fragment = new AllMissionsFragment();
getFragmentManager().beginTransaction()
.replace(R.id.frame, fragment)
.replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
@@ -87,4 +87,8 @@ public class DownloadActivity extends AppCompatActivity {
}
}
@Override
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

@@ -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;
@@ -51,9 +50,6 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
protected Button errorButtonRetry;
protected TextView errorTextView;
@State
protected boolean useAsFrontPage = false;
@Override
public void onViewCreated(View rootView, Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
@@ -66,9 +62,6 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
wasLoading.set(isLoading.get());
}
public void useAsFrontPage(boolean value) {
useAsFrontPage = value;
}
/*//////////////////////////////////////////////////////////////////////////
// Init
@@ -93,12 +86,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
RxView.clicks(errorButtonRetry)
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
onRetryButtonClicked();
}
});
.subscribe(o -> onRetryButtonClicked());
}
protected void onRetryButtonClicked() {
@@ -242,21 +230,4 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected void openUrlInBrowser(String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
}
protected void shareUrl(String subject, String url) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, url);
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
}
}

View File

@@ -2,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;
@@ -14,24 +13,16 @@ public class BlankFragment extends BaseFragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
if(activity != null && activity.getSupportActionBar() != null) {
activity.getSupportActionBar()
.setTitle("NewPipe");
}
setTitle("NewPipe");
return inflater.inflate(R.layout.fragment_blank, container, false);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser) {
if(activity != null && activity.getSupportActionBar() != null) {
activity.getSupportActionBar()
.setTitle("NewPipe");
}
// leave this inline. Will make it harder for copy cats.
// If you are a Copy cat FUCK YOU.
// I WILL FIND YOU, AND I WILL ...
}
setTitle("NewPipe");
// leave this inline. Will make it harder for copy cats.
// If you are a Copy cat FUCK YOU.
// I WILL FIND YOU, AND I WILL ...
}
}

View File

@@ -0,0 +1,17 @@
package org.schabi.newpipe.fragments;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
public class EmptyFragment extends BaseFragment {
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_empty, container, false);
}
}

View File

@@ -1,6 +1,5 @@
package org.schabi.newpipe.fragments;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -10,48 +9,37 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.kiosk.KioskList;
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
import org.schabi.newpipe.local.feed.FeedFragment;
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.settings.tabs.Tab;
import org.schabi.newpipe.settings.tabs.TabsManager;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
import java.util.List;
public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener {
public int currentServiceId = -1;
private ViewPager viewPager;
private SelectedTabsPagerAdapter pagerAdapter;
private TabLayout tabLayout;
/*//////////////////////////////////////////////////////////////////////////
// Constants
//////////////////////////////////////////////////////////////////////////*/
private List<Tab> tabsList = new ArrayList<>();
private TabsManager tabsManager;
private static final int FALLBACK_SERVICE_ID = ServiceList.YouTube.getServiceId();
private static final String FALLBACK_CHANNEL_URL = "https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ";
private static final String FALLBACK_CHANNEL_NAME = "Music";
private static final String FALLBACK_KIOSK_ID = "Trending";
private static final int KIOSK_MENU_OFFSET = 2000;
private boolean hasTabsChanged = false;
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
@@ -61,11 +49,22 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
tabsManager = TabsManager.getManager(activity);
tabsManager.setSavedTabsListener(() -> {
if (DEBUG) {
Log.d(TAG, "TabsManager.SavedTabsChangeListener: onTabsChanged called, isResumed = " + isResumed());
}
if (isResumed()) {
updateTabs();
} else {
hasTabsChanged = true;
}
});
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
currentServiceId = ServiceHelper.getSelectedServiceId(activity);
return inflater.inflate(R.layout.fragment_main, container, false);
}
@@ -73,30 +72,34 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
protected void initViews(View rootView, Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
TabLayout tabLayout = rootView.findViewById(R.id.main_tab_layout);
tabLayout = rootView.findViewById(R.id.main_tab_layout);
viewPager = rootView.findViewById(R.id.pager);
/* Nested fragment, use child fragment here to maintain backstack in view pager. */
PagerAdapter adapter = new PagerAdapter(getChildFragmentManager());
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(adapter.getCount());
pagerAdapter = new SelectedTabsPagerAdapter(getChildFragmentManager());
viewPager.setAdapter(pagerAdapter);
tabLayout.setupWithViewPager(viewPager);
tabLayout.addOnTabSelectedListener(this);
updateTabs();
}
int channelIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_channel);
int whatsHotIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_hot);
int bookmarkIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_bookmark);
@Override
public void onResume() {
super.onResume();
if (isSubscriptionsPageOnlySelected()) {
tabLayout.getTabAt(0).setIcon(channelIcon);
tabLayout.getTabAt(1).setIcon(bookmarkIcon);
} else {
tabLayout.getTabAt(0).setIcon(whatsHotIcon);
tabLayout.getTabAt(1).setIcon(channelIcon);
tabLayout.getTabAt(2).setIcon(bookmarkIcon);
if (hasTabsChanged) {
hasTabsChanged = false;
updateTabs();
}
}
@Override
public void onDestroy() {
super.onDestroy();
tabsManager.unsetSavedTabsListener();
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@@ -106,16 +109,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
super.onCreateOptionsMenu(menu, inflater);
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
inflater.inflate(R.menu.main_fragment_menu, menu);
SubMenu kioskMenu = menu.addSubMenu(Menu.NONE, Menu.NONE, 200, getString(R.string.kiosk));
try {
createKioskMenu(kioskMenu, inflater);
} catch (Exception e) {
ErrorActivity.reportError(activity, e,
activity.getClass(),
null,
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
"none", "", R.string.app_ui_crash));
}
ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
@@ -127,7 +120,14 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_search:
NavigationHelper.openSearchFragment(getFragmentManager(), ServiceHelper.getSelectedServiceId(activity), "");
try {
NavigationHelper.openSearchFragment(
getFragmentManager(),
ServiceHelper.getSelectedServiceId(activity),
"");
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
return true;
}
return super.onOptionsItemSelected(item);
@@ -137,9 +137,33 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
// Tabs
//////////////////////////////////////////////////////////////////////////*/
public void updateTabs() {
tabsList.clear();
tabsList.addAll(tabsManager.getTabs());
pagerAdapter.notifyDataSetChanged();
viewPager.setOffscreenPageLimit(pagerAdapter.getCount());
updateTabsIcon();
updateCurrentTitle();
}
private void updateTabsIcon() {
for (int i = 0; i < tabsList.size(); i++) {
final TabLayout.Tab tabToSet = tabLayout.getTabAt(i);
if (tabToSet != null) {
tabToSet.setIcon(tabsList.get(i).getTabIconRes(activity));
}
}
}
private void updateCurrentTitle() {
setTitle(tabsList.get(viewPager.getCurrentItem()).getTabName(requireContext()));
}
@Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
public void onTabSelected(TabLayout.Tab selectedTab) {
if (DEBUG) Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]");
updateCurrentTitle();
}
@Override
@@ -148,129 +172,58 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
@Override
public void onTabReselected(TabLayout.Tab tab) {
if (DEBUG) Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]");
updateCurrentTitle();
}
private class PagerAdapter extends FragmentPagerAdapter {
PagerAdapter(FragmentManager fm) {
super(fm);
private class SelectedTabsPagerAdapter extends FragmentPagerAdapter {
private SelectedTabsPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return isSubscriptionsPageOnlySelected() ? new SubscriptionFragment() : getMainPageFragment();
case 1:
if(PreferenceManager.getDefaultSharedPreferences(getActivity())
.getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key))
.equals(getString(R.string.subscription_page_key))) {
return new BookmarkFragment();
} else {
return new SubscriptionFragment();
}
case 2:
return new BookmarkFragment();
default:
return new BlankFragment();
final Tab tab = tabsList.get(position);
Throwable throwable = null;
Fragment fragment = null;
try {
fragment = tab.getFragment();
} catch (ExtractionException e) {
throwable = e;
}
if (throwable != null) {
ErrorActivity.reportError(activity, throwable, activity.getClass(), null,
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
return new BlankFragment();
}
if (fragment instanceof BaseFragment) {
((BaseFragment) fragment).useAsFrontPage(true);
}
return fragment;
}
@Override
public CharSequence getPageTitle(int position) {
//return getString(this.tabTitles[position]);
return "";
public int getItemPosition(Object object) {
// Causes adapter to reload all Fragments when
// notifyDataSetChanged is called
return POSITION_NONE;
}
@Override
public int getCount() {
return isSubscriptionsPageOnlySelected() ? 2 : 3;
return tabsList.size();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Main page content
//////////////////////////////////////////////////////////////////////////*/
private boolean isSubscriptionsPageOnlySelected() {
return PreferenceManager.getDefaultSharedPreferences(activity)
.getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key))
.equals(getString(R.string.subscription_page_key));
}
private Fragment getMainPageFragment() {
if (getActivity() == null) return new BlankFragment();
try {
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(getActivity());
final String setMainPage = preferences.getString(getString(R.string.main_page_content_key),
getString(R.string.main_page_selectd_kiosk_id));
if (setMainPage.equals(getString(R.string.blank_page_key))) {
return new BlankFragment();
} else if (setMainPage.equals(getString(R.string.kiosk_page_key))) {
int serviceId = preferences.getInt(getString(R.string.main_page_selected_service),
FALLBACK_SERVICE_ID);
String kioskId = preferences.getString(getString(R.string.main_page_selectd_kiosk_id),
FALLBACK_KIOSK_ID);
KioskFragment fragment = KioskFragment.getInstance(serviceId, kioskId);
fragment.useAsFrontPage(true);
return fragment;
} else if (setMainPage.equals(getString(R.string.feed_page_key))) {
FeedFragment fragment = new FeedFragment();
fragment.useAsFrontPage(true);
return fragment;
} else if (setMainPage.equals(getString(R.string.channel_page_key))) {
int serviceId = preferences.getInt(getString(R.string.main_page_selected_service),
FALLBACK_SERVICE_ID);
String url = preferences.getString(getString(R.string.main_page_selected_channel_url),
FALLBACK_CHANNEL_URL);
String name = preferences.getString(getString(R.string.main_page_selected_channel_name),
FALLBACK_CHANNEL_NAME);
ChannelFragment fragment = ChannelFragment.getInstance(serviceId, url, name);
fragment.useAsFrontPage(true);
return fragment;
} else {
return new BlankFragment();
}
} catch (Exception e) {
ErrorActivity.reportError(activity, e,
activity.getClass(),
null,
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
"none", "", R.string.app_ui_crash));
return new BlankFragment();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Select Kiosk
//////////////////////////////////////////////////////////////////////////*/
private void createKioskMenu(Menu menu, MenuInflater menuInflater)
throws Exception {
StreamingService service = NewPipe.getService(currentServiceId);
KioskList kl = service.getKioskList();
int i = 0;
for (final String ks : kl.getAvailableKiosks()) {
menu.add(0, KIOSK_MENU_OFFSET + i, Menu.NONE,
KioskTranslator.getTranslatedKioskName(ks, getContext()))
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
try {
NavigationHelper.openKioskFragment(getFragmentManager(), currentServiceId, ks);
} catch (Exception e) {
ErrorActivity.reportError(activity, e,
activity.getClass(),
null,
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
"none", "", R.string.app_ui_crash));
}
return true;
}
});
i++;
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
getChildFragmentManager()
.beginTransaction()
.remove((Fragment) object)
.commitNowAllowingStateLoss();
}
}
}

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

@@ -0,0 +1,86 @@
package org.schabi.newpipe.fragments.detail;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
public class TabAdaptor extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
private final FragmentManager fragmentManager;
public TabAdaptor(FragmentManager fm) {
super(fm);
this.fragmentManager = fm;
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
public void clearAllItems() {
mFragmentList.clear();
mFragmentTitleList.clear();
}
public void removeItem(int position){
mFragmentList.remove(position == 0 ? 0 : position - 1);
mFragmentTitleList.remove(position == 0 ? 0 : position - 1);
}
public void updateItem(int position, Fragment fragment){
mFragmentList.set(position, fragment);
}
public void updateItem(String title, Fragment fragment){
int index = mFragmentTitleList.indexOf(title);
if(index != -1){
updateItem(index, fragment);
}
}
@Override
public int getItemPosition(Object object) {
if (mFragmentList.contains(object)) return mFragmentList.indexOf(object);
else return POSITION_NONE;
}
public int getItemPositionByTitle(String title) {
return mFragmentTitleList.indexOf(title);
}
@Nullable
public String getItemTitle(int position) {
if (position < 0 || position >= mFragmentTitleList.size()) {
return null;
}
return mFragmentTitleList.get(position);
}
public void notifyDataSetUpdate(){
notifyDataSetChanged();
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
}
}

View File

@@ -10,13 +10,16 @@ import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
@@ -24,7 +27,6 @@ import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -34,7 +36,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
@@ -42,7 +43,6 @@ import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.nirhart.parallaxscroll.views.ParallaxScrollView;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
@@ -52,28 +52,32 @@ import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
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.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.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.fragments.EmptyFragment;
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
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.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
@@ -81,9 +85,10 @@ import org.schabi.newpipe.util.InfoCache;
import org.schabi.newpipe.util.ListHelper;
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.ThemeHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import java.io.Serializable;
import java.util.Collection;
@@ -98,6 +103,7 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class VideoDetailFragment
@@ -108,26 +114,27 @@ public class VideoDetailFragment
View.OnLongClickListener {
public static final String AUTO_PLAY = "auto_play";
// Amount of videos to show on start
private static final int INITIAL_RELATED_VIDEOS = 8;
private InfoItemBuilder infoItemBuilder = null;
private int updateFlags = 0;
private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1;
private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2;
private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4;
private static final int COMMENTS_UPDATE_FLAG = 0x4;
private boolean autoPlayEnabled;
private boolean showRelatedStreams;
private boolean wasRelatedStreamsExpanded = false;
private boolean showComments;
private String selectedTabTag;
@State protected int serviceId = Constants.NO_SERVICE_ID;
@State protected String name;
@State protected String url;
@State
protected int serviceId = Constants.NO_SERVICE_ID;
@State
protected String name;
@State
protected String url;
private StreamInfo currentInfo;
private Disposable currentWorker;
@NonNull
private CompositeDisposable disposables = new CompositeDisposable();
private List<VideoStream> sortedVideoStreams;
@@ -141,7 +148,6 @@ public class VideoDetailFragment
private Spinner spinnerToolbar;
private ParallaxScrollView parallaxScrollRootView;
private LinearLayout contentRootLayoutHiding;
private View thumbnailBackgroundButton;
@@ -174,10 +180,15 @@ public class VideoDetailFragment
private ImageView thumbsDownImageView;
private TextView thumbsDisabledTextView;
private TextView nextStreamTitle;
private LinearLayout relatedStreamRootLayout;
private LinearLayout relatedStreamsView;
private ImageButton relatedStreamExpandButton;
private static final String COMMENTS_TAB_TAG = "COMMENTS";
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
private AppBarLayout appBarLayout;
private ViewPager viewPager;
private TabAdaptor pageAdapter;
private TabLayout tabLayout;
private FrameLayout relatedStreamsLayout;
/*////////////////////////////////////////////////////////////////////////*/
@@ -193,12 +204,20 @@ public class VideoDetailFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(Bundle savedInstanceState) {
public void
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
showRelatedStreams = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(getString(R.string.show_next_video_key), true);
showComments = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(getString(R.string.show_comments_key), true);
selectedTabTag = PreferenceManager.getDefaultSharedPreferences(activity)
.getString(getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG);
PreferenceManager.getDefaultSharedPreferences(activity)
.registerOnSharedPreferenceChangeListener(this);
}
@@ -212,6 +231,10 @@ public class VideoDetailFragment
public void onPause() {
super.onPause();
if (currentWorker != null) currentWorker.dispose();
PreferenceManager.getDefaultSharedPreferences(getContext())
.edit()
.putString(getString(R.string.stream_info_selected_tab_key), pageAdapter.getItemTitle(viewPager.getCurrentItem()))
.apply();
}
@Override
@@ -220,14 +243,16 @@ public class VideoDetailFragment
if (updateFlags != 0) {
if (!isLoading.get() && currentInfo != null) {
if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) initRelatedVideos(currentInfo);
if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) startLoading(false);
if ((updateFlags & RESOLUTIONS_MENU_UPDATE_FLAG) != 0) setupActionBar(currentInfo);
if ((updateFlags & COMMENTS_UPDATE_FLAG) != 0) startLoading(false);
}
if ((updateFlags & TOOLBAR_ITEMS_UPDATE_FLAG) != 0
&& menu != null) {
updateMenuItemVisibility();
}
updateFlags = 0;
}
@@ -284,6 +309,9 @@ public class VideoDetailFragment
updateFlags |= RESOLUTIONS_MENU_UPDATE_FLAG;
} else if (key.equals(getString(R.string.show_play_with_kodi_key))) {
updateFlags |= TOOLBAR_ITEMS_UPDATE_FLAG;
} else if (key.equals(getString(R.string.show_comments_key))) {
showComments = sharedPreferences.getBoolean(key, true);
updateFlags |= COMMENTS_UPDATE_FLAG;
}
}
@@ -293,7 +321,6 @@ public class VideoDetailFragment
private static final String INFO_KEY = "info_key";
private static final String STACK_KEY = "stack_key";
private static final String WAS_RELATED_EXPANDED_KEY = "was_related_expanded_key";
@Override
public void onSaveInstanceState(Bundle outState) {
@@ -302,10 +329,6 @@ public class VideoDetailFragment
// Check if the next video label and video is visible,
// if it is, include the two elements in the next check
int nextCount = currentInfo != null && currentInfo.getNextVideo() != null ? 2 : 0;
if (relatedStreamsView != null
&& relatedStreamsView.getChildCount() > INITIAL_RELATED_VIDEOS + nextCount) {
outState.putSerializable(WAS_RELATED_EXPANDED_KEY, true);
}
if (!isLoading.get() && currentInfo != null && isVisible()) {
outState.putSerializable(INFO_KEY, currentInfo);
@@ -318,12 +341,11 @@ public class VideoDetailFragment
protected void onRestoreInstanceState(@NonNull Bundle savedState) {
super.onRestoreInstanceState(savedState);
wasRelatedStreamsExpanded = savedState.getBoolean(WAS_RELATED_EXPANDED_KEY, false);
Serializable serializable = savedState.getSerializable(INFO_KEY);
if (serializable instanceof StreamInfo) {
//noinspection unchecked
currentInfo = (StreamInfo) serializable;
InfoCache.getInstance().putInfo(serviceId, url, currentInfo);
InfoCache.getInstance().putInfo(serviceId, url, currentInfo, InfoItem.InfoType.STREAM);
}
serializable = savedState.getSerializable(STACK_KEY);
@@ -331,6 +353,7 @@ public class VideoDetailFragment
//noinspection unchecked
stack.addAll((Collection<? extends StackItem>) serializable);
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -355,7 +378,8 @@ public class VideoDetailFragment
}
break;
case R.id.detail_controls_download:
if (PermissionHelper.checkStoragePermissions(activity, PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
if (PermissionHelper.checkStoragePermissions(activity,
PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
this.openDownloadDialog();
}
break;
@@ -363,11 +387,15 @@ public class VideoDetailFragment
if (TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
Log.w(TAG, "Can't open channel because we got no channel URL");
} else {
try {
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:
@@ -381,9 +409,6 @@ public class VideoDetailFragment
case R.id.detail_title_root_layout:
toggleTitleAndDescription();
break;
case R.id.detail_related_streams_expand:
toggleExpandRelatedVideos(currentInfo);
break;
}
}
@@ -418,32 +443,6 @@ public class VideoDetailFragment
}
}
private void toggleExpandRelatedVideos(StreamInfo info) {
if (DEBUG) Log.d(TAG, "toggleExpandRelatedVideos() called with: info = [" + info + "]");
if (!showRelatedStreams) return;
int nextCount = info.getNextVideo() != null ? 2 : 0;
int initialCount = INITIAL_RELATED_VIDEOS + nextCount;
if (relatedStreamsView.getChildCount() > initialCount) {
relatedStreamsView.removeViews(initialCount,
relatedStreamsView.getChildCount() - (initialCount));
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(
activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand)));
return;
}
//Log.d(TAG, "toggleExpandRelatedVideos() called with: info = [" + info + "], from = [" + INITIAL_RELATED_VIDEOS + "]");
for (int i = INITIAL_RELATED_VIDEOS; i < info.getRelatedStreams().size(); i++) {
InfoItem item = info.getRelatedStreams().get(i);
//Log.d(TAG, "i = " + i);
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
}
relatedStreamExpandButton.setImageDrawable(
ContextCompat.getDrawable(activity,
ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.collapse)));
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
@@ -453,8 +452,6 @@ public class VideoDetailFragment
super.initViews(rootView, savedInstanceState);
spinnerToolbar = activity.findViewById(R.id.toolbar).findViewById(R.id.toolbar_spinner);
parallaxScrollRootView = rootView.findViewById(R.id.detail_main_content);
thumbnailBackgroundButton = rootView.findViewById(R.id.detail_thumbnail_root_layout);
thumbnailImageView = rootView.findViewById(R.id.detail_thumbnail_image_view);
thumbnailPlayButton = rootView.findViewById(R.id.detail_thumbnail_play_button);
@@ -490,30 +487,23 @@ public class VideoDetailFragment
uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view);
uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view);
relatedStreamRootLayout = rootView.findViewById(R.id.detail_related_streams_root_layout);
nextStreamTitle = rootView.findViewById(R.id.detail_next_stream_title);
relatedStreamsView = rootView.findViewById(R.id.detail_related_streams_view);
appBarLayout = rootView.findViewById(R.id.appbarlayout);
viewPager = rootView.findViewById(R.id.viewpager);
pageAdapter = new TabAdaptor(getChildFragmentManager());
viewPager.setAdapter(pageAdapter);
tabLayout = rootView.findViewById(R.id.tablayout);
tabLayout.setupWithViewPager(viewPager);
relatedStreamExpandButton = rootView.findViewById(R.id.detail_related_streams_expand);
relatedStreamsLayout = rootView.findViewById(R.id.relatedStreamsLayout);
infoItemBuilder = new InfoItemBuilder(activity);
setHeightThumbnail();
}
@Override
protected void initListeners() {
super.initListeners();
infoItemBuilder.setOnStreamSelectedListener(new OnClickGesture<StreamInfoItem>() {
@Override
public void selected(StreamInfoItem selectedItem) {
selectAndLoadVideo(selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
}
@Override
public void held(StreamInfoItem selectedItem) {
showStreamDialog(selectedItem);
}
});
videoTitleRoot.setOnClickListener(this);
uploaderRootLayout.setOnClickListener(this);
@@ -523,7 +513,6 @@ public class VideoDetailFragment
detailControlsAddToPlaylist.setOnClickListener(this);
detailControlsDownload.setOnClickListener(this);
detailControlsDownload.setOnLongClickListener(this);
relatedStreamExpandButton.setOnClickListener(this);
detailControlsBackground.setLongClickable(true);
detailControlsPopup.setLongClickable(true);
@@ -540,7 +529,8 @@ public class VideoDetailFragment
final String[] commands = new String[]{
context.getResources().getString(R.string.enqueue_on_background),
context.getResources().getString(R.string.enqueue_on_popup),
context.getResources().getString(R.string.append_playlist)
context.getResources().getString(R.string.append_playlist),
context.getResources().getString(R.string.share)
};
final DialogInterface.OnClickListener actions = (DialogInterface dialogInterface, int i) -> {
@@ -557,6 +547,9 @@ public class VideoDetailFragment
.show(getFragmentManager(), TAG);
}
break;
case 3:
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
break;
default:
break;
}
@@ -602,41 +595,6 @@ public class VideoDetailFragment
}
}
private void initRelatedVideos(StreamInfo info) {
if (relatedStreamsView.getChildCount() > 0) relatedStreamsView.removeAllViews();
if (info.getNextVideo() != null && showRelatedStreams) {
nextStreamTitle.setVisibility(View.VISIBLE);
relatedStreamsView.addView(
infoItemBuilder.buildView(relatedStreamsView, info.getNextVideo()));
relatedStreamsView.addView(getSeparatorView());
relatedStreamRootLayout.setVisibility(View.VISIBLE);
} else nextStreamTitle.setVisibility(View.GONE);
if (info.getRelatedStreams() != null
&& !info.getRelatedStreams().isEmpty() && showRelatedStreams) {
//long first = System.nanoTime(), each;
int to = info.getRelatedStreams().size() >= INITIAL_RELATED_VIDEOS
? INITIAL_RELATED_VIDEOS
: info.getRelatedStreams().size();
for (int i = 0; i < to; i++) {
InfoItem item = info.getRelatedStreams().get(i);
//each = System.nanoTime();
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
//if (DEBUG) Log.d(TAG, "each took " + ((System.nanoTime() - each) / 1000000L) + "ms");
}
//if (DEBUG) Log.d(TAG, "Total time " + ((System.nanoTime() - first) / 1000000L) + "ms");
relatedStreamRootLayout.setVisibility(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);
relatedStreamExpandButton.setVisibility(View.GONE);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
@@ -670,7 +628,7 @@ public class VideoDetailFragment
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if(isLoading.get()) {
if (isLoading.get()) {
// if is still loading block menu
return true;
}
@@ -679,13 +637,13 @@ public class VideoDetailFragment
switch (id) {
case R.id.menu_item_share: {
if (currentInfo != null) {
shareUrl(currentInfo.getName(), currentInfo.getUrl());
ShareUtils.shareUrl(this.getContext(), currentInfo.getName(), currentInfo.getOriginalUrl());
}
return true;
}
case R.id.menu_item_openInBrowser: {
if (currentInfo != null) {
openUrlInBrowser(currentInfo.getUrl());
ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl());
}
return true;
}
@@ -694,7 +652,7 @@ public class VideoDetailFragment
NavigationHelper.playWithKore(activity, Uri.parse(
url.replace("https", "http")));
} catch (Exception e) {
if(DEBUG) Log.i(TAG, "Failed to start kore", e);
if (DEBUG) Log.i(TAG, "Failed to start kore", e);
showInstallKoreDialog(activity);
}
return true;
@@ -708,7 +666,8 @@ public class VideoDetailFragment
builder.setMessage(R.string.kore_not_found)
.setPositiveButton(R.string.install, (DialogInterface dialog, int which) ->
NavigationHelper.installKore(context))
.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {});
.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
});
builder.create().show();
}
@@ -722,10 +681,16 @@ public class VideoDetailFragment
boolean isExternalPlayerEnabled = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.use_external_video_player_key), false);
sortedVideoStreams = ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false);
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() {
@@ -748,19 +713,19 @@ public class VideoDetailFragment
* Stack that contains the "navigation history".<br>
* The peek is the current video.
*/
protected LinkedList<StackItem> stack = new LinkedList<>();
public void clearHistory() {
stack.clear();
}
protected final LinkedList<StackItem> stack = new LinkedList<>();
public void pushToStack(int serviceId, String videoUrl, String name) {
if (DEBUG) {
Log.d(TAG, "pushToStack() called with: serviceId = [" + serviceId + "], videoUrl = [" + videoUrl + "], name = [" + name + "]");
Log.d(TAG, "pushToStack() called with: serviceId = ["
+ serviceId + "], videoUrl = [" + videoUrl + "], name = [" + name + "]");
}
if (stack.size() > 0 && stack.peek().getServiceId() == serviceId && stack.peek().getUrl().equals(videoUrl)) {
Log.d(TAG, "pushToStack() called with: serviceId == peek.serviceId = [" + serviceId + "], videoUrl == peek.getUrl = [" + videoUrl + "]");
if (stack.size() > 0
&& stack.peek().getServiceId() == serviceId
&& stack.peek().getUrl().equals(videoUrl)) {
Log.d(TAG, "pushToStack() called with: serviceId == peek.serviceId = ["
+ serviceId + "], videoUrl == peek.getUrl = [" + videoUrl + "]");
return;
} else {
Log.d(TAG, "pushToStack() wasn't equal");
@@ -791,7 +756,11 @@ public class VideoDetailFragment
// Get stack item from the new top
StackItem peek = stack.peek();
selectAndLoadVideo(peek.getServiceId(), peek.getUrl(), !TextUtils.isEmpty(peek.getTitle()) ? peek.getTitle() : "");
selectAndLoadVideo(peek.getServiceId(),
peek.getUrl(),
!TextUtils.isEmpty(peek.getTitle())
? peek.getTitle()
: "");
return true;
}
@@ -811,28 +780,22 @@ public class VideoDetailFragment
}
public void prepareAndHandleInfo(final StreamInfo info, boolean scrollToTop) {
if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = [" + info + "], scrollToTop = [" + scrollToTop + "]");
if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = ["
+ info + "], scrollToTop = [" + scrollToTop + "]");
setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName());
setInitialData(info.getServiceId(), info.getUrl(), info.getName());
pushToStack(serviceId, url, name);
showLoading();
initTabs();
Log.d(TAG, "prepareAndHandleInfo() called parallaxScrollRootView.getScrollY(): "
+ parallaxScrollRootView.getScrollY());
final boolean greaterThanThreshold = parallaxScrollRootView.getScrollY() > (int)
(getResources().getDisplayMetrics().heightPixels * .1f);
if (scrollToTop) appBarLayout.setExpanded(true, true);
handleResult(info);
showContent();
if (scrollToTop) parallaxScrollRootView.smoothScrollTo(0, 0);
animateView(contentRootLayoutHiding,
false,
greaterThanThreshold ? 250 : 0, 0, () -> {
handleResult(info);
showContentWithAnimation(120, 0, .01f);
});
}
protected void prepareAndLoadInfo() {
parallaxScrollRootView.smoothScrollTo(0, 0);
appBarLayout.setExpanded(true, true);
pushToStack(serviceId, url, name);
startLoading(false);
}
@@ -841,6 +804,7 @@ public class VideoDetailFragment
public void startLoading(boolean forceLoad) {
super.startLoading(forceLoad);
initTabs();
currentInfo = null;
if (currentWorker != null) currentWorker.dispose();
@@ -850,12 +814,54 @@ public class VideoDetailFragment
.subscribe((@NonNull StreamInfo result) -> {
isLoading.set(false);
currentInfo = result;
showContentWithAnimation(120, 0, 0);
handleResult(result);
showContent();
}, (@NonNull Throwable throwable) -> {
isLoading.set(false);
onError(throwable);
});
}
private void initTabs() {
if (pageAdapter.getCount() != 0) {
selectedTabTag = pageAdapter.getItemTitle(viewPager.getCurrentItem());
}
pageAdapter.clearAllItems();
if(shouldShowComments()){
pageAdapter.addFragment(CommentsFragment.getInstance(serviceId, url, name), COMMENTS_TAB_TAG);
}
if(showRelatedStreams && null == relatedStreamsLayout){
//temp empty fragment. will be updated in handleResult
pageAdapter.addFragment(new Fragment(), RELATED_TAB_TAG);
}
if(pageAdapter.getCount() == 0){
pageAdapter.addFragment(new EmptyFragment(), EMPTY_TAB_TAG);
}
pageAdapter.notifyDataSetUpdate();
if(pageAdapter.getCount() < 2){
tabLayout.setVisibility(View.GONE);
}else{
int position = pageAdapter.getItemPositionByTitle(selectedTabTag);
if(position != -1) viewPager.setCurrentItem(position);
tabLayout.setVisibility(View.VISIBLE);
}
}
private boolean shouldShowComments() {
try {
return showComments && NewPipe.getService(serviceId)
.getServiceInfo()
.getMediaCapabilities()
.contains(COMMENTS);
} catch (ExtractionException e) {
return false;
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -872,10 +878,7 @@ public class VideoDetailFragment
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 16) {
openNormalBackgroundPlayer(append);
} else {
NavigationHelper.playOnExternalPlayer(activity,
currentInfo.getName(),
currentInfo.getUploaderName(),
audioStream);
startOnExternalPlayer(activity, currentInfo, audioStream);
}
}
@@ -902,12 +905,9 @@ public class VideoDetailFragment
if (PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
NavigationHelper.playOnExternalPlayer(activity,
currentInfo.getName(),
currentInfo.getUploaderName(),
selectedVideoStream);
startOnExternalPlayer(activity, currentInfo, selectedVideoStream);
} else {
openNormalPlayer(selectedVideoStream);
openNormalPlayer();
}
}
@@ -920,24 +920,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);
}
@@ -949,6 +938,20 @@ public class VideoDetailFragment
this.autoPlayEnabled = autoplay;
}
private void startOnExternalPlayer(@NonNull final Context context,
@NonNull final StreamInfo info,
@NonNull final Stream selectedStream) {
NavigationHelper.playOnExternalPlayer(context, currentInfo.getName(),
currentInfo.getUploaderName(), selectedStream);
final HistoryRecordManager recordManager = new HistoryRecordManager(requireContext());
disposables.add(recordManager.onViewed(info).onErrorComplete()
.subscribe(
ignored -> {/* successful */},
error -> Log.e(TAG, "Register view failure: ", error)
));
}
@Nullable
private VideoStream getSelectedVideoStream() {
return sortedVideoStreams != null ? sortedVideoStreams.get(selectedVideoStreamIndex) : null;
@@ -978,24 +981,6 @@ public class VideoDetailFragment
}));
}
private View getSeparatorView() {
View separator = new View(activity);
LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1);
int m8 = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
int m5 = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics());
params.setMargins(m8, m5, m8, m5);
separator.setLayoutParams(params);
TypedValue typedValue = new TypedValue();
activity.getTheme().resolveAttribute(R.attr.separator_color, typedValue, true);
separator.setBackgroundColor(typedValue.data);
return separator;
}
private void setHeightThumbnail() {
final DisplayMetrics metrics = getResources().getDisplayMetrics();
boolean isPortrait = metrics.heightPixels > metrics.widthPixels;
@@ -1007,49 +992,8 @@ public class VideoDetailFragment
thumbnailImageView.setMinimumHeight(height);
}
private void showContentWithAnimation(long duration,
long delay,
@FloatRange(from = 0.0f, to = 1.0f) float translationPercent) {
int translationY = (int) (getResources().getDisplayMetrics().heightPixels *
(translationPercent > 0.0f ? translationPercent : .06f));
contentRootLayoutHiding.animate().setListener(null).cancel();
contentRootLayoutHiding.setAlpha(0f);
contentRootLayoutHiding.setTranslationY(translationY);
contentRootLayoutHiding.setVisibility(View.VISIBLE);
contentRootLayoutHiding.animate()
.alpha(1f)
.translationY(0)
.setStartDelay(delay)
.setDuration(duration)
.setInterpolator(new FastOutSlowInInterpolator())
.start();
uploaderRootLayout.animate().setListener(null).cancel();
uploaderRootLayout.setAlpha(0f);
uploaderRootLayout.setTranslationY(translationY);
uploaderRootLayout.setVisibility(View.VISIBLE);
uploaderRootLayout.animate()
.alpha(1f)
.translationY(0)
.setStartDelay((long) (duration * .5f) + delay)
.setDuration(duration)
.setInterpolator(new FastOutSlowInInterpolator())
.start();
if (showRelatedStreams) {
relatedStreamRootLayout.animate().setListener(null).cancel();
relatedStreamRootLayout.setAlpha(0f);
relatedStreamRootLayout.setTranslationY(translationY);
relatedStreamRootLayout.setVisibility(View.VISIBLE);
relatedStreamRootLayout.animate()
.alpha(1f)
.translationY(0)
.setStartDelay((long) (duration * .8f) + delay)
.setDuration(duration)
.setInterpolator(new FastOutSlowInInterpolator())
.start();
}
private void showContent() {
AnimationUtils.slideUp(contentRootLayoutHiding,120, 96, 0.06f);
}
protected void setInitialData(int serviceId, String url, String name) {
@@ -1084,7 +1028,7 @@ public class VideoDetailFragment
public void showLoading() {
super.showLoading();
animateView(contentRootLayoutHiding, false, 200);
contentRootLayoutHiding.setVisibility(View.INVISIBLE);
animateView(spinnerToolbar, false, 200);
animateView(thumbnailPlayButton, false, 50);
animateView(detailDurationView, false, 100);
@@ -1098,6 +1042,14 @@ public class VideoDetailFragment
videoTitleToggleArrow.setVisibility(View.GONE);
videoTitleRoot.setClickable(false);
if(relatedStreamsLayout != null){
if(showRelatedStreams){
relatedStreamsLayout.setVisibility(View.INVISIBLE);
}else{
relatedStreamsLayout.setVisibility(View.GONE);
}
}
imageLoader.cancelDisplayTask(thumbnailImageView);
imageLoader.cancelDisplayTask(uploaderThumb);
thumbnailImageView.setImageBitmap(null);
@@ -1109,7 +1061,20 @@ public class VideoDetailFragment
super.handleResult(info);
setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName());
pushToStack(serviceId, url, name);
if(showRelatedStreams){
if(null == relatedStreamsLayout){ //phone
pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(currentInfo));
pageAdapter.notifyDataSetUpdate();
}else{ //tablet
getChildFragmentManager().beginTransaction()
.replace(R.id.relatedStreamsLayout, RelatedVideosFragment.getInstance(currentInfo))
.commitNow();
relatedStreamsLayout.setVisibility(View.VISIBLE);
}
}
//pushToStack(serviceId, url, name);
animateView(thumbnailPlayButton, true, 200);
videoTitleTextView.setText(name);
@@ -1160,20 +1125,22 @@ public class VideoDetailFragment
if (info.getDuration() > 0) {
detailDurationView.setText(Localization.getDurationString(info.getDuration()));
detailDurationView.setBackgroundColor(ContextCompat.getColor(activity, R.color.duration_background_color));
detailDurationView.setBackgroundColor(
ContextCompat.getColor(activity, R.color.duration_background_color));
animateView(detailDurationView, true, 100);
} else if (info.getStreamType() == StreamType.LIVE_STREAM) {
detailDurationView.setText(R.string.duration_live);
detailDurationView.setBackgroundColor(ContextCompat.getColor(activity, R.color.live_duration_background_color));
detailDurationView.setBackgroundColor(
ContextCompat.getColor(activity, R.color.live_duration_background_color));
animateView(detailDurationView, true, 100);
} else {
detailDurationView.setVisibility(View.GONE);
}
videoDescriptionView.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 (!TextUtils.isEmpty(info.getUploadDate())) {
videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate()));
@@ -1183,11 +1150,6 @@ public class VideoDetailFragment
animateView(spinnerToolbar, true, 500);
setupActionBar(info);
initThumbnailViews(info);
initRelatedVideos(info);
if (wasRelatedStreamsExpanded) {
toggleExpandRelatedVideos(currentInfo);
wasRelatedStreamsExpanded = false;
}
setTitleToUrl(info.getServiceId(), info.getUrl(), info.getName());
setTitleToUrl(info.getServiceId(), info.getOriginalUrl(), info.getName());
@@ -1207,10 +1169,10 @@ public class VideoDetailFragment
spinnerToolbar.setVisibility(View.GONE);
break;
default:
if(info.getAudioStreams().isEmpty()) detailControlsBackground.setVisibility(View.GONE);
if (!info.getVideoStreams().isEmpty()
|| !info.getVideoOnlyStreams().isEmpty()) break;
detailControlsBackground.setVisibility(View.GONE);
detailControlsPopup.setVisibility(View.GONE);
spinnerToolbar.setVisibility(View.GONE);
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_white_24dp);
@@ -1231,13 +1193,22 @@ 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) {
Toast.makeText(activity,
R.string.could_not_setup_download_menu,
Toast.LENGTH_LONG).show();
e.printStackTrace();
ErrorActivity.ErrorInfo info = ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
ServiceList.all()
.get(currentInfo
.getServiceId())
.getServiceInfo()
.getName(), "",
R.string.could_not_setup_download_menu);
ErrorActivity.reportError(getActivity(),
e,
getActivity().getClass(),
getActivity().findViewById(android.R.id.content), info);
}
}
@@ -1279,4 +1250,4 @@ public class VideoDetailFragment
showError(getString(R.string.blocked_by_gema), false, R.drawable.gruese_die_gema);
}
}
}

View File

@@ -3,9 +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;
@@ -16,16 +22,19 @@ import android.view.View;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
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;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StateSaver;
import java.util.Collections;
@@ -34,7 +43,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
@@ -42,6 +51,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
@@ -57,12 +69,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;
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -117,13 +148,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());
@@ -152,18 +195,37 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() {
@Override
public void selected(ChannelInfoItem selectedItem) {
onItemSelected(selectedItem);
NavigationHelper.openChannelFragment(useAsFrontPage ? getParentFragment().getFragmentManager() : getFragmentManager(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
try {
onItemSelected(selectedItem);
NavigationHelper.openChannelFragment(getFM(),
selectedItem.getServiceId(),
selectedItem.getUrl(),
selectedItem.getName());
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
}
});
infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<PlaylistInfoItem>() {
@Override
public void selected(PlaylistInfoItem selectedItem) {
try {
onItemSelected(selectedItem);
NavigationHelper.openPlaylistFragment(getFM(),
selectedItem.getServiceId(),
selectedItem.getUrl(),
selectedItem.getName());
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
}
});
infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() {
@Override
public void selected(CommentsInfoItem selectedItem) {
onItemSelected(selectedItem);
NavigationHelper.openPlaylistFragment(useAsFrontPage ? getParentFragment().getFragmentManager() : getFragmentManager(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
}
});
@@ -178,7 +240,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
private void onStreamSelected(StreamInfoItem selectedItem) {
onItemSelected(selectedItem);
NavigationHelper.openVideoDetailFragment(useAsFrontPage ? getParentFragment().getFragmentManager() : getFragmentManager(),
NavigationHelper.openVideoDetailFragment(getFM(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
}
@@ -194,25 +256,33 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
if (context == null || context.getResources() == null || getActivity() == null) return;
final String[] commands = new String[]{
context.getResources().getString(R.string.direct_on_background),
context.getResources().getString(R.string.enqueue_on_background),
context.getResources().getString(R.string.enqueue_on_popup),
context.getResources().getString(R.string.append_playlist)
context.getResources().getString(R.string.append_playlist),
context.getResources().getString(R.string.share)
};
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
NavigationHelper.playOnBackgroundPlayer(context, new SinglePlayQueue(item));
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
break;
case 2:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
break;
case 3:
if (getFragmentManager() != null) {
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
.show(getFragmentManager(), TAG);
}
break;
case 4:
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
break;
default:
break;
}
@@ -290,4 +360,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

@@ -16,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>
@@ -166,7 +165,6 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
public void handleResult(@NonNull I result) {
super.handleResult(result);
url = result.getUrl();
name = result.getName();
setTitle(name);

View File

@@ -36,16 +36,17 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.subscription.SubscriptionService;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.local.subscription.SubscriptionService;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
import java.util.ArrayList;
import java.util.Collections;
@@ -68,7 +69,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;
@@ -161,38 +162,39 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
context.getResources().getString(R.string.start_here_on_main),
context.getResources().getString(R.string.start_here_on_background),
context.getResources().getString(R.string.start_here_on_popup),
context.getResources().getString(R.string.append_playlist)
context.getResources().getString(R.string.append_playlist),
context.getResources().getString(R.string.share)
};
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
break;
case 2:
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
break;
case 3:
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
break;
case 4:
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
break;
case 5:
if (getFragmentManager() != null) {
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
.show(getFragmentManager(), TAG);
}
break;
default:
break;
}
final DialogInterface.OnClickListener actions = (DialogInterface dialogInterface, int i) -> {
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
break;
case 2:
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
break;
case 3:
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
break;
case 4:
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
break;
case 5:
if (getFragmentManager() != null) {
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
.show(getFragmentManager(), TAG);
}
break;
case 6:
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
break;
default:
break;
}
};
@@ -232,10 +234,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
openRssFeed();
break;
case R.id.menu_item_openInBrowser:
openUrlInBrowser(url);
ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl());
break;
case R.id.menu_item_share:
shareUrl(name, url);
ShareUtils.shareUrl(this.getContext(), name, currentInfo.getOriginalUrl());
break;
default:
return super.onOptionsItemSelected(item);
@@ -250,12 +252,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
private void monitorSubscription(final ChannelInfo info) {
final Consumer<Throwable> onError = new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
final Consumer<Throwable> onError = (Throwable throwable) -> {
animateView(headerSubscribeButton, false, 100);
showSnackBarError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Get subscription status", 0);
}
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
NewPipe.getNameOfService(currentInfo.getServiceId()),
"Get subscription status",
0);
};
final Observable<List<SubscriptionEntity>> observable = subscriptionService.subscriptionTable()
@@ -271,50 +273,38 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
// so only update the UI for the latest emission ("sync" the subscribe button's state)
.debounce(100, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<SubscriptionEntity>>() {
@Override
public void accept(List<SubscriptionEntity> subscriptionEntities) throws Exception {
updateSubscribeButton(!subscriptionEntities.isEmpty());
}
}, onError));
.subscribe((List<SubscriptionEntity> subscriptionEntities) ->
updateSubscribeButton(!subscriptionEntities.isEmpty())
, onError));
}
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription) {
return new Function<Object, Object>() {
@Override
public Object apply(@NonNull Object o) throws Exception {
subscriptionService.subscriptionTable().insert(subscription);
return o;
}
return (@NonNull Object o) -> {
subscriptionService.subscriptionTable().insert(subscription);
return o;
};
}
private Function<Object, Object> mapOnUnsubscribe(final SubscriptionEntity subscription) {
return new Function<Object, Object>() {
@Override
public Object apply(@NonNull Object o) throws Exception {
subscriptionService.subscriptionTable().delete(subscription);
return o;
}
return (@NonNull Object o) -> {
subscriptionService.subscriptionTable().delete(subscription);
return o;
};
}
private void updateSubscription(final ChannelInfo info) {
if (DEBUG) Log.d(TAG, "updateSubscription() called with: info = [" + info + "]");
final Action onComplete = new Action() {
@Override
public void run() throws Exception {
final Action onComplete = () -> {
if (DEBUG) Log.d(TAG, "Updated subscription: " + info.getUrl());
}
};
final Consumer<Throwable> onError = new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(info.getServiceId()), "Updating Subscription for " + info.getUrl(), R.string.subscription_update_failed);
}
};
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
onUnrecoverableError(throwable,
UserAction.SUBSCRIPTION,
NewPipe.getNameOfService(info.getServiceId()),
"Updating Subscription for " + info.getUrl(),
R.string.subscription_update_failed);
disposables.add(subscriptionService.updateChannelInfo(info)
.subscribeOn(Schedulers.io())
@@ -323,19 +313,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
}
private Disposable monitorSubscribeButton(final Button subscribeButton, final Function<Object, Object> action) {
final Consumer<Object> onNext = new Consumer<Object>() {
@Override
public void accept(@NonNull Object o) throws Exception {
final Consumer<Object> onNext = (@NonNull Object o) -> {
if (DEBUG) Log.d(TAG, "Changed subscription status to this channel!");
}
};
final Consumer<Throwable> onError = new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Subscription Change", R.string.subscription_change_failed);
}
};
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
onUnrecoverableError(throwable,
UserAction.SUBSCRIPTION,
NewPipe.getNameOfService(currentInfo.getServiceId()),
"Subscription Change",
R.string.subscription_change_failed);
/* Emit clicks from main thread unto io thread */
return RxView.clicks(subscribeButton)
@@ -347,25 +334,25 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
}
private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor(final ChannelInfo info) {
return new Consumer<List<SubscriptionEntity>>() {
@Override
public void accept(List<SubscriptionEntity> subscriptionEntities) throws Exception {
if (DEBUG)
Log.d(TAG, "subscriptionService.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]");
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
return (List<SubscriptionEntity> subscriptionEntities) -> {
if (DEBUG)
Log.d(TAG, "subscriptionService.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]");
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
if (subscriptionEntities.isEmpty()) {
if (DEBUG) Log.d(TAG, "No subscription to this channel!");
SubscriptionEntity channel = new SubscriptionEntity();
channel.setServiceId(info.getServiceId());
channel.setUrl(info.getUrl());
channel.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount());
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel));
} else {
if (DEBUG) Log.d(TAG, "Found subscription to this channel!");
final SubscriptionEntity subscription = subscriptionEntities.get(0);
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription));
}
if (subscriptionEntities.isEmpty()) {
if (DEBUG) Log.d(TAG, "No subscription to this channel!");
SubscriptionEntity channel = new SubscriptionEntity();
channel.setServiceId(info.getServiceId());
channel.setUrl(info.getUrl());
channel.setData(info.getName(),
info.getAvatarUrl(),
info.getDescription(),
info.getSubscriberCount());
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel));
} else {
if (DEBUG) Log.d(TAG, "Found subscription to this channel!");
final SubscriptionEntity subscription = subscriptionEntities.get(0);
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription));
}
};
}
@@ -432,10 +419,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
if (result.getSubscriberCount() != -1) {
headerSubscribersTextView.setVisibility(View.VISIBLE);
if (result.getSubscriberCount() >= 0) {
headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.getSubscriberCount()));
headerSubscribersTextView.setVisibility(View.VISIBLE);
} else headerSubscribersTextView.setVisibility(View.GONE);
} else {
headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
}
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
@@ -483,8 +472,11 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
super.handleNextItems(result);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(serviceId),
"Get next page of: " + url, R.string.general_error);
showSnackBarError(result.getErrors(),
UserAction.REQUESTED_CHANNEL,
NewPipe.getNameOfService(serviceId),
"Get next page of: " + url,
R.string.general_error);
}
}
@@ -497,7 +489,11 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
if (super.onError(exception)) return true;
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(serviceId), url, errorId);
onUnrecoverableError(exception,
UserAction.REQUESTED_CHANNEL,
NewPipe.getNameOfService(serviceId),
url,
errorId);
return true;
}
@@ -508,6 +504,6 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
@Override
public void setTitle(String title) {
super.setTitle(title);
headerTitleView.setText(title);
if (!useAsFrontPage) headerTitleView.setText(title);
}
}

View File

@@ -0,0 +1,149 @@
package org.schabi.newpipe.fragments.list.comments;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.comments.CommentsInfo;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
private CompositeDisposable disposables = new CompositeDisposable();
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private boolean mIsVisibleToUser = false;
public static CommentsFragment getInstance(int serviceId, String url, String name) {
CommentsFragment instance = new CommentsFragment();
instance.setInitialData(serviceId, url, name);
return instance;
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_comments, container, false);
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.clear();
}
/*//////////////////////////////////////////////////////////////////////////
// Load and handle
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
}
@Override
protected Single<CommentsInfo> loadResult(boolean forceLoad) {
return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad);
}
/*//////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@Override
public void showLoading() {
super.showLoading();
}
@Override
public void handleResult(@NonNull CommentsInfo result) {
super.handleResult(result);
AnimationUtils.slideUp(getView(),120, 96, 0.06f);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
}
if (disposables != null) disposables.clear();
}
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(),
UserAction.REQUESTED_COMMENTS,
NewPipe.getNameOfService(serviceId),
"Get next page of: " + url,
R.string.general_error);
}
}
/*//////////////////////////////////////////////////////////////////////////
// OnError
//////////////////////////////////////////////////////////////////////////*/
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
hideLoading();
showSnackBarError(exception, UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(serviceId), url, R.string.error_unable_to_load_comments);
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setTitle(String title) {
return;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
return;
}
@Override
protected boolean isGridLayout() {
return false;
}
}

View File

@@ -5,28 +5,23 @@ 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;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.NavigationHelper;
import icepick.State;
import io.reactivex.Single;
@@ -59,6 +54,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
protected String kioskId = "";
protected String kioskTranslatedName;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
@@ -74,11 +70,10 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
throws ExtractionException {
KioskFragment instance = new KioskFragment();
StreamingService service = NewPipe.getService(serviceId);
UrlIdHandler kioskTypeUrlIdHandler = service.getKioskList()
.getUrlIdHandlerByType(kioskId);
ListLinkHandlerFactory kioskLinkHandlerFactory = service.getKioskList()
.getListLinkHandlerFactoryByType(kioskId);
instance.setInitialData(serviceId,
kioskTypeUrlIdHandler.getUrl(kioskId),
kioskId);
kioskLinkHandlerFactory.fromId(kioskId).getUrl(), kioskId);
instance.kioskId = kioskId;
return instance;
}
@@ -133,20 +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);
return ExtractorHelper.getKioskInfo(serviceId,
url,
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);
return ExtractorHelper.getMoreKioskItems(serviceId,
url,
currentNextPageUrl);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -164,7 +155,9 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
super.handleResult(result);
name = kioskTranslatedName;
setTitle(kioskTranslatedName);
if(!useAsFrontPage) {
setTitle(kioskTranslatedName);
}
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(),

View File

@@ -6,6 +6,7 @@ import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
@@ -29,21 +30,24 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
@@ -93,7 +97,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
super.onCreate(savedInstanceState);
disposables = new CompositeDisposable();
isBookmarkButtonReady = new AtomicBoolean(false);
remotePlaylistManager = new RemotePlaylistManager(NewPipeDatabase.getInstance(getContext()));
remotePlaylistManager = new RemotePlaylistManager(NewPipeDatabase.getInstance(
requireContext()));
}
@Override
@@ -142,6 +147,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
context.getResources().getString(R.string.start_here_on_main),
context.getResources().getString(R.string.start_here_on_background),
context.getResources().getString(R.string.start_here_on_popup),
context.getResources().getString(R.string.share)
};
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
@@ -162,6 +168,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
case 4:
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
break;
case 5:
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
break;
default:
break;
}
@@ -222,10 +231,10 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_openInBrowser:
openUrlInBrowser(url);
ShareUtils.openUrlInBrowser(this.getContext(), url);
break;
case R.id.menu_item_share:
shareUrl(name, url);
ShareUtils.shareUrl(this.getContext(), name, url);
break;
case R.id.menu_item_bookmark:
onBookmarkClicked();
@@ -261,11 +270,16 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
if (!TextUtils.isEmpty(result.getUploaderName())) {
headerUploaderName.setText(result.getUploaderName());
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
headerUploaderLayout.setOnClickListener(v ->
headerUploaderLayout.setOnClickListener(v -> {
try {
NavigationHelper.openChannelFragment(getFragmentManager(),
result.getServiceId(), result.getUploaderUrl(),
result.getUploaderName())
);
result.getServiceId(),
result.getUploaderUrl(),
result.getUploaderName());
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
});
}
}
@@ -281,20 +295,27 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
}
remotePlaylistManager.getPlaylist(result)
.flatMap(lists -> getUpdateProcessor(lists, result), (lists, id) -> lists)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getPlaylistBookmarkSubscriber());
remotePlaylistManager.onUpdate(result)
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(integer -> {/* Do nothing*/}, this::onError);
headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
headerBackgroundButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
headerPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue());
return true;
});
headerBackgroundButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue());
return true;
});
}
private PlayQueue getPlayQueue() {
@@ -336,7 +357,11 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
if (super.onError(exception)) return true;
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId), url, errorId);
onUnrecoverableError(exception,
UserAction.REQUESTED_PLAYLIST,
NewPipe.getNameOfService(serviceId),
url,
errorId);
return true;
}
@@ -344,6 +369,17 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
// Utils
//////////////////////////////////////////////////////////////////////////*/
private Flowable<Integer> getUpdateProcessor(@NonNull List<PlaylistRemoteEntity> playlists,
@NonNull PlaylistInfo result) {
final Flowable<Integer> noItemToUpdate = Flowable.just(/*noItemToUpdate=*/-1);
if (playlists.isEmpty()) return noItemToUpdate;
final PlaylistRemoteEntity playlistEntity = playlists.get(0);
if (playlistEntity.isIdenticalTo(result)) return noItemToUpdate;
return remotePlaylistManager.onUpdate(playlists.get(0).getUid(), result).toFlowable();
}
private Subscriber<List<PlaylistRemoteEntity>> getPlaylistBookmarkSubscriber() {
return new Subscriber<List<PlaylistRemoteEntity>>() {
@Override
@@ -416,4 +452,4 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
playlistBookmarkButton.setIcon(ThemeHelper.resolveResourceIdFromAttr(activity, iconAttr));
playlistBookmarkButton.setTitle(titleRes);
}
}
}

View File

@@ -12,6 +12,7 @@ import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.TooltipCompat;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -37,26 +38,30 @@ import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.search.SearchInfo;
import org.schabi.newpipe.util.FireTvUtils;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.LayoutManagerSmoothScroller;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import icepick.State;
@@ -65,14 +70,15 @@ import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import static android.support.v7.widget.helper.ItemTouchHelper.Callback.makeMovementFlags;
import static java.util.Arrays.asList;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class SearchFragment
extends BaseListFragment<SearchResult, ListExtractor.InfoItemsPage>
extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage>
implements BackPressable {
/*//////////////////////////////////////////////////////////////////////////
@@ -92,27 +98,41 @@ public class SearchFragment
@State
protected int filterItemCheckedId = -1;
private SearchEngine.Filter filter = SearchEngine.Filter.ANY;
@State
protected int serviceId = Constants.NO_SERVICE_ID;
// this three represet the current search query
@State
protected String searchQuery;
protected String searchString;
/**
* No content filter should add like contentfilter = all
* be aware of this when implementing an extractor.
*/
@State
protected String lastSearchedQuery;
protected String[] contentFilter = new String[0];
@State
protected String sortFilter;
// these represtent the last search
@State
protected String lastSearchedString;
@State
protected boolean wasSearchFocused = false;
private int currentPage = 0;
private int currentNextPage = 0;
private Map<Integer, String> menuItemToFilterName;
private StreamingService service;
private String currentPageUrl;
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;
@@ -130,11 +150,11 @@ public class SearchFragment
/*////////////////////////////////////////////////////////////////////////*/
public static SearchFragment getInstance(int serviceId, String query) {
public static SearchFragment getInstance(int serviceId, String searchString) {
SearchFragment searchFragment = new SearchFragment();
searchFragment.setQuery(serviceId, query);
searchFragment.setQuery(serviceId, searchString, new String[0], "");
if (!TextUtils.isEmpty(query)) {
if (!TextUtils.isEmpty(searchString)) {
searchFragment.setSearchOnResume();
}
@@ -158,7 +178,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);
@@ -202,13 +222,22 @@ public class SearchFragment
if (DEBUG) Log.d(TAG, "onResume() called");
super.onResume();
if (!TextUtils.isEmpty(searchQuery)) {
try {
service = NewPipe.getService(serviceId);
} catch (Exception e) {
ErrorActivity.reportError(getActivity(), e, getActivity().getClass(),
getActivity().findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
"",
"", R.string.general_error));
}
if (!TextUtils.isEmpty(searchString)) {
if (wasLoading.getAndSet(false)) {
if (currentNextPage > currentPage) loadMoreItems();
else search(searchQuery);
search(searchString, contentFilter, sortFilter);
} else if (infoListAdapter.getItemsList().size() == 0) {
if (savedState == null) {
search(searchQuery);
search(searchString, contentFilter, sortFilter);
} else if (!isLoading.get() && !wasSearchFocused) {
infoListAdapter.clearStreamItemList();
showEmptyState();
@@ -218,7 +247,7 @@ public class SearchFragment
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver();
if (TextUtils.isEmpty(searchQuery) || wasSearchFocused) {
if (TextUtils.isEmpty(searchString) || wasSearchFocused) {
showKeyboardSearch();
showSuggestionsPanel();
} else {
@@ -247,8 +276,9 @@ public class SearchFragment
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case ReCaptchaActivity.RECAPTCHA_REQUEST:
if (resultCode == Activity.RESULT_OK && !TextUtils.isEmpty(searchQuery)) {
search(searchQuery);
if (resultCode == Activity.RESULT_OK
&& !TextUtils.isEmpty(searchString)) {
search(searchString, contentFilter, sortFilter);
} else Log.e(TAG, "ReCaptcha failed");
break;
@@ -268,7 +298,23 @@ public class SearchFragment
suggestionsPanel = rootView.findViewById(R.id.suggestions_panel);
suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list);
suggestionsRecyclerView.setAdapter(suggestionListAdapter);
suggestionsRecyclerView.setLayoutManager(new LayoutManagerSmoothScroller(activity));
new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return getSuggestionMovementFlags(recyclerView, viewHolder);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder viewHolder1) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
onSuggestionItemSwiped(viewHolder, i);
}
}).attachToRecyclerView(suggestionsRecyclerView);
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
@@ -282,20 +328,22 @@ public class SearchFragment
@Override
public void writeTo(Queue<Object> objectsToSave) {
super.writeTo(objectsToSave);
objectsToSave.add(currentPage);
objectsToSave.add(currentNextPage);
objectsToSave.add(currentPageUrl);
objectsToSave.add(nextPageUrl);
}
@Override
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects);
currentPage = (int) savedObjects.poll();
currentNextPage = (int) savedObjects.poll();
currentPageUrl = (String) savedObjects.poll();
nextPageUrl = (String) savedObjects.poll();
}
@Override
public void onSaveInstanceState(Bundle bundle) {
searchQuery = searchEditText != null ? searchEditText.getText().toString() : searchQuery;
searchString = searchEditText != null
? searchEditText.getText().toString()
: searchString;
super.onSaveInstanceState(bundle);
}
@@ -305,8 +353,11 @@ public class SearchFragment
@Override
public void reloadContent() {
if (!TextUtils.isEmpty(searchQuery) || (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
search(!TextUtils.isEmpty(searchQuery) ? searchQuery : searchEditText.getText().toString());
if (!TextUtils.isEmpty(searchString)
|| (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
search(!TextUtils.isEmpty(searchString)
? searchString
: searchEditText.getText().toString(), this.contentFilter, "");
} else {
if (searchEditText != null) {
searchEditText.setText("");
@@ -330,22 +381,35 @@ public class SearchFragment
supportActionBar.setDisplayHomeAsUpEnabled(true);
}
inflater.inflate(R.menu.menu_search, menu);
menuItemToFilterName = new HashMap<>();
int itemId = 0;
boolean isFirstItem = true;
final Context c = getContext();
for(String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
menuItemToFilterName.put(itemId, filter);
MenuItem item = menu.add(1,
itemId++,
0,
ServiceHelper.getTranslatedFilterString(filter, c));
if(isFirstItem) {
item.setChecked(true);
isFirstItem = false;
}
}
menu.setGroupCheckable(1, true, true);
restoreFilterChecked(menu, filterItemCheckedId);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_filter_all:
case R.id.menu_filter_video:
case R.id.menu_filter_channel:
case R.id.menu_filter_playlist:
changeFilter(item, getFilterFromMenuId(item.getItemId()));
return true;
default:
return super.onOptionsItemSelected(item);
}
List<String> contentFilter = new ArrayList<>(1);
contentFilter.add(menuItemToFilterName.get(item.getItemId()));
changeContentFilter(item, contentFilter);
return true;
}
private void restoreFilterChecked(Menu menu, int itemId) {
@@ -354,21 +418,6 @@ public class SearchFragment
if (item == null) return;
item.setChecked(true);
filter = getFilterFromMenuId(itemId);
}
}
private SearchEngine.Filter getFilterFromMenuId(int itemId) {
switch (itemId) {
case R.id.menu_filter_video:
return SearchEngine.Filter.STREAM;
case R.id.menu_filter_channel:
return SearchEngine.Filter.CHANNEL;
case R.id.menu_filter_playlist:
return SearchEngine.Filter.PLAYLIST;
case R.id.menu_filter_all:
default:
return SearchEngine.Filter.ANY;
}
}
@@ -379,14 +428,21 @@ public class SearchFragment
private TextWatcher textWatcher;
private void showSearchOnStart() {
if (DEBUG) Log.d(TAG, "showSearchOnStart() called, searchQuery → " + searchQuery+", lastSearchedQuery → " + lastSearchedQuery);
searchEditText.setText(searchQuery);
if (DEBUG) Log.d(TAG, "showSearchOnStart() called, searchQuery → "
+ searchString
+ ", lastSearchedQuery → "
+ lastSearchedString);
searchEditText.setText(searchString);
if (TextUtils.isEmpty(searchQuery) || TextUtils.isEmpty(searchEditText.getText())) {
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
searchToolbarContainer.setTranslationX(100);
searchToolbarContainer.setAlpha(0f);
searchToolbarContainer.setVisibility(View.VISIBLE);
searchToolbarContainer.animate().translationX(0).alpha(1f).setDuration(200).setInterpolator(new DecelerateInterpolator()).start();
searchToolbarContainer.animate()
.translationX(0)
.alpha(1f)
.setDuration(200)
.setInterpolator(new DecelerateInterpolator()).start();
} else {
searchToolbarContainer.setTranslationX(0);
searchToolbarContainer.setAlpha(1f);
@@ -396,47 +452,41 @@ public class SearchFragment
private void initSearchListeners() {
if (DEBUG) Log.d(TAG, "initSearchListeners() called");
searchClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
if (TextUtils.isEmpty(searchEditText.getText())) {
NavigationHelper.gotoMainFragment(getFragmentManager());
return;
}
searchEditText.setText("");
suggestionListAdapter.setItems(new ArrayList<SuggestionItem>());
showKeyboardSearch();
searchClear.setOnClickListener(v -> {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
if (TextUtils.isEmpty(searchEditText.getText())) {
NavigationHelper.gotoMainFragment(getFragmentManager());
return;
}
searchEditText.setText("");
suggestionListAdapter.setItems(new ArrayList<>());
showKeyboardSearch();
});
TooltipCompat.setTooltipText(searchClear, getString(R.string.clear));
searchEditText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel();
}
searchEditText.setOnClickListener(v -> {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel();
}
if(FireTvUtils.isFireTv()){
showKeyboardSearch();
}
});
searchEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (DEBUG) Log.d(TAG, "onFocusChange() called with: v = [" + v + "], hasFocus = [" + hasFocus + "]");
if (isSuggestionsEnabled && hasFocus && errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel();
}
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
if (DEBUG) Log.d(TAG, "onFocusChange() called with: v = [" + v + "], hasFocus = [" + hasFocus + "]");
if (isSuggestionsEnabled && hasFocus && errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel();
}
});
suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() {
@Override
public void onSuggestionItemSelected(SuggestionItem item) {
search(item.query);
search(item.query, new String[0], "");
searchEditText.setText(item.query);
}
@@ -469,21 +519,24 @@ public class SearchFragment
}
};
searchEditText.addTextChangedListener(textWatcher);
searchEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (DEBUG) {
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
}
if (event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
search(searchEditText.getText().toString());
return true;
}
return false;
}
});
searchEditText.setOnEditorActionListener(
(TextView v, int actionId, KeyEvent event) -> {
if (DEBUG) {
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
}
if(actionId == EditorInfo.IME_ACTION_PREVIOUS){
hideKeyboardSearch();
} else if (event != null
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
search(searchEditText.getText().toString(), new String[0], "");
return true;
}
return false;
});
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver();
if (suggestionDisposable == null || suggestionDisposable.isDisposed())
initSuggestionObserver();
}
private void unsetSearchListeners() {
@@ -513,8 +566,9 @@ public class SearchFragment
if (searchEditText == null) return;
if (searchEditText.requestFocus()) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT);
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
}
}
@@ -522,8 +576,9 @@ public class SearchFragment
if (DEBUG) Log.d(TAG, "hideKeyboardSearch() called");
if (searchEditText == null) return;
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
searchEditText.clearFocus();
}
@@ -545,8 +600,7 @@ public class SearchFragment
.onNext(searchEditText.getText().toString()),
throwable -> showSnackBarError(throwable,
UserAction.DELETE_FROM_HISTORY, "none",
"Deleting item failed", R.string.general_error)
);
"Deleting item failed", R.string.general_error));
disposables.add(onDelete);
})
.show();
@@ -554,10 +608,12 @@ public class SearchFragment
@Override
public boolean onBackPressed() {
if (suggestionsPanel.getVisibility() == View.VISIBLE && infoListAdapter.getItemsList().size() > 0 && !isLoading.get()) {
if (suggestionsPanel.getVisibility() == View.VISIBLE
&& infoListAdapter.getItemsList().size() > 0
&& !isLoading.get()) {
hideSuggestionsPanel();
hideKeyboardSearch();
searchEditText.setText(lastSearchedQuery);
searchEditText.setText(lastSearchedString);
return true;
}
return false;
@@ -573,8 +629,10 @@ public class SearchFragment
final Observable<String> observable = suggestionPublisher
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
.startWith(searchQuery != null ? searchQuery : "")
.filter(query -> isSuggestionsEnabled);
.startWith(searchString != null
? searchString
: "")
.filter(searchString -> isSuggestionsEnabled);
suggestionDisposable = observable
.switchMap(query -> {
@@ -594,7 +652,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<>();
@@ -645,56 +703,44 @@ public class SearchFragment
// no-op
}
private void search(final String query) {
if (DEBUG) Log.d(TAG, "search() called with: query = [" + query + "]");
if (query.isEmpty()) return;
private void search(final String searchString, String[] contentFilter, String sortFilter) {
if (DEBUG) Log.d(TAG, "search() called with: query = [" + searchString + "]");
if (searchString.isEmpty()) return;
try {
final StreamingService service = NewPipe.getServiceByUrl(query);
final StreamingService service = NewPipe.getServiceByUrl(searchString);
if (service != null) {
showLoading();
disposables.add(Observable
.fromCallable(new Callable<Intent>() {
@Override
public Intent call() throws Exception {
return NavigationHelper.getIntentByLink(activity, service, query);
}
})
.fromCallable(() ->
NavigationHelper.getIntentByLink(activity, service, searchString))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Intent>() {
@Override
public void accept(Intent intent) throws Exception {
getFragmentManager().popBackStackImmediate();
activity.startActivity(intent);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
showError(getString(R.string.url_not_supported_toast), false);
}
}));
.subscribe(intent -> {
getFragmentManager().popBackStackImmediate();
activity.startActivity(intent);
}, throwable ->
showError(getString(R.string.url_not_supported_toast), false)));
return;
}
} catch (Exception e) {
// Exception occurred, it's not a url
}
lastSearchedQuery = query;
searchQuery = query;
currentPage = 0;
lastSearchedString = this.searchString;
this.searchString = searchString;
infoListAdapter.clearStreamItemList();
hideSuggestionsPanel();
hideKeyboardSearch();
historyRecordManager.onSearched(serviceId, query)
historyRecordManager.onSearched(serviceId, searchString)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> {},
error -> showSnackBarError(error, UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId), query, 0)
NewPipe.getNameOfService(serviceId), searchString, 0)
);
suggestionPublisher.onNext(query);
suggestionPublisher.onNext(searchString);
startLoading(false);
}
@@ -703,20 +749,29 @@ public class SearchFragment
super.startLoading(forceLoad);
if (disposables != null) disposables.clear();
if (searchDisposable != null) searchDisposable.dispose();
searchDisposable = ExtractorHelper.searchFor(serviceId, searchQuery, currentPage, contentCountry, filter)
searchDisposable = ExtractorHelper.searchFor(serviceId,
searchString,
Arrays.asList(contentFilter),
sortFilter)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
.subscribe(this::handleResult, this::onError);
}
@Override
protected void loadMoreItems() {
if(nextPageUrl == null || nextPageUrl.isEmpty()) return;
isLoading.set(true);
showListFooter(true);
if (searchDisposable != null) searchDisposable.dispose();
currentNextPage = currentPage + 1;
searchDisposable = ExtractorHelper.getMoreSearchItems(serviceId, searchQuery, currentNextPage, contentCountry, filter)
searchDisposable = ExtractorHelper.getMoreSearchItems(
serviceId,
searchString,
asList(contentFilter),
sortFilter,
nextPageUrl)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
@@ -739,19 +794,22 @@ public class SearchFragment
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void changeFilter(MenuItem item, SearchEngine.Filter filter) {
this.filter = filter;
private void changeContentFilter(MenuItem item, List<String> contentFilter) {
this.filterItemCheckedId = item.getItemId();
item.setChecked(true);
if (!TextUtils.isEmpty(searchQuery)) {
search(searchQuery);
this.contentFilter = new String[] {contentFilter.get(0)};
if (!TextUtils.isEmpty(searchString)) {
search(searchString, this.contentFilter, sortFilter);
}
}
private void setQuery(int serviceId, String searchQuery) {
private void setQuery(int serviceId, String searchString, String[] contentfilter, String sortFilter) {
this.serviceId = serviceId;
this.searchQuery = searchQuery;
this.searchString = searchString;
this.contentFilter = contentfilter;
this.sortFilter = sortFilter;
}
/*//////////////////////////////////////////////////////////////////////////
@@ -772,8 +830,11 @@ public class SearchFragment
if (DEBUG) Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]");
if (super.onError(exception)) return;
int errorId = exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception, UserAction.GET_SUGGESTIONS, NewPipe.getNameOfService(serviceId), searchQuery, errorId);
int errorId = exception instanceof ParsingException
? R.string.parsing_error
: R.string.general_error;
onUnrecoverableError(exception, UserAction.GET_SUGGESTIONS,
NewPipe.getNameOfService(serviceId), searchString, errorId);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -798,16 +859,22 @@ public class SearchFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
public void handleResult(@NonNull SearchResult result) {
if (!result.errors.isEmpty()) {
showSnackBarError(result.errors, UserAction.SEARCHED, NewPipe.getNameOfService(serviceId), searchQuery, 0);
public void handleResult(@NonNull SearchInfo result) {
final List<Throwable> exceptions = result.getErrors();
if (!exceptions.isEmpty()
&& !(exceptions.size() == 1
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)){
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId), searchString, 0);
}
lastSearchedQuery = searchQuery;
lastSearchedString = searchString;
nextPageUrl = result.getNextPageUrl();
currentPageUrl = result.getUrl();
if (infoListAdapter.getItemsList().size() == 0) {
if (!result.getResults().isEmpty()) {
infoListAdapter.addInfoItemList(result.getResults());
if (!result.getRelatedItems().isEmpty()) {
infoListAdapter.addInfoItemList(result.getRelatedItems());
} else {
infoListAdapter.clearStreamItemList();
showEmptyState();
@@ -821,12 +888,14 @@ public class SearchFragment
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
showListFooter(false);
currentPage = Integer.parseInt(result.getNextPageUrl());
currentPageUrl = result.getNextPageUrl();
infoListAdapter.addInfoItemList(result.getItems());
nextPageUrl = result.getNextPageUrl();
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.SEARCHED, NewPipe.getNameOfService(serviceId)
, "\"" + searchQuery + "\" → page " + currentPage, 0);
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId)
, "\"" + searchString + "\" → page: " + nextPageUrl, 0);
}
super.handleNextItems(result);
}
@@ -835,14 +904,41 @@ public class SearchFragment
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
if (exception instanceof SearchEngine.NothingFoundException) {
if (exception instanceof SearchExtractor.NothingFoundException) {
infoListAdapter.clearStreamItemList();
showEmptyState();
} else {
int errorId = exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception, UserAction.SEARCHED, NewPipe.getNameOfService(serviceId), searchQuery, errorId);
int errorId = exception instanceof ParsingException
? R.string.parsing_error
: R.string.general_error;
onUnrecoverableError(exception, UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId), searchString, errorId);
}
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Suggestion item touch helper
//////////////////////////////////////////////////////////////////////////*/
public int getSuggestionMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition();
final SuggestionItem item = suggestionListAdapter.getItem(position);
return item.fromHistory ? makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
}
public void onSuggestionItemSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
final int position = viewHolder.getAdapterPosition();
final String query = suggestionListAdapter.getItem(position).query;
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> suggestionPublisher
.onNext(searchEditText.getText().toString()),
throwable -> showSnackBarError(throwable,
UserAction.DELETE_FROM_HISTORY, "none",
"Deleting item failed", R.string.general_error));
disposables.add(onDelete);
}
}

View File

@@ -75,7 +75,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
});
}
private SuggestionItem getItem(int position) {
SuggestionItem getItem(int position) {
return items.get(position);
}

View File

@@ -0,0 +1,208 @@
package org.schabi.newpipe.fragments.list.videos;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Switch;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.RelatedStreamInfo;
import java.io.Serializable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo> implements SharedPreferences.OnSharedPreferenceChangeListener{
private CompositeDisposable disposables = new CompositeDisposable();
private RelatedStreamInfo relatedStreamInfo;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private View headerRootLayout;
private Switch aSwitch;
private boolean mIsVisibleToUser = false;
public static RelatedVideosFragment getInstance(StreamInfo info) {
RelatedVideosFragment instance = new RelatedVideosFragment();
instance.setInitialData(info);
return instance;
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_related_streams, container, false);
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.clear();
}
protected View getListHeader(){
if(relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null){
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.related_streams_header, itemsList, false);
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
aSwitch.setChecked(autoplay);
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
prefEdit.putBoolean(getString(R.string.auto_queue_key), b);
prefEdit.apply();
}
});
return headerRootLayout;
}else{
return null;
}
}
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
}
@Override
protected Single<RelatedStreamInfo> loadResult(boolean forceLoad) {
return Single.fromCallable(() -> relatedStreamInfo);
}
/*//////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@Override
public void showLoading() {
super.showLoading();
if(null != headerRootLayout) headerRootLayout.setVisibility(View.INVISIBLE);
}
@Override
public void handleResult(@NonNull RelatedStreamInfo result) {
super.handleResult(result);
if(null != headerRootLayout) headerRootLayout.setVisibility(View.VISIBLE);
AnimationUtils.slideUp(getView(),120, 96, 0.06f);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
}
if (disposables != null) disposables.clear();
}
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(),
UserAction.REQUESTED_STREAM,
NewPipe.getNameOfService(serviceId),
"Get next page of: " + url,
R.string.general_error);
}
}
/*//////////////////////////////////////////////////////////////////////////
// OnError
//////////////////////////////////////////////////////////////////////////*/
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
hideLoading();
showSnackBarError(exception, UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(serviceId), url, R.string.general_error);
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setTitle(String title) {
return;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
return;
}
private void setInitialData(StreamInfo info) {
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
if(this.relatedStreamInfo == null) this.relatedStreamInfo = RelatedStreamInfo.getInfo(info);
}
private static final String INFO_KEY = "related_info_key";
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, relatedStreamInfo);
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedState) {
super.onRestoreInstanceState(savedState);
if (savedState != null) {
Serializable serializable = savedState.getSerializable(INFO_KEY);
if(serializable instanceof RelatedStreamInfo){
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
}
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
if(null != aSwitch) aSwitch.setChecked(autoplay);
}
@Override
protected boolean isGridLayout() {
return false;
}
}

View File

@@ -10,10 +10,13 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
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.CommentsInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
@@ -45,11 +48,12 @@ 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;
private OnClickGesture<PlaylistInfoItem> onPlaylistSelectedListener;
private OnClickGesture<CommentsInfoItem> onCommentsSelectedListener;
public InfoItemBuilder(Context context) {
this.context = context;
@@ -73,6 +77,8 @@ public class InfoItemBuilder {
return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent) : new ChannelInfoItemHolder(this, parent);
case PLAYLIST:
return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent) : new PlaylistInfoItemHolder(this, parent);
case COMMENT:
return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) : new CommentsInfoItemHolder(this, parent);
default:
Log.e(TAG, "Trollolo");
throw new RuntimeException("InfoType not expected = " + infoType.name());
@@ -111,4 +117,12 @@ public class InfoItemBuilder {
this.onPlaylistSelectedListener = listener;
}
public OnClickGesture<CommentsInfoItem> getOnCommentsSelectedListener() {
return onCommentsSelectedListener;
}
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> onCommentsSelectedListener) {
this.onCommentsSelectedListener = onCommentsSelectedListener;
}
}

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;
@@ -8,15 +9,22 @@ import android.view.ViewGroup;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
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.CommentsInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
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;
import org.schabi.newpipe.util.OnClickGesture;
import java.util.ArrayList;
@@ -51,14 +59,20 @@ 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 static final int MINI_COMMENT_HOLDER_TYPE = 0x400;
private static final int COMMENT_HOLDER_TYPE = 0x401;
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;
@@ -89,10 +103,18 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
infoItemBuilder.setOnPlaylistSelectedListener(listener);
}
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> listener) {
infoItemBuilder.setOnCommentsSelectedListener(listener);
}
public void useMiniItemVariants(boolean useMiniVariant) {
this.useMiniVariant = useMiniVariant;
}
public void setGridItemVariants(boolean useGridVariant) {
this.useGridVariant = useGridVariant;
}
public void addInfoItemList(List<InfoItem> data) {
if (data != null) {
if (DEBUG) {
@@ -205,11 +227,13 @@ 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;
case COMMENT:
return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE;
default:
Log.e(TAG, "Trollolo");
return -1;
@@ -218,7 +242,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
if (DEBUG)
Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
switch (type) {
case HEADER_TYPE:
return new HFHolder(header);
@@ -228,17 +253,27 @@ 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);
case MINI_COMMENT_HOLDER_TYPE:
return new CommentsMiniInfoItemHolder(infoItemBuilder, parent);
case COMMENT_HOLDER_TYPE:
return new CommentsInfoItemHolder(infoItemBuilder, parent);
default:
Log.e(TAG, "Trollolo");
return null;
return new FallbackViewHolder(new View(parent.getContext()));
}
}
@@ -256,4 +291,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,53 @@
package org.schabi.newpipe.info_list.holder;
import android.view.ViewGroup;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.util.Localization;
/*
* Created by Christian Schabesberger on 12.02.17.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* ChannelInfoItemHolder .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 CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
public final TextView itemTitleView;
public CommentsInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_comments_item, parent);
itemTitleView = itemView.findViewById(R.id.itemTitleView);
}
@Override
public void updateFromItem(final InfoItem infoItem) {
super.updateFromItem(infoItem);
if (!(infoItem instanceof CommentsInfoItem)) return;
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
itemTitleView.setText(item.getAuthorName());
}
}

View File

@@ -0,0 +1,150 @@
package org.schabi.newpipe.info_list.holder;
import android.support.v7.app.AppCompatActivity;
import android.text.util.Linkify;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.jsoup.helper.StringUtil;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.CommentTextOnTouchListener;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.hdodenhof.circleimageview.CircleImageView;
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
public final CircleImageView itemThumbnailView;
private final TextView itemContentView;
private final TextView itemLikesCountView;
private final TextView itemDislikesCountView;
private final TextView itemPublishedTime;
private static final int commentDefaultLines = 2;
private static final int commentExpandedLines = 1000;
private String commentText;
private String streamUrl;
private static final Pattern pattern = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)");
private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() {
@Override
public String transformUrl(Matcher match, String url) {
int timestamp = 0;
String hours = match.group(1);
String minutes = match.group(2);
String seconds = match.group(3);
if(hours != null) timestamp += (Integer.parseInt(hours.replace(":", ""))*3600);
if(minutes != null) timestamp += (Integer.parseInt(minutes.replace(":", ""))*60);
if(seconds != null) timestamp += (Integer.parseInt(seconds));
return streamUrl + url.replace(match.group(0), "#timestamp=" + String.valueOf(timestamp));
}
};
CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
itemDislikesCountView = itemView.findViewById(R.id.detail_thumbs_down_count_view);
itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime);
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
}
public CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
this(infoItemBuilder, R.layout.list_comments_mini_item, parent);
}
@Override
public void updateFromItem(final InfoItem infoItem) {
if (!(infoItem instanceof CommentsInfoItem)) return;
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
itemBuilder.getImageLoader()
.displayImage(item.getAuthorThumbnail(),
itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
itemThumbnailView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(StringUtil.isBlank(item.getAuthorEndpoint())) return;
try {
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
NavigationHelper.openChannelFragment(
activity.getSupportFragmentManager(),
item.getServiceId(),
item.getAuthorEndpoint(),
item.getAuthorName());
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
}
}
});
streamUrl = item.getUrl();
itemContentView.setLines(commentDefaultLines);
commentText = item.getCommentText();
itemContentView.setText(commentText);
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
if (itemContentView.getLineCount() == 0) {
itemContentView.post(() -> ellipsize());
} else {
ellipsize();
}
if (null != item.getLikeCount()) {
itemLikesCountView.setText(String.valueOf(item.getLikeCount()));
}
itemPublishedTime.setText(item.getPublishedTime());
itemView.setOnClickListener(view -> {
toggleEllipsize();
if (itemBuilder.getOnCommentsSelectedListener() != null) {
itemBuilder.getOnCommentsSelectedListener().selected(item);
}
});
}
private void ellipsize() {
if (itemContentView.getLineCount() > commentDefaultLines){
int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1);
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine -2);
if(end == -1) end = Math.max(endOfLastLine -2, 0);
String newVal = itemContentView.getText().subSequence(0, end) + "";
itemContentView.setText(newVal);
}
linkify();
}
private void toggleEllipsize() {
if (itemContentView.getText().toString().equals(commentText)) {
if (itemContentView.getLineCount() > commentDefaultLines) ellipsize();
} else {
expand();
}
}
private void expand() {
itemContentView.setMaxLines(commentExpandedLines);
itemContentView.setText(commentText);
linkify();
}
private void linkify(){
Linkify.addLinks(itemContentView, Linkify.WEB_URLS);
Linkify.addLinks(itemContentView, pattern, null, null, timestampLink);
itemContentView.setMovementMethod(null);
}
}

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,19 +1,23 @@
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;
import org.schabi.newpipe.util.OnClickGesture;
@@ -51,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;
@@ -133,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;
@@ -194,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() + "]");
@@ -217,15 +230,23 @@ 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 null;
return new FallbackViewHolder(new View(parent.getContext()));
}
}
@@ -246,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

@@ -66,11 +66,10 @@ public final class BookmarkFragment
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
Bundle savedInstanceState) {
if (activity != null && activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
activity.setTitle(R.string.tab_subscriptions);
}
if(!useAsFrontPage) {
setTitle(activity.getString(R.string.tab_bookmarks));
}
return inflater.inflate(R.layout.fragment_bookmarks, container, false);
}
@@ -99,9 +98,7 @@ public final class BookmarkFragment
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
@Override
public void selected(LocalItem selectedItem) {
// Requires the parent fragment to find holder for fragment replacement
if (getParentFragment() == null) return;
final FragmentManager fragmentManager = getParentFragment().getFragmentManager();
final FragmentManager fragmentManager = getFM();
if (selectedItem instanceof PlaylistMetadataEntry) {
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
@@ -110,8 +107,11 @@ public final class BookmarkFragment
} else if (selectedItem instanceof PlaylistRemoteEntity) {
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
NavigationHelper.openPlaylistFragment(fragmentManager, entry.getServiceId(),
entry.getUrl(), entry.getName());
NavigationHelper.openPlaylistFragment(
fragmentManager,
entry.getServiceId(),
entry.getUrl(),
entry.getName());
}
}

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> {
@@ -71,6 +69,10 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
if(!useAsFrontPage) {
setTitle(activity.getString(R.string.fragment_whats_new));
}
return inflater.inflate(R.layout.fragment_feed, container, false);
}
@@ -105,20 +107,19 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
super.onDestroyView();
}
/*@Override
protected RecyclerView.LayoutManager getListLayoutManager() {
boolean isPortrait = getResources().getDisplayMetrics().heightPixels > getResources().getDisplayMetrics().widthPixels;
return new GridLayoutManager(activity, isPortrait ? 1 : 2);
}*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (activity != null && isVisibleToUser) {
setTitle(activity.getString(R.string.fragment_whats_new));
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setTitle(R.string.fragment_whats_new);
}
if(useAsFrontPage) {
supportActionBar.setDisplayShowTitleEnabled(true);
@@ -176,19 +177,9 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
showLoading();
showListFooter(true);
subscriptionObserver = subscriptionService.getSubscription()
.onErrorReturnItem(Collections.<SubscriptionEntity>emptyList())
.onErrorReturnItem(Collections.emptyList())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<SubscriptionEntity>>() {
@Override
public void accept(List<SubscriptionEntity> subscriptionEntities) throws Exception {
handleResult(subscriptionEntities);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
onError(throwable);
}
});
.subscribe(this::handleResult, this::onError);
}
@Override
@@ -239,13 +230,12 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
if (!itemsLoaded.contains(subscriptionEntity.getServiceId() + subscriptionEntity.getUrl())) {
subscriptionService.getChannelInfo(subscriptionEntity)
.observeOn(AndroidSchedulers.mainThread())
.onErrorComplete(new Predicate<Throwable>() {
@Override
public boolean test(@io.reactivex.annotations.NonNull Throwable throwable) throws Exception {
return FeedFragment.super.onError(throwable);
}
})
.subscribe(getChannelInfoObserver(subscriptionEntity.getServiceId(), subscriptionEntity.getUrl()));
.onErrorComplete(
(@io.reactivex.annotations.NonNull Throwable throwable) ->
FeedFragment.super.onError(throwable))
.subscribe(
getChannelInfoObserver(subscriptionEntity.getServiceId(),
subscriptionEntity.getUrl()));
} else {
requestFeed(1);
}
@@ -316,7 +306,10 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
@Override
public void onError(Throwable exception) {
showSnackBarError(exception, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(serviceId), url, 0);
showSnackBarError(exception,
UserAction.SUBSCRIPTION,
NewPipe.getNameOfService(serviceId),
url, 0);
requestFeed(1);
onDone();
}
@@ -361,12 +354,7 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
delayHandler.removeCallbacksAndMessages(null);
// Add a little of a delay when requesting more items because the cache is so fast,
// that the view seems stuck to the user when he scroll to the bottom
delayHandler.postDelayed(new Runnable() {
@Override
public void run() {
requestFeed(FEED_LOAD_COUNT);
}
}, 300);
delayHandler.postDelayed(() -> requestFeed(FEED_LOAD_COUNT), 300);
}
@Override
@@ -423,7 +411,9 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
int heightPixels = getResources().getDisplayMetrics().heightPixels;
int itemHeightPixels = activity.getResources().getDimensionPixelSize(R.dimen.video_item_search_height);
int items = itemHeightPixels > 0 ? heightPixels / itemHeightPixels + OFF_SCREEN_ITEMS_COUNT : MIN_ITEMS_INITIAL_LOAD;
int items = itemHeightPixels > 0
? heightPixels / itemHeightPixels + OFF_SCREEN_ITEMS_COUNT
: MIN_ITEMS_INITIAL_LOAD;
return Math.max(MIN_ITEMS_INITIAL_LOAD, items);
}
@@ -441,8 +431,14 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "Requesting feed", errorId);
int errorId = exception instanceof ExtractionException
? R.string.parsing_error
: R.string.general_error;
onUnrecoverableError(exception,
UserAction.SOMETHING_ELSE,
"none",
"Requesting feed",
errorId);
return true;
}
}

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

@@ -8,7 +8,11 @@ import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@@ -21,13 +25,16 @@ 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.local.BaseLocalListFragment;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.BaseLocalListFragment;
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.settings.SettingsActivity;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
@@ -56,7 +63,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,
@@ -73,7 +80,7 @@ public class StatisticsPlaylistFragment
return results;
case MOST_PLAYED:
Collections.sort(results, (left, right) ->
((Long) right.watchCount).compareTo(left.watchCount));
Long.compare(right.watchCount, left.watchCount));
return results;
default: return null;
}
@@ -96,6 +103,20 @@ public class StatisticsPlaylistFragment
return inflater.inflate(R.layout.fragment_playlist, container, false);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (activity != null && isVisibleToUser) {
setTitle(activity.getString(R.string.title_activity_history));
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_history, menu);
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Views
///////////////////////////////////////////////////////////////////////////
@@ -103,7 +124,9 @@ public class StatisticsPlaylistFragment
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
setTitle(getString(R.string.title_last_played));
if(!useAsFrontPage) {
setTitle(getString(R.string.title_last_played));
}
}
@Override
@@ -129,8 +152,10 @@ public class StatisticsPlaylistFragment
public void selected(LocalItem selectedItem) {
if (selectedItem instanceof StreamStatisticsEntry) {
final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem;
NavigationHelper.openVideoDetailFragment(getFragmentManager(),
item.serviceId, item.url, item.title);
NavigationHelper.openVideoDetailFragment(getFM(),
item.serviceId,
item.url,
item.title);
}
}
@@ -143,6 +168,53 @@ public class StatisticsPlaylistFragment
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_history_clear:
new AlertDialog.Builder(activity)
.setTitle(R.string.delete_view_history_alert)
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
.setPositiveButton(R.string.delete, ((dialog, which) -> {
final Disposable onDelete = recordManager.deleteWholeStreamHistory()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> Toast.makeText(getContext(),
R.string.view_history_deleted,
Toast.LENGTH_SHORT).show(),
throwable -> ErrorActivity.reportError(getContext(),
throwable,
SettingsActivity.class, null,
ErrorActivity.ErrorInfo.make(
UserAction.DELETE_FROM_HISTORY,
"none",
"Delete view history",
R.string.general_error)));
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> {},
throwable -> ErrorActivity.reportError(getContext(),
throwable,
SettingsActivity.class, null,
ErrorActivity.ErrorInfo.make(
UserAction.DELETE_FROM_HISTORY,
"none",
"Delete search history",
R.string.general_error)));
disposables.add(onClearOrphans);
disposables.add(onDelete);
}))
.create()
.show();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Loading
///////////////////////////////////////////////////////////////////////////
@@ -298,6 +370,7 @@ public class StatisticsPlaylistFragment
context.getResources().getString(R.string.start_here_on_background),
context.getResources().getString(R.string.start_here_on_popup),
context.getResources().getString(R.string.delete),
context.getResources().getString(R.string.share)
};
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
@@ -321,6 +394,9 @@ public class StatisticsPlaylistFragment
case 5:
deleteEntry(index);
break;
case 6:
ShareUtils.shareUrl(this.getContext(), item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
break;
default:
break;
}
@@ -337,7 +413,7 @@ public class StatisticsPlaylistFragment
final Disposable onDelete = recordManager.deleteStreamHistory(entry.streamId)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDelted -> {
howManyDeleted -> {
if(getView() != null) {
Snackbar.make(getView(), R.string.one_item_deleted,
Snackbar.LENGTH_SHORT).show();

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

@@ -34,6 +34,7 @@ import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.ShareUtils;
import java.util.ArrayList;
import java.util.Collections;
@@ -459,7 +460,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,
@@ -520,7 +525,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
context.getResources().getString(R.string.start_here_on_background),
context.getResources().getString(R.string.start_here_on_popup),
context.getResources().getString(R.string.set_as_playlist_thumbnail),
context.getResources().getString(R.string.delete)
context.getResources().getString(R.string.delete),
context.getResources().getString(R.string.share)
};
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
@@ -549,6 +555,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
case 6:
deleteItem(item);
break;
case 7:
ShareUtils.shareUrl(this.getContext(), item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
break;
default:
break;
}

View File

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

View File

@@ -1,20 +1,30 @@
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;
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;
@@ -41,10 +51,13 @@ import org.schabi.newpipe.info_list.InfoListAdapter;
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;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.CollapsibleView;
@@ -69,7 +82,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;
@@ -77,8 +90,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;
@@ -97,6 +112,8 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
PreferenceManager.getDefaultSharedPreferences(activity)
.registerOnSharedPreferenceChangeListener(this);
}
@Override
@@ -124,6 +141,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
@@ -150,9 +176,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
/////////////////////////////////////////////////////////////////////////*/
@@ -207,7 +249,8 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
}
private void setupImportFromItems(final ViewGroup listHolder) {
final View previousBackupItem = addItemView(getString(R.string.previous_export), ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_backup), listHolder);
final View previousBackupItem = addItemView(getString(R.string.previous_export),
ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_backup), listHolder);
previousBackupItem.setOnClickListener(item -> onImportPreviousSelected());
final int iconColor = ThemeHelper.isLightThemeSelected(getContext()) ? Color.BLACK : Color.WHITE;
@@ -239,8 +282,8 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
}
private void onImportFromServiceSelected(int serviceId) {
if (getParentFragment() == null) return;
NavigationHelper.openSubscriptionsImportFragment(getParentFragment().getFragmentManager(), serviceId);
FragmentManager fragmentManager = getFM();
NavigationHelper.openSubscriptionsImportFragment(fragmentManager, serviceId);
}
private void onImportPreviousSelected() {
@@ -283,16 +326,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));
@@ -316,20 +362,108 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
super.initListeners();
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() {
@Override
public void selected(ChannelInfoItem selectedItem) {
// Requires the parent fragment to find holder for fragment replacement
NavigationHelper.openChannelFragment(getParentFragment().getFragmentManager(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
final FragmentManager fragmentManager = getFM();
NavigationHelper.openChannelFragment(fragmentManager,
selectedItem.getServiceId(),
selectedItem.getUrl(),
selectedItem.getName());
}
public void held(ChannelInfoItem selectedItem) {
showLongTapDialog(selectedItem);
}
});
//noinspection ConstantConditions
whatsNewItemListHeader.setOnClickListener(v ->
NavigationHelper.openWhatsNewFragment(getParentFragment().getFragmentManager()));
whatsNewItemListHeader.setOnClickListener(v -> {
FragmentManager fragmentManager = getFM();
NavigationHelper.openWhatsNewFragment(fragmentManager);
});
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) {
ShareUtils.shareUrl(this.getContext(), 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();
@@ -397,10 +531,13 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
private List<InfoItem> getSubscriptionItems(List<SubscriptionEntity> subscriptions) {
List<InfoItem> items = new ArrayList<>();
for (final SubscriptionEntity subscription : subscriptions) items.add(subscription.toChannelInfoItem());
for (final SubscriptionEntity subscription : subscriptions) {
items.add(subscription.toChannelInfoItem());
}
Collections.sort(items,
(InfoItem o1, InfoItem o2) -> o1.getName().compareToIgnoreCase(o2.getName()));
(InfoItem o1, InfoItem o2) ->
o1.getName().compareToIgnoreCase(o2.getName()));
return items;
}
@@ -429,7 +566,29 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
resetFragment();
if (super.onError(exception)) return true;
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "Subscriptions", R.string.general_error);
onUnrecoverableError(exception,
UserAction.SOMETHING_ELSE,
"none",
"Subscriptions",
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);
@@ -147,11 +147,16 @@ public class SubscriptionService {
}
private boolean isSubscriptionUpToDate(final ChannelInfo info, final SubscriptionEntity entity) {
return info.getUrl().equals(entity.getUrl()) &&
return equalsAndNotNull(info.getUrl(), entity.getUrl()) &&
info.getServiceId() == entity.getServiceId() &&
info.getName().equals(entity.getName()) &&
info.getAvatarUrl().equals(entity.getAvatarUrl()) &&
info.getDescription().equals(entity.getDescription()) &&
equalsAndNotNull(info.getAvatarUrl(), entity.getAvatarUrl()) &&
equalsAndNotNull(info.getDescription(), entity.getDescription()) &&
info.getSubscriberCount() == entity.getSubscriberCount();
}
private boolean equalsAndNotNull(final Object o1, final Object o2) {
return (o1 != null && o2 != null)
&& o1.equals(o2);
}
}

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

@@ -0,0 +1,30 @@
package org.schabi.newpipe.player;
import android.content.Context;
import android.content.ContextWrapper;
/**
* Fixes a leak caused by AudioManager using an Activity context.
* Tracked at https://android-review.googlesource.com/#/c/140481/1 and
* https://github.com/square/leakcanary/issues/205
* Source:
* https://gist.github.com/jankovd/891d96f476f7a9ce24e2
*/
public class AudioServiceLeakFix extends ContextWrapper {
AudioServiceLeakFix(Context base) {
super(base);
}
public static ContextWrapper preventLeakOf(Context base) {
return new AudioServiceLeakFix(base);
}
@Override
public Object getSystemService(String name) {
if (Context.AUDIO_SERVICE.equals(name)) {
return getApplicationContext().getSystemService(name);
}
return super.getSystemService(name);
}
}

View File

@@ -28,7 +28,6 @@ import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
@@ -39,17 +38,16 @@ import android.widget.RemoteViews;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.source.MediaSource;
import com.nostra13.universalimageloader.core.assist.FailReason;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -94,7 +92,6 @@ public final class BackgroundPlayer extends Service {
private NotificationCompat.Builder notBuilder;
private RemoteViews notRemoteView;
private RemoteViews bigNotRemoteView;
private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha";
private boolean shouldUpdateOnProgress;
@@ -133,6 +130,11 @@ public final class BackgroundPlayer extends Service {
onClose();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
@@ -192,7 +194,9 @@ public final class BackgroundPlayer extends Service {
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCustomContentView(notRemoteView)
.setCustomBigContentView(bigNotRemoteView);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) builder.setPriority(NotificationCompat.PRIORITY_MAX);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
builder.setPriority(NotificationCompat.PRIORITY_MAX);
}
return builder;
}
@@ -249,15 +253,6 @@ public final class BackgroundPlayer extends Service {
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
}
private void setControlsOpacity(@IntRange(from = 0, to = 255) int opacity) {
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity);
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity);
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity);
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity);
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationFRewind, setAlphaMethodName, opacity);
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationFRewind, setAlphaMethodName, opacity);
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@@ -279,8 +274,18 @@ public final class BackgroundPlayer extends Service {
protected class BasePlayerImpl extends BasePlayer {
@NonNull final private AudioPlaybackResolver resolver;
private int cachedDuration;
private String cachedDurationString;
BasePlayerImpl(Context context) {
super(context);
this.resolver = new AudioPlaybackResolver(context, dataSource);
}
@Override
public void initPlayer(boolean playOnReady) {
super.initPlayer(playOnReady);
}
@Override
@@ -293,30 +298,41 @@ public final class BackgroundPlayer extends Service {
startForeground(NOTIFICATION_ID, notBuilder.build());
}
@Override
public void initThumbnail(final String url) {
resetNotification();
if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
updateNotification(-1);
super.initThumbnail(url);
/*//////////////////////////////////////////////////////////////////////////
// Thumbnail Loading
//////////////////////////////////////////////////////////////////////////*/
private void updateNotificationThumbnail() {
if (basePlayerImpl == null) return;
if (notRemoteView != null) {
notRemoteView.setImageViewBitmap(R.id.notificationCover,
basePlayerImpl.getThumbnail());
}
if (bigNotRemoteView != null) {
bigNotRemoteView.setImageViewBitmap(R.id.notificationCover,
basePlayerImpl.getThumbnail());
}
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
if (loadedImage != null) {
// rebuild notification here since remote view does not release bitmaps, causing memory leaks
resetNotification();
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
updateNotification(-1);
}
resetNotification();
updateNotificationThumbnail();
updateNotification(-1);
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
super.onLoadingFailed(imageUri, view, failReason);
resetNotification();
updateNotificationThumbnail();
updateNotification(-1);
}
/*//////////////////////////////////////////////////////////////////////////
// States Implementation
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onPrepared(boolean playWhenReady) {
super.onPrepared(playWhenReady);
@@ -335,9 +351,14 @@ public final class BackgroundPlayer extends Service {
if (!shouldUpdateOnProgress) return;
resetNotification();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Oreo*/) updateNotificationThumbnail();
if (bigNotRemoteView != null) {
if(cachedDuration != duration) {
cachedDuration = duration;
cachedDurationString = getTimeString(duration);
}
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration));
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + cachedDurationString);
}
if (notRemoteView != null) {
notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
@@ -390,29 +411,18 @@ public final class BackgroundPlayer extends Service {
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info,
final int newPlayQueueIndex,
final boolean hasPlayQueueItemChanged) {
if (shouldUpdateOnProgress || hasPlayQueueItemChanged) {
resetNotification();
updateNotification(-1);
updateMetadata();
}
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
super.onMetadataChanged(tag);
resetNotification();
updateNotificationThumbnail();
updateNotification(-1);
updateMetadata();
}
@Override
@Nullable
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
final MediaSource liveSource = super.sourceOf(item, info);
if (liveSource != null) return liveSource;
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
if (index < 0 || index >= info.getAudioStreams().size()) return null;
final AudioStream audio = info.getAudioStreams().get(index);
return buildMediaSource(audio.getUrl(), PlayerHelper.cacheKeyOf(info, audio),
MediaFormat.getSuffixById(audio.getFormatId()));
return resolver.resolve(info);
}
@Override
@@ -439,8 +449,8 @@ public final class BackgroundPlayer extends Service {
}
private void updateMetadata() {
if (activityListener != null && currentInfo != null) {
activityListener.onMetadataUpdate(currentInfo);
if (activityListener != null && getCurrentMetadata() != null) {
activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
}
}
@@ -531,44 +541,36 @@ public final class BackgroundPlayer extends Service {
updatePlayback();
}
@Override
public void onBlocked() {
super.onBlocked();
setControlsOpacity(77);
updateNotification(-1);
}
@Override
public void onPlaying() {
super.onPlaying();
setControlsOpacity(255);
resetNotification();
updateNotificationThumbnail();
updateNotification(R.drawable.ic_pause_white);
lockManager.acquireWifiAndCpu();
}
@Override
public void onPaused() {
super.onPaused();
resetNotification();
updateNotificationThumbnail();
updateNotification(R.drawable.ic_play_arrow_white);
lockManager.releaseWifiAndCpu();
}
@Override
public void onCompleted() {
super.onCompleted();
setControlsOpacity(255);
resetNotification();
if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
if (bigNotRemoteView != null) {
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
}
if (notRemoteView != null) {
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
}
updateNotificationThumbnail();
updateNotification(R.drawable.ic_replay_white);
lockManager.releaseWifiAndCpu();
}
}

View File

@@ -24,16 +24,14 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
@@ -49,15 +47,14 @@ import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.Util;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.helper.AudioReactor;
import org.schabi.newpipe.player.helper.LoadController;
@@ -72,6 +69,8 @@ import org.schabi.newpipe.player.playback.PlaybackListener;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.SerializedCache;
import java.io.IOException;
@@ -82,12 +81,12 @@ import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.SerialDisposable;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
/**
* Base for the players, joining the common properties
@@ -98,26 +97,54 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
public abstract class BasePlayer implements
Player.EventListener, PlaybackListener, ImageLoadingListener {
public static final boolean DEBUG = true;
@NonNull public static final String TAG = "BasePlayer";
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
@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 private LoadControl loadControl;
@NonNull
final private RenderersFactory renderFactory;
@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";
/*//////////////////////////////////////////////////////////////////////////
@@ -129,11 +156,17 @@ public abstract class BasePlayer implements
protected PlayQueue playQueue;
protected PlayQueueAdapter playQueueAdapter;
@Nullable
protected MediaSourceManager playbackManager;
protected StreamInfo currentInfo;
protected PlayQueueItem currentItem;
@Nullable
private PlayQueueItem currentItem;
@Nullable
private MediaSourceTag currentMetadata;
@Nullable
private Bitmap currentThumbnail;
@Nullable
protected Toast errorToast;
/*//////////////////////////////////////////////////////////////////////////
@@ -145,18 +178,11 @@ public abstract class BasePlayer implements
protected final static int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
protected final static int RECOVERY_SKIP_THRESHOLD_MILLIS = 3000; // 3 seconds
protected CustomTrackSelector trackSelector;
protected PlayerDataSource dataSource;
protected SimpleExoPlayer simpleExoPlayer;
protected AudioReactor audioReactor;
protected MediaSessionManager mediaSessionManager;
private boolean isPrepared = false;
private boolean isSynchronizing = false;
protected Disposable progressUpdateReactor;
protected CompositeDisposable databaseUpdateReactor;
//////////////////////////////////////////////////////////////////////////*/
@@ -171,33 +197,34 @@ public abstract class BasePlayer implements
};
this.intentFilter = new IntentFilter();
setupBroadcastReceiver(intentFilter);
context.registerReceiver(broadcastReceiver, intentFilter);
this.recordManager = new HistoryRecordManager(context);
this.progressUpdateReactor = new SerialDisposable();
this.databaseUpdateReactor = new CompositeDisposable();
final String userAgent = Downloader.USER_AGENT;
final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
this.dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter);
final TrackSelection.Factory trackSelectionFactory = PlayerHelper.getQualitySelector(context);
this.trackSelector = new CustomTrackSelector(trackSelectionFactory);
this.loadControl = new LoadController(context);
this.renderFactory = new DefaultRenderersFactory(context);
}
public void setup() {
if (simpleExoPlayer == null) initPlayer(/*playOnInit=*/true);
if (simpleExoPlayer == null) {
initPlayer(/*playOnInit=*/true);
}
initListeners();
}
public void initPlayer(final boolean playOnReady) {
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
databaseUpdateReactor = new CompositeDisposable();
final String userAgent = Downloader.USER_AGENT;
final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter);
final TrackSelection.Factory trackSelectionFactory =
PlayerHelper.getQualitySelector(context, bandwidthMeter);
trackSelector = new CustomTrackSelector(trackSelectionFactory);
final LoadControl loadControl = new LoadController(context);
final RenderersFactory renderFactory = new DefaultRenderersFactory(context);
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderFactory, trackSelector, loadControl);
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderFactory, trackSelector, loadControl);
simpleExoPlayer.addListener(this);
simpleExoPlayer.setPlayWhenReady(playOnReady);
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
@@ -205,9 +232,12 @@ public abstract class BasePlayer implements
audioReactor = new AudioReactor(context, simpleExoPlayer);
mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer,
new BasePlayerMediaSession(this));
registerBroadcastReceiver();
}
public void initListeners() {}
public void initListeners() {
}
public void handleIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
@@ -224,7 +254,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);
}
@@ -235,20 +266,36 @@ public abstract class BasePlayer implements
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
final float playbackSpeed = intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed());
final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch());
final boolean playbackSkipSilence = intent.getBooleanExtra(PLAYBACK_SKIP_SILENCE,
getPlaybackSkipSilence());
// seek to timestamp if stream is already playing
if (simpleExoPlayer != null
&& queue.size() == 1
&& playQueue != null
&& playQueue.getItem() != null
&& queue.getItem().getUrl().equals(playQueue.getItem().getUrl())
&& queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET
) {
simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
return;
}
// Good to go...
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, /*playOnInit=*/true);
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
/*playOnInit=*/true);
}
protected void initPlayback(@NonNull final PlayQueue queue,
@Player.RepeatMode final int repeatMode,
final float playbackSpeed,
final float playbackPitch,
final boolean playbackSkipSilence,
final boolean playOnReady) {
destroyPlayer();
initPlayer(playOnReady);
setRepeatMode(repeatMode);
setPlaybackParameters(playbackSpeed, playbackPitch);
setPlaybackParameters(playbackSpeed, playbackPitch, playbackSkipSilence);
playQueue = queue;
playQueue.init();
@@ -270,7 +317,7 @@ public abstract class BasePlayer implements
if (playQueue != null) playQueue.dispose();
if (audioReactor != null) audioReactor.dispose();
if (playbackManager != null) playbackManager.dispose();
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
if (mediaSessionManager != null) mediaSessionManager.dispose();
if (playQueueAdapter != null) {
playQueueAdapter.unsetSelectedListener();
@@ -283,20 +330,21 @@ public abstract class BasePlayer implements
destroyPlayer();
unregisterBroadcastReceiver();
trackSelector = null;
simpleExoPlayer = null;
mediaSessionManager = null;
databaseUpdateReactor.clear();
progressUpdateReactor.set(null);
}
/*//////////////////////////////////////////////////////////////////////////
// Thumbnail Loading
//////////////////////////////////////////////////////////////////////////*/
public void initThumbnail(final String url) {
private void initThumbnail(final String url) {
if (DEBUG) Log.d(TAG, "Thumbnail - initThumbnail() called");
if (url == null || url.isEmpty()) return;
ImageLoader.getInstance().resume();
ImageLoader.getInstance().loadImage(url, this);
ImageLoader.getInstance().loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS,
this);
}
@Override
@@ -309,6 +357,7 @@ public abstract class BasePlayer implements
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
Log.e(TAG, "Thumbnail - onLoadingFailed() called on imageUri = [" + imageUri + "]",
failReason.getCause());
currentThumbnail = null;
}
@Override
@@ -316,64 +365,14 @@ public abstract class BasePlayer implements
if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingComplete() called with: " +
"imageUri = [" + imageUri + "], view = [" + view + "], " +
"loadedImage = [" + loadedImage + "]");
currentThumbnail = loadedImage;
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: " +
"imageUri = [" + imageUri + "], view = [" + view + "]");
}
/*//////////////////////////////////////////////////////////////////////////
// MediaSource Building
//////////////////////////////////////////////////////////////////////////*/
public MediaSource buildLiveMediaSource(@NonNull final String sourceUrl,
@C.ContentType final int type) {
if (DEBUG) {
Log.d(TAG, "buildLiveMediaSource() called with: url = [" + sourceUrl +
"], content type = [" + type + "]");
}
if (dataSource == null) return null;
final Uri uri = Uri.parse(sourceUrl);
switch (type) {
case C.TYPE_SS:
return dataSource.getLiveSsMediaSourceFactory().createMediaSource(uri);
case C.TYPE_DASH:
return dataSource.getLiveDashMediaSourceFactory().createMediaSource(uri);
case C.TYPE_HLS:
return dataSource.getLiveHlsMediaSourceFactory().createMediaSource(uri);
default:
throw new IllegalStateException("Unsupported type: " + type);
}
}
public MediaSource buildMediaSource(@NonNull final String sourceUrl,
@NonNull final String cacheKey,
@NonNull final String overrideExtension) {
if (DEBUG) {
Log.d(TAG, "buildMediaSource() called with: url = [" + sourceUrl +
"], cacheKey = [" + cacheKey + "]" +
"], overrideExtension = [" + overrideExtension + "]");
}
if (dataSource == null) return null;
final Uri uri = Uri.parse(sourceUrl);
@C.ContentType final int type = TextUtils.isEmpty(overrideExtension) ?
Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
switch (type) {
case C.TYPE_SS:
return dataSource.getLiveSsMediaSourceFactory().createMediaSource(uri);
case C.TYPE_DASH:
return dataSource.getDashMediaSourceFactory().createMediaSource(uri);
case C.TYPE_HLS:
return dataSource.getHlsMediaSourceFactory().createMediaSource(uri);
case C.TYPE_OTHER:
return dataSource.getExtractorMediaSourceFactory(cacheKey).createMediaSource(uri);
default:
throw new IllegalStateException("Unsupported type: " + type);
}
currentThumbnail = null;
}
/*//////////////////////////////////////////////////////////////////////////
@@ -398,11 +397,17 @@ public abstract class BasePlayer implements
}
}
public void unregisterBroadcastReceiver() {
protected void registerBroadcastReceiver() {
// Try to unregister current first
unregisterBroadcastReceiver();
context.registerReceiver(broadcastReceiver, intentFilter);
}
protected void unregisterBroadcastReceiver() {
try {
context.unregisterReceiver(broadcastReceiver);
} catch (final IllegalArgumentException unregisteredException) {
Log.e(TAG, "Broadcast receiver already unregistered.", unregisteredException);
Log.w(TAG, "Broadcast receiver already unregistered (" + unregisteredException.getMessage() + ")");
}
}
@@ -455,13 +460,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");
@@ -509,13 +516,11 @@ public abstract class BasePlayer implements
public abstract void onUpdateProgress(int currentProgress, int duration, int bufferPercent);
protected void startProgressLoop() {
if (progressUpdateReactor != null) progressUpdateReactor.dispose();
progressUpdateReactor = getProgressReactor();
progressUpdateReactor.set(getProgressReactor());
}
protected void stopProgressLoop() {
if (progressUpdateReactor != null) progressUpdateReactor.dispose();
progressUpdateReactor = null;
progressUpdateReactor.set(null);
}
public void triggerProgressUpdate() {
@@ -530,7 +535,8 @@ public abstract class BasePlayer implements
private Disposable getProgressReactor() {
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> triggerProgressUpdate());
.subscribe(ignored -> triggerProgressUpdate(),
error -> Log.e(TAG, "Progress update failure: ", error));
}
/*//////////////////////////////////////////////////////////////////////////
@@ -544,28 +550,16 @@ public abstract class BasePlayer implements
(manifest == null ? "no manifest" : "available manifest") + ", " +
"timeline size = [" + timeline.getWindowCount() + "], " +
"reason = [" + reason + "]");
if (playQueue == null) return;
switch (reason) {
case Player.TIMELINE_CHANGE_REASON_RESET: // called after #block
case Player.TIMELINE_CHANGE_REASON_PREPARED: // called after #unblock
case Player.TIMELINE_CHANGE_REASON_DYNAMIC: // called after playlist changes
// Ensures MediaSourceManager#update is complete
final boolean isPlaylistStable = timeline.getWindowCount() == playQueue.size();
// Ensure dynamic/livestream timeline changes does not cause negative position
if (isPlaylistStable && !isCurrentWindowValid() && !isSynchronizing) {
if (DEBUG) Log.d(TAG, "Playback - negative time position reached, " +
"clamping to default position.");
seekToDefault();
}
break;
}
maybeUpdateCurrentMetadata();
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
if (DEBUG) Log.d(TAG, "ExoPlayer - onTracksChanged(), " +
"track group size = " + trackGroups.length);
maybeUpdateCurrentMetadata();
}
@Override
@@ -585,6 +579,8 @@ public abstract class BasePlayer implements
} else if (isLoading && !isProgressLoopRunning()) {
startProgressLoop();
}
maybeUpdateCurrentMetadata();
}
@Override
@@ -608,6 +604,7 @@ public abstract class BasePlayer implements
}
break;
case Player.STATE_READY: //3
maybeUpdateCurrentMetadata();
maybeCorrectSeekPosition();
if (!isPrepared) {
isPrepared = true;
@@ -624,56 +621,37 @@ public abstract class BasePlayer implements
}
private void maybeCorrectSeekPosition() {
if (playQueue == null || simpleExoPlayer == null || currentInfo == null) return;
if (playQueue == null || simpleExoPlayer == null || currentMetadata == null) return;
final int currentSourceIndex = playQueue.getIndex();
final PlayQueueItem currentSourceItem = playQueue.getItem();
if (currentSourceItem == null) return;
final long recoveryPositionMillis = currentSourceItem.getRecoveryPosition();
final boolean isCurrentWindowCorrect =
simpleExoPlayer.getCurrentPeriodIndex() == currentSourceIndex;
final StreamInfo currentInfo = currentMetadata.getMetadata();
final long presetStartPositionMillis = currentInfo.getStartPosition() * 1000;
if (recoveryPositionMillis != PlayQueueItem.RECOVERY_UNSET && isCurrentWindowCorrect) {
// Is recovering previous playback?
if (DEBUG) Log.d(TAG, "Playback - Rewinding to recovery time=" +
"[" + getTimeString((int)recoveryPositionMillis) + "]");
seekTo(recoveryPositionMillis);
playQueue.unsetRecovery(currentSourceIndex);
} else if (isSynchronizing && isLive()) {
if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time");
// Is still synchronizing?
seekToDefault();
} else if (isSynchronizing && presetStartPositionMillis > 0L) {
if (presetStartPositionMillis > 0L) {
// Has another start position?
if (DEBUG) Log.d(TAG, "Playback - Seeking to preset start " +
"position=[" + presetStartPositionMillis + "]");
// Has another start position?
seekTo(presetStartPositionMillis);
currentInfo.setStartPosition(0);
}
isSynchronizing = false;
}
/**
* 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: " +
@@ -707,7 +685,7 @@ public abstract class BasePlayer implements
setRecovery();
final Throwable cause = error.getCause();
if (cause instanceof BehindLiveWindowException) {
if (error instanceof BehindLiveWindowException) {
reload();
} else if (cause instanceof UnknownHostException) {
playQueue.error(/*isNetworkProblem=*/true);
@@ -726,22 +704,29 @@ public abstract class BasePlayer implements
public void onPositionDiscontinuity(@Player.DiscontinuityReason final int reason) {
if (DEBUG) Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with " +
"reason = [" + reason + "]");
// Refresh the playback if there is a transition to the next video
final int newPeriodIndex = simpleExoPlayer.getCurrentPeriodIndex();
if (playQueue == null) return;
/* Discontinuity reasons!! Thank you ExoPlayer lords */
// Refresh the playback if there is a transition to the next video
final int newWindowIndex = simpleExoPlayer.getCurrentWindowIndex();
switch (reason) {
case DISCONTINUITY_REASON_PERIOD_TRANSITION:
if (newPeriodIndex == playQueue.getIndex()) {
// When player is in single repeat mode and a period transition occurs,
// we need to register a view count here since no metadata has changed
if (getRepeatMode() == Player.REPEAT_MODE_ONE &&
newWindowIndex == playQueue.getIndex()) {
registerView();
} else {
playQueue.offsetIndex(+1);
break;
}
case DISCONTINUITY_REASON_SEEK:
case DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
case DISCONTINUITY_REASON_INTERNAL:
if (playQueue.getIndex() != newWindowIndex) {
playQueue.setIndex(newWindowIndex);
}
break;
}
maybeUpdateCurrentMetadata();
}
@Override
@@ -787,7 +772,7 @@ public abstract class BasePlayer implements
if (DEBUG) Log.d(TAG, "Playback - onPlaybackBlock() called");
currentItem = null;
currentInfo = null;
currentMetadata = null;
simpleExoPlayer.stop();
isPrepared = false;
@@ -804,42 +789,21 @@ public abstract class BasePlayer implements
simpleExoPlayer.prepare(mediaSource);
}
@Override
public void onPlaybackSynchronize(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info) {
public void onPlaybackSynchronize(@NonNull final PlayQueueItem item) {
if (DEBUG) Log.d(TAG, "Playback - onPlaybackSynchronize() called with " +
(info != null ? "available" : "null") + " info, " +
"item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]");
if (simpleExoPlayer == null || playQueue == null) return;
final boolean onPlaybackInitial = currentItem == null;
final boolean hasPlayQueueItemChanged = currentItem != item;
final boolean hasStreamInfoChanged = currentInfo != info;
final int currentPlayQueueIndex = playQueue.indexOf(item);
final int currentPlaylistIndex = simpleExoPlayer.getCurrentWindowIndex();
final int currentPlaylistSize = simpleExoPlayer.getCurrentTimeline().getWindowCount();
// when starting playback on the last item when not repeating, maybe auto queue
if (info != null && currentPlayQueueIndex == playQueue.size() - 1 &&
getRepeatMode() == Player.REPEAT_MODE_OFF &&
PlayerHelper.isAutoQueueEnabled(context)) {
final PlayQueue autoQueue = PlayerHelper.autoQueueOf(info, playQueue.getStreams());
if (autoQueue != null) playQueue.append(autoQueue.getStreams());
}
// If nothing to synchronize
if (!hasPlayQueueItemChanged && !hasStreamInfoChanged) {
return;
}
if (!hasPlayQueueItemChanged) return;
currentItem = item;
currentInfo = info;
if (hasPlayQueueItemChanged) {
// updates only to the stream info should not trigger another view count
registerView();
initThumbnail(info == null ? item.getThumbnailUrl() : info.getThumbnailUrl());
}
onMetadataChanged(item, info, currentPlayQueueIndex, hasPlayQueueItemChanged);
// Check if on wrong window
if (currentPlayQueueIndex != playQueue.getIndex()) {
@@ -854,39 +818,29 @@ public abstract class BasePlayer implements
"index=[" + currentPlayQueueIndex + "] with " +
"playlist length=[" + currentPlaylistSize + "]");
// If not playing correct stream, change window position and sets flag
// for synchronizing once window position is corrected
// @see maybeCorrectSeekPosition()
} else if (currentPlaylistIndex != currentPlayQueueIndex || onPlaybackInitial ||
!isPlaying()) {
if (DEBUG) Log.d(TAG, "Playback - Rewinding to correct" +
" index=[" + currentPlayQueueIndex + "]," +
" from=[" + currentPlaylistIndex + "], size=[" + currentPlaylistSize + "].");
isSynchronizing = true;
simpleExoPlayer.seekToDefaultPosition(currentPlayQueueIndex);
if (item.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
simpleExoPlayer.seekTo(currentPlayQueueIndex, item.getRecoveryPosition());
playQueue.unsetRecovery(currentPlayQueueIndex);
} else {
simpleExoPlayer.seekToDefaultPosition(currentPlayQueueIndex);
}
}
}
abstract protected void onMetadataChanged(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info,
final int newPlayQueueIndex,
final boolean hasPlayQueueItemChanged);
@Nullable
@Override
public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) {
final StreamType streamType = info.getStreamType();
if (!(streamType == StreamType.AUDIO_LIVE_STREAM || streamType == StreamType.LIVE_STREAM)) {
return null;
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
final StreamInfo info = tag.getMetadata();
if (DEBUG) {
Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName());
}
if (!info.getHlsUrl().isEmpty()) {
return buildLiveMediaSource(info.getHlsUrl(), C.TYPE_HLS);
} else if (!info.getDashMpdUrl().isEmpty()) {
return buildLiveMediaSource(info.getDashMpdUrl(), C.TYPE_DASH);
}
return null;
initThumbnail(info.getThumbnailUrl());
registerView();
}
@Override
@@ -983,8 +937,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();
@@ -1019,9 +973,7 @@ public abstract class BasePlayer implements
public void seekTo(long positionMillis) {
if (DEBUG) Log.d(TAG, "seekBy() called with: position = [" + positionMillis + "]");
if (simpleExoPlayer == null || positionMillis < 0 ||
positionMillis > simpleExoPlayer.getDuration()) return;
simpleExoPlayer.seekTo(positionMillis);
if (simpleExoPlayer != null) simpleExoPlayer.seekTo(positionMillis);
}
public void seekBy(long offsetMillis) {
@@ -1045,12 +997,14 @@ public abstract class BasePlayer implements
//////////////////////////////////////////////////////////////////////////*/
private void registerView() {
if (databaseUpdateReactor == null || currentInfo == null) return;
databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete()
if (currentMetadata == null) return;
final StreamInfo currentInfo = currentMetadata.getMetadata();
final Disposable viewRegister = recordManager.onViewed(currentInfo).onErrorComplete()
.subscribe(
ignored -> {/* successful */},
error -> Log.e(TAG, "Player onViewed() failure: ", error)
));
);
databaseUpdateReactor.add(viewRegister);
}
protected void reload() {
@@ -1064,7 +1018,7 @@ public abstract class BasePlayer implements
}
protected void savePlaybackState(final StreamInfo info, final long progress) {
if (info == null || databaseUpdateReactor == null) return;
if (info == null) return;
final Disposable stateSaver = recordManager.saveStreamState(info, progress)
.observeOn(AndroidSchedulers.mainThread())
.onErrorComplete()
@@ -1076,7 +1030,8 @@ public abstract class BasePlayer implements
}
private void savePlaybackState() {
if (simpleExoPlayer == null || currentInfo == null) return;
if (simpleExoPlayer == null || currentMetadata == null) return;
final StreamInfo currentInfo = currentMetadata.getMetadata();
if (simpleExoPlayer.getCurrentPosition() > RECOVERY_SKIP_THRESHOLD_MILLIS &&
simpleExoPlayer.getCurrentPosition() <
@@ -1084,6 +1039,36 @@ public abstract class BasePlayer implements
savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
}
}
private void maybeUpdateCurrentMetadata() {
if (simpleExoPlayer == null) return;
final MediaSourceTag metadata;
try {
metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag();
} catch (IndexOutOfBoundsException | ClassCastException error) {
if (DEBUG) Log.d(TAG, "Could not update metadata: " + error.getMessage());
if (DEBUG) error.printStackTrace();
return;
}
if (metadata == null) return;
maybeAutoQueueNextStream(metadata);
if (currentMetadata == metadata) return;
currentMetadata = metadata;
onMetadataChanged(metadata);
}
private void maybeAutoQueueNextStream(@NonNull final MediaSourceTag currentMetadata) {
if (playQueue == null || playQueue.getIndex() != playQueue.size() - 1 ||
getRepeatMode() != Player.REPEAT_MODE_OFF ||
!PlayerHelper.isAutoQueueEnabled(context)) return;
// auto queue when starting playback on the last item when not repeating
final PlayQueue autoQueue = PlayerHelper.autoQueueOf(currentMetadata.getMetadata(),
playQueue.getStreams());
if (autoQueue != null) playQueue.append(autoQueue.getStreams());
}
/*//////////////////////////////////////////////////////////////////////////
// Getters and Setters
//////////////////////////////////////////////////////////////////////////*/
@@ -1100,19 +1085,37 @@ public abstract class BasePlayer implements
return currentState;
}
@Nullable
public MediaSourceTag getCurrentMetadata() {
return currentMetadata;
}
@NonNull
public String getVideoUrl() {
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUrl();
return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getUrl();
}
@NonNull
public String getVideoTitle() {
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getTitle();
return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getName();
}
@NonNull
public String getUploaderName() {
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader();
return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getUploaderName();
}
/** Checks if the current playback is a livestream AND is playing at or beyond the live edge */
@Nullable
public Bitmap getThumbnail() {
return currentThumbnail == null ?
BitmapFactory.decodeResource(context.getResources(), R.drawable.dummy_thumbnail) :
currentThumbnail;
}
/**
* 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;
@@ -1134,11 +1137,15 @@ public abstract class BasePlayer implements
return simpleExoPlayer.isCurrentWindowDynamic();
} 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();
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();
@@ -1146,11 +1153,13 @@ public abstract class BasePlayer implements
@Player.RepeatMode
public int getRepeatMode() {
return simpleExoPlayer.getRepeatMode();
return simpleExoPlayer == null
? Player.REPEAT_MODE_OFF
: simpleExoPlayer.getRepeatMode();
}
public void setRepeatMode(@Player.RepeatMode final int repeatMode) {
simpleExoPlayer.setRepeatMode(repeatMode);
if (simpleExoPlayer != null) simpleExoPlayer.setRepeatMode(repeatMode);
}
public float getPlaybackSpeed() {
@@ -1161,19 +1170,22 @@ public abstract class BasePlayer implements
return getPlaybackParameters().pitch;
}
public boolean getPlaybackSkipSilence() {
return getPlaybackParameters().skipSilence;
}
public void setPlaybackSpeed(float speed) {
setPlaybackParameters(speed, getPlaybackPitch());
setPlaybackParameters(speed, getPlaybackPitch(), getPlaybackSkipSilence());
}
public PlaybackParameters getPlaybackParameters() {
final PlaybackParameters defaultParameters = new PlaybackParameters(1f, 1f);
if (simpleExoPlayer == null) return defaultParameters;
if (simpleExoPlayer == null) return PlaybackParameters.DEFAULT;
final PlaybackParameters parameters = simpleExoPlayer.getPlaybackParameters();
return parameters == null ? defaultParameters : parameters;
return parameters == null ? PlaybackParameters.DEFAULT : parameters;
}
public void setPlaybackParameters(float speed, float pitch) {
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch));
public void setPlaybackParameters(float speed, float pitch, boolean skipSilence) {
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence));
}
public PlayQueue getPlayQueue() {
@@ -1189,7 +1201,7 @@ public abstract class BasePlayer implements
}
public boolean isProgressLoopRunning() {
return progressUpdateReactor != null && !progressUpdateReactor.isDisposed();
return progressUpdateReactor.get() != null;
}
public void setRecovery() {
@@ -1209,4 +1221,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

@@ -25,6 +25,7 @@ import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
@@ -35,6 +36,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.DisplayMetrics;
@@ -44,8 +46,11 @@ import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
@@ -57,7 +62,6 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.SubtitleView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
@@ -66,10 +70,13 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.ThemeHelper;
@@ -80,6 +87,7 @@ import java.util.UUID;
import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING;
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION;
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
import static org.schabi.newpipe.util.AnimationUtils.Type.SCALE_AND_ALPHA;
import static org.schabi.newpipe.util.AnimationUtils.Type.SLIDE_AND_ALPHA;
import static org.schabi.newpipe.util.AnimationUtils.animateRotation;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -103,6 +111,7 @@ public final class MainVideoPlayer extends AppCompatActivity
@Nullable private PlayerState playerState;
private boolean isInMultiWindow;
private boolean isBackPressed;
/*//////////////////////////////////////////////////////////////////////////
// Activity LifeCycle
@@ -114,12 +123,17 @@ public final class MainVideoPlayer extends AppCompatActivity
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(this);
ThemeHelper.setTheme(this);
getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) getWindow().setStatusBarColor(Color.BLACK);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.screenBrightness = PlayerHelper.getScreenBrightness(getApplicationContext());
getWindow().setAttributes(lp);
hideSystemUi();
setContentView(R.layout.activity_main_player);
playerImpl = new VideoPlayerImpl(this);
playerImpl = new VideoPlayerImpl(this);
playerImpl.setup(findViewById(android.R.id.content));
if (savedInstanceState != null && savedInstanceState.get(KEY_SAVED_STATE) != null) {
@@ -146,7 +160,10 @@ public final class MainVideoPlayer extends AppCompatActivity
protected void onNewIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
super.onNewIntent(intent);
playerImpl.handleIntent(intent);
if (intent != null) {
playerState = null;
playerImpl.handleIntent(intent);
}
}
@Override
@@ -160,6 +177,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
@@ -171,7 +192,7 @@ public final class MainVideoPlayer extends AppCompatActivity
playerImpl.setPlaybackQuality(playerState.getPlaybackQuality());
playerImpl.initPlayback(playerState.getPlayQueue(), playerState.getRepeatMode(),
playerState.getPlaybackSpeed(), playerState.getPlaybackPitch(),
playerState.wasPlaying());
playerState.isPlaybackSkipSilence(), playerState.wasPlaying());
}
}
@@ -185,6 +206,12 @@ public final class MainVideoPlayer extends AppCompatActivity
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
isBackPressed = true;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
if (DEBUG) Log.d(TAG, "onSaveInstanceState() called");
@@ -192,9 +219,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.isPlaying());
if(!playerImpl.gotDestroyed()) {
playerState = createPlayerState();
}
StateSaver.tryToSave(isChangingConfigurations(), null, outState, this);
}
@@ -202,13 +229,36 @@ public final class MainVideoPlayer extends AppCompatActivity
protected void onStop() {
if (DEBUG) Log.d(TAG, "onStop() called");
super.onStop();
PlayerHelper.setScreenBrightness(getApplicationContext(),
getWindow().getAttributes().screenBrightness);
if (playerImpl == null) return;
if (!isBackPressed) {
playerImpl.minimize();
}
playerState = createPlayerState();
playerImpl.destroy();
isInMultiWindow = false;
isBackPressed = false;
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(newBase));
}
/*//////////////////////////////////////////////////////////////////////////
// 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";
@@ -234,14 +284,9 @@ public final class MainVideoPlayer extends AppCompatActivity
if (DEBUG) Log.d(TAG, "showSystemUi() called");
if (playerImpl != null && playerImpl.queueVisible) return;
final int visibility;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
} else {
visibility = View.STATUS_BAR_VISIBLE;
}
final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@ColorInt final int systenUiColor =
@@ -310,11 +355,7 @@ public final class MainVideoPlayer extends AppCompatActivity
protected void setShuffleButton(final ImageButton shuffleButton, final boolean shuffled) {
final int shuffleAlpha = shuffled ? 255 : 77;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
shuffleButton.setImageAlpha(shuffleAlpha);
} else {
shuffleButton.setAlpha(shuffleAlpha);
}
shuffleButton.setImageAlpha(shuffleAlpha);
}
private boolean isInMultiWindow() {
@@ -326,18 +367,27 @@ public final class MainVideoPlayer extends AppCompatActivity
////////////////////////////////////////////////////////////////////////////
@Override
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch) {
if (playerImpl != null) playerImpl.setPlaybackParameters(playbackTempo, playbackPitch);
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
boolean playbackSkipSilence) {
if (playerImpl != null) {
playerImpl.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence);
}
}
///////////////////////////////////////////////////////////////////////////
@SuppressWarnings({"unused", "WeakerAccess"})
private class VideoPlayerImpl extends VideoPlayer {
private final float MAX_GESTURE_LENGTH = 0.75f;
private TextView titleTextView;
private TextView channelTextView;
private TextView volumeTextView;
private TextView brightnessTextView;
private RelativeLayout volumeRelativeLayout;
private ProgressBar volumeProgressBar;
private ImageView volumeImageView;
private RelativeLayout brightnessRelativeLayout;
private ProgressBar brightnessProgressBar;
private ImageView brightnessImageView;
private ImageButton queueButton;
private ImageButton repeatButton;
private ImageButton shuffleButton;
@@ -345,6 +395,7 @@ public final class MainVideoPlayer extends AppCompatActivity
private ImageButton playPauseButton;
private ImageButton playPreviousButton;
private ImageButton playNextButton;
private Button closeButton;
private RelativeLayout queueLayout;
private ImageButton itemsListCloseButton;
@@ -354,6 +405,7 @@ public final class MainVideoPlayer extends AppCompatActivity
private boolean queueVisible;
private ImageButton moreOptionsButton;
private ImageButton shareButton;
private ImageButton toggleOrientationButton;
private ImageButton switchPopupButton;
private ImageButton switchBackgroundButton;
@@ -361,6 +413,8 @@ public final class MainVideoPlayer extends AppCompatActivity
private RelativeLayout windowRootLayout;
private View secondaryControls;
private int maxGestureLength;
VideoPlayerImpl(final Context context) {
super("VideoPlayerImpl" + MainVideoPlayer.TAG, context);
}
@@ -370,8 +424,12 @@ public final class MainVideoPlayer extends AppCompatActivity
super.initViews(rootView);
this.titleTextView = rootView.findViewById(R.id.titleTextView);
this.channelTextView = rootView.findViewById(R.id.channelTextView);
this.volumeTextView = rootView.findViewById(R.id.volumeTextView);
this.brightnessTextView = rootView.findViewById(R.id.brightnessTextView);
this.volumeRelativeLayout = rootView.findViewById(R.id.volumeRelativeLayout);
this.volumeProgressBar = rootView.findViewById(R.id.volumeProgressBar);
this.volumeImageView = rootView.findViewById(R.id.volumeImageView);
this.brightnessRelativeLayout = rootView.findViewById(R.id.brightnessRelativeLayout);
this.brightnessProgressBar = rootView.findViewById(R.id.brightnessProgressBar);
this.brightnessImageView = rootView.findViewById(R.id.brightnessImageView);
this.queueButton = rootView.findViewById(R.id.queueButton);
this.repeatButton = rootView.findViewById(R.id.repeatButton);
this.shuffleButton = rootView.findViewById(R.id.shuffleButton);
@@ -379,9 +437,11 @@ public final class MainVideoPlayer extends AppCompatActivity
this.playPauseButton = rootView.findViewById(R.id.playPauseButton);
this.playPreviousButton = rootView.findViewById(R.id.playPreviousButton);
this.playNextButton = rootView.findViewById(R.id.playNextButton);
this.closeButton = rootView.findViewById(R.id.closeButton);
this.moreOptionsButton = rootView.findViewById(R.id.moreOptionsButton);
this.secondaryControls = rootView.findViewById(R.id.secondaryControls);
this.shareButton = rootView.findViewById(R.id.share);
this.toggleOrientationButton = rootView.findViewById(R.id.toggleOrientation);
this.switchBackgroundButton = rootView.findViewById(R.id.switchBackground);
this.switchPopupButton = rootView.findViewById(R.id.switchPopup);
@@ -413,7 +473,7 @@ public final class MainVideoPlayer extends AppCompatActivity
public void initListeners() {
super.initListeners();
MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
PlayerGestureListener listener = new PlayerGestureListener();
gestureDetector = new GestureDetector(context, listener);
gestureDetector.setIsLongpressEnabled(false);
getRootView().setOnTouchListener(listener);
@@ -425,11 +485,44 @@ public final class MainVideoPlayer extends AppCompatActivity
playPauseButton.setOnClickListener(this);
playPreviousButton.setOnClickListener(this);
playNextButton.setOnClickListener(this);
closeButton.setOnClickListener(this);
moreOptionsButton.setOnClickListener(this);
shareButton.setOnClickListener(this);
toggleOrientationButton.setOnClickListener(this);
switchBackgroundButton.setOnClickListener(this);
switchPopupButton.setOnClickListener(this);
getRootView().addOnLayoutChangeListener((view, l, t, r, b, ol, ot, or, ob) -> {
if (l != ol || t != ot || r != or || b != ob) {
// Use smaller value to be consistent between screen orientations
// (and to make usage easier)
int width = r - l, height = b - t;
maxGestureLength = (int) (Math.min(width, height) * MAX_GESTURE_LENGTH);
if (DEBUG) Log.d(TAG, "maxGestureLength = " + maxGestureLength);
volumeProgressBar.setMax(maxGestureLength);
brightnessProgressBar.setMax(maxGestureLength);
setInitialGestureValues();
}
});
}
public void minimize() {
switch (PlayerHelper.getMinimizeOnExitAction(context)) {
case PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND:
onPlayBackgroundButtonClicked();
break;
case PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP:
onFullScreenButtonClicked();
break;
case PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE:
default:
// No action
break;
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -452,14 +545,11 @@ public final class MainVideoPlayer extends AppCompatActivity
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info,
final int newPlayQueueIndex,
final boolean hasPlayQueueItemChanged) {
super.onMetadataChanged(item, info, newPlayQueueIndex, false);
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
super.onMetadataChanged(tag);
titleTextView.setText(getVideoTitle());
channelTextView.setText(getUploaderName());
titleTextView.setText(tag.getMetadata().getName());
channelTextView.setText(tag.getMetadata().getUploaderName());
}
@Override
@@ -492,6 +582,7 @@ public final class MainVideoPlayer extends AppCompatActivity
this.getRepeatMode(),
this.getPlaybackSpeed(),
this.getPlaybackPitch(),
this.getPlaybackSkipSilence(),
this.getPlaybackQuality()
);
context.startService(intent);
@@ -513,6 +604,7 @@ public final class MainVideoPlayer extends AppCompatActivity
this.getRepeatMode(),
this.getPlaybackSpeed(),
this.getPlaybackPitch(),
this.getPlaybackSkipSilence(),
this.getPlaybackQuality()
);
context.startService(intent);
@@ -547,6 +639,9 @@ public final class MainVideoPlayer extends AppCompatActivity
} else if (v.getId() == moreOptionsButton.getId()) {
onMoreOptionsClicked();
} else if (v.getId() == shareButton.getId()) {
onShareClicked();
} else if (v.getId() == toggleOrientationButton.getId()) {
onScreenRotationClicked();
@@ -556,6 +651,9 @@ public final class MainVideoPlayer extends AppCompatActivity
} else if (v.getId() == switchBackgroundButton.getId()) {
onPlayBackgroundButtonClicked();
} else if (v.getId() == closeButton.getId()) {
onPlaybackShutdown();
return;
}
if (getCurrentState() != STATE_COMPLETED) {
@@ -600,6 +698,13 @@ public final class MainVideoPlayer extends AppCompatActivity
showControls(DEFAULT_CONTROLS_DURATION);
}
private void onShareClicked() {
// share video at the current time (youtube.com/watch?v=ID&t=SECONDS)
ShareUtils.shareUrl(MainVideoPlayer.this,
playerImpl.getVideoTitle(),
playerImpl.getVideoUrl() + "&t=" + String.valueOf(playerImpl.getPlaybackSeekBar().getProgress()/1000));
}
private void onScreenRotationClicked() {
if (DEBUG) Log.d(TAG, "onScreenRotationClicked() called");
toggleOrientation();
@@ -608,7 +713,8 @@ public final class MainVideoPlayer extends AppCompatActivity
@Override
public void onPlaybackSpeedClicked() {
PlaybackParameterDialog.newInstance(getPlaybackSpeed(), getPlaybackPitch())
PlaybackParameterDialog
.newInstance(getPlaybackSpeed(), getPlaybackPitch(), getPlaybackSkipSilence())
.show(getSupportFragmentManager(), TAG);
}
@@ -627,25 +733,43 @@ 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
protected int getDefaultResolutionIndex(final List<VideoStream> sortedVideos) {
return ListHelper.getDefaultResolutionIndex(context, sortedVideos);
}
protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
return new VideoPlaybackResolver.QualityResolver() {
@Override
public int getDefaultResolutionIndex(List<VideoStream> sortedVideos) {
return ListHelper.getDefaultResolutionIndex(context, sortedVideos);
}
@Override
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
final String playbackQuality) {
return ListHelper.getDefaultResolutionIndex(context, sortedVideos, playbackQuality);
@Override
public int getOverrideResolutionIndex(List<VideoStream> sortedVideos,
String playbackQuality) {
return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality);
}
};
}
/*//////////////////////////////////////////////////////////////////////////
@@ -663,13 +787,13 @@ public final class MainVideoPlayer extends AppCompatActivity
super.onBlocked();
playPauseButton.setImageResource(R.drawable.ic_pause_white);
animatePlayButtons(false, 100);
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
getRootView().setKeepScreenOn(true);
}
@Override
public void onBuffering() {
super.onBuffering();
animatePlayButtons(false, 100);
getRootView().setKeepScreenOn(true);
}
@@ -679,6 +803,7 @@ public final class MainVideoPlayer extends AppCompatActivity
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
playPauseButton.setImageResource(R.drawable.ic_pause_white);
animatePlayButtons(true, 200);
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
});
getRootView().setKeepScreenOn(true);
@@ -690,6 +815,7 @@ public final class MainVideoPlayer extends AppCompatActivity
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
animatePlayButtons(true, 200);
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
});
showSystemUi();
@@ -709,8 +835,8 @@ public final class MainVideoPlayer extends AppCompatActivity
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, () -> {
playPauseButton.setImageResource(R.drawable.ic_replay_white);
animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
animateView(closeButton, true, DEFAULT_CONTROLS_DURATION);
});
getRootView().setKeepScreenOn(false);
super.onCompleted();
}
@@ -719,6 +845,13 @@ public final class MainVideoPlayer extends AppCompatActivity
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void setInitialGestureValues() {
if (getAudioReactor() != null) {
final float currentVolumeNormalized = (float) getAudioReactor().getVolume() / getAudioReactor().getMaxVolume();
volumeProgressBar.setProgress((int) (volumeProgressBar.getMax() * currentVolumeNormalized));
}
}
@Override
public void showControlsThenHide() {
if (queueVisible) return;
@@ -738,8 +871,8 @@ public final class MainVideoPlayer extends AppCompatActivity
if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
getControlsVisibilityHandler().removeCallbacksAndMessages(null);
getControlsVisibilityHandler().postDelayed(() ->
animateView(getControlsRoot(), false, duration, 0,
MainVideoPlayer.this::hideSystemUi),
animateView(getControlsRoot(), false, duration, 0,
MainVideoPlayer.this::hideSystemUi),
/*delayMillis=*/delay
);
}
@@ -787,6 +920,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);
}
};
}
@@ -822,12 +960,28 @@ public final class MainVideoPlayer extends AppCompatActivity
return channelTextView;
}
public TextView getVolumeTextView() {
return volumeTextView;
public RelativeLayout getVolumeRelativeLayout() {
return volumeRelativeLayout;
}
public TextView getBrightnessTextView() {
return brightnessTextView;
public ProgressBar getVolumeProgressBar() {
return volumeProgressBar;
}
public ImageView getVolumeImageView() {
return volumeImageView;
}
public RelativeLayout getBrightnessRelativeLayout() {
return brightnessRelativeLayout;
}
public ProgressBar getBrightnessProgressBar() {
return brightnessProgressBar;
}
public ImageView getBrightnessImageView() {
return brightnessImageView;
}
public ImageButton getRepeatButton() {
@@ -837,15 +991,18 @@ public final class MainVideoPlayer extends AppCompatActivity
public ImageButton getPlayPauseButton() {
return playPauseButton;
}
public int getMaxGestureLength() {
return maxGestureLength;
}
}
private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
private class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
private boolean isMoving;
@Override
public boolean onDoubleTap(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
if (!playerImpl.isPlaying()) return false;
if (e.getX() > playerImpl.getRootView().getWidth() * 2 / 3) {
playerImpl.onFastForward();
@@ -879,89 +1036,97 @@ public final class MainVideoPlayer extends AppCompatActivity
return super.onDown(e);
}
private final boolean isPlayerGestureEnabled = PlayerHelper.isPlayerGestureEnabled(getApplicationContext());
private static final int MOVEMENT_THRESHOLD = 40;
private final float stepsBrightness = 15, stepBrightness = (1f / stepsBrightness), minBrightness = .01f;
private float currentBrightness = .5f;
private final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(getApplicationContext());
private final boolean isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(getApplicationContext());
private int currentVolume, maxVolume = playerImpl.getAudioReactor().getMaxVolume();
private final float stepsVolume = 15, stepVolume = (float) Math.ceil(maxVolume / stepsVolume), minVolume = 0;
private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume();
private final String brightnessUnicode = new String(Character.toChars(0x2600));
private final String volumeUnicode = new String(Character.toChars(0x1F508));
private final int MOVEMENT_THRESHOLD = 40;
private final int eventsThreshold = 8;
private boolean triggered = false;
private int eventsNum;
// TODO: Improve video gesture controls
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (!isPlayerGestureEnabled) return false;
public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) {
if (!isVolumeGestureEnabled && !isBrightnessGestureEnabled) return false;
//noinspection PointlessBooleanExpression
if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " +
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" +
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]");
float abs = Math.abs(e2.getY() - e1.getY());
if (!triggered) {
triggered = abs > MOVEMENT_THRESHOLD;
final boolean insideThreshold = Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD;
if (!isMoving && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY))
|| playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
return false;
}
if (eventsNum++ % eventsThreshold != 0 || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) return false;
isMoving = true;
// boolean up = !((e2.getY() - e1.getY()) > 0) && distanceY > 0; // Android's origin point is on top
boolean up = distanceY > 0;
boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
boolean acceptVolumeArea = acceptAnyArea || initialEvent.getX() > playerImpl.getRootView().getWidth() / 2;
boolean acceptBrightnessArea = acceptAnyArea || !acceptVolumeArea;
if (e1.getX() > playerImpl.getRootView().getWidth() / 2) {
double floor = Math.floor(up ? stepVolume : -stepVolume);
currentVolume = (int) (playerImpl.getAudioReactor().getVolume() + floor);
if (currentVolume >= maxVolume) currentVolume = maxVolume;
if (currentVolume <= minVolume) currentVolume = (int) minVolume;
if (isVolumeGestureEnabled && acceptVolumeArea) {
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
float currentProgressPercent =
(float) playerImpl.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
int currentVolume = (int) (maxVolume * currentProgressPercent);
playerImpl.getAudioReactor().setVolume(currentVolume);
currentVolume = playerImpl.getAudioReactor().getVolume();
if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
final String volumeText = volumeUnicode + " " + Math.round((((float) currentVolume) / maxVolume) * 100) + "%";
playerImpl.getVolumeTextView().setText(volumeText);
if (playerImpl.getVolumeTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getVolumeTextView(), true, 200);
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);
} else {
WindowManager.LayoutParams lp = getWindow().getAttributes();
currentBrightness += up ? stepBrightness : -stepBrightness;
if (currentBrightness >= 1f) currentBrightness = 1f;
if (currentBrightness <= minBrightness) currentBrightness = minBrightness;
final int resId =
currentProgressPercent <= 0 ? R.drawable.ic_volume_off_white_72dp
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_72dp
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_72dp
: R.drawable.ic_volume_up_white_72dp;
lp.screenBrightness = currentBrightness;
getWindow().setAttributes(lp);
if (DEBUG) Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentBrightness);
int brightnessNormalized = Math.round(currentBrightness * 100);
playerImpl.getVolumeImageView().setImageDrawable(
AppCompatResources.getDrawable(getApplicationContext(), resId)
);
final String brightnessText = brightnessUnicode + " " + (brightnessNormalized == 1 ? 0 : brightnessNormalized) + "%";
playerImpl.getBrightnessTextView().setText(brightnessText);
if (playerImpl.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
}
} else if (isBrightnessGestureEnabled && acceptBrightnessArea) {
playerImpl.getBrightnessProgressBar().incrementProgressBy((int) distanceY);
float currentProgressPercent =
(float) playerImpl.getBrightnessProgressBar().getProgress() / playerImpl.getMaxGestureLength();
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
layoutParams.screenBrightness = currentProgressPercent;
getWindow().setAttributes(layoutParams);
if (playerImpl.getBrightnessTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getBrightnessTextView(), true, 200);
if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
if (DEBUG) Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentProgressPercent);
final int resId =
currentProgressPercent < 0.25 ? R.drawable.ic_brightness_low_white_72dp
: currentProgressPercent < 0.75 ? R.drawable.ic_brightness_medium_white_72dp
: R.drawable.ic_brightness_high_white_72dp;
playerImpl.getBrightnessImageView().setImageDrawable(
AppCompatResources.getDrawable(getApplicationContext(), resId)
);
if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
}
}
return true;
}
private void onScrollEnd() {
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
triggered = false;
eventsNum = 0;
/* if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);*/
if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getVolumeTextView(), false, 200, 200);
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
}
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {

View File

@@ -14,21 +14,26 @@ public class PlayerState implements Serializable {
private final float playbackSpeed;
private final float playbackPitch;
@Nullable private final String playbackQuality;
private final boolean playbackSkipSilence;
private final boolean wasPlaying;
PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode,
final float playbackSpeed, final float playbackPitch, final boolean wasPlaying) {
this(playQueue, repeatMode, playbackSpeed, playbackPitch, null, wasPlaying);
final float playbackSpeed, final float playbackPitch,
final boolean playbackSkipSilence, final boolean wasPlaying) {
this(playQueue, repeatMode, playbackSpeed, playbackPitch, null,
playbackSkipSilence, wasPlaying);
}
PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode,
final float playbackSpeed, final float playbackPitch,
@Nullable final String playbackQuality, final boolean wasPlaying) {
@Nullable final String playbackQuality, final boolean playbackSkipSilence,
final boolean wasPlaying) {
this.playQueue = playQueue;
this.repeatMode = repeatMode;
this.playbackSpeed = playbackSpeed;
this.playbackPitch = playbackPitch;
this.playbackQuality = playbackQuality;
this.playbackSkipSilence = playbackSkipSilence;
this.wasPlaying = wasPlaying;
}
@@ -62,6 +67,10 @@ public class PlayerState implements Serializable {
return playbackQuality;
}
public boolean isPlaybackSkipSilence() {
return playbackSkipSilence;
}
public boolean wasPlaying() {
return wasPlaying;
}

View File

@@ -19,6 +19,8 @@
package org.schabi.newpipe.player;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -34,7 +36,7 @@ import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.NotificationCompat;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -42,7 +44,9 @@ import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AnticipateInterpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.PopupMenu;
@@ -56,16 +60,16 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.SubtitleView;
import com.nostra13.universalimageloader.core.assist.FailReason;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.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.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -75,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;
/**
@@ -98,11 +101,18 @@ public final class PopupVideoPlayer extends Service {
private static final int MINIMUM_SHOW_EXTRA_WIDTH_DP = 300;
private WindowManager windowManager;
private WindowManager.LayoutParams windowLayoutParams;
private GestureDetector gestureDetector;
private static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
private static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS |
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
private WindowManager windowManager;
private WindowManager.LayoutParams popupLayoutParams;
private GestureDetector popupGestureDetector;
private View closeOverlayView;
private FloatingActionButton closeOverlayButton;
private int shutdownFlingVelocity;
private int tossFlingVelocity;
private float screenWidth, screenHeight;
@@ -117,6 +127,7 @@ public final class PopupVideoPlayer extends Service {
private VideoPlayerImpl playerImpl;
private LockManager lockManager;
private boolean isPopupClosing = false;
/*//////////////////////////////////////////////////////////////////////////
// Service-Activity Binder
@@ -145,7 +156,10 @@ public final class PopupVideoPlayer extends Service {
public int onStartCommand(final Intent intent, int flags, int startId) {
if (DEBUG)
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
if (playerImpl.getPlayer() == null) initPopup();
if (playerImpl.getPlayer() == null) {
initPopup();
initPopupCloseOverlay();
}
if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true);
playerImpl.handleIntent(intent);
@@ -155,15 +169,21 @@ public final class PopupVideoPlayer extends Service {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (DEBUG) Log.d(TAG, "onConfigurationChanged() called with: newConfig = [" + newConfig + "]");
updateScreenSize();
updatePopupSize(windowLayoutParams.width, -1);
checkPositionBounds();
updatePopupSize(popupLayoutParams.width, -1);
checkPopupPositionBounds();
}
@Override
public void onDestroy() {
if (DEBUG) Log.d(TAG, "onDestroy() called");
onClose();
closePopup();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
}
@Override
@@ -181,7 +201,6 @@ public final class PopupVideoPlayer extends Service {
View rootView = View.inflate(this, R.layout.player_popup, null);
playerImpl.setup(rootView);
shutdownFlingVelocity = PlayerHelper.getShutdownFlingVelocity(this);
tossFlingVelocity = PlayerHelper.getTossFlingVelocity(this);
updateScreenSize();
@@ -191,28 +210,56 @@ public final class PopupVideoPlayer extends Service {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
popupWidth = popupRememberSizeAndPos ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize;
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_PHONE : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_PHONE :
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
windowLayoutParams = new WindowManager.LayoutParams(
popupLayoutParams = new WindowManager.LayoutParams(
(int) popupWidth, (int) getMinimumVideoHeight(popupWidth),
layoutParamType,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
IDLE_WINDOW_FLAGS,
PixelFormat.TRANSLUCENT);
windowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
int centerX = (int) (screenWidth / 2f - popupWidth / 2f);
int centerY = (int) (screenHeight / 2f - popupHeight / 2f);
windowLayoutParams.x = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
windowLayoutParams.y = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
popupLayoutParams.x = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
popupLayoutParams.y = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
checkPositionBounds();
checkPopupPositionBounds();
MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
gestureDetector = new GestureDetector(this, listener);
PopupWindowGestureListener listener = new PopupWindowGestureListener();
popupGestureDetector = new GestureDetector(this, listener);
rootView.setOnTouchListener(listener);
playerImpl.getLoadingPanel().setMinimumWidth(windowLayoutParams.width);
playerImpl.getLoadingPanel().setMinimumHeight(windowLayoutParams.height);
windowManager.addView(rootView, windowLayoutParams);
playerImpl.getLoadingPanel().setMinimumWidth(popupLayoutParams.width);
playerImpl.getLoadingPanel().setMinimumHeight(popupLayoutParams.height);
windowManager.addView(rootView, popupLayoutParams);
}
@SuppressLint("RtlHardcoded")
private void initPopupCloseOverlay() {
if (DEBUG) Log.d(TAG, "initPopupCloseOverlay() called");
closeOverlayView = View.inflate(this, R.layout.player_popup_close_overlay, null);
closeOverlayButton = closeOverlayView.findViewById(R.id.closeButton);
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_PHONE :
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
layoutParamType,
flags,
PixelFormat.TRANSLUCENT);
closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
closeOverlayLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
closeOverlayButton.setVisibility(View.GONE);
windowManager.addView(closeOverlayView, closeOverlayLayoutParams);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -228,6 +275,7 @@ public final class PopupVideoPlayer extends Service {
notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getUploaderName());
notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getThumbnail());
notRemoteView.setOnClickPendingIntent(R.id.notificationPlayPause,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
@@ -243,11 +291,15 @@ public final class PopupVideoPlayer extends Service {
setRepeatModeRemote(notRemoteView, playerImpl.getRepeatMode());
return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContent(notRemoteView);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
builder.setPriority(NotificationCompat.PRIORITY_MAX);
}
return builder;
}
/**
@@ -267,44 +319,105 @@ public final class PopupVideoPlayer extends Service {
// Misc
//////////////////////////////////////////////////////////////////////////*/
public void onClose() {
if (DEBUG) Log.d(TAG, "onClose() called");
public void closePopup() {
if (DEBUG) Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing);
if (isPopupClosing) return;
isPopupClosing = true;
if (playerImpl != null) {
if (playerImpl.getRootView() != null) {
windowManager.removeView(playerImpl.getRootView());
playerImpl.setRootView(null);
}
playerImpl.setRootView(null);
playerImpl.stopActivityBinding();
playerImpl.destroy();
playerImpl = null;
}
mBinder = null;
if (lockManager != null) lockManager.releaseWifiAndCpu();
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
mBinder = null;
playerImpl = null;
stopForeground(true);
stopSelf();
animateOverlayAndFinishService();
}
private void animateOverlayAndFinishService() {
final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight() - closeOverlayButton.getY());
closeOverlayButton.animate().setListener(null).cancel();
closeOverlayButton.animate()
.setInterpolator(new AnticipateInterpolator())
.translationY(targetTranslationY)
.setDuration(400)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
end();
}
@Override
public void onAnimationEnd(Animator animation) {
end();
}
private void end() {
windowManager.removeView(closeOverlayView);
stopForeground(true);
stopSelf();
}
}).start();
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void checkPositionBounds() {
if (windowLayoutParams.x > screenWidth - windowLayoutParams.width)
windowLayoutParams.x = (int) (screenWidth - windowLayoutParams.width);
if (windowLayoutParams.x < 0) windowLayoutParams.x = 0;
if (windowLayoutParams.y > screenHeight - windowLayoutParams.height)
windowLayoutParams.y = (int) (screenHeight - windowLayoutParams.height);
if (windowLayoutParams.y < 0) windowLayoutParams.y = 0;
/**
* @see #checkPopupPositionBounds(float, float)
*/
@SuppressWarnings("UnusedReturnValue")
private boolean checkPopupPositionBounds() {
return checkPopupPositionBounds(screenWidth, screenHeight);
}
/**
* Check if {@link #popupLayoutParams}' position is within a arbitrary boundary that goes from (0,0) to (boundaryWidth,boundaryHeight).
* <p>
* If it's out of these boundaries, {@link #popupLayoutParams}' position is changed and {@code true} is returned
* to represent this change.
*
* @return if the popup was out of bounds and have been moved back to it
*/
private boolean checkPopupPositionBounds(final float boundaryWidth, final float boundaryHeight) {
if (DEBUG) {
Log.d(TAG, "checkPopupPositionBounds() called with: boundaryWidth = [" + boundaryWidth + "], boundaryHeight = [" + boundaryHeight + "]");
}
if (popupLayoutParams.x < 0) {
popupLayoutParams.x = 0;
return true;
} else if (popupLayoutParams.x > boundaryWidth - popupLayoutParams.width) {
popupLayoutParams.x = (int) (boundaryWidth - popupLayoutParams.width);
return true;
}
if (popupLayoutParams.y < 0) {
popupLayoutParams.y = 0;
return true;
} else if (popupLayoutParams.y > boundaryHeight - popupLayoutParams.height) {
popupLayoutParams.y = (int) (boundaryHeight - popupLayoutParams.height);
return true;
}
return false;
}
private void savePositionAndSize() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(PopupVideoPlayer.this);
sharedPreferences.edit().putInt(POPUP_SAVED_X, windowLayoutParams.x).apply();
sharedPreferences.edit().putInt(POPUP_SAVED_Y, windowLayoutParams.y).apply();
sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, windowLayoutParams.width).apply();
sharedPreferences.edit().putInt(POPUP_SAVED_X, popupLayoutParams.x).apply();
sharedPreferences.edit().putInt(POPUP_SAVED_Y, popupLayoutParams.y).apply();
sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, popupLayoutParams.width).apply();
}
private float getMinimumVideoHeight(float width) {
@@ -339,13 +452,13 @@ public final class PopupVideoPlayer extends Service {
if (height == -1) height = (int) getMinimumVideoHeight(width);
else height = (int) (height > maximumHeight ? maximumHeight : height < minimumHeight ? minimumHeight : height);
windowLayoutParams.width = width;
windowLayoutParams.height = height;
popupLayoutParams.width = width;
popupLayoutParams.height = height;
popupWidth = width;
popupHeight = height;
if (DEBUG) Log.d(TAG, "updatePopupSize() updated values: width = [" + width + "], height = [" + height + "]");
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
}
protected void setRepeatModeRemote(final RemoteViews remoteViews, final int repeatMode) {
@@ -366,6 +479,12 @@ public final class PopupVideoPlayer extends Service {
}
}
private void updateWindowFlags(final int flags) {
if (popupLayoutParams == null || windowManager == null || playerImpl == null) return;
popupLayoutParams.flags = flags;
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
}
///////////////////////////////////////////////////////////////////////////
protected class VideoPlayerImpl extends VideoPlayer implements View.OnLayoutChangeListener {
@@ -374,6 +493,7 @@ public final class PopupVideoPlayer extends Service {
private ImageView videoPlayPause;
private View extraOptionsView;
private View closingOverlayView;
@Override
public void handleIntent(Intent intent) {
@@ -394,12 +514,18 @@ public final class PopupVideoPlayer extends Service {
fullScreenButton = rootView.findViewById(R.id.fullScreenButton);
fullScreenButton.setOnClickListener(v -> onFullScreenButtonClicked());
videoPlayPause = rootView.findViewById(R.id.videoPlayPause);
videoPlayPause.setOnClickListener(this::onPlayPauseButtonPressed);
extraOptionsView = rootView.findViewById(R.id.extraOptionsView);
closingOverlayView = rootView.findViewById(R.id.closingOverlay);
rootView.addOnLayoutChangeListener(this);
}
@Override
public void initListeners() {
super.initListeners();
videoPlayPause.setOnClickListener(v -> onPlayPause());
}
@Override
protected void setupSubtitleView(@NonNull SubtitleView view,
final float captionScale,
@@ -410,10 +536,6 @@ public final class PopupVideoPlayer extends Service {
view.setStyle(captionStyle);
}
private void onPlayPauseButtonPressed(View ib) {
onPlayPause();
}
@Override
public void onLayoutChange(final View view, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
@@ -428,21 +550,6 @@ public final class PopupVideoPlayer extends Service {
super.destroy();
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
if (loadedImage != null) {
// rebuild notification here since remote view does not release bitmaps, causing memory leaks
notBuilder = createNotification();
if (notRemoteView != null) {
notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
}
updateNotification(-1);
}
}
@Override
public void onFullScreenButtonClicked() {
super.onFullScreenButtonClicked();
@@ -450,28 +557,19 @@ 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.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);
onClose();
closePopup();
}
@Override
@@ -510,14 +608,48 @@ public final class PopupVideoPlayer extends Service {
}
@Override
protected int getDefaultResolutionIndex(final List<VideoStream> sortedVideos) {
return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
return new VideoPlaybackResolver.QualityResolver() {
@Override
public int getDefaultResolutionIndex(List<VideoStream> sortedVideos) {
return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
}
@Override
public int getOverrideResolutionIndex(List<VideoStream> sortedVideos,
String playbackQuality) {
return ListHelper.getPopupResolutionIndex(context, sortedVideos,
playbackQuality);
}
};
}
/*//////////////////////////////////////////////////////////////////////////
// Thumbnail Loading
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
if (playerImpl == null) return;
// rebuild notification here since remote view does not release bitmaps,
// causing memory leaks
resetNotification();
updateNotification(-1);
}
@Override
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
final String playbackQuality) {
return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos, playbackQuality);
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
super.onLoadingFailed(imageUri, view, failReason);
resetNotification();
updateNotification(-1);
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
super.onLoadingCancelled(imageUri, view);
resetNotification();
updateNotification(-1);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -538,8 +670,8 @@ public final class PopupVideoPlayer extends Service {
}
private void updateMetadata() {
if (activityListener != null && currentInfo != null) {
activityListener.onMetadataUpdate(currentInfo);
if (activityListener != null && getCurrentMetadata() != null) {
activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
}
}
@@ -571,8 +703,9 @@ public final class PopupVideoPlayer extends Service {
public void onRepeatModeChanged(int i) {
super.onRepeatModeChanged(i);
setRepeatModeRemote(notRemoteView, i);
updateNotification(-1);
updatePlayback();
resetNotification();
updateNotification(-1);
}
@Override
@@ -585,18 +718,17 @@ public final class PopupVideoPlayer extends Service {
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info,
final int newPlayQueueIndex,
final boolean hasPlayQueueItemChanged) {
super.onMetadataChanged(item, info, newPlayQueueIndex, false);
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
super.onMetadataChanged(tag);
resetNotification();
updateNotification(-1);
updateMetadata();
}
@Override
public void onPlaybackShutdown() {
super.onPlaybackShutdown();
onClose();
closePopup();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -622,7 +754,7 @@ public final class PopupVideoPlayer extends Service {
if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
switch (intent.getAction()) {
case ACTION_CLOSE:
onClose();
closePopup();
break;
case ACTION_PLAY_PAUSE:
onPlayPause();
@@ -652,46 +784,70 @@ public final class PopupVideoPlayer extends Service {
@Override
public void onBlocked() {
super.onBlocked();
resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
public void onPlaying() {
super.onPlaying();
updateNotification(R.drawable.ic_pause_white);
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
lockManager.acquireWifiAndCpu();
updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
resetNotification();
updateNotification(R.drawable.ic_pause_white);
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
startForeground(NOTIFICATION_ID, notBuilder.build());
lockManager.acquireWifiAndCpu();
}
@Override
public void onBuffering() {
super.onBuffering();
resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
public void onPaused() {
super.onPaused();
updateWindowFlags(IDLE_WINDOW_FLAGS);
resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
videoPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_white);
lockManager.releaseWifiAndCpu();
stopForeground(false);
}
@Override
public void onPausedSeek() {
super.onPausedSeek();
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
}
@Override
public void onCompleted() {
super.onCompleted();
updateWindowFlags(IDLE_WINDOW_FLAGS);
resetNotification();
updateNotification(R.drawable.ic_replay_white);
videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white);
lockManager.releaseWifiAndCpu();
stopForeground(false);
}
@Override
@@ -709,16 +865,15 @@ public final class PopupVideoPlayer extends Service {
super.hideControlsAndButton(duration, delay, videoPlayPause);
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
/*package-private*/ void enableVideoRenderer(final boolean enable) {
final int videoRendererIndex = getRendererIndex(C.TRACK_TYPE_VIDEO);
if (trackSelector != null && videoRendererIndex != RENDERER_UNAVAILABLE) {
trackSelector.setRendererDisabled(videoRendererIndex, !enable);
if (videoRendererIndex != RENDERER_UNAVAILABLE) {
trackSelector.setParameters(trackSelector.buildUponParameters()
.setRendererDisabled(videoRendererIndex, !enable));
}
}
@@ -730,12 +885,15 @@ public final class PopupVideoPlayer extends Service {
public TextView getResizingIndicator() {
return resizingIndicator;
}
public View getClosingOverlayView() {
return closingOverlayView;
}
}
private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
private class PopupWindowGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
private int initialPopupX, initialPopupY;
private boolean isMoving;
private boolean isResizing;
@Override
@@ -771,10 +929,15 @@ public final class PopupVideoPlayer extends Service {
@Override
public boolean onDown(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]");
initialPopupX = windowLayoutParams.x;
initialPopupY = windowLayoutParams.y;
popupWidth = windowLayoutParams.width;
popupHeight = windowLayoutParams.height;
// Fix popup position when the user touch it, it may have the wrong one
// because the soft input is visible (the draggable area is currently resized).
checkPopupPositionBounds(closeOverlayView.getWidth(), closeOverlayView.getHeight());
initialPopupX = popupLayoutParams.x;
initialPopupY = popupLayoutParams.y;
popupWidth = popupLayoutParams.width;
popupHeight = popupLayoutParams.height;
return super.onDown(e);
}
@@ -782,20 +945,22 @@ public final class PopupVideoPlayer extends Service {
public void onLongPress(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
updateScreenSize();
checkPositionBounds();
checkPopupPositionBounds();
updatePopupSize((int) screenWidth, -1);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (isResizing || playerImpl == null) return super.onScroll(e1, e2, distanceX, distanceY);
public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) {
if (isResizing || playerImpl == null) return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
if (!isMoving) {
animateView(closeOverlayButton, true, 200);
}
if (playerImpl.getCurrentState() != BasePlayer.STATE_BUFFERING
&& (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f)) playerImpl.showControls(0);
isMoving = true;
float diffX = (int) (e2.getRawX() - e1.getRawX()), posX = (int) (initialPopupX + diffX);
float diffY = (int) (e2.getRawY() - e1.getRawY()), posY = (int) (initialPopupY + diffY);
float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX()), posX = (int) (initialPopupX + diffX);
float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY()), posY = (int) (initialPopupY + diffY);
if (posX > (screenWidth - popupWidth)) posX = (int) (screenWidth - popupWidth);
else if (posX < 0) posX = 0;
@@ -803,26 +968,49 @@ public final class PopupVideoPlayer extends Service {
if (posY > (screenHeight - popupHeight)) posY = (int) (screenHeight - popupHeight);
else if (posY < 0) posY = 0;
windowLayoutParams.x = (int) posX;
windowLayoutParams.y = (int) posY;
popupLayoutParams.x = (int) posX;
popupLayoutParams.y = (int) posY;
final View closingOverlayView = playerImpl.getClosingOverlayView();
if (isInsideClosingRadius(movingEvent)) {
if (closingOverlayView.getVisibility() == View.GONE) {
animateView(closingOverlayView, true, 250);
}
} else {
if (closingOverlayView.getVisibility() == View.VISIBLE) {
animateView(closingOverlayView, false, 0);
}
}
//noinspection PointlessBooleanExpression
if (DEBUG && false) Log.d(TAG, "PopupVideoPlayer.onScroll = " +
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]" +
", posXy = [" + posX + ", " + posY + "]" +
", popupWh = [" + popupWidth + " x " + popupHeight + "]");
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
if (DEBUG && false) {
Log.d(TAG, "PopupVideoPlayer.onScroll = " +
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" + ", e1.getX,Y = [" + initialEvent.getX() + ", " + initialEvent.getY() + "]" +
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" + ", e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "]" +
", distanceX,Y = [" + distanceX + ", " + distanceY + "]" +
", posX,Y = [" + posX + ", " + posY + "]" +
", popupW,H = [" + popupWidth + " x " + popupHeight + "]");
}
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
return true;
}
private void onScrollEnd() {
private void onScrollEnd(MotionEvent event) {
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
if (playerImpl == null) return;
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
if (isInsideClosingRadius(event)) {
closePopup();
} else {
animateView(playerImpl.getClosingOverlayView(), false, 0);
if (!isPopupClosing) {
animateView(closeOverlayButton, false, 200);
}
}
}
@Override
@@ -832,14 +1020,11 @@ public final class PopupVideoPlayer extends Service {
final float absVelocityX = Math.abs(velocityX);
final float absVelocityY = Math.abs(velocityY);
if (absVelocityX > shutdownFlingVelocity) {
onClose();
return true;
} else if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
if (absVelocityX > tossFlingVelocity) windowLayoutParams.x = (int) velocityX;
if (absVelocityY > tossFlingVelocity) windowLayoutParams.y = (int) velocityY;
checkPositionBounds();
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
if (absVelocityX > tossFlingVelocity) popupLayoutParams.x = (int) velocityX;
if (absVelocityY > tossFlingVelocity) popupLayoutParams.y = (int) velocityY;
checkPopupPositionBounds();
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
return true;
}
return false;
@@ -847,7 +1032,7 @@ public final class PopupVideoPlayer extends Service {
@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
popupGestureDetector.onTouchEvent(event);
if (playerImpl == null) return false;
if (event.getPointerCount() == 2 && !isResizing) {
if (DEBUG) Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
@@ -870,7 +1055,7 @@ public final class PopupVideoPlayer extends Service {
Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
if (isMoving) {
isMoving = false;
onScrollEnd();
onScrollEnd(event);
}
if (isResizing) {
@@ -878,7 +1063,10 @@ public final class PopupVideoPlayer extends Service {
animateView(playerImpl.getResizingIndicator(), false, 100, 0);
playerImpl.changeState(playerImpl.getCurrentState());
}
savePositionAndSize();
if (!isPopupClosing) {
savePositionAndSize();
}
}
v.performClick();
@@ -894,13 +1082,13 @@ public final class PopupVideoPlayer extends Service {
final float diff = Math.abs(firstPointerX - secondPointerX);
if (firstPointerX > secondPointerX) {
// second pointer is the anchor (the leftmost pointer)
windowLayoutParams.x = (int) (event.getRawX() - diff);
popupLayoutParams.x = (int) (event.getRawX() - diff);
} else {
// first pointer is the anchor
windowLayoutParams.x = (int) event.getRawX();
popupLayoutParams.x = (int) event.getRawX();
}
checkPositionBounds();
checkPopupPositionBounds();
updateScreenSize();
final int width = (int) Math.min(screenWidth, diff);
@@ -908,5 +1096,29 @@ public final class PopupVideoPlayer extends Service {
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private int distanceFromCloseButton(MotionEvent popupMotionEvent) {
final int closeOverlayButtonX = closeOverlayButton.getLeft() + closeOverlayButton.getWidth() / 2;
final int closeOverlayButtonY = closeOverlayButton.getTop() + closeOverlayButton.getHeight() / 2;
float fingerX = popupLayoutParams.x + popupMotionEvent.getX();
float fingerY = popupLayoutParams.y + popupMotionEvent.getY();
return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) + Math.pow(closeOverlayButtonY - fingerY, 2));
}
private float getClosingRadius() {
final int buttonRadius = closeOverlayButton.getWidth() / 2;
// 20% wider than the button itself
return buttonRadius * 1.2f;
}
private boolean isInsideClosingRadius(MotionEvent popupMotionEvent) {
return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius();
}
}
}

View File

@@ -3,7 +3,6 @@ package org.schabi.newpipe.player;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.Settings;
@@ -16,6 +15,7 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
@@ -187,6 +187,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
this.player.getRepeatMode(),
this.player.getPlaybackSpeed(),
this.player.getPlaybackPitch(),
this.player.getPlaybackSkipSilence(),
null
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@@ -340,6 +341,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return true;
});
final MenuItem share = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/3,
Menu.NONE, R.string.share);
share.setOnMenuItemClickListener(menuItem -> {
shareUrl(item.getTitle(), item.getUrl());
return true;
});
menu.show();
}
@@ -366,6 +374,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);
}
};
}
@@ -459,13 +472,16 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private void openPlaybackParameterDialog() {
if (player == null) return;
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(),
player.getPlaybackPitch()).show(getSupportFragmentManager(), getTag());
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
player.getPlaybackSkipSilence()).show(getSupportFragmentManager(), getTag());
}
@Override
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch) {
if (player != null) player.setPlaybackParameters(playbackTempo, playbackPitch);
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
boolean playbackSkipSilence) {
if (player != null) {
player.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence);
}
}
////////////////////////////////////////////////////////////////////////////
@@ -509,6 +525,18 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
.show(getSupportFragmentManager(), getTag());
}
////////////////////////////////////////////////////////////////////////////
// Share
////////////////////////////////////////////////////////////////////////////
private void shareUrl(String subject, String url) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, url);
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
}
////////////////////////////////////////////////////////////////////////////
// Binding Service Listener
////////////////////////////////////////////////////////////////////////////
@@ -539,6 +567,12 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
if (player != null) {
progressLiveSync.setClickable(!player.isLiveEdge());
}
// this will make shure progressCurrentTime has the same width as progressEndTime
final ViewGroup.LayoutParams endTimeParams = progressEndTime.getLayoutParams();
final ViewGroup.LayoutParams currentTimeParams = progressCurrentTime.getLayoutParams();
currentTimeParams.width = progressEndTime.getWidth();
progressCurrentTime.setLayoutParams(currentTimeParams);
}
@Override
@@ -618,11 +652,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
}
final int shuffleAlpha = shuffled ? 255 : 77;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
shuffleButton.setImageAlpha(shuffleAlpha);
} else {
shuffleButton.setAlpha(shuffleAlpha);
}
shuffleButton.setImageAlpha(shuffleAlpha);
}
private void onPlaybackParameterChanged(final PlaybackParameters parameters) {

View File

@@ -29,7 +29,6 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.NonNull;
@@ -47,11 +46,9 @@ import android.widget.SeekBar;
import android.widget.TextView;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
@@ -62,21 +59,17 @@ import com.google.android.exoplayer2.video.VideoListener;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.Subtitles;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ListHelper;
import java.util.ArrayList;
import java.util.List;
import static com.google.android.exoplayer2.C.SELECTION_FLAG_AUTOSELECT;
import static com.google.android.exoplayer2.C.TIME_UNSET;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -105,13 +98,12 @@ public abstract class VideoPlayer extends BasePlayer
public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis
public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
private ArrayList<VideoStream> availableStreams;
private List<VideoStream> availableStreams;
private int selectedStreamIndex;
protected String playbackQuality;
protected boolean wasPlaying = false;
@NonNull final private VideoPlaybackResolver resolver;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
@@ -145,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;
///////////////////////////////////////////////////////////////////////////
@@ -162,6 +154,7 @@ public abstract class VideoPlayer extends BasePlayer
public VideoPlayer(String debugTag, Context context) {
super(context);
this.TAG = debugTag;
this.resolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
}
public void setup(View rootView) {
@@ -219,7 +212,6 @@ public abstract class VideoPlayer extends BasePlayer
@Override
public void initListeners() {
super.initListeners();
playbackSeekBar.setOnSeekBarChangeListener(this);
playbackSpeedTextView.setOnClickListener(this);
qualityTextView.setOnClickListener(this);
@@ -241,7 +233,8 @@ public abstract class VideoPlayer extends BasePlayer
// Setup audio session with onboard equalizer
if (Build.VERSION.SDK_INT >= 21) {
trackSelector.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context));
trackSelector.setParameters(trackSelector.buildUponParameters()
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
}
}
@@ -297,81 +290,73 @@ public abstract class VideoPlayer extends BasePlayer
0, Menu.NONE, R.string.caption_none);
captionOffItem.setOnMenuItemClickListener(menuItem -> {
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) {
trackSelector.setRendererDisabled(textRendererIndex, true);
if (textRendererIndex != RENDERER_UNAVAILABLE) {
trackSelector.setParameters(trackSelector.buildUponParameters()
.setRendererDisabled(textRendererIndex, true));
}
return true;
});
// Add all available captions
for (int i = 0; i < availableLanguages.size(); i++) {
final String captionLanguage = availableLanguages.get(i);
MenuItem captionItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
i + 1, Menu.NONE, captionLanguage);
captionItem.setOnMenuItemClickListener(menuItem -> {
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) {
if (textRendererIndex != RENDERER_UNAVAILABLE) {
trackSelector.setPreferredTextLanguage(captionLanguage);
trackSelector.setRendererDisabled(textRendererIndex, false);
trackSelector.setParameters(trackSelector.buildUponParameters()
.setRendererDisabled(textRendererIndex, false));
}
return true;
});
}
captionPopupMenu.setOnDismissListener(this);
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
protected abstract int getDefaultResolutionIndex(final List<VideoStream> sortedVideos);
private void updateStreamRelatedViews() {
if (getCurrentMetadata() == null) return;
protected abstract int getOverrideResolutionIndex(final List<VideoStream> sortedVideos, final String playbackQuality);
final MediaSourceTag tag = getCurrentMetadata();
final StreamInfo metadata = tag.getMetadata();
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info,
final int newPlayQueueIndex,
final boolean hasPlayQueueItemChanged) {
qualityTextView.setVisibility(View.GONE);
playbackSpeedTextView.setVisibility(View.GONE);
playbackEndTime.setVisibility(View.GONE);
playbackLiveSync.setVisibility(View.GONE);
final StreamType streamType = info == null ? StreamType.NONE : info.getStreamType();
switch (streamType) {
switch (metadata.getStreamType()) {
case AUDIO_STREAM:
surfaceView.setVisibility(View.GONE);
endScreen.setVisibility(View.VISIBLE);
playbackEndTime.setVisibility(View.VISIBLE);
break;
case AUDIO_LIVE_STREAM:
surfaceView.setVisibility(View.GONE);
endScreen.setVisibility(View.VISIBLE);
playbackLiveSync.setVisibility(View.VISIBLE);
break;
case LIVE_STREAM:
surfaceView.setVisibility(View.VISIBLE);
endScreen.setVisibility(View.GONE);
playbackLiveSync.setVisibility(View.VISIBLE);
break;
case VIDEO_STREAM:
if (info.getVideoStreams().size() + info.getVideoOnlyStreams().size() == 0) break;
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
info.getVideoStreams(), info.getVideoOnlyStreams(), false);
availableStreams = new ArrayList<>(videos);
if (playbackQuality == null) {
selectedStreamIndex = getDefaultResolutionIndex(videos);
} else {
selectedStreamIndex = getOverrideResolutionIndex(videos, getPlaybackQuality());
}
if (metadata.getVideoStreams().size() + metadata.getVideoOnlyStreams().size() == 0)
break;
availableStreams = tag.getSortedAvailableVideoStreams();
selectedStreamIndex = tag.getSelectedVideoStreamIndex();
buildQualityMenu();
qualityTextView.setVisibility(View.VISIBLE);
qualityTextView.setVisibility(View.VISIBLE);
surfaceView.setVisibility(View.VISIBLE);
default:
endScreen.setVisibility(View.GONE);
playbackEndTime.setVisibility(View.VISIBLE);
break;
}
@@ -379,69 +364,21 @@ public abstract class VideoPlayer extends BasePlayer
buildPlaybackSpeedMenu();
playbackSpeedTextView.setVisibility(View.VISIBLE);
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
protected abstract VideoPlaybackResolver.QualityResolver getQualityResolver();
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
super.onMetadataChanged(tag);
updateStreamRelatedViews();
}
@Override
@Nullable
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
final MediaSource liveSource = super.sourceOf(item, info);
if (liveSource != null) return liveSource;
List<MediaSource> mediaSources = new ArrayList<>();
// Create video stream source
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
info.getVideoStreams(), info.getVideoOnlyStreams(), false);
final int index;
if (videos.isEmpty()) {
index = -1;
} else if (playbackQuality == null) {
index = getDefaultResolutionIndex(videos);
} else {
index = getOverrideResolutionIndex(videos, getPlaybackQuality());
}
final VideoStream video = index >= 0 && index < videos.size() ? videos.get(index) : null;
if (video != null) {
final MediaSource streamSource = buildMediaSource(video.getUrl(),
PlayerHelper.cacheKeyOf(info, video),
MediaFormat.getSuffixById(video.getFormatId()));
mediaSources.add(streamSource);
}
// Create optional audio stream source
final List<AudioStream> audioStreams = info.getAudioStreams();
final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get(
ListHelper.getDefaultAudioFormat(context, audioStreams));
// Use the audio stream if there is no video stream, or
// Merge with audio stream in case if video does not contain audio
if (audio != null && ((video != null && video.isVideoOnly) || video == null)) {
final MediaSource audioSource = buildMediaSource(audio.getUrl(),
PlayerHelper.cacheKeyOf(info, audio),
MediaFormat.getSuffixById(audio.getFormatId()));
mediaSources.add(audioSource);
}
// If there is no audio or video sources, then this media source cannot be played back
if (mediaSources.isEmpty()) return null;
// Below are auxiliary media sources
// Create subtitle sources
for (final Subtitles subtitle : info.getSubtitles()) {
final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType());
if (mimeType == null) continue;
final Format textFormat = Format.createTextSampleFormat(null, mimeType,
SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle));
final MediaSource textSource = dataSource.getSampleMediaSourceFactory()
.createMediaSource(Uri.parse(subtitle.getURL()), textFormat, TIME_UNSET);
mediaSources.add(textSource);
}
if (mediaSources.size() == 1) {
return mediaSources.get(0);
} else {
return new MergingMediaSource(mediaSources.toArray(
new MediaSource[mediaSources.size()]));
}
return resolver.resolve(info);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -460,7 +397,6 @@ public abstract class VideoPlayer extends BasePlayer
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
animateView(endScreen, false, 0);
loadingPanel.setBackgroundColor(Color.BLACK);
animateView(loadingPanel, true, 0);
animateView(surfaceForeground, true, 100);
@@ -470,6 +406,8 @@ public abstract class VideoPlayer extends BasePlayer
public void onPlaying() {
super.onPlaying();
updateStreamRelatedViews();
showAndAnimateControl(-1, true);
playbackSeekBar.setEnabled(true);
@@ -480,14 +418,12 @@ public abstract class VideoPlayer extends BasePlayer
loadingPanel.setVisibility(View.GONE);
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
animateView(endScreen, false, 0);
}
@Override
public void onBuffering() {
if (DEBUG) Log.d(TAG, "onBuffering() called");
loadingPanel.setBackgroundColor(Color.TRANSPARENT);
animateView(loadingPanel, true, 500);
}
@Override
@@ -552,8 +488,7 @@ public abstract class VideoPlayer extends BasePlayer
final int textRenderer = getRendererIndex(C.TRACK_TYPE_TEXT);
if (captionTextView == null) return;
if (trackSelector == null || trackSelector.getCurrentMappedTrackInfo() == null ||
textRenderer == RENDERER_UNAVAILABLE) {
if (trackSelector.getCurrentMappedTrackInfo() == null || textRenderer == RENDERER_UNAVAILABLE) {
captionTextView.setVisibility(View.GONE);
return;
}
@@ -571,12 +506,12 @@ public abstract class VideoPlayer extends BasePlayer
}
// Normalize mismatching language strings
final String preferredLanguage = trackSelector.getPreferredTextLanguage();
final String preferredLanguage = trackSelector.getParameters().preferredTextLanguage;
// Build UI
buildCaptionMenu(availableLanguages);
if (trackSelector.getRendererDisabled(textRenderer) || preferredLanguage == null ||
!availableLanguages.contains(preferredLanguage)) {
if (trackSelector.getParameters().getRendererDisabled(textRenderer) ||
preferredLanguage == null || (!availableLanguages.contains(preferredLanguage)
&& !containsCaseInsensitive(availableLanguages, preferredLanguage))) {
captionTextView.setText(R.string.caption_none);
} else {
captionTextView.setText(preferredLanguage);
@@ -584,6 +519,15 @@ public abstract class VideoPlayer extends BasePlayer
captionTextView.setVisibility(availableLanguages.isEmpty() ? View.GONE : View.VISIBLE);
}
// workaround to match normalized captions like english to English or deutsch to Deutsch
private static boolean containsCaseInsensitive(List<String> list, String toFind) {
for(String i : list){
if(i.equalsIgnoreCase(toFind))
return true;
}
return false;
}
/*//////////////////////////////////////////////////////////////////////////
// General Player
//////////////////////////////////////////////////////////////////////////*/
@@ -745,12 +689,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
//////////////////////////////////////////////////////////////////////////*/
@@ -905,11 +854,12 @@ public abstract class VideoPlayer extends BasePlayer
//////////////////////////////////////////////////////////////////////////*/
public void setPlaybackQuality(final String quality) {
this.playbackQuality = quality;
this.resolver.setPlaybackQuality(quality);
}
@Nullable
public String getPlaybackQuality() {
return playbackQuality;
return resolver.getPlaybackQuality();
}
public AspectRatioFrameLayout getAspectRatioFrameLayout() {

View File

@@ -12,13 +12,11 @@ import android.os.Build;
import android.support.annotation.NonNull;
import android.util.Log;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.analytics.AnalyticsListener;
public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
AudioRendererEventListener {
AnalyticsListener {
private static final String TAG = "AudioFocusReactor";
@@ -42,7 +40,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
this.player = player;
this.context = context;
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
player.addAudioDebugListener(this);
player.addAnalyticsListener(this);
if (SHOULD_BUILD_FOCUS_REQUEST) {
request = new AudioFocusRequest.Builder(FOCUS_GAIN_TYPE)
@@ -57,7 +55,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
public void dispose() {
abandonAudioFocus();
player.removeAudioDebugListener(this);
player.removeAnalyticsListener(this);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -116,7 +114,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 +129,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);
player.setVolume(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) {
@@ -164,29 +162,12 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAudioSessionId(int i) {
public void onAudioSessionId(EventTime eventTime, int audioSessionId) {
if (!PlayerHelper.isUsingDSP(context)) return;
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, i);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId);
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
context.sendBroadcast(intent);
}
@Override
public void onAudioEnabled(DecoderCounters decoderCounters) {}
@Override
public void onAudioDecoderInitialized(String s, long l, long l1) {}
@Override
public void onAudioInputFormatChanged(Format format) {}
@Override
public void onAudioSinkUnderrun(int bufferSize,
long bufferSizeMs,
long elapsedSinceLastFeedMs) {}
@Override
public void onAudioDisabled(DecoderCounters decoderCounters) {}
}

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 {
@@ -38,14 +33,14 @@ import java.io.File;
public CacheFactory(@NonNull final Context context,
@NonNull final String userAgent,
@NonNull final TransferListener<? super DataSource> transferListener) {
@NonNull final TransferListener transferListener) {
this(context, userAgent, transferListener, PlayerHelper.getPreferredCacheSize(context),
PlayerHelper.getPreferredFileSize(context));
}
private CacheFactory(@NonNull final Context context,
@NonNull final String userAgent,
@NonNull final TransferListener<? super DataSource> transferListener,
@NonNull final TransferListener transferListener,
final long maxCacheSize,
final long maxFileSize) {
this.maxFileSize = maxFileSize;

View File

@@ -2,17 +2,12 @@ package org.schabi.newpipe.player.helper;
import android.content.Context;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS;
import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_TARGET_BUFFER_BYTES;
public class LoadController implements LoadControl {
@@ -36,15 +31,10 @@ public class LoadController implements LoadControl {
final int optimalPlaybackBufferMs) {
this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000;
final DefaultAllocator allocator = new DefaultAllocator(true,
C.DEFAULT_BUFFER_SEGMENT_SIZE);
internalLoadControl = new DefaultLoadControl(allocator,
/*minBufferMs=*/minimumPlaybackbufferMs,
/*maxBufferMs=*/optimalPlaybackBufferMs,
/*bufferForPlaybackMs=*/initialPlaybackBufferMs,
/*bufferForPlaybackAfterRebufferMs=*/initialPlaybackBufferMs,
DEFAULT_TARGET_BUFFER_BYTES, DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS);
DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs,
initialPlaybackBufferMs, initialPlaybackBufferMs);
internalLoadControl = builder.createDefaultLoadControl();
}
/*//////////////////////////////////////////////////////////////////////////

View File

@@ -11,7 +11,6 @@ import android.view.KeyEvent;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import org.schabi.newpipe.player.mediasession.DummyPlaybackPreparer;
import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
import org.schabi.newpipe.player.mediasession.PlayQueueNavigator;
import org.schabi.newpipe.player.mediasession.PlayQueuePlaybackController;
@@ -26,10 +25,12 @@ public class MediaSessionManager {
@NonNull final Player player,
@NonNull final MediaSessionCallback callback) {
this.mediaSession = new MediaSessionCompat(context, TAG);
this.mediaSession.setActive(true);
this.sessionConnector = new MediaSessionConnector(mediaSession,
new PlayQueuePlaybackController(callback));
this.sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback));
this.sessionConnector.setPlayer(player, new DummyPlaybackPreparer());
this.sessionConnector.setPlayer(player, null);
}
@Nullable
@@ -37,4 +38,14 @@ public class MediaSessionManager {
public KeyEvent handleMediaButtonIntent(final Intent intent) {
return MediaButtonReceiver.handleIntent(mediaSession, intent);
}
/**
* Should be called on player destruction to prevent leakage.
* */
public void dispose() {
this.sessionConnector.setPlayer(null, null);
this.sessionConnector.setQueueNavigator(null);
this.mediaSession.setActive(false);
this.mediaSession.release();
}
}

View File

@@ -21,25 +21,34 @@ import static org.schabi.newpipe.player.BasePlayer.DEBUG;
public class PlaybackParameterDialog extends DialogFragment {
@NonNull private static final String TAG = "PlaybackParameterDialog";
public static final double MINIMUM_PLAYBACK_VALUE = 0.25f;
// Minimum allowable range in ExoPlayer
public static final double MINIMUM_PLAYBACK_VALUE = 0.10f;
public static final double MAXIMUM_PLAYBACK_VALUE = 3.00f;
public static final char STEP_UP_SIGN = '+';
public static final char STEP_DOWN_SIGN = '-';
public static final double PLAYBACK_STEP_VALUE = 0.05f;
public static final double NIGHTCORE_TEMPO = 1.20f;
public static final double NIGHTCORE_PITCH_LOWER = 1.15f;
public static final double NIGHTCORE_PITCH_UPPER = 1.25f;
public static final double STEP_ONE_PERCENT_VALUE = 0.01f;
public static final double STEP_FIVE_PERCENT_VALUE = 0.05f;
public static final double STEP_TEN_PERCENT_VALUE = 0.10f;
public static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f;
public static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f;
public static final double DEFAULT_TEMPO = 1.00f;
public static final double DEFAULT_PITCH = 1.00f;
public static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE;
public static final boolean DEFAULT_SKIP_SILENCE = false;
@NonNull private static final String INITIAL_TEMPO_KEY = "initial_tempo_key";
@NonNull private static final String INITIAL_PITCH_KEY = "initial_pitch_key";
@NonNull private static final String TEMPO_KEY = "tempo_key";
@NonNull private static final String PITCH_KEY = "pitch_key";
@NonNull private static final String STEP_SIZE_KEY = "step_size_key";
public interface Callback {
void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch);
void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch,
final boolean playbackSkipSilence);
}
@Nullable private Callback callback;
@@ -50,31 +59,36 @@ public class PlaybackParameterDialog extends DialogFragment {
private double initialTempo = DEFAULT_TEMPO;
private double initialPitch = DEFAULT_PITCH;
private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
private double tempo = DEFAULT_TEMPO;
private double pitch = DEFAULT_PITCH;
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 CheckBox unhookingCheckbox;
@Nullable private TextView nightCorePresetText;
@Nullable private TextView resetPresetText;
@Nullable private CheckBox skipSilenceCheckbox;
public static PlaybackParameterDialog newInstance(final double playbackTempo,
final double playbackPitch) {
final double playbackPitch,
final boolean playbackSkipSilence) {
PlaybackParameterDialog dialog = new PlaybackParameterDialog();
dialog.initialTempo = playbackTempo;
dialog.initialPitch = playbackPitch;
dialog.tempo = playbackTempo;
dialog.pitch = playbackPitch;
dialog.initialSkipSilence = playbackSkipSilence;
return dialog;
}
@@ -98,6 +112,10 @@ public class PlaybackParameterDialog extends DialogFragment {
if (savedInstanceState != null) {
initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO);
initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH);
tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO);
pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH);
stepSize = savedInstanceState.getDouble(STEP_SIZE_KEY, DEFAULT_STEP);
}
}
@@ -106,6 +124,10 @@ public class PlaybackParameterDialog extends DialogFragment {
super.onSaveInstanceState(outState);
outState.putDouble(INITIAL_TEMPO_KEY, initialTempo);
outState.putDouble(INITIAL_PITCH_KEY, initialPitch);
outState.putDouble(TEMPO_KEY, getCurrentTempo());
outState.putDouble(PITCH_KEY, getCurrentPitch());
outState.putDouble(STEP_SIZE_KEY, getCurrentStepSize());
}
/*//////////////////////////////////////////////////////////////////////////
@@ -123,7 +145,9 @@ public class PlaybackParameterDialog extends DialogFragment {
.setView(view)
.setCancelable(true)
.setNegativeButton(R.string.cancel, (dialogInterface, i) ->
setPlaybackParameters(initialTempo, initialPitch))
setPlaybackParameters(initialTempo, initialPitch, initialSkipSilence))
.setNeutralButton(R.string.playback_reset, (dialogInterface, i) ->
setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH, DEFAULT_SKIP_SILENCE))
.setPositiveButton(R.string.finish, (dialogInterface, i) ->
setCurrentPlaybackParameters());
@@ -136,83 +160,55 @@ public class PlaybackParameterDialog extends DialogFragment {
private void setupControlViews(@NonNull View rootView) {
setupHookingControl(rootView);
setupSkipSilenceControl(rootView);
setupTempoControl(rootView);
setupPitchControl(rootView);
setupPresetControl(rootView);
changeStepSize(stepSize);
setupStepSizeSelector(rootView);
}
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);
if (tempoCurrentText != null)
tempoCurrentText.setText(PlayerHelper.formatSpeed(initialTempo));
tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
if (tempoMaximumText != null)
tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE));
if (tempoMinimumText != null)
tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE));
if (tempoStepUpText != null) {
tempoStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
tempoStepUpText.setOnClickListener(view -> {
onTempoSliderUpdated(getCurrentTempo() + PLAYBACK_STEP_VALUE);
setCurrentPlaybackParameters();
});
}
if (tempoStepDownText != null) {
tempoStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
tempoStepDownText.setOnClickListener(view -> {
onTempoSliderUpdated(getCurrentTempo() - PLAYBACK_STEP_VALUE);
setCurrentPlaybackParameters();
});
}
if (tempoSlider != null) {
tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
tempoSlider.setProgress(strategy.progressOf(initialTempo));
tempoSlider.setProgress(strategy.progressOf(tempo));
tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener());
}
}
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);
if (pitchCurrentText != null)
pitchCurrentText.setText(PlayerHelper.formatPitch(initialPitch));
pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
if (pitchMaximumText != null)
pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE));
if (pitchMinimumText != null)
pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE));
if (pitchStepUpText != null) {
pitchStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
pitchStepUpText.setOnClickListener(view -> {
onPitchSliderUpdated(getCurrentPitch() + PLAYBACK_STEP_VALUE);
setCurrentPlaybackParameters();
});
}
if (pitchStepDownText != null) {
pitchStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
pitchStepDownText.setOnClickListener(view -> {
onPitchSliderUpdated(getCurrentPitch() - PLAYBACK_STEP_VALUE);
setCurrentPlaybackParameters();
});
}
if (pitchSlider != null) {
pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
pitchSlider.setProgress(strategy.progressOf(initialPitch));
pitchSlider.setProgress(strategy.progressOf(pitch));
pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener());
}
}
@@ -220,7 +216,7 @@ public class PlaybackParameterDialog extends DialogFragment {
private void setupHookingControl(@NonNull View rootView) {
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
if (unhookingCheckbox != null) {
unhookingCheckbox.setChecked(initialPitch != initialTempo);
unhookingCheckbox.setChecked(pitch != tempo);
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
if (isChecked) return;
// When unchecked, slide back to the minimum of current tempo or pitch
@@ -231,24 +227,84 @@ public class PlaybackParameterDialog extends DialogFragment {
}
}
private void setupPresetControl(@NonNull View rootView) {
nightCorePresetText = rootView.findViewById(R.id.presetNightcore);
if (nightCorePresetText != null) {
nightCorePresetText.setOnClickListener(view -> {
final double randomPitch = NIGHTCORE_PITCH_LOWER +
Math.random() * (NIGHTCORE_PITCH_UPPER - NIGHTCORE_PITCH_LOWER);
private void setupSkipSilenceControl(@NonNull View rootView) {
skipSilenceCheckbox = rootView.findViewById(R.id.skipSilenceCheckbox);
if (skipSilenceCheckbox != null) {
skipSilenceCheckbox.setChecked(initialSkipSilence);
skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) ->
setCurrentPlaybackParameters());
}
}
setTempoSlider(NIGHTCORE_TEMPO);
setPitchSlider(randomPitch);
private void setupStepSizeSelector(@NonNull final View rootView) {
TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
TextView stepSizeTwentyFivePercentText = rootView.findViewById(R.id.stepSizeTwentyFivePercent);
TextView stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent);
if (stepSizeOnePercentText != null) {
stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE));
stepSizeOnePercentText.setOnClickListener(view ->
changeStepSize(STEP_ONE_PERCENT_VALUE));
}
if (stepSizeFivePercentText != null) {
stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE));
stepSizeFivePercentText.setOnClickListener(view ->
changeStepSize(STEP_FIVE_PERCENT_VALUE));
}
if (stepSizeTenPercentText != null) {
stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE));
stepSizeTenPercentText.setOnClickListener(view ->
changeStepSize(STEP_TEN_PERCENT_VALUE));
}
if (stepSizeTwentyFivePercentText != null) {
stepSizeTwentyFivePercentText.setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE));
stepSizeTwentyFivePercentText.setOnClickListener(view ->
changeStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE));
}
if (stepSizeOneHundredPercentText != null) {
stepSizeOneHundredPercentText.setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE));
stepSizeOneHundredPercentText.setOnClickListener(view ->
changeStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE));
}
}
private void changeStepSize(final double stepSize) {
this.stepSize = stepSize;
if (tempoStepUpText != null) {
tempoStepUpText.setText(getStepUpPercentString(stepSize));
tempoStepUpText.setOnClickListener(view -> {
onTempoSliderUpdated(getCurrentTempo() + stepSize);
setCurrentPlaybackParameters();
});
}
resetPresetText = rootView.findViewById(R.id.presetReset);
if (resetPresetText != null) {
resetPresetText.setOnClickListener(view -> {
setTempoSlider(DEFAULT_TEMPO);
setPitchSlider(DEFAULT_PITCH);
if (tempoStepDownText != null) {
tempoStepDownText.setText(getStepDownPercentString(stepSize));
tempoStepDownText.setOnClickListener(view -> {
onTempoSliderUpdated(getCurrentTempo() - stepSize);
setCurrentPlaybackParameters();
});
}
if (pitchStepUpText != null) {
pitchStepUpText.setText(getStepUpPercentString(stepSize));
pitchStepUpText.setOnClickListener(view -> {
onPitchSliderUpdated(getCurrentPitch() + stepSize);
setCurrentPlaybackParameters();
});
}
if (pitchStepDownText != null) {
pitchStepDownText.setText(getStepDownPercentString(stepSize));
pitchStepDownText.setOnClickListener(view -> {
onPitchSliderUpdated(getCurrentPitch() - stepSize);
setCurrentPlaybackParameters();
});
}
@@ -342,10 +398,11 @@ public class PlaybackParameterDialog extends DialogFragment {
//////////////////////////////////////////////////////////////////////////*/
private void setCurrentPlaybackParameters() {
setPlaybackParameters(getCurrentTempo(), getCurrentPitch());
setPlaybackParameters(getCurrentTempo(), getCurrentPitch(), getCurrentSkipSilence());
}
private void setPlaybackParameters(final double tempo, final double pitch) {
private void setPlaybackParameters(final double tempo, final double pitch,
final boolean skipSilence) {
if (callback != null && tempoCurrentText != null && pitchCurrentText != null) {
if (DEBUG) Log.d(TAG, "Setting playback parameters to " +
"tempo=[" + tempo + "], " +
@@ -353,27 +410,40 @@ public class PlaybackParameterDialog extends DialogFragment {
tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
callback.onPlaybackParameterChanged((float) tempo, (float) pitch);
callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence);
}
}
private double getCurrentTempo() {
return tempoSlider == null ? initialTempo : strategy.valueOf(
return tempoSlider == null ? tempo : strategy.valueOf(
tempoSlider.getProgress());
}
private double getCurrentPitch() {
return pitchSlider == null ? initialPitch : strategy.valueOf(
return pitchSlider == null ? pitch : strategy.valueOf(
pitchSlider.getProgress());
}
private double getCurrentStepSize() {
return stepSize;
}
private boolean getCurrentSkipSilence() {
return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked();
}
@NonNull
private static String getStepUpPercentString(final double percent) {
return STEP_UP_SIGN + PlayerHelper.formatPitch(percent);
return STEP_UP_SIGN + getPercentString(percent);
}
@NonNull
private static String getStepDownPercentString(final double percent) {
return STEP_DOWN_SIGN + PlayerHelper.formatPitch(percent);
return STEP_DOWN_SIGN + getPercentString(percent);
}
@NonNull
private static String getPercentString(final double percent) {
return PlayerHelper.formatPitch(percent);
}
}

View File

@@ -12,6 +12,7 @@ import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener;
public class PlayerDataSource {
@@ -24,7 +25,7 @@ public class PlayerDataSource {
public PlayerDataSource(@NonNull final Context context,
@NonNull final String userAgent,
@NonNull final TransferListener<? super DataSource> transferListener) {
@NonNull final TransferListener transferListener) {
cacheDataSourceFactory = new CacheFactory(context, userAgent, transferListener);
cachelessDataSourceFactory = new DefaultDataSourceFactory(context, userAgent, transferListener);
}
@@ -32,21 +33,21 @@ public class PlayerDataSource {
public SsMediaSource.Factory getLiveSsMediaSourceFactory() {
return new SsMediaSource.Factory(new DefaultSsChunkSource.Factory(
cachelessDataSourceFactory), cachelessDataSourceFactory)
.setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY)
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
}
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
return new HlsMediaSource.Factory(cachelessDataSourceFactory)
.setAllowChunklessPreparation(true)
.setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY);
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
}
public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
return new DashMediaSource.Factory(new DefaultDashChunkSource.Factory(
cachelessDataSourceFactory), cachelessDataSourceFactory)
.setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY)
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS, true);
}
public SsMediaSource.Factory getSsMediaSourceFactory() {
@@ -65,7 +66,7 @@ public class PlayerDataSource {
public ExtractorMediaSource.Factory getExtractorMediaSourceFactory() {
return new ExtractorMediaSource.Factory(cacheDataSourceFactory)
.setMinLoadableRetryCount(EXTRACTOR_MINIMUM_RETRY);
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
}
public ExtractorMediaSource.Factory getExtractorMediaSourceFactory(@NonNull final String key) {

View File

@@ -4,30 +4,31 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.accessibility.CaptioningManager;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.MimeTypes;
import org.schabi.newpipe.R;
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;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import java.lang.annotation.Retention;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
@@ -37,10 +38,15 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FILL;
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT;
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
public class PlayerHelper {
private PlayerHelper() {}
@@ -50,15 +56,23 @@ public class PlayerHelper {
private static final NumberFormat speedFormatter = new DecimalFormat("0.##x");
private static final NumberFormat pitchFormatter = new DecimalFormat("##%");
@Retention(SOURCE)
@IntDef({MINIMIZE_ON_EXIT_MODE_NONE, MINIMIZE_ON_EXIT_MODE_BACKGROUND,
MINIMIZE_ON_EXIT_MODE_POPUP})
public @interface MinimizeMode {
int MINIMIZE_ON_EXIT_MODE_NONE = 0;
int MINIMIZE_ON_EXIT_MODE_BACKGROUND = 1;
int MINIMIZE_ON_EXIT_MODE_POPUP = 2;
}
////////////////////////////////////////////////////////////////////////////
// Exposed helpers
////////////////////////////////////////////////////////////////////////////
public static String getTimeString(int milliSeconds) {
long seconds = (milliSeconds % 60000L) / 1000L;
long minutes = (milliSeconds % 3600000L) / 60000L;
long hours = (milliSeconds % 86400000L) / 3600000L;
long days = (milliSeconds % (86400000L * 7L)) / 86400000L;
int seconds = (milliSeconds % 60000) / 1000;
int minutes = (milliSeconds % 3600000) / 60000;
int hours = (milliSeconds % 86400000) / 3600000;
int days = (milliSeconds % (86400000 * 7)) / 86400000;
stringBuilder.setLength(0);
return days > 0 ? stringFormatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString()
@@ -74,7 +88,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;
@@ -84,8 +98,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)+ ")" : "");
}
@@ -132,7 +146,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();
@@ -145,7 +159,7 @@ public class PlayerHelper {
}
}
Collections.shuffle(autoQueueItems);
return autoQueueItems.isEmpty() ? null : new SinglePlayQueue(autoQueueItems.get(0));
return autoQueueItems.isEmpty() ? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
}
////////////////////////////////////////////////////////////////////////////
@@ -156,12 +170,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) {
@@ -172,9 +186,25 @@ public class PlayerHelper {
return isAutoQueueEnabled(context, false);
}
@MinimizeMode
public static int getMinimizeOnExitAction(@NonNull final Context context) {
final String defaultAction = context.getString(R.string.minimize_on_exit_none_key);
final String popupAction = context.getString(R.string.minimize_on_exit_popup_key);
final String backgroundAction = context.getString(R.string.minimize_on_exit_background_key);
final String action = getMinimizeOnExitAction(context, defaultAction);
if (action.equals(popupAction)) {
return MINIMIZE_ON_EXIT_MODE_POPUP;
} else if (action.equals(backgroundAction)) {
return MINIMIZE_ON_EXIT_MODE_BACKGROUND;
} else {
return MINIMIZE_ON_EXIT_MODE_NONE;
}
}
@NonNull
public static SeekParameters getSeekParameters(@NonNull final Context context) {
return isUsingInexactSeek(context, false) ?
return isUsingInexactSeek(context) ?
SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
}
@@ -209,10 +239,8 @@ public class PlayerHelper {
return 60000;
}
public static TrackSelection.Factory getQualitySelector(@NonNull final Context context,
@NonNull final BandwidthMeter meter) {
return new AdaptiveTrackSelection.Factory(meter,
AdaptiveTrackSelection.DEFAULT_MAX_INITIAL_BITRATE,
public static TrackSelection.Factory getQualitySelector(@NonNull final Context context) {
return new AdaptiveTrackSelection.Factory(
/*bufferDurationRequiredForQualityIncrease=*/1000,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
@@ -223,10 +251,6 @@ public class PlayerHelper {
return true;
}
public static int getShutdownFlingVelocity(@NonNull final Context context) {
return 10000;
}
public static int getTossFlingVelocity(@NonNull final Context context) {
return 2500;
}
@@ -248,7 +272,6 @@ public class PlayerHelper {
* System font scaling:
* Very small - 0.25f, Small - 0.5f, Normal - 1.0f, Large - 1.5f, Very Large - 2.0f
* */
@NonNull
public static float getCaptionScale(@NonNull final Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return 1f;
@@ -260,6 +283,16 @@ public class PlayerHelper {
return captioningManager.getFontScale();
}
public static float getScreenBrightness(@NonNull final Context context) {
//a value of less than 0, the default, means to use the preferred screen brightness
return getScreenBrightness(context, -1);
}
public static void setScreenBrightness(@NonNull final Context context, final float setScreenBrightness) {
setScreenBrightness(context, setScreenBrightness, System.currentTimeMillis());
}
////////////////////////////////////////////////////////////////////////////
// Private helpers
////////////////////////////////////////////////////////////////////////////
@@ -273,23 +306,54 @@ 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) {
return getPreferences(context).getBoolean(context.getString(R.string.auto_queue_key), b);
}
private static void setScreenBrightness(@NonNull final Context context, final float screenBrightness, final long timestamp) {
SharedPreferences.Editor editor = getPreferences(context).edit();
editor.putFloat(context.getString(R.string.screen_brightness_key), screenBrightness);
editor.putLong(context.getString(R.string.screen_brightness_timestamp_key), timestamp);
editor.apply();
}
private static float getScreenBrightness(@NonNull final Context context, final float screenBrightness) {
SharedPreferences sp = getPreferences(context);
long timestamp = sp.getLong(context.getString(R.string.screen_brightness_timestamp_key), 0);
// hypothesis: 4h covers a viewing block, eg evening. External lightning conditions will change in the next
// viewing block so we fall back to the default brightness
if ((System.currentTimeMillis() - timestamp) > TimeUnit.HOURS.toMillis(4)) {
return screenBrightness;
} else {
return sp.getFloat(context.getString(R.string.screen_brightness_key), screenBrightness);
}
}
private static String getMinimizeOnExitAction(@NonNull final Context context,
final String key) {
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

@@ -1,45 +0,0 @@
package org.schabi.newpipe.player.mediasession;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
public class DummyPlaybackPreparer implements MediaSessionConnector.PlaybackPreparer {
@Override
public long getSupportedPrepareActions() {
return 0;
}
@Override
public void onPrepare() {
}
@Override
public void onPrepareFromMediaId(String mediaId, Bundle extras) {
}
@Override
public void onPrepareFromSearch(String query, Bundle extras) {
}
@Override
public void onPrepareFromUri(Uri uri, Bundle extras) {
}
@Override
public String[] getCommands() {
return new String[0];
}
@Override
public void onCommand(Player player, String command, Bundle extras, ResultReceiver cb) {
}
}

View File

@@ -13,5 +13,4 @@ public interface MediaSessionCallback {
void onPlay();
void onPause();
void onSetShuffle(final boolean isShuffled);
}

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,7 +1,5 @@
package org.schabi.newpipe.player.mediasession;
import android.support.v4.media.session.PlaybackStateCompat;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.DefaultPlaybackController;
@@ -22,10 +20,4 @@ public class PlayQueuePlaybackController extends DefaultPlaybackController {
public void onPause(Player player) {
callback.onPause();
}
@Override
public void onSetShuffleMode(Player player, int shuffleMode) {
callback.onSetShuffle(shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL
|| shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP);
}
}

View File

@@ -1,17 +1,19 @@
package org.schabi.newpipe.player.mediasource;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.source.BaseMediaSource;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.TransferListener;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import java.io.IOException;
public class FailedMediaSource implements ManagedMediaSource {
public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSource {
private final String TAG = "FailedMediaSource@" + Integer.toHexString(hashCode());
public static class FailedMediaSourceException extends Exception {
@@ -72,26 +74,27 @@ public class FailedMediaSource implements ManagedMediaSource {
return System.currentTimeMillis() >= retryTimestamp;
}
@Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
Log.e(TAG, "Loading failed source: ", error);
}
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
throw new IOException(error);
}
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
return null;
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {}
@Override
public void releaseSource() {}
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
Log.e(TAG, "Loading failed source: ", error);
}
@Override
protected void releaseSourceInternal() {}
@Override
public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity,

View File

@@ -1,11 +1,14 @@
package org.schabi.newpipe.player.mediasource;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.TransferListener;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
@@ -34,8 +37,8 @@ public class LoadedMediaSource implements ManagedMediaSource {
}
@Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
source.prepareSource(player, isTopLevelSource, listener);
public void prepareSource(SourceInfoRefreshListener listener, @Nullable TransferListener mediaTransferListener) {
source.prepareSource(listener, mediaTransferListener);
}
@Override
@@ -44,8 +47,8 @@ public class LoadedMediaSource implements ManagedMediaSource {
}
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
return source.createPeriod(id, allocator);
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
return source.createPeriod(id, allocator, startPositionUs);
}
@Override
@@ -54,8 +57,18 @@ public class LoadedMediaSource implements ManagedMediaSource {
}
@Override
public void releaseSource() {
source.releaseSource();
public void releaseSource(SourceInfoRefreshListener listener) {
source.releaseSource(listener);
}
@Override
public void addEventListener(Handler handler, MediaSourceEventListener eventListener) {
source.addEventListener(handler, eventListener);
}
@Override
public void removeEventListener(MediaSourceEventListener eventListener) {
source.removeEventListener(eventListener);
}
@Override

View File

@@ -1,16 +1,16 @@
package org.schabi.newpipe.player.mediasource;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.ShuffleOrder;
public class ManagedMediaSourcePlaylist {
@NonNull private final DynamicConcatenatingMediaSource internalSource;
@NonNull private final ConcatenatingMediaSource internalSource;
public ManagedMediaSourcePlaylist() {
internalSource = new DynamicConcatenatingMediaSource(/*isPlaylistAtomic=*/false,
internalSource = new ConcatenatingMediaSource(/*isPlaylistAtomic=*/false,
new ShuffleOrder.UnshuffledShuffleOrder(0));
}
@@ -32,12 +32,8 @@ public class ManagedMediaSourcePlaylist {
null : (ManagedMediaSource) internalSource.getMediaSource(index);
}
public void dispose() {
internalSource.releaseSource();
}
@NonNull
public DynamicConcatenatingMediaSource getParentMediaSource() {
public ConcatenatingMediaSource getParentMediaSource() {
return internalSource;
}
@@ -46,7 +42,7 @@ public class ManagedMediaSourcePlaylist {
//////////////////////////////////////////////////////////////////////////*/
/**
* Expands the {@link DynamicConcatenatingMediaSource} by appending it with a
* Expands the {@link ConcatenatingMediaSource} by appending it with a
* {@link PlaceholderMediaSource}.
*
* @see #append(ManagedMediaSource)
@@ -56,17 +52,17 @@ public class ManagedMediaSourcePlaylist {
}
/**
* Appends a {@link ManagedMediaSource} to the end of {@link DynamicConcatenatingMediaSource}.
* @see DynamicConcatenatingMediaSource#addMediaSource
* Appends a {@link ManagedMediaSource} to the end of {@link ConcatenatingMediaSource}.
* @see ConcatenatingMediaSource#addMediaSource
* */
public synchronized void append(@NonNull final ManagedMediaSource source) {
internalSource.addMediaSource(source);
}
/**
* Removes a {@link ManagedMediaSource} from {@link DynamicConcatenatingMediaSource}
* Removes a {@link ManagedMediaSource} from {@link ConcatenatingMediaSource}
* at the given index. If this index is out of bound, then the removal is ignored.
* @see DynamicConcatenatingMediaSource#removeMediaSource(int)
* @see ConcatenatingMediaSource#removeMediaSource(int)
* */
public synchronized void remove(final int index) {
if (index < 0 || index > internalSource.getSize()) return;
@@ -75,10 +71,10 @@ public class ManagedMediaSourcePlaylist {
}
/**
* Moves a {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
* Moves a {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
* from the given source index to the target index. If either index is out of bound,
* then the call is ignored.
* @see DynamicConcatenatingMediaSource#moveMediaSource(int, int)
* @see ConcatenatingMediaSource#moveMediaSource(int, int)
* */
public synchronized void move(final int source, final int target) {
if (source < 0 || target < 0) return;
@@ -90,31 +86,33 @@ public class ManagedMediaSourcePlaylist {
/**
* Invalidates the {@link ManagedMediaSource} at the given index by replacing it
* with a {@link PlaceholderMediaSource}.
* @see #update(int, ManagedMediaSource, Runnable)
* @see #update(int, ManagedMediaSource, Handler, Runnable)
* */
public synchronized void invalidate(final int index,
@Nullable final Handler handler,
@Nullable final Runnable finalizingAction) {
if (get(index) instanceof PlaceholderMediaSource) return;
update(index, new PlaceholderMediaSource(), finalizingAction);
update(index, new PlaceholderMediaSource(), handler, finalizingAction);
}
/**
* Updates the {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
* Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
* at the given index with a given {@link ManagedMediaSource}.
* @see #update(int, ManagedMediaSource, Runnable)
* @see #update(int, ManagedMediaSource, Handler, Runnable)
* */
public synchronized void update(final int index, @NonNull final ManagedMediaSource source) {
update(index, source, /*doNothing=*/null);
update(index, source, null, /*doNothing=*/null);
}
/**
* Updates the {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
* Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
* at the given index with a given {@link ManagedMediaSource}. If the index is out of bound,
* then the replacement is ignored.
* @see DynamicConcatenatingMediaSource#addMediaSource
* @see DynamicConcatenatingMediaSource#removeMediaSource(int, Runnable)
* @see ConcatenatingMediaSource#addMediaSource
* @see ConcatenatingMediaSource#removeMediaSource(int, Handler, Runnable)
* */
public synchronized void update(final int index, @NonNull final ManagedMediaSource source,
@Nullable final Handler handler,
@Nullable final Runnable finalizingAction) {
if (index < 0 || index >= internalSource.getSize()) return;
@@ -130,6 +128,6 @@ public class ManagedMediaSourcePlaylist {
// Because of the above race condition, it is thus only safe to synchronize the player
// in the finalizing action AFTER the removal is complete and the timeline has changed.
internalSource.removeMediaSource(index, finalizingAction);
internalSource.removeMediaSource(index, handler, finalizingAction);
}
}

View File

@@ -1,22 +1,22 @@
package org.schabi.newpipe.player.mediasource;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.source.BaseMediaSource;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.TransferListener;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import java.io.IOException;
public class PlaceholderMediaSource implements ManagedMediaSource {
public class PlaceholderMediaSource extends BaseMediaSource implements ManagedMediaSource {
// Do nothing, so this will stall the playback
@Override public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {}
@Override public void maybeThrowSourceInfoRefreshError() throws IOException {}
@Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { return null; }
@Override public void maybeThrowSourceInfoRefreshError() {}
@Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { return null; }
@Override public void releasePeriod(MediaPeriod mediaPeriod) {}
@Override public void releaseSource() {}
@Override protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {}
@Override protected void releaseSourceInternal() {}
@Override
public boolean shouldBeReplacedWith(@NonNull PlayQueueItem newIdentity,

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();
}
}

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