mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-10-12 08:32:15 +02:00
Compare commits
337 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
48f966e7db | ||
![]() |
d8746dc592 | ||
![]() |
f24fab0fa2 | ||
![]() |
261620fd13 | ||
![]() |
9af6effad9 | ||
![]() |
92602916dd | ||
![]() |
6c7b90e1c3 | ||
![]() |
d8e57144f7 | ||
![]() |
e69c45a246 | ||
![]() |
3f87a6d714 | ||
![]() |
841124b1f3 | ||
![]() |
257a878ef4 | ||
![]() |
07d3f82912 | ||
![]() |
7d3eb4f5a6 | ||
![]() |
0efcc55373 | ||
![]() |
eafceb8a6c | ||
![]() |
5ac8318e71 | ||
![]() |
7981f9dc91 | ||
![]() |
a9bf04cbc6 | ||
![]() |
4b5591d884 | ||
![]() |
c08197f025 | ||
![]() |
5d22eabfa3 | ||
![]() |
c3a38e384a | ||
![]() |
0d3dd94559 | ||
![]() |
9cdaa37519 | ||
![]() |
202a319d69 | ||
![]() |
198384b2ed | ||
![]() |
9d8b070f1e | ||
![]() |
28ba2d5008 | ||
![]() |
37ddd63d27 | ||
![]() |
d7f8b8c1e0 | ||
![]() |
9e70c5bbea | ||
![]() |
4dd572063e | ||
![]() |
db9cf95648 | ||
![]() |
df6bae4712 | ||
![]() |
56cb8209b8 | ||
![]() |
aba9c2e113 | ||
![]() |
3d25008739 | ||
![]() |
9437f057d0 | ||
![]() |
917d6089a7 | ||
![]() |
f19e99e3ad | ||
![]() |
2d0fb05fa6 | ||
![]() |
c617f2dfad | ||
![]() |
747c5fc89a | ||
![]() |
4f662ef203 | ||
![]() |
eb274ad88f | ||
![]() |
bd7b41be9b | ||
![]() |
076720bfff | ||
![]() |
2aadae407e | ||
![]() |
b16bb07774 | ||
![]() |
81dd083388 | ||
![]() |
92d4cef1e2 | ||
![]() |
d2cd79dcf5 | ||
![]() |
0ef8b391e4 | ||
![]() |
0fbd105977 | ||
![]() |
5c23af541e | ||
![]() |
db021ff78b | ||
![]() |
31294c6f54 | ||
![]() |
03d3495759 | ||
![]() |
4644e105a8 | ||
![]() |
af5002508d | ||
![]() |
9416d2b9c7 | ||
![]() |
f60f5d5a6c | ||
![]() |
26d00f87a8 | ||
![]() |
6c33ea423c | ||
![]() |
ad354aca4e | ||
![]() |
ce6a5ebf0a | ||
![]() |
d66ef38e8d | ||
![]() |
1b6c49f621 | ||
![]() |
0262c83815 | ||
![]() |
c212934130 | ||
![]() |
ac30e47e59 | ||
![]() |
d640057453 | ||
![]() |
0cea136d4d | ||
![]() |
9b3d93e734 | ||
![]() |
ca3782ac62 | ||
![]() |
dc9ffb7fd5 | ||
![]() |
b21b3a55fe | ||
![]() |
e62bd85a7a | ||
![]() |
fed221b008 | ||
![]() |
935298d159 | ||
![]() |
909919250c | ||
![]() |
d24f278f7c | ||
![]() |
ca408a495c | ||
![]() |
ee99719137 | ||
![]() |
5fb0729eae | ||
![]() |
a1db3187cd | ||
![]() |
488a3cf1ff | ||
![]() |
1253a620a4 | ||
![]() |
64547fc4a7 | ||
![]() |
d643d140cf | ||
![]() |
222e2e3242 | ||
![]() |
ea7b6daf26 | ||
![]() |
14cfab495d | ||
![]() |
ed4b4a1a3c | ||
![]() |
511c552188 | ||
![]() |
8c650219eb | ||
![]() |
189b779abe | ||
![]() |
c26074b184 | ||
![]() |
6c70779ff1 | ||
![]() |
73897481ac | ||
![]() |
a62c523ed5 | ||
![]() |
0f46f57b52 | ||
![]() |
bc4d65a20c | ||
![]() |
f1e9a44ad9 | ||
![]() |
61972c4719 | ||
![]() |
9183c9c220 | ||
![]() |
1ce59f13ff | ||
![]() |
1bac5db6d5 | ||
![]() |
58f2c4d8e6 | ||
![]() |
2c2c61b2fc | ||
![]() |
98b04a4a83 | ||
![]() |
3ac64c44f4 | ||
![]() |
3375847302 | ||
![]() |
14043c86f5 | ||
![]() |
030117780a | ||
![]() |
971c9fe5a1 | ||
![]() |
77c6d3d576 | ||
![]() |
6edbfe2a6f | ||
![]() |
d0a3125df4 | ||
![]() |
d8c76d4c21 | ||
![]() |
a1cc0897df | ||
![]() |
e88a90f242 | ||
![]() |
1ae54f6f8c | ||
![]() |
8a4cb484fa | ||
![]() |
93defbf341 | ||
![]() |
ca1089da9a | ||
![]() |
dd07c7db0e | ||
![]() |
dfe932e37a | ||
![]() |
568cf9c259 | ||
![]() |
b4ef20e785 | ||
![]() |
3a56dabf42 | ||
![]() |
3ac4899b96 | ||
![]() |
b7b228d9ce | ||
![]() |
45339fd6d2 | ||
![]() |
1856a5a82f | ||
![]() |
b2aa703b62 | ||
![]() |
b31897d65d | ||
![]() |
a92f776ebe | ||
![]() |
b84ed675f5 | ||
![]() |
21eb98a52c | ||
![]() |
75dd8d492b | ||
![]() |
338893ded4 | ||
![]() |
b459900040 | ||
![]() |
d31300f4f9 | ||
![]() |
f2285c0b19 | ||
![]() |
684cb81974 | ||
![]() |
9db272f30e | ||
![]() |
c4a5e8dc86 | ||
![]() |
df4dd0122f | ||
![]() |
8fed18b2ac | ||
![]() |
ecabbb57e6 | ||
![]() |
8d1d4092aa | ||
![]() |
6185c4ddcf | ||
![]() |
3ccbbccd10 | ||
![]() |
30e0ccc77b | ||
![]() |
50f7b72b09 | ||
![]() |
a5828c7949 | ||
![]() |
22a5e72470 | ||
![]() |
5d14dca818 | ||
![]() |
f97cb821f8 | ||
![]() |
d02dce5562 | ||
![]() |
c6bd42843b | ||
![]() |
43e4fbfcd0 | ||
![]() |
ac8430cbba | ||
![]() |
86d619be30 | ||
![]() |
fec7672598 | ||
![]() |
44d2744a8c | ||
![]() |
22bfbe96c8 | ||
![]() |
6c5c42c2b5 | ||
![]() |
b1653b359e | ||
![]() |
11098afab5 | ||
![]() |
a64051e0f1 | ||
![]() |
db8ac4a9ae | ||
![]() |
890474a635 | ||
![]() |
a39b10eee9 | ||
![]() |
97b933a990 | ||
![]() |
4926e90514 | ||
![]() |
6dca975844 | ||
![]() |
e31743770a | ||
![]() |
d8aab62d75 | ||
![]() |
f90603a18f | ||
![]() |
5fce9facbe | ||
![]() |
32b48d5cdb | ||
![]() |
3e4f0d682b | ||
![]() |
98449ddfe0 | ||
![]() |
26040d4e9f | ||
![]() |
83cea5e1ee | ||
![]() |
3e73a5dbd3 | ||
![]() |
dd424a4cb3 | ||
![]() |
0c79f5cce3 | ||
![]() |
794c3703e5 | ||
![]() |
b674006fcc | ||
![]() |
559c397b2f | ||
![]() |
646698f1ed | ||
![]() |
c9b938ae55 | ||
![]() |
990c220fa0 | ||
![]() |
38641d4edf | ||
![]() |
450072bd23 | ||
![]() |
25eb93fae0 | ||
![]() |
bcf6f60571 | ||
![]() |
2076b8f1d7 | ||
![]() |
f19cfb75e6 | ||
![]() |
ceaacc771d | ||
![]() |
48067e3285 | ||
![]() |
003855a94c | ||
![]() |
e7a26b436d | ||
![]() |
ab94bc18dc | ||
![]() |
36e91ea155 | ||
![]() |
67499bdc65 | ||
![]() |
222c8fdb62 | ||
![]() |
9d648bad51 | ||
![]() |
1a62b9a161 | ||
![]() |
33021e8fe0 | ||
![]() |
5a2ae4c3e3 | ||
![]() |
9eaef84cc8 | ||
![]() |
fcc4d655f5 | ||
![]() |
8d0ac4f5f0 | ||
![]() |
d56cf003f8 | ||
![]() |
34d0c0b1ba | ||
![]() |
9bbbffbe7a | ||
![]() |
ccb9bceecc | ||
![]() |
c1a67ff1f8 | ||
![]() |
bfda8dcc02 | ||
![]() |
ff90f257cc | ||
![]() |
9b84046865 | ||
![]() |
f62bfeac08 | ||
![]() |
49a58dcab1 | ||
![]() |
e2acbeddc2 | ||
![]() |
449b17d830 | ||
![]() |
7ed460ce02 | ||
![]() |
edcb692f78 | ||
![]() |
f33586f062 | ||
![]() |
d61bf26e17 | ||
![]() |
2e662b5745 | ||
![]() |
9714fa369b | ||
![]() |
0e8acba08e | ||
![]() |
a18e588e55 | ||
![]() |
bf55e3c0cc | ||
![]() |
486d114e64 | ||
![]() |
ac5c060a98 | ||
![]() |
86a9d197cb | ||
![]() |
70aac81900 | ||
![]() |
28e78d98f6 | ||
![]() |
fabc5ae032 | ||
![]() |
e40882455d | ||
![]() |
f1483e8c8e | ||
![]() |
bd40e2c3ff | ||
![]() |
9479498713 | ||
![]() |
6bd8523ec2 | ||
![]() |
c864b15c34 | ||
![]() |
069654c5f9 | ||
![]() |
5ac17ee6b8 | ||
![]() |
26e22f97ee | ||
![]() |
ad5535af81 | ||
![]() |
cdcfb4ffce | ||
![]() |
5403ac8893 | ||
![]() |
9b26457781 | ||
![]() |
939cc56951 | ||
![]() |
23309e6fdf | ||
![]() |
3f60c961d9 | ||
![]() |
9e7f07e196 | ||
![]() |
eda4439ee8 | ||
![]() |
ad02558ade | ||
![]() |
0d1901cfe5 | ||
![]() |
ce493d1ae2 | ||
![]() |
09312ecd1d | ||
![]() |
2f274d5f52 | ||
![]() |
6dbf226365 | ||
![]() |
6401b5f54d | ||
![]() |
ac2dd81d39 | ||
![]() |
44b2e62eef | ||
![]() |
7d5d4df761 | ||
![]() |
da7716db60 | ||
![]() |
862c5d342b | ||
![]() |
6167d5dbfc | ||
![]() |
f9341bea79 | ||
![]() |
3a608bb582 | ||
![]() |
c926482b3c | ||
![]() |
9562972c42 | ||
![]() |
dd56a5d869 | ||
![]() |
8ae45240b2 | ||
![]() |
6d845af7f1 | ||
![]() |
733ebb8caf | ||
![]() |
695a194467 | ||
![]() |
8a56257ade | ||
![]() |
4d5dc0d39c | ||
![]() |
8589ee14ff | ||
![]() |
1727387c9c | ||
![]() |
e1146e4655 | ||
![]() |
c29b0645bd | ||
![]() |
96dac0f979 | ||
![]() |
fda9b59129 | ||
![]() |
fa5896ee5b | ||
![]() |
3c6d27b504 | ||
![]() |
6ef25eb861 | ||
![]() |
ec28e972bb | ||
![]() |
d1a9033525 | ||
![]() |
2d5bc3ada8 | ||
![]() |
506ffb9885 | ||
![]() |
8ef702fa07 | ||
![]() |
9fc38b5bb8 | ||
![]() |
54ac5e8940 | ||
![]() |
e2341363d4 | ||
![]() |
f1aa3d8c90 | ||
![]() |
cf3e53eb71 | ||
![]() |
b8865e925d | ||
![]() |
2e9a860aaa | ||
![]() |
e4bef056e6 | ||
![]() |
803b8eab28 | ||
![]() |
08bbfc50ee | ||
![]() |
4e6722f201 | ||
![]() |
a29e2116a7 | ||
![]() |
515be677a9 | ||
![]() |
d694c5f511 | ||
![]() |
66c753f3a3 | ||
![]() |
7047b62442 | ||
![]() |
5cc73555cd | ||
![]() |
219922cd82 | ||
![]() |
7124d9bca5 | ||
![]() |
08127e5806 | ||
![]() |
6417bd91ef | ||
![]() |
cde5f7d12e | ||
![]() |
395c9587b6 | ||
![]() |
17197ad670 | ||
![]() |
910c10f554 | ||
![]() |
04e974b326 | ||
![]() |
e7abeb5ad9 | ||
![]() |
2a18eacf62 | ||
![]() |
af42e32ae6 | ||
![]() |
12b93d6637 | ||
![]() |
930c971035 | ||
![]() |
06f20c66f8 | ||
![]() |
7c875a8541 | ||
![]() |
f85e19c75d | ||
![]() |
5e2aa51627 | ||
![]() |
75a44fb30a |
@@ -8,18 +8,20 @@ android {
|
||||
applicationId "org.schabi.newpipe"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 70
|
||||
versionName "0.15.0"
|
||||
versionCode 730
|
||||
versionName "0.16.1"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
debug {
|
||||
multiDexEnabled true
|
||||
debuggable true
|
||||
@@ -33,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
|
||||
@@ -54,7 +57,7 @@ dependencies {
|
||||
exclude module: 'support-annotations'
|
||||
})
|
||||
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:99915e4527c0'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:e072bf6461b295'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.mockito:mockito-core:2.23.0'
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -35,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">
|
||||
@@ -119,7 +113,6 @@
|
||||
<activity
|
||||
android:name=".ReCaptchaActivity"
|
||||
android:label="@string/reCaptchaActivity"/>
|
||||
<activity android:name=".download.ExtSDDownloadFailedActivity" />
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
@@ -184,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>
|
||||
@@ -210,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"/>
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
@@ -65,6 +66,7 @@ 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>[]
|
||||
@@ -88,6 +90,8 @@ public class App extends Application {
|
||||
}
|
||||
refWatcher = installLeakCanary();
|
||||
|
||||
app = this;
|
||||
|
||||
// Initialize settings first because others inits can use its values
|
||||
SettingsActivity.initSettings(this);
|
||||
|
||||
@@ -100,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() {
|
||||
@@ -211,6 +218,31 @@ public class App extends Application {
|
||||
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
|
||||
@@ -226,4 +258,8 @@ public class App extends Application {
|
||||
protected boolean isDisposedRxExceptionsReported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static App getApp() {
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,230 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* AsyncTask to check if there is a newer version of the NewPipe github apk available or not.
|
||||
* If there is a newer version we show a notification, informing the user. On tapping
|
||||
* the notification, the user will be directed to the download link.
|
||||
*/
|
||||
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||
|
||||
private static final Application app = App.getApp();
|
||||
private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
||||
private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json";
|
||||
private static final int timeoutPeriod = 30;
|
||||
|
||||
private SharedPreferences mPrefs;
|
||||
private OkHttpClient client;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||
|
||||
// Check if user has enabled/ disabled update checking
|
||||
// and if the current apk is a github one or not.
|
||||
if (!mPrefs.getBoolean(app.getString(R.string.update_app_key), true)
|
||||
|| !isGithubApk()) {
|
||||
this.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
|
||||
// Make a network request to get latest NewPipe data.
|
||||
if (client == null) {
|
||||
|
||||
client = new OkHttpClient
|
||||
.Builder()
|
||||
.readTimeout(timeoutPeriod, TimeUnit.SECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(newPipeApiUrl)
|
||||
.build();
|
||||
|
||||
try {
|
||||
Response response = client.newCall(request).execute();
|
||||
return response.body().string();
|
||||
} catch (IOException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"app update API fail", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String response) {
|
||||
|
||||
// Parse the json from the response.
|
||||
if (response != null) {
|
||||
|
||||
try {
|
||||
JSONObject mainObject = new JSONObject(response);
|
||||
JSONObject flavoursObject = mainObject.getJSONObject("flavors");
|
||||
JSONObject githubObject = flavoursObject.getJSONObject("github");
|
||||
JSONObject githubStableObject = githubObject.getJSONObject("stable");
|
||||
|
||||
String versionName = githubStableObject.getString("version");
|
||||
String versionCode = githubStableObject.getString("version_code");
|
||||
String apkLocationUrl = githubStableObject.getString("apk");
|
||||
|
||||
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
|
||||
|
||||
} catch (JSONException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"could not parse app update JSON data", R.string.app_ui_crash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to compare the current and latest available app version.
|
||||
* If a newer version is available, we show the update notification.
|
||||
* @param versionName
|
||||
* @param apkLocationUrl
|
||||
*/
|
||||
private void compareAppVersionAndShowNotification(String versionName,
|
||||
String apkLocationUrl,
|
||||
String versionCode) {
|
||||
|
||||
int NOTIFICATION_ID = 2000;
|
||||
|
||||
if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
|
||||
|
||||
// A pending intent to open the apk location url in the browser.
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
||||
PendingIntent pendingIntent
|
||||
= PendingIntent.getActivity(app, 0, intent, 0);
|
||||
|
||||
NotificationCompat.Builder notificationBuilder = new NotificationCompat
|
||||
.Builder(app, app.getString(R.string.app_update_notification_channel_id))
|
||||
.setSmallIcon(R.drawable.ic_newpipe_update)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
|
||||
.setContentText(app.getString(R.string.app_update_notification_content_text)
|
||||
+ " " + versionName);
|
||||
|
||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app);
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the apk's SHA1 key.
|
||||
* https://stackoverflow.com/questions/9293019/get-certificate-fingerprint-from-android-app#22506133
|
||||
*/
|
||||
private static String getCertificateSHA1Fingerprint() {
|
||||
|
||||
PackageManager pm = app.getPackageManager();
|
||||
String packageName = app.getPackageName();
|
||||
int flags = PackageManager.GET_SIGNATURES;
|
||||
PackageInfo packageInfo = null;
|
||||
|
||||
try {
|
||||
packageInfo = pm.getPackageInfo(packageName, flags);
|
||||
} catch (PackageManager.NameNotFoundException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not find package info", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
Signature[] signatures = packageInfo.signatures;
|
||||
byte[] cert = signatures[0].toByteArray();
|
||||
InputStream input = new ByteArrayInputStream(cert);
|
||||
|
||||
CertificateFactory cf = null;
|
||||
X509Certificate c = null;
|
||||
|
||||
try {
|
||||
cf = CertificateFactory.getInstance("X509");
|
||||
c = (X509Certificate) cf.generateCertificate(input);
|
||||
} catch (CertificateException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Certificate error", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
String hexString = null;
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
byte[] publicKey = md.digest(c.getEncoded());
|
||||
hexString = byte2HexFormatted(publicKey);
|
||||
} catch (NoSuchAlgorithmException ex1) {
|
||||
ErrorActivity.reportError(app, ex1, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
||||
} catch (CertificateEncodingException ex2) {
|
||||
ErrorActivity.reportError(app, ex2, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
return hexString;
|
||||
}
|
||||
|
||||
private static String byte2HexFormatted(byte[] arr) {
|
||||
|
||||
StringBuilder str = new StringBuilder(arr.length * 2);
|
||||
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
String h = Integer.toHexString(arr[i]);
|
||||
int l = h.length();
|
||||
if (l == 1) h = "0" + h;
|
||||
if (l > 2) h = h.substring(l - 2, l);
|
||||
str.append(h.toUpperCase());
|
||||
if (i < (arr.length - 1)) str.append(':');
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public static boolean isGithubApk() {
|
||||
|
||||
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
|
||||
}
|
||||
}
|
@@ -3,18 +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;
|
||||
|
||||
@@ -139,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);
|
||||
}
|
||||
@@ -177,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());
|
||||
}
|
||||
}
|
@@ -457,7 +457,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
stream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
|
||||
location = NewPipeSettings.getVideoDownloadPath(context);// assume that subtitle & video go together
|
||||
location = NewPipeSettings.getVideoDownloadPath(context);// assume that subtitle & video files go together
|
||||
kind = 's';
|
||||
break;
|
||||
default:
|
||||
@@ -477,7 +477,6 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
final String finalFileName = fileName;
|
||||
|
||||
DownloadManagerService.checkForRunningMission(context, location, fileName, (listed, finished) -> {
|
||||
// should be safe run the following code without "getActivity().runOnUiThread()"
|
||||
if (listed) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.download_dialog_title)
|
||||
@@ -511,11 +510,11 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
|
||||
if (secondaryStream != null) {
|
||||
secondaryStreamUrl = secondaryStream.getStream().getUrl();
|
||||
psName = selectedStream.getFormat() == MediaFormat.MPEG_4 ? Postprocessing.ALGORITHM_MP4_DASH_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER;
|
||||
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 weak internet connections
|
||||
// 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;
|
||||
}
|
||||
|
@@ -1,38 +0,0 @@
|
||||
package org.schabi.newpipe.download;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
public class ExtSDDownloadFailedActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.download_to_sdcard_error_title)
|
||||
.setMessage(R.string.download_to_sdcard_error_message)
|
||||
.setPositiveButton(R.string.yes, (DialogInterface dialogInterface, int i) -> {
|
||||
NewPipeSettings.resetDownloadFolders(this);
|
||||
finish();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (DialogInterface dialogInterface, int i) -> {
|
||||
dialogInterface.dismiss();
|
||||
finish();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
package org.schabi.newpipe.fragments.detail;
|
||||
|
||||
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 void notifyDataSetUpdate(){
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@ 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;
|
||||
@@ -220,6 +221,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
}
|
||||
});
|
||||
|
||||
infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() {
|
||||
@Override
|
||||
public void selected(CommentsInfoItem selectedItem) {
|
||||
onItemSelected(selectedItem);
|
||||
}
|
||||
});
|
||||
|
||||
itemsList.clearOnScrollListeners();
|
||||
itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
|
||||
@Override
|
||||
|
@@ -233,10 +233,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
openRssFeed();
|
||||
break;
|
||||
case R.id.menu_item_openInBrowser:
|
||||
openUrlInBrowser(url);
|
||||
openUrlInBrowser(currentInfo.getOriginalUrl());
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
shareUrl(name, url);
|
||||
shareUrl(name, currentInfo.getOriginalUrl());
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -39,6 +39,7 @@ import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
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;
|
||||
@@ -104,8 +105,13 @@ public class SearchFragment
|
||||
// this three represet the current search query
|
||||
@State
|
||||
protected String searchString;
|
||||
|
||||
/**
|
||||
* No content filter should add like contentfilter = all
|
||||
* be aware of this when implementing an extractor.
|
||||
*/
|
||||
@State
|
||||
protected String[] contentFilter;
|
||||
protected String[] contentFilter = new String[0];
|
||||
@State
|
||||
protected String sortFilter;
|
||||
|
||||
@@ -335,7 +341,7 @@ public class SearchFragment
|
||||
|| (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
|
||||
search(!TextUtils.isEmpty(searchString)
|
||||
? searchString
|
||||
: searchEditText.getText().toString(), new String[0], "");
|
||||
: searchEditText.getText().toString(), this.contentFilter, "");
|
||||
} else {
|
||||
if (searchEditText != null) {
|
||||
searchEditText.setText("");
|
||||
@@ -449,6 +455,9 @@ public class SearchFragment
|
||||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||
showSuggestionsPanel();
|
||||
}
|
||||
if(FireTvUtils.isFireTv()){
|
||||
showKeyboardSearch();
|
||||
}
|
||||
});
|
||||
|
||||
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
|
||||
@@ -499,7 +508,9 @@ public class SearchFragment
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
|
||||
}
|
||||
if (event != null
|
||||
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], "");
|
||||
@@ -541,7 +552,7 @@ public class SearchFragment
|
||||
if (searchEditText.requestFocus()) {
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,8 +562,7 @@ public class SearchFragment
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
|
||||
InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
||||
|
||||
searchEditText.clearFocus();
|
||||
}
|
||||
@@ -736,6 +746,7 @@ public class SearchFragment
|
||||
|
||||
@Override
|
||||
protected void loadMoreItems() {
|
||||
if(nextPageUrl == null || nextPageUrl.isEmpty()) return;
|
||||
isLoading.set(true);
|
||||
showListFooter(true);
|
||||
if (searchDisposable != null) searchDisposable.dispose();
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
@@ -50,6 +53,7 @@ public class InfoItemBuilder {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,10 +9,13 @@ 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;
|
||||
@@ -63,6 +66,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
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;
|
||||
@@ -98,6 +103,10 @@ 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;
|
||||
}
|
||||
@@ -223,6 +232,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
|
||||
case PLAYLIST:
|
||||
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;
|
||||
@@ -231,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);
|
||||
@@ -255,6 +267,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
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 new FallbackViewHolder(new View(parent.getContext()));
|
||||
|
@@ -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());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
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;
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ellipsize if not already ellipsized
|
||||
if (null == itemContentView.getEllipsize()) {
|
||||
itemContentView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
itemContentView.setMaxLines(commentDefaultLines);
|
||||
}
|
||||
|
||||
itemContentView.setText(item.getCommentText());
|
||||
if (null != item.getLikeCount()) {
|
||||
itemLikesCountView.setText(String.valueOf(item.getLikeCount()));
|
||||
}
|
||||
itemPublishedTime.setText(item.getPublishedTime());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
toggleEllipsize(item.getCommentText());
|
||||
if (itemBuilder.getOnCommentsSelectedListener() != null) {
|
||||
itemBuilder.getOnCommentsSelectedListener().selected(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleEllipsize(String text) {
|
||||
// toggle ellipsize
|
||||
if (null == itemContentView.getEllipsize()) {
|
||||
itemContentView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
itemContentView.setMaxLines(commentDefaultLines);
|
||||
} else {
|
||||
itemContentView.setEllipsize(null);
|
||||
itemContentView.setMaxLines(commentExpandedLines);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -626,6 +626,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
@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();
|
||||
|
@@ -131,7 +131,7 @@ 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);
|
||||
player.setVolume(DUCK_AUDIO_TO);
|
||||
}
|
||||
|
||||
private void animateAudio(final float from, final float to) {
|
||||
|
@@ -45,7 +45,9 @@ import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MOD
|
||||
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.*;
|
||||
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() {}
|
||||
|
@@ -1,7 +1,9 @@
|
||||
package org.schabi.newpipe.player.playback;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.media.MediaDescriptionCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
|
||||
import org.schabi.newpipe.player.BasePlayer;
|
||||
import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
|
||||
@@ -54,6 +56,12 @@ public class BasePlayerMediaSession implements MediaSessionCallback {
|
||||
.setTitle(item.getTitle())
|
||||
.setSubtitle(item.getUploader());
|
||||
|
||||
// set additional metadata for A2DP/AVRCP
|
||||
Bundle additionalMetadata = new Bundle();
|
||||
additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader());
|
||||
additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration());
|
||||
descriptionBuilder.setExtras(additionalMetadata);
|
||||
|
||||
final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl());
|
||||
if (thumbnailUri != null) descriptionBuilder.setIconUri(thumbnailUri);
|
||||
|
||||
|
@@ -13,6 +13,7 @@ import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
@@ -93,15 +94,17 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
||||
// Below are auxiliary media sources
|
||||
|
||||
// Create subtitle sources
|
||||
for (final SubtitlesStream subtitle : info.getSubtitles()) {
|
||||
final String mimeType = PlayerHelper.subtitleMimeTypesOf(subtitle.getFormat());
|
||||
if (mimeType == null) continue;
|
||||
if(info.getSubtitles() != null) {
|
||||
for (final SubtitlesStream subtitle : info.getSubtitles()) {
|
||||
final String mimeType = PlayerHelper.subtitleMimeTypesOf(subtitle.getFormat());
|
||||
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);
|
||||
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) {
|
||||
|
@@ -15,6 +15,7 @@ public enum UserAction {
|
||||
REQUESTED_CHANNEL("requested channel"),
|
||||
REQUESTED_PLAYLIST("requested playlist"),
|
||||
REQUESTED_KIOSK("requested kiosk"),
|
||||
REQUESTED_COMMENTS("requested comments"),
|
||||
DELETE_FROM_HISTORY("delete from history"),
|
||||
PLAY_STREAM("Play stream");
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import android.os.Bundle;
|
||||
import android.support.v7.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.CheckForNewAppVersionTask;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class MainSettingsFragment extends BasePreferenceFragment {
|
||||
@@ -13,6 +14,13 @@ public class MainSettingsFragment extends BasePreferenceFragment {
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.main_settings);
|
||||
|
||||
if (!CheckForNewAppVersionTask.isGithubApk()) {
|
||||
final Preference update = findPreference(getString(R.string.update_pref_screen_key));
|
||||
getPreferenceScreen().removePreference(update);
|
||||
|
||||
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply();
|
||||
}
|
||||
|
||||
if (!DEBUG) {
|
||||
final Preference debug = findPreference(getString(R.string.debug_pref_screen_key));
|
||||
getPreferenceScreen().removePreference(debug);
|
||||
|
@@ -0,0 +1,33 @@
|
||||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.CheckForNewAppVersionTask;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class UpdateSettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String updateToggleKey = getString(R.string.update_app_key);
|
||||
findPreference(updateToggleKey).setOnPreferenceChangeListener(updatePreferenceChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.update_settings);
|
||||
}
|
||||
|
||||
private Preference.OnPreferenceChangeListener updatePreferenceChange
|
||||
= (preference, newValue) -> {
|
||||
|
||||
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key),
|
||||
(boolean) newValue).apply();
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
@@ -25,6 +25,7 @@ import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.FloatRange;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||
import android.util.Log;
|
||||
@@ -363,4 +364,24 @@ public class AnimationUtils {
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
public static void slideUp(final View view,
|
||||
long duration,
|
||||
long delay,
|
||||
@FloatRange(from = 0.0f, to = 1.0f) float translationPercent) {
|
||||
int translationY = (int) (view.getResources().getDisplayMetrics().heightPixels *
|
||||
(translationPercent));
|
||||
|
||||
view.animate().setListener(null).cancel();
|
||||
view.setAlpha(0f);
|
||||
view.setTranslationY(translationY);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.animate()
|
||||
.alpha(1f)
|
||||
.translationY(0)
|
||||
.setStartDelay(delay)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
@@ -29,10 +29,12 @@ import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.SuggestionExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
@@ -46,6 +48,7 @@ import org.schabi.newpipe.report.UserAction;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.Maybe;
|
||||
@@ -60,7 +63,7 @@ public final class ExtractorHelper {
|
||||
}
|
||||
|
||||
private static void checkServiceId(int serviceId) {
|
||||
if(serviceId == Constants.NO_SERVICE_ID) {
|
||||
if (serviceId == Constants.NO_SERVICE_ID) {
|
||||
throw new IllegalArgumentException("serviceId is NO_SERVICE_ID");
|
||||
}
|
||||
}
|
||||
@@ -95,17 +98,20 @@ public final class ExtractorHelper {
|
||||
public static Single<List<String>> suggestionsFor(final int serviceId,
|
||||
final String query) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(() ->
|
||||
NewPipe.getService(serviceId)
|
||||
.getSuggestionExtractor()
|
||||
.suggestionList(query));
|
||||
return Single.fromCallable(() -> {
|
||||
SuggestionExtractor extractor = NewPipe.getService(serviceId)
|
||||
.getSuggestionExtractor();
|
||||
return extractor != null
|
||||
? extractor.suggestionList(query)
|
||||
: Collections.emptyList();
|
||||
});
|
||||
}
|
||||
|
||||
public static Single<StreamInfo> getStreamInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.STREAM, Single.fromCallable(() ->
|
||||
StreamInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
@@ -113,29 +119,45 @@ public final class ExtractorHelper {
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.CHANNEL, Single.fromCallable(() ->
|
||||
ChannelInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
public static Single<InfoItemsPage> getMoreChannelItems(final int serviceId,
|
||||
final String url,
|
||||
final String nextStreamsUrl) {
|
||||
final String url,
|
||||
final String nextStreamsUrl) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(() ->
|
||||
ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl));
|
||||
}
|
||||
|
||||
public static Single<CommentsInfo> getCommentsInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.COMMENT, Single.fromCallable(() ->
|
||||
CommentsInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
public static Single<InfoItemsPage> getMoreCommentItems(final int serviceId,
|
||||
final CommentsInfo info,
|
||||
final String nextPageUrl) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(() ->
|
||||
CommentsInfo.getMoreItems(NewPipe.getService(serviceId), info, nextPageUrl));
|
||||
}
|
||||
|
||||
public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() ->
|
||||
PlaylistInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
public static Single<InfoItemsPage> getMorePlaylistItems(final int serviceId,
|
||||
final String url,
|
||||
final String nextStreamsUrl) {
|
||||
final String url,
|
||||
final String nextStreamsUrl) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(() ->
|
||||
PlaylistInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl));
|
||||
@@ -144,7 +166,7 @@ public final class ExtractorHelper {
|
||||
public static Single<KioskInfo> getKioskInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() ->
|
||||
KioskInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
@@ -168,16 +190,17 @@ public final class ExtractorHelper {
|
||||
private static <I extends Info> Single<I> checkCache(boolean forceLoad,
|
||||
int serviceId,
|
||||
String url,
|
||||
InfoItem.InfoType infoType,
|
||||
Single<I> loadFromNetwork) {
|
||||
checkServiceId(serviceId);
|
||||
loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info));
|
||||
loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info, infoType));
|
||||
|
||||
Single<I> load;
|
||||
if (forceLoad) {
|
||||
cache.removeInfo(serviceId, url);
|
||||
cache.removeInfo(serviceId, url, infoType);
|
||||
load = loadFromNetwork;
|
||||
} else {
|
||||
load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url),
|
||||
load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url, infoType),
|
||||
loadFromNetwork.toMaybe())
|
||||
.firstElement() //Take the first valid
|
||||
.toSingle();
|
||||
@@ -189,20 +212,20 @@ public final class ExtractorHelper {
|
||||
/**
|
||||
* Default implementation uses the {@link InfoCache} to get cached results
|
||||
*/
|
||||
public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url) {
|
||||
public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url, InfoItem.InfoType infoType) {
|
||||
checkServiceId(serviceId);
|
||||
return Maybe.defer(() -> {
|
||||
//noinspection unchecked
|
||||
I info = (I) cache.getFromKey(serviceId, url);
|
||||
if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info);
|
||||
//noinspection unchecked
|
||||
I info = (I) cache.getFromKey(serviceId, url, infoType);
|
||||
if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info);
|
||||
|
||||
// Only return info if it's not null (it is cached)
|
||||
if (info != null) {
|
||||
return Maybe.just(info);
|
||||
}
|
||||
// Only return info if it's not null (it is cached)
|
||||
if (info != null) {
|
||||
return Maybe.just(info);
|
||||
}
|
||||
|
||||
return Maybe.empty();
|
||||
});
|
||||
return Maybe.empty();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user