mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-09-24 08:40:51 +02:00
Compare commits
221 Commits
v0.11.5
...
v0.12.0-be
Author | SHA1 | Date | |
---|---|---|---|
![]() |
72a9940863 | ||
![]() |
46e088b5f3 | ||
![]() |
a3468b51e2 | ||
![]() |
34f19c4268 | ||
![]() |
1d2c616ce0 | ||
![]() |
99e0f0c3e4 | ||
![]() |
1a92dfb019 | ||
![]() |
cc7f27fb53 | ||
![]() |
e8402008bc | ||
![]() |
c1a302834c | ||
![]() |
762f374f93 | ||
![]() |
e21d2bd511 | ||
![]() |
d936ca6b89 | ||
![]() |
88ac821070 | ||
![]() |
c20837d5f5 | ||
![]() |
81a4c66f92 | ||
![]() |
e4dfb02cb0 | ||
![]() |
0abaab4880 | ||
![]() |
a1aaa52c2a | ||
![]() |
b3a509ad14 | ||
![]() |
ad0f58090f | ||
![]() |
43c4e619c2 | ||
![]() |
427397ba7b | ||
![]() |
b51abf1ea6 | ||
![]() |
76e082159d | ||
![]() |
d36c371c1d | ||
![]() |
a5b2100f8a | ||
![]() |
fe19780f06 | ||
![]() |
83f1d7af82 | ||
![]() |
1916616b07 | ||
![]() |
0a6a684acc | ||
![]() |
6d9aecd500 | ||
![]() |
4d25db2e11 | ||
![]() |
77d5714059 | ||
![]() |
76c59cbdea | ||
![]() |
212f7dfc93 | ||
![]() |
9ba37ce34c | ||
![]() |
6c439bfbc4 | ||
![]() |
352d0db08b | ||
![]() |
be8ce1fce5 | ||
![]() |
f6356e576a | ||
![]() |
83a34a8ba1 | ||
![]() |
fb7a855eda | ||
![]() |
9c1d778623 | ||
![]() |
1bad2a023d | ||
![]() |
999da51e99 | ||
![]() |
ea4b965eeb | ||
![]() |
33160e83cb | ||
![]() |
3e5e7f49cc | ||
![]() |
cc02b01c2b | ||
![]() |
243e5391db | ||
![]() |
230ad5c04f | ||
![]() |
289cfaa407 | ||
![]() |
0798745c16 | ||
![]() |
094695a7ff | ||
![]() |
86f041b803 | ||
![]() |
00e65153f4 | ||
![]() |
b12f0490f3 | ||
![]() |
42a2bc8a9a | ||
![]() |
8adca3725d | ||
![]() |
b57d4b3048 | ||
![]() |
9c7aa241e4 | ||
![]() |
c3ec9b2ad7 | ||
![]() |
195070f8f5 | ||
![]() |
cbfe91f36f | ||
![]() |
738e2ac344 | ||
![]() |
ba0be665ae | ||
![]() |
0ba6f8b39f | ||
![]() |
2773f5fbc8 | ||
![]() |
263a816c3b | ||
![]() |
e7d148336b | ||
![]() |
829059ea01 | ||
![]() |
622d698ff8 | ||
![]() |
f09b04dce0 | ||
![]() |
59f8583895 | ||
![]() |
f506fc0478 | ||
![]() |
880676d670 | ||
![]() |
6485327b97 | ||
![]() |
5773152ed3 | ||
![]() |
e88312659b | ||
![]() |
c4d0ba549f | ||
![]() |
cb41afb11f | ||
![]() |
6d27aea9f2 | ||
![]() |
39e1f9cb76 | ||
![]() |
8fb7d64f79 | ||
![]() |
08fdef4870 | ||
![]() |
aa0196b9d0 | ||
![]() |
a3426f92ac | ||
![]() |
d50d4254c5 | ||
![]() |
50cdadc4a2 | ||
![]() |
5aa9b6cb12 | ||
![]() |
44fc8d80e0 | ||
![]() |
817fa57bfe | ||
![]() |
668e2da01b | ||
![]() |
7f3982d153 | ||
![]() |
f62ae930c7 | ||
![]() |
d0808ce159 | ||
![]() |
7b19dadbf5 | ||
![]() |
43ab0283d9 | ||
![]() |
c27e9d5901 | ||
![]() |
e0d21627bb | ||
![]() |
1b1dd6ef88 | ||
![]() |
10700007d5 | ||
![]() |
c5ec8d04c1 | ||
![]() |
490b250db6 | ||
![]() |
0630423c8e | ||
![]() |
c2e06517e1 | ||
![]() |
b01ae33d1e | ||
![]() |
a55ee32058 | ||
![]() |
c3941d5bec | ||
![]() |
6020dc2b2d | ||
![]() |
7ab41e0c3a | ||
![]() |
c0a75f5b98 | ||
![]() |
efd4db40ef | ||
![]() |
3c3fe7bf83 | ||
![]() |
268762166a | ||
![]() |
53a1833e26 | ||
![]() |
1ff8b5fb9f | ||
![]() |
225b43ca3c | ||
![]() |
75a58d6381 | ||
![]() |
62814f083e | ||
![]() |
6f9deea873 | ||
![]() |
d3160eed9d | ||
![]() |
9b4a07de34 | ||
![]() |
d31eeac49e | ||
![]() |
84c5d27416 | ||
![]() |
17d77aa31f | ||
![]() |
388ec3e3d3 | ||
![]() |
f0829f9ef3 | ||
![]() |
81f481833c | ||
![]() |
a74c4168f3 | ||
![]() |
776dbc34f7 | ||
![]() |
168ac91ab8 | ||
![]() |
9bd26798b6 | ||
![]() |
4ae81a2de4 | ||
![]() |
3c314ced0a | ||
![]() |
ba9d0d7707 | ||
![]() |
38946e4b0f | ||
![]() |
f71242a036 | ||
![]() |
960fd9be38 | ||
![]() |
40844dcd76 | ||
![]() |
420d28c713 | ||
![]() |
5bbd6afaf1 | ||
![]() |
77a06c7604 | ||
![]() |
1f4f87d3bd | ||
![]() |
2e8d86575e | ||
![]() |
ef0659f436 | ||
![]() |
e18e69966f | ||
![]() |
e7b4b88055 | ||
![]() |
de65d1e1fc | ||
![]() |
7ea0862f95 | ||
![]() |
efc7049dfd | ||
![]() |
629549d76f | ||
![]() |
756fb795d6 | ||
![]() |
13d1974a5b | ||
![]() |
d3168a9022 | ||
![]() |
059378eedf | ||
![]() |
e973868a90 | ||
![]() |
2a0e5d6835 | ||
![]() |
5537abe2c3 | ||
![]() |
5eae235b3c | ||
![]() |
bfc7718a21 | ||
![]() |
405d6bee78 | ||
![]() |
f65f2da890 | ||
![]() |
30ab58c33d | ||
![]() |
87ba5a7eb6 | ||
![]() |
6e8593af91 | ||
![]() |
0ab1d3fc40 | ||
![]() |
f22d13e695 | ||
![]() |
cdde61a460 | ||
![]() |
989ce126f1 | ||
![]() |
28618e822e | ||
![]() |
6772381afc | ||
![]() |
75b45beabc | ||
![]() |
56d53e9b01 | ||
![]() |
3a8b04e2d1 | ||
![]() |
1ce7d66fb1 | ||
![]() |
7b5a9b69fe | ||
![]() |
837b22ccac | ||
![]() |
7146719393 | ||
![]() |
71ee604c69 | ||
![]() |
9945a5b813 | ||
![]() |
7254387042 | ||
![]() |
3a7f2a94a6 | ||
![]() |
6bea4aa96b | ||
![]() |
fa262bbceb | ||
![]() |
3139fe0170 | ||
![]() |
ef6c5de65b | ||
![]() |
07799563b5 | ||
![]() |
b1de4b7bd6 | ||
![]() |
2b8ae9a5ea | ||
![]() |
7c52d3ec5d | ||
![]() |
aefaa7619e | ||
![]() |
ca202290bf | ||
![]() |
cbdbc4cba2 | ||
![]() |
8abf904a78 | ||
![]() |
ecf7969c46 | ||
![]() |
a473e3d623 | ||
![]() |
1f8e90858e | ||
![]() |
2c2edca8fa | ||
![]() |
50e86ff1ca | ||
![]() |
02ecc5011a | ||
![]() |
6e666a018b | ||
![]() |
66fbb2ce1e | ||
![]() |
b00722ec0a | ||
![]() |
77b1413319 | ||
![]() |
87b8d60c9d | ||
![]() |
db5203e1ff | ||
![]() |
ea022670c4 | ||
![]() |
54b009cc49 | ||
![]() |
80c3acace9 | ||
![]() |
cea9428b47 | ||
![]() |
f8ffbfabbe | ||
![]() |
836a1e652b | ||
![]() |
d8544e0b84 | ||
![]() |
4817d7fddc | ||
![]() |
e6a385a85e | ||
![]() |
3634f68364 | ||
![]() |
f17ffa94fe | ||
![]() |
d52bcd46a1 | ||
![]() |
7e58b0b6fe |
@@ -8,8 +8,8 @@ android {
|
|||||||
applicationId "org.schabi.newpipe"
|
applicationId "org.schabi.newpipe"
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 27
|
targetSdkVersion 27
|
||||||
versionCode 46
|
versionCode 48
|
||||||
versionName "0.11.5"
|
versionName "0.12.0"
|
||||||
|
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@@ -55,7 +55,7 @@ dependencies {
|
|||||||
exclude module: 'support-annotations'
|
exclude module: 'support-annotations'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:1c97da8b51b3610'
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:7716b1437815'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.mockito:mockito-core:1.10.19'
|
testImplementation 'org.mockito:mockito-core:1.10.19'
|
||||||
@@ -73,7 +73,7 @@ dependencies {
|
|||||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||||
implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1'
|
implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1'
|
||||||
implementation 'com.nononsenseapps:filepicker:3.0.1'
|
implementation 'com.nononsenseapps:filepicker:3.0.1'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer:r2.5.4'
|
implementation 'com.google.android.exoplayer:exoplayer:2.6.0'
|
||||||
|
|
||||||
debugImplementation 'com.facebook.stetho:stetho:1.5.0'
|
debugImplementation 'com.facebook.stetho:stetho:1.5.0'
|
||||||
debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.5.0'
|
debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.5.0'
|
||||||
@@ -89,4 +89,11 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'frankiesardo:icepick:3.2.0'
|
implementation 'frankiesardo:icepick:3.2.0'
|
||||||
annotationProcessor 'frankiesardo:icepick-processor:3.2.0'
|
annotationProcessor 'frankiesardo:icepick-processor:3.2.0'
|
||||||
|
|
||||||
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
|
||||||
|
betaImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
|
||||||
|
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
|
||||||
|
|
||||||
|
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
|
||||||
|
debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
|
||||||
}
|
}
|
||||||
|
7
app/proguard-rules.pro
vendored
7
app/proguard-rules.pro
vendored
@@ -35,3 +35,10 @@
|
|||||||
@icepick.* <fields>;
|
@icepick.* <fields>;
|
||||||
}
|
}
|
||||||
-keepnames class * { @icepick.State *;}
|
-keepnames class * { @icepick.State *;}
|
||||||
|
|
||||||
|
# Rules for OkHttp. Copy paste from https://github.com/square/okhttp
|
||||||
|
-dontwarn okhttp3.**
|
||||||
|
-dontwarn okio.**
|
||||||
|
-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
|
||||||
|
@@ -1,9 +1,26 @@
|
|||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.multidex.MultiDex;
|
import android.support.multidex.MultiDex;
|
||||||
|
|
||||||
import com.facebook.stetho.Stetho;
|
import com.facebook.stetho.Stetho;
|
||||||
|
import com.facebook.stetho.okhttp3.StethoInterceptor;
|
||||||
|
import com.squareup.leakcanary.AndroidHeapDumper;
|
||||||
|
import com.squareup.leakcanary.DefaultLeakDirectoryProvider;
|
||||||
|
import com.squareup.leakcanary.HeapDumper;
|
||||||
|
import com.squareup.leakcanary.LeakCanary;
|
||||||
|
import com.squareup.leakcanary.LeakDirectoryProvider;
|
||||||
|
import com.squareup.leakcanary.RefWatcher;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Downloader;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
public class DebugApp extends App {
|
public class DebugApp extends App {
|
||||||
private static final String TAG = DebugApp.class.toString();
|
private static final String TAG = DebugApp.class.toString();
|
||||||
@@ -17,10 +34,15 @@ public class DebugApp extends App {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
initStetho();
|
initStetho();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Downloader getDownloader() {
|
||||||
|
return org.schabi.newpipe.Downloader.init(new OkHttpClient.Builder()
|
||||||
|
.addNetworkInterceptor(new StethoInterceptor()));
|
||||||
|
}
|
||||||
|
|
||||||
private void initStetho() {
|
private void initStetho() {
|
||||||
// Create an InitializerBuilder
|
// Create an InitializerBuilder
|
||||||
Stetho.InitializerBuilder initializerBuilder =
|
Stetho.InitializerBuilder initializerBuilder =
|
||||||
@@ -42,4 +64,41 @@ public class DebugApp extends App {
|
|||||||
// Initialize Stetho with the Initializer
|
// Initialize Stetho with the Initializer
|
||||||
Stetho.initialize(initializer);
|
Stetho.initialize(initializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isDisposedRxExceptionsReported() {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RefWatcher installLeakCanary() {
|
||||||
|
return LeakCanary.refWatcher(this)
|
||||||
|
.heapDumper(new ToggleableHeapDumper(this))
|
||||||
|
// give each object 10 seconds to be gc'ed, before leak canary gets nosy on it
|
||||||
|
.watchDelay(10, TimeUnit.SECONDS)
|
||||||
|
.buildAndInstall();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ToggleableHeapDumper implements HeapDumper {
|
||||||
|
private final HeapDumper dumper;
|
||||||
|
private final SharedPreferences preferences;
|
||||||
|
private final String dumpingAllowanceKey;
|
||||||
|
|
||||||
|
ToggleableHeapDumper(@NonNull final Context context) {
|
||||||
|
LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
|
||||||
|
this.dumper = new AndroidHeapDumper(context, leakDirectoryProvider);
|
||||||
|
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
this.dumpingAllowanceKey = context.getString(R.string.allow_heap_dumping_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDumpingAllowed() {
|
||||||
|
return preferences.getBoolean(dumpingAllowanceKey, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File dumpHeap() {
|
||||||
|
return isDumpingAllowed() ? dumper.dumpHeap() : HeapDumper.RETRY_LATER;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:logo="@mipmap/ic_launcher"
|
android:logo="@mipmap/ic_launcher"
|
||||||
android:theme="@style/DarkTheme"
|
android:theme="@style/OpeningTheme"
|
||||||
tools:ignore="AllowBackup">
|
tools:ignore="AllowBackup">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".player.old.PlayVideoActivity"
|
android:name=".player.old.PlayVideoActivity"
|
||||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||||
android:theme="@style/VideoPlayerTheme"
|
android:theme="@style/OldVideoPlayerTheme"
|
||||||
tools:ignore="UnusedAttribute"/>
|
tools:ignore="UnusedAttribute"/>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
@@ -56,8 +56,7 @@
|
|||||||
android:name=".player.MainVideoPlayer"
|
android:name=".player.MainVideoPlayer"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"/>
|
||||||
android:theme="@style/PlayerTheme"/>
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".settings.SettingsActivity"
|
android:name=".settings.SettingsActivity"
|
||||||
@@ -123,8 +122,12 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".RouterActivity"
|
android:name=".RouterActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:label="@string/preferred_player_share_menu_title"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@android:style/Theme.NoDisplay">
|
android:theme="@style/RouterActivityThemeDark">
|
||||||
|
|
||||||
|
<!-- Youtube filter -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||||
@@ -173,19 +176,8 @@
|
|||||||
<data android:scheme="vnd.youtube"/>
|
<data android:scheme="vnd.youtube"/>
|
||||||
<data android:scheme="vnd.youtube.launch"/>
|
<data android:scheme="vnd.youtube.launch"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEND"/>
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<!-- Hooktube filter -->
|
||||||
|
|
||||||
<data android:mimeType="text/plain"/>
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name=".RouterPopupActivity"
|
|
||||||
android:label="@string/popup_mode_share_menu_title"
|
|
||||||
android:taskAffinity=""
|
|
||||||
android:theme="@style/PopupPermissionsTheme">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||||
@@ -196,15 +188,18 @@
|
|||||||
|
|
||||||
<data android:scheme="http"/>
|
<data android:scheme="http"/>
|
||||||
<data android:scheme="https"/>
|
<data android:scheme="https"/>
|
||||||
<data android:host="youtube.com"/>
|
<data android:host="hooktube.com"/>
|
||||||
<data android:host="m.youtube.com"/>
|
<data android:host="*.hooktube.com"/>
|
||||||
<data android:host="www.youtube.com"/>
|
|
||||||
<!-- video prefix -->
|
<!-- video prefix -->
|
||||||
<data android:pathPrefix="/v/"/>
|
<data android:pathPrefix="/v/"/>
|
||||||
<data android:pathPrefix="/embed/"/>
|
<data android:pathPrefix="/embed/"/>
|
||||||
<data android:pathPrefix="/watch"/>
|
<data android:pathPrefix="/watch"/>
|
||||||
<data android:pathPrefix="/attribution_link"/>
|
<!-- channel prefix -->
|
||||||
|
<data android:pathPrefix="/channel/"/>
|
||||||
|
<data android:pathPrefix="/user/"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Soundcloud filter -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||||
@@ -215,25 +210,22 @@
|
|||||||
|
|
||||||
<data android:scheme="http"/>
|
<data android:scheme="http"/>
|
||||||
<data android:scheme="https"/>
|
<data android:scheme="https"/>
|
||||||
<data android:host="youtu.be"/>
|
<data android:host="soundcloud.com"/>
|
||||||
|
<data android:host="m.soundcloud.com"/>
|
||||||
|
<data android:host="www.soundcloud.com"/>
|
||||||
<data android:pathPrefix="/"/>
|
<data android:pathPrefix="/"/>
|
||||||
</intent-filter>
|
</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"/>
|
<!-- Share filter -->
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
|
||||||
|
|
||||||
<data android:scheme="vnd.youtube"/>
|
|
||||||
<data android:scheme="vnd.youtube.launch"/>
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND"/>
|
<action android:name="android.intent.action.SEND"/>
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".RouterActivity$FetcherService"
|
||||||
|
android:exported="false"/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
@@ -5,16 +5,21 @@ import android.app.NotificationChannel;
|
|||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
||||||
|
import com.squareup.leakcanary.LeakCanary;
|
||||||
|
import com.squareup.leakcanary.RefWatcher;
|
||||||
|
|
||||||
import org.acra.ACRA;
|
import org.acra.ACRA;
|
||||||
import org.acra.config.ACRAConfiguration;
|
import org.acra.config.ACRAConfiguration;
|
||||||
import org.acra.config.ACRAConfigurationException;
|
import org.acra.config.ACRAConfigurationException;
|
||||||
import org.acra.config.ConfigurationBuilder;
|
import org.acra.config.ConfigurationBuilder;
|
||||||
import org.acra.sender.ReportSenderFactory;
|
import org.acra.sender.ReportSenderFactory;
|
||||||
|
import org.schabi.newpipe.extractor.Downloader;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.report.AcraReportSenderFactory;
|
import org.schabi.newpipe.report.AcraReportSenderFactory;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
@@ -26,9 +31,13 @@ import org.schabi.newpipe.util.StateSaver;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.annotations.NonNull;
|
import io.reactivex.annotations.NonNull;
|
||||||
import io.reactivex.exceptions.CompositeException;
|
import io.reactivex.exceptions.CompositeException;
|
||||||
|
import io.reactivex.exceptions.MissingBackpressureException;
|
||||||
|
import io.reactivex.exceptions.OnErrorNotImplementedException;
|
||||||
import io.reactivex.exceptions.UndeliverableException;
|
import io.reactivex.exceptions.UndeliverableException;
|
||||||
import io.reactivex.functions.Consumer;
|
import io.reactivex.functions.Consumer;
|
||||||
import io.reactivex.plugins.RxJavaPlugins;
|
import io.reactivex.plugins.RxJavaPlugins;
|
||||||
@@ -53,6 +62,7 @@ import io.reactivex.plugins.RxJavaPlugins;
|
|||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application {
|
||||||
protected static final String TAG = App.class.toString();
|
protected static final String TAG = App.class.toString();
|
||||||
|
private RefWatcher refWatcher;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static final Class<? extends ReportSenderFactory>[] reportSenderFactoryClasses = new Class[]{AcraReportSenderFactory.class};
|
private static final Class<? extends ReportSenderFactory>[] reportSenderFactoryClasses = new Class[]{AcraReportSenderFactory.class};
|
||||||
@@ -68,54 +78,98 @@ public class App extends Application {
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
|
if (LeakCanary.isInAnalyzerProcess(this)) {
|
||||||
|
// This process is dedicated to LeakCanary for heap analysis.
|
||||||
|
// You should not init your app in this process.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refWatcher = installLeakCanary();
|
||||||
|
|
||||||
// Initialize settings first because others inits can use its values
|
// Initialize settings first because others inits can use its values
|
||||||
SettingsActivity.initSettings(this);
|
SettingsActivity.initSettings(this);
|
||||||
|
|
||||||
NewPipe.init(Downloader.getInstance());
|
NewPipe.init(getDownloader());
|
||||||
NewPipeDatabase.init(this);
|
NewPipeDatabase.init(this);
|
||||||
StateSaver.init(this);
|
StateSaver.init(this);
|
||||||
initNotificationChannel();
|
initNotificationChannel();
|
||||||
|
|
||||||
// Initialize image loader
|
// Initialize image loader
|
||||||
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this).build();
|
ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50));
|
||||||
ImageLoader.getInstance().init(config);
|
|
||||||
|
|
||||||
configureRxJavaErrorHandler();
|
configureRxJavaErrorHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Downloader getDownloader() {
|
||||||
|
return org.schabi.newpipe.Downloader.init(null);
|
||||||
|
}
|
||||||
|
|
||||||
private void configureRxJavaErrorHandler() {
|
private void configureRxJavaErrorHandler() {
|
||||||
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
|
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
|
||||||
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
|
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
|
||||||
@Override
|
@Override
|
||||||
public void accept(@NonNull Throwable throwable) throws Exception {
|
public void accept(@NonNull Throwable throwable) throws Exception {
|
||||||
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : throwable = [" + throwable.getClass().getName() + "]");
|
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " +
|
||||||
|
"throwable = [" + throwable.getClass().getName() + "]");
|
||||||
|
|
||||||
if (throwable instanceof UndeliverableException) {
|
if (throwable instanceof UndeliverableException) {
|
||||||
// As UndeliverableException is a wrapper, get the cause of it to get the "real" exception
|
// As UndeliverableException is a wrapper, get the cause of it to get the "real" exception
|
||||||
throwable = throwable.getCause();
|
throwable = throwable.getCause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<Throwable> errors;
|
||||||
if (throwable instanceof CompositeException) {
|
if (throwable instanceof CompositeException) {
|
||||||
for (Throwable element : ((CompositeException) throwable).getExceptions()) {
|
errors = ((CompositeException) throwable).getExceptions();
|
||||||
if (checkThrowable(element)) return;
|
} else {
|
||||||
|
errors = Collections.singletonList(throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final Throwable error : errors) {
|
||||||
|
if (isThrowableIgnored(error)) return;
|
||||||
|
if (isThrowableCritical(error)) {
|
||||||
|
reportException(error);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkThrowable(throwable)) return;
|
// Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
|
||||||
|
// When exception is not reported, log it
|
||||||
|
if (isDisposedRxExceptionsReported()) {
|
||||||
|
reportException(throwable);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
|
||||||
|
// Don't crash the application over a simple network problem
|
||||||
|
return ExtractorHelper.hasAssignableCauseThrowable(throwable,
|
||||||
|
IOException.class, SocketException.class, // network api cancellation
|
||||||
|
InterruptedException.class, InterruptedIOException.class); // blocking code disposed
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isThrowableCritical(@NonNull final Throwable throwable) {
|
||||||
|
// Though these exceptions cannot be ignored
|
||||||
|
return ExtractorHelper.hasAssignableCauseThrowable(throwable,
|
||||||
|
NullPointerException.class, IllegalArgumentException.class, // bug in app
|
||||||
|
OnErrorNotImplementedException.class, MissingBackpressureException.class,
|
||||||
|
IllegalStateException.class); // bug in operator
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reportException(@NonNull final Throwable throwable) {
|
||||||
// Throw uncaught exception that will trigger the report system
|
// Throw uncaught exception that will trigger the report system
|
||||||
Thread.currentThread().getUncaughtExceptionHandler()
|
Thread.currentThread().getUncaughtExceptionHandler()
|
||||||
.uncaughtException(Thread.currentThread(), throwable);
|
.uncaughtException(Thread.currentThread(), throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkThrowable(@NonNull Throwable throwable) {
|
|
||||||
// Don't crash the application over a simple network problem
|
|
||||||
return ExtractorHelper.hasAssignableCauseThrowable(throwable,
|
|
||||||
IOException.class, SocketException.class, InterruptedException.class, InterruptedIOException.class);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ImageLoaderConfiguration getImageLoaderConfigurations(final int memoryCacheSizeMb,
|
||||||
|
final int diskCacheSizeMb) {
|
||||||
|
return new ImageLoaderConfiguration.Builder(this)
|
||||||
|
.memoryCache(new LRULimitedMemoryCache(memoryCacheSizeMb * 1024 * 1024))
|
||||||
|
.diskCacheSize(diskCacheSizeMb * 1024 * 1024)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
private void initACRA() {
|
private void initACRA() {
|
||||||
try {
|
try {
|
||||||
@@ -149,4 +203,18 @@ public class App extends Application {
|
|||||||
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
mNotificationManager.createNotificationChannel(mChannel);
|
mNotificationManager.createNotificationChannel(mChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static RefWatcher getRefWatcher(Context context) {
|
||||||
|
final App application = (App) context.getApplicationContext();
|
||||||
|
return application.refWatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RefWatcher installLeakCanary() {
|
||||||
|
return RefWatcher.DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isDisposedRxExceptionsReported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,7 @@ import android.view.View;
|
|||||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
||||||
|
import com.squareup.leakcanary.RefWatcher;
|
||||||
|
|
||||||
import icepick.Icepick;
|
import icepick.Icepick;
|
||||||
|
|
||||||
@@ -67,6 +68,14 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
RefWatcher refWatcher = App.getRefWatcher(getActivity());
|
||||||
|
if (refWatcher != null) refWatcher.watch(this);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@@ -77,17 +86,6 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
protected void initListeners() {
|
protected void initListeners() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Utils
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
protected final int resolveResourceIdFromAttr(@AttrRes int attr) {
|
|
||||||
TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{attr});
|
|
||||||
int attributeResourceId = a.getResourceId(0, 0);
|
|
||||||
a.recycle();
|
|
||||||
return attributeResourceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// DisplayImageOptions default configurations
|
// DisplayImageOptions default configurations
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@@ -1,20 +1,20 @@
|
|||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.util.Collections;
|
||||||
import java.io.InterruptedIOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -38,32 +38,38 @@ import javax.net.ssl.HttpsURLConnection;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||||
|
|
||||||
public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0";
|
public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0";
|
||||||
private static String mCookies = "";
|
|
||||||
|
|
||||||
private static Downloader instance = null;
|
private static Downloader instance;
|
||||||
|
private String mCookies;
|
||||||
|
private OkHttpClient client;
|
||||||
|
|
||||||
private Downloader() {
|
private Downloader(OkHttpClient.Builder builder) {
|
||||||
|
this.client = builder
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
//.cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"), 16 * 1024 * 1024))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It's recommended to call exactly once in the entire lifetime of the application.
|
||||||
|
*
|
||||||
|
* @param builder if null, default builder will be used
|
||||||
|
*/
|
||||||
|
public static Downloader init(@Nullable OkHttpClient.Builder builder) {
|
||||||
|
return instance = new Downloader(builder != null ? builder : new OkHttpClient.Builder());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Downloader getInstance() {
|
public static Downloader getInstance() {
|
||||||
if (instance == null) {
|
|
||||||
synchronized (Downloader.class) {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new Downloader();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized void setCookies(String cookies) {
|
public String getCookies() {
|
||||||
Downloader.mCookies = cookies;
|
return mCookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized String getCookies() {
|
public void setCookies(String cookies) {
|
||||||
return Downloader.mCookies;
|
mCookies = cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,14 +98,32 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String download(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException {
|
public String download(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException {
|
||||||
URL url = new URL(siteUrl);
|
final Request.Builder requestBuilder = new Request.Builder()
|
||||||
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
|
.method("GET", null).url(siteUrl)
|
||||||
Iterator it = customProperties.entrySet().iterator();
|
.addHeader("User-Agent", USER_AGENT);
|
||||||
while (it.hasNext()) {
|
|
||||||
Map.Entry pair = (Map.Entry) it.next();
|
for (Map.Entry<String, String> header : customProperties.entrySet()) {
|
||||||
con.setRequestProperty((String) pair.getKey(), (String) pair.getValue());
|
requestBuilder.addHeader(header.getKey(), header.getValue());
|
||||||
}
|
}
|
||||||
return dl(con);
|
|
||||||
|
if (!TextUtils.isEmpty(mCookies)) {
|
||||||
|
requestBuilder.addHeader("Cookie", mCookies);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Request request = requestBuilder.build();
|
||||||
|
final Response response = client.newCall(request).execute();
|
||||||
|
final ResponseBody body = response.body();
|
||||||
|
|
||||||
|
if (response.code() == 429) {
|
||||||
|
throw new ReCaptchaException("reCaptcha Challenge requested");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body == null) {
|
||||||
|
response.close();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return body.string();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,57 +135,6 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String download(String siteUrl) throws IOException, ReCaptchaException {
|
public String download(String siteUrl) throws IOException, ReCaptchaException {
|
||||||
URL url = new URL(siteUrl);
|
return download(siteUrl, Collections.emptyMap());
|
||||||
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
|
|
||||||
//HttpsURLConnection con = NetCipher.getHttpsURLConnection(url);
|
|
||||||
return dl(con);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common functionality between download(String url) and download(String url, String language)
|
|
||||||
*/
|
|
||||||
private static String dl(HttpsURLConnection con) throws IOException, ReCaptchaException {
|
|
||||||
StringBuilder response = new StringBuilder();
|
|
||||||
BufferedReader in = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
con.setReadTimeout(30 * 1000);// 30s
|
|
||||||
con.setRequestMethod("GET");
|
|
||||||
con.setRequestProperty("User-Agent", USER_AGENT);
|
|
||||||
|
|
||||||
if (getCookies().length() > 0) {
|
|
||||||
con.setRequestProperty("Cookie", getCookies());
|
|
||||||
}
|
|
||||||
|
|
||||||
in = new BufferedReader(new InputStreamReader(con.getInputStream()));
|
|
||||||
|
|
||||||
String inputLine;
|
|
||||||
while ((inputLine = in.readLine()) != null) {
|
|
||||||
response.append(inputLine);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("Downloader", "dl() ----- Exception thrown → " + e.getClass().getName());
|
|
||||||
|
|
||||||
if (ExtractorHelper.isInterruptedCaused(e)) {
|
|
||||||
throw new InterruptedIOException(e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* HTTP 429 == Too Many Request
|
|
||||||
* Receive from Youtube.com = ReCaptcha challenge request
|
|
||||||
* See : https://github.com/rg3/youtube-dl/issues/5138
|
|
||||||
*/
|
|
||||||
if (con.getResponseCode() == 429) {
|
|
||||||
throw new ReCaptchaException("reCaptcha Challenge requested");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IOException(con.getResponseCode() + " " + con.getResponseMessage(), e);
|
|
||||||
} finally {
|
|
||||||
if (in != null) {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ import android.support.annotation.NonNull;
|
|||||||
import org.schabi.newpipe.database.AppDatabase;
|
import org.schabi.newpipe.database.AppDatabase;
|
||||||
|
|
||||||
import static org.schabi.newpipe.database.AppDatabase.DATABASE_NAME;
|
import static org.schabi.newpipe.database.AppDatabase.DATABASE_NAME;
|
||||||
|
import static org.schabi.newpipe.database.Migrations.MIGRATION_11_12;
|
||||||
|
|
||||||
public final class NewPipeDatabase {
|
public final class NewPipeDatabase {
|
||||||
|
|
||||||
@@ -17,15 +18,24 @@ public final class NewPipeDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void init(Context context) {
|
public static void init(Context context) {
|
||||||
databaseInstance = Room.databaseBuilder(context.getApplicationContext(),
|
databaseInstance = Room
|
||||||
AppDatabase.class, DATABASE_NAME
|
.databaseBuilder(context, AppDatabase.class, DATABASE_NAME)
|
||||||
).build();
|
.addMigrations(MIGRATION_11_12)
|
||||||
|
.fallbackToDestructiveMigration()
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@Deprecated
|
||||||
public static AppDatabase getInstance() {
|
public static AppDatabase getInstance() {
|
||||||
if (databaseInstance == null) throw new RuntimeException("Database not initialized");
|
if (databaseInstance == null) throw new RuntimeException("Database not initialized");
|
||||||
|
|
||||||
return databaseInstance;
|
return databaseInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static AppDatabase getInstance(Context context) {
|
||||||
|
if (databaseInstance == null) init(context);
|
||||||
|
return databaseInstance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -107,7 +107,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
// find cookies : s_gl & goojf and Add cookies to Downloader
|
// find cookies : s_gl & goojf and Add cookies to Downloader
|
||||||
if (find_access_cookies(cookies)) {
|
if (find_access_cookies(cookies)) {
|
||||||
// Give cookies to Downloader class
|
// Give cookies to Downloader class
|
||||||
Downloader.setCookies(mCookies);
|
Downloader.getInstance().setCookies(mCookies);
|
||||||
|
|
||||||
// Closing activity and return to parent
|
// Closing activity and return to parent
|
||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,49 +0,0 @@
|
|||||||
package org.schabi.newpipe;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|
||||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
|
||||||
import org.schabi.newpipe.util.Constants;
|
|
||||||
import org.schabi.newpipe.util.PermissionHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the url from the intent and open a popup player
|
|
||||||
*/
|
|
||||||
public class RouterPopupActivity extends RouterActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void handleUrl(String url) {
|
|
||||||
if (!PermissionHelper.isPopupEnabled(this)) {
|
|
||||||
PermissionHelper.showPopupEnablementToast(this);
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
StreamingService service;
|
|
||||||
try {
|
|
||||||
service = NewPipe.getServiceByUrl(url);
|
|
||||||
} catch (ExtractionException e) {
|
|
||||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent callIntent = new Intent(this, PopupVideoPlayer.class);
|
|
||||||
switch (service.getLinkTypeByUrl(url)) {
|
|
||||||
case STREAM:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
callIntent.putExtra(Constants.KEY_URL, url);
|
|
||||||
callIntent.putExtra(Constants.KEY_SERVICE_ID, service.getServiceId());
|
|
||||||
startService(callIntent);
|
|
||||||
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,23 +4,52 @@ import android.arch.persistence.room.Database;
|
|||||||
import android.arch.persistence.room.RoomDatabase;
|
import android.arch.persistence.room.RoomDatabase;
|
||||||
import android.arch.persistence.room.TypeConverters;
|
import android.arch.persistence.room.TypeConverters;
|
||||||
|
|
||||||
import org.schabi.newpipe.database.history.Converters;
|
|
||||||
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
|
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
|
||||||
import org.schabi.newpipe.database.history.dao.WatchHistoryDAO;
|
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO;
|
||||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||||
import org.schabi.newpipe.database.history.model.WatchHistoryEntry;
|
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||||
|
import org.schabi.newpipe.database.playlist.dao.PlaylistDAO;
|
||||||
|
import org.schabi.newpipe.database.playlist.dao.PlaylistRemoteDAO;
|
||||||
|
import org.schabi.newpipe.database.playlist.dao.PlaylistStreamDAO;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||||
|
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
||||||
|
import org.schabi.newpipe.database.stream.dao.StreamStateDAO;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionDAO;
|
import org.schabi.newpipe.database.subscription.SubscriptionDAO;
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.Migrations.DB_VER_12_0;
|
||||||
|
|
||||||
@TypeConverters({Converters.class})
|
@TypeConverters({Converters.class})
|
||||||
@Database(entities = {SubscriptionEntity.class, WatchHistoryEntry.class, SearchHistoryEntry.class}, version = 1, exportSchema = false)
|
@Database(
|
||||||
|
entities = {
|
||||||
|
SubscriptionEntity.class, SearchHistoryEntry.class,
|
||||||
|
StreamEntity.class, StreamHistoryEntity.class, StreamStateEntity.class,
|
||||||
|
PlaylistEntity.class, PlaylistStreamEntity.class, PlaylistRemoteEntity.class
|
||||||
|
},
|
||||||
|
version = DB_VER_12_0,
|
||||||
|
exportSchema = false
|
||||||
|
)
|
||||||
public abstract class AppDatabase extends RoomDatabase {
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
|
|
||||||
public static final String DATABASE_NAME = "newpipe.db";
|
public static final String DATABASE_NAME = "newpipe.db";
|
||||||
|
|
||||||
public abstract SubscriptionDAO subscriptionDAO();
|
public abstract SubscriptionDAO subscriptionDAO();
|
||||||
|
|
||||||
public abstract WatchHistoryDAO watchHistoryDAO();
|
|
||||||
|
|
||||||
public abstract SearchHistoryDAO searchHistoryDAO();
|
public abstract SearchHistoryDAO searchHistoryDAO();
|
||||||
|
|
||||||
|
public abstract StreamDAO streamDAO();
|
||||||
|
|
||||||
|
public abstract StreamHistoryDAO streamHistoryDAO();
|
||||||
|
|
||||||
|
public abstract StreamStateDAO streamStateDAO();
|
||||||
|
|
||||||
|
public abstract PlaylistDAO playlistDAO();
|
||||||
|
|
||||||
|
public abstract PlaylistStreamDAO playlistStreamDAO();
|
||||||
|
|
||||||
|
public abstract PlaylistRemoteDAO playlistRemoteDAO();
|
||||||
}
|
}
|
||||||
|
@@ -23,9 +23,6 @@ public interface BasicDAO<Entity> {
|
|||||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
@Insert(onConflict = OnConflictStrategy.FAIL)
|
||||||
List<Long> insertAll(final Collection<Entity> entities);
|
List<Long> insertAll(final Collection<Entity> entities);
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
long upsert(final Entity entity);
|
|
||||||
|
|
||||||
/* Searches */
|
/* Searches */
|
||||||
Flowable<List<Entity>> getAll();
|
Flowable<List<Entity>> getAll();
|
||||||
|
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
package org.schabi.newpipe.database.history;
|
package org.schabi.newpipe.database;
|
||||||
|
|
||||||
import android.arch.persistence.room.TypeConverter;
|
import android.arch.persistence.room.TypeConverter;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public class Converters {
|
public class Converters {
|
||||||
@@ -25,4 +27,14 @@ public class Converters {
|
|||||||
public static Long dateToTimestamp(Date date) {
|
public static Long dateToTimestamp(Date date) {
|
||||||
return date == null ? null : date.getTime();
|
return date == null ? null : date.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static StreamType streamTypeOf(String value) {
|
||||||
|
return StreamType.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String stringOf(StreamType streamType) {
|
||||||
|
return streamType.name();
|
||||||
|
}
|
||||||
}
|
}
|
13
app/src/main/java/org/schabi/newpipe/database/LocalItem.java
Normal file
13
app/src/main/java/org/schabi/newpipe/database/LocalItem.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package org.schabi.newpipe.database;
|
||||||
|
|
||||||
|
public interface LocalItem {
|
||||||
|
enum LocalItemType {
|
||||||
|
PLAYLIST_LOCAL_ITEM,
|
||||||
|
PLAYLIST_REMOTE_ITEM,
|
||||||
|
|
||||||
|
PLAYLIST_STREAM_ITEM,
|
||||||
|
STATISTIC_STREAM_ITEM,
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalItemType getLocalItemType();
|
||||||
|
}
|
@@ -0,0 +1,61 @@
|
|||||||
|
package org.schabi.newpipe.database;
|
||||||
|
|
||||||
|
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||||
|
import android.arch.persistence.room.migration.Migration;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
public class Migrations {
|
||||||
|
|
||||||
|
public static final int DB_VER_11_0 = 1;
|
||||||
|
public static final int DB_VER_12_0 = 2;
|
||||||
|
|
||||||
|
public static final Migration MIGRATION_11_12 = new Migration(DB_VER_11_0, DB_VER_12_0) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||||
|
/*
|
||||||
|
* Unfortunately these queries must be hardcoded due to the possibility of
|
||||||
|
* schema and names changing at a later date, thus invalidating the older migration
|
||||||
|
* scripts if they are not hardcoded.
|
||||||
|
* */
|
||||||
|
|
||||||
|
// Not much we can do about this, since room doesn't create tables before migration.
|
||||||
|
// It's either this or blasting the entire database anew.
|
||||||
|
database.execSQL("CREATE INDEX `index_search_history_search` ON `search_history` (`search`)");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `streams` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `title` TEXT, `stream_type` TEXT, `duration` INTEGER, `uploader` TEXT, `thumbnail_url` TEXT)");
|
||||||
|
database.execSQL("CREATE UNIQUE INDEX `index_streams_service_id_url` ON `streams` (`service_id`, `url`)");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_history` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )");
|
||||||
|
database.execSQL("CREATE INDEX `index_stream_history_stream_id` ON `stream_history` (`stream_id`)");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_state` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `playlists` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `thumbnail_url` TEXT)");
|
||||||
|
database.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `playlist_stream_join` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
||||||
|
database.execSQL("CREATE UNIQUE INDEX `index_playlist_stream_join_playlist_id_join_index` ON `playlist_stream_join` (`playlist_id`, `join_index`)");
|
||||||
|
database.execSQL("CREATE INDEX `index_playlist_stream_join_stream_id` ON `playlist_stream_join` (`stream_id`)");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `remote_playlists` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)");
|
||||||
|
database.execSQL("CREATE INDEX `index_remote_playlists_name` ON `remote_playlists` (`name`)");
|
||||||
|
database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` ON `remote_playlists` (`service_id`, `url`)");
|
||||||
|
|
||||||
|
// Populate streams table with existing entries in watch history
|
||||||
|
// Latest data first, thus ignoring older entries with the same indices
|
||||||
|
database.execSQL("INSERT OR IGNORE INTO streams (service_id, url, title, " +
|
||||||
|
"stream_type, duration, uploader, thumbnail_url) " +
|
||||||
|
|
||||||
|
"SELECT service_id, url, title, 'VIDEO_STREAM', duration, " +
|
||||||
|
"uploader, thumbnail_url " +
|
||||||
|
|
||||||
|
"FROM watch_history " +
|
||||||
|
"ORDER BY creation_date DESC");
|
||||||
|
|
||||||
|
// Once the streams have PKs, join them with the normalized history table
|
||||||
|
// and populate it with the remaining data from watch history
|
||||||
|
database.execSQL("INSERT INTO stream_history (stream_id, access_date, repeat_count)" +
|
||||||
|
"SELECT uid, creation_date, 1 " +
|
||||||
|
"FROM watch_history INNER JOIN streams " +
|
||||||
|
"ON watch_history.service_id == streams.service_id " +
|
||||||
|
"AND watch_history.url == streams.url " +
|
||||||
|
"ORDER BY creation_date DESC");
|
||||||
|
|
||||||
|
database.execSQL("DROP TABLE IF EXISTS watch_history");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@@ -2,7 +2,9 @@ package org.schabi.newpipe.database.history.dao;
|
|||||||
|
|
||||||
import android.arch.persistence.room.Dao;
|
import android.arch.persistence.room.Dao;
|
||||||
import android.arch.persistence.room.Query;
|
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 org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -20,8 +22,9 @@ public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
|
|||||||
|
|
||||||
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
|
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
|
||||||
|
|
||||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
|
@Query("SELECT * FROM " + TABLE_NAME +
|
||||||
@Override
|
" WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
|
||||||
|
@Nullable
|
||||||
SearchHistoryEntry getLatestEntry();
|
SearchHistoryEntry getLatestEntry();
|
||||||
|
|
||||||
@Query("DELETE FROM " + TABLE_NAME)
|
@Query("DELETE FROM " + TABLE_NAME)
|
||||||
|
@@ -0,0 +1,68 @@
|
|||||||
|
package org.schabi.newpipe.database.history.dao;
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.Flowable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_REPEAT_COUNT;
|
||||||
|
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LATEST_DATE;
|
||||||
|
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
||||||
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
||||||
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity> {
|
||||||
|
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE +
|
||||||
|
" WHERE " + STREAM_ACCESS_DATE + " = " +
|
||||||
|
"(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")")
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public abstract StreamHistoryEntity getLatestEntry();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE)
|
||||||
|
public abstract Flowable<List<StreamHistoryEntity>> getAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("DELETE FROM " + STREAM_HISTORY_TABLE)
|
||||||
|
public abstract int deleteAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flowable<List<StreamHistoryEntity>> listByService(int serviceId) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT * FROM " + STREAM_TABLE +
|
||||||
|
" INNER JOIN " + STREAM_HISTORY_TABLE +
|
||||||
|
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID +
|
||||||
|
" ORDER BY " + STREAM_ACCESS_DATE + " DESC")
|
||||||
|
public abstract Flowable<List<StreamHistoryEntry>> getHistory();
|
||||||
|
|
||||||
|
@Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
|
||||||
|
public abstract int deleteStreamHistory(final long streamId);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM " + STREAM_TABLE +
|
||||||
|
|
||||||
|
// Select the latest entry and watch count for each stream id on history table
|
||||||
|
" INNER JOIN " +
|
||||||
|
"(SELECT " + JOIN_STREAM_ID + ", " +
|
||||||
|
" MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", " +
|
||||||
|
" SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT +
|
||||||
|
" FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")" +
|
||||||
|
|
||||||
|
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID)
|
||||||
|
public abstract Flowable<List<StreamStatisticsEntry>> getStatistics();
|
||||||
|
}
|
@@ -1,37 +0,0 @@
|
|||||||
package org.schabi.newpipe.database.history.dao;
|
|
||||||
|
|
||||||
import android.arch.persistence.room.Dao;
|
|
||||||
import android.arch.persistence.room.Query;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.database.history.model.WatchHistoryEntry;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import io.reactivex.Flowable;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.database.history.model.WatchHistoryEntry.CREATION_DATE;
|
|
||||||
import static org.schabi.newpipe.database.history.model.WatchHistoryEntry.ID;
|
|
||||||
import static org.schabi.newpipe.database.history.model.WatchHistoryEntry.SERVICE_ID;
|
|
||||||
import static org.schabi.newpipe.database.history.model.WatchHistoryEntry.TABLE_NAME;
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
public interface WatchHistoryDAO extends HistoryDAO<WatchHistoryEntry> {
|
|
||||||
|
|
||||||
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
|
|
||||||
|
|
||||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
|
|
||||||
@Override
|
|
||||||
WatchHistoryEntry getLatestEntry();
|
|
||||||
|
|
||||||
@Query("DELETE FROM " + TABLE_NAME)
|
|
||||||
@Override
|
|
||||||
int deleteAll();
|
|
||||||
|
|
||||||
@Query("SELECT * FROM " + TABLE_NAME + ORDER_BY_CREATION_DATE)
|
|
||||||
@Override
|
|
||||||
Flowable<List<WatchHistoryEntry>> getAll();
|
|
||||||
|
|
||||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE)
|
|
||||||
@Override
|
|
||||||
Flowable<List<WatchHistoryEntry>> listByService(int serviceId);
|
|
||||||
}
|
|
@@ -1,60 +0,0 @@
|
|||||||
package org.schabi.newpipe.database.history.model;
|
|
||||||
|
|
||||||
import android.arch.persistence.room.ColumnInfo;
|
|
||||||
import android.arch.persistence.room.Entity;
|
|
||||||
import android.arch.persistence.room.Ignore;
|
|
||||||
import android.arch.persistence.room.PrimaryKey;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
public abstract class HistoryEntry {
|
|
||||||
|
|
||||||
public static final String ID = "id";
|
|
||||||
public static final String SERVICE_ID = "service_id";
|
|
||||||
public static final String CREATION_DATE = "creation_date";
|
|
||||||
|
|
||||||
@ColumnInfo(name = CREATION_DATE)
|
|
||||||
private Date creationDate;
|
|
||||||
|
|
||||||
@ColumnInfo(name = SERVICE_ID)
|
|
||||||
private int serviceId;
|
|
||||||
|
|
||||||
@ColumnInfo(name = ID)
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
private long id;
|
|
||||||
|
|
||||||
public HistoryEntry(Date creationDate, int serviceId) {
|
|
||||||
this.serviceId = serviceId;
|
|
||||||
this.creationDate = creationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getCreationDate() {
|
|
||||||
return creationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreationDate(Date creationDate) {
|
|
||||||
this.creationDate = creationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getServiceId() {
|
|
||||||
return serviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setServiceId(int serviceId) {
|
|
||||||
this.serviceId = serviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
public boolean hasEqualValues(HistoryEntry otherEntry) {
|
|
||||||
return otherEntry != null && getServiceId() == otherEntry.getServiceId();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,23 +3,66 @@ package org.schabi.newpipe.database.history.model;
|
|||||||
import android.arch.persistence.room.ColumnInfo;
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
import android.arch.persistence.room.Entity;
|
import android.arch.persistence.room.Entity;
|
||||||
import android.arch.persistence.room.Ignore;
|
import android.arch.persistence.room.Ignore;
|
||||||
|
import android.arch.persistence.room.Index;
|
||||||
|
import android.arch.persistence.room.PrimaryKey;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
@Entity(tableName = SearchHistoryEntry.TABLE_NAME)
|
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARCH;
|
||||||
public class SearchHistoryEntry extends HistoryEntry {
|
|
||||||
|
|
||||||
|
@Entity(tableName = SearchHistoryEntry.TABLE_NAME,
|
||||||
|
indices = {@Index(value = SEARCH)})
|
||||||
|
public class SearchHistoryEntry {
|
||||||
|
|
||||||
|
public static final String ID = "id";
|
||||||
public static final String TABLE_NAME = "search_history";
|
public static final String TABLE_NAME = "search_history";
|
||||||
|
public static final String SERVICE_ID = "service_id";
|
||||||
|
public static final String CREATION_DATE = "creation_date";
|
||||||
public static final String SEARCH = "search";
|
public static final String SEARCH = "search";
|
||||||
|
|
||||||
|
@ColumnInfo(name = ID)
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@ColumnInfo(name = CREATION_DATE)
|
||||||
|
private Date creationDate;
|
||||||
|
|
||||||
|
@ColumnInfo(name = SERVICE_ID)
|
||||||
|
private int serviceId;
|
||||||
|
|
||||||
@ColumnInfo(name = SEARCH)
|
@ColumnInfo(name = SEARCH)
|
||||||
private String search;
|
private String search;
|
||||||
|
|
||||||
public SearchHistoryEntry(Date creationDate, int serviceId, String search) {
|
public SearchHistoryEntry(Date creationDate, int serviceId, String search) {
|
||||||
super(creationDate, serviceId);
|
this.serviceId = serviceId;
|
||||||
|
this.creationDate = creationDate;
|
||||||
this.search = search;
|
this.search = search;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreationDate() {
|
||||||
|
return creationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreationDate(Date creationDate) {
|
||||||
|
this.creationDate = creationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getServiceId() {
|
||||||
|
return serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServiceId(int serviceId) {
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
public String getSearch() {
|
public String getSearch() {
|
||||||
return search;
|
return search;
|
||||||
}
|
}
|
||||||
@@ -29,9 +72,8 @@ public class SearchHistoryEntry extends HistoryEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
@Override
|
public boolean hasEqualValues(SearchHistoryEntry otherEntry) {
|
||||||
public boolean hasEqualValues(HistoryEntry otherEntry) {
|
return getServiceId() == otherEntry.getServiceId() &&
|
||||||
return otherEntry instanceof SearchHistoryEntry && super.hasEqualValues(otherEntry)
|
getSearch().equals(otherEntry.getSearch());
|
||||||
&& getSearch().equals(((SearchHistoryEntry) otherEntry).getSearch());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,79 @@
|
|||||||
|
package org.schabi.newpipe.database.history.model;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
import android.arch.persistence.room.Entity;
|
||||||
|
import android.arch.persistence.room.ForeignKey;
|
||||||
|
import android.arch.persistence.room.Ignore;
|
||||||
|
import android.arch.persistence.room.Index;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||||
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||||
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
||||||
|
|
||||||
|
@Entity(tableName = STREAM_HISTORY_TABLE,
|
||||||
|
primaryKeys = {JOIN_STREAM_ID, STREAM_ACCESS_DATE},
|
||||||
|
// No need to index for timestamp as they will almost always be unique
|
||||||
|
indices = {@Index(value = {JOIN_STREAM_ID})},
|
||||||
|
foreignKeys = {
|
||||||
|
@ForeignKey(entity = StreamEntity.class,
|
||||||
|
parentColumns = StreamEntity.STREAM_ID,
|
||||||
|
childColumns = JOIN_STREAM_ID,
|
||||||
|
onDelete = CASCADE, onUpdate = CASCADE)
|
||||||
|
})
|
||||||
|
public class StreamHistoryEntity {
|
||||||
|
final public static String STREAM_HISTORY_TABLE = "stream_history";
|
||||||
|
final public static String JOIN_STREAM_ID = "stream_id";
|
||||||
|
final public static String STREAM_ACCESS_DATE = "access_date";
|
||||||
|
final public static String STREAM_REPEAT_COUNT = "repeat_count";
|
||||||
|
|
||||||
|
@ColumnInfo(name = JOIN_STREAM_ID)
|
||||||
|
private long streamUid;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@ColumnInfo(name = STREAM_ACCESS_DATE)
|
||||||
|
private Date accessDate;
|
||||||
|
|
||||||
|
@ColumnInfo(name = STREAM_REPEAT_COUNT)
|
||||||
|
private long repeatCount;
|
||||||
|
|
||||||
|
public StreamHistoryEntity(long streamUid, @NonNull Date accessDate, long repeatCount) {
|
||||||
|
this.streamUid = streamUid;
|
||||||
|
this.accessDate = accessDate;
|
||||||
|
this.repeatCount = repeatCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public StreamHistoryEntity(long streamUid, @NonNull Date accessDate) {
|
||||||
|
this(streamUid, accessDate, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStreamUid() {
|
||||||
|
return streamUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStreamUid(long streamUid) {
|
||||||
|
this.streamUid = streamUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getAccessDate() {
|
||||||
|
return accessDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccessDate(@NonNull Date accessDate) {
|
||||||
|
this.accessDate = accessDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRepeatCount() {
|
||||||
|
return repeatCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRepeatCount(long repeatCount) {
|
||||||
|
this.repeatCount = repeatCount;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
package org.schabi.newpipe.database.history.model;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class StreamHistoryEntry {
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_ID)
|
||||||
|
final public long uid;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_SERVICE_ID)
|
||||||
|
final public int serviceId;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_URL)
|
||||||
|
final public String url;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_TITLE)
|
||||||
|
final public String title;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
|
||||||
|
final public StreamType streamType;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
|
||||||
|
final public long duration;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_UPLOADER)
|
||||||
|
final public String uploader;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_THUMBNAIL_URL)
|
||||||
|
final public String thumbnailUrl;
|
||||||
|
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||||
|
final public long streamId;
|
||||||
|
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
|
||||||
|
final public Date accessDate;
|
||||||
|
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
|
||||||
|
final public long repeatCount;
|
||||||
|
|
||||||
|
public StreamHistoryEntry(long uid, int serviceId, String url, String title,
|
||||||
|
StreamType streamType, long duration, String uploader,
|
||||||
|
String thumbnailUrl, long streamId, Date accessDate,
|
||||||
|
long repeatCount) {
|
||||||
|
this.uid = uid;
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
this.url = url;
|
||||||
|
this.title = title;
|
||||||
|
this.streamType = streamType;
|
||||||
|
this.duration = duration;
|
||||||
|
this.uploader = uploader;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
this.streamId = streamId;
|
||||||
|
this.accessDate = accessDate;
|
||||||
|
this.repeatCount = repeatCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StreamHistoryEntity toStreamHistoryEntity() {
|
||||||
|
return new StreamHistoryEntity(streamId, accessDate, repeatCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasEqualValues(StreamHistoryEntry other) {
|
||||||
|
return this.uid == other.uid && streamId == other.streamId &&
|
||||||
|
accessDate.compareTo(other.accessDate) == 0;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,109 +0,0 @@
|
|||||||
package org.schabi.newpipe.database.history.model;
|
|
||||||
|
|
||||||
import android.arch.persistence.room.ColumnInfo;
|
|
||||||
import android.arch.persistence.room.Entity;
|
|
||||||
import android.arch.persistence.room.Ignore;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
@Entity(tableName = WatchHistoryEntry.TABLE_NAME)
|
|
||||||
public class WatchHistoryEntry extends HistoryEntry {
|
|
||||||
|
|
||||||
public static final String TABLE_NAME = "watch_history";
|
|
||||||
public static final String TITLE = "title";
|
|
||||||
public static final String URL = "url";
|
|
||||||
public static final String STREAM_ID = "stream_id";
|
|
||||||
public static final String THUMBNAIL_URL = "thumbnail_url";
|
|
||||||
public static final String UPLOADER = "uploader";
|
|
||||||
public static final String DURATION = "duration";
|
|
||||||
|
|
||||||
@ColumnInfo(name = TITLE)
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@ColumnInfo(name = URL)
|
|
||||||
private String url;
|
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_ID)
|
|
||||||
private String streamId;
|
|
||||||
|
|
||||||
@ColumnInfo(name = THUMBNAIL_URL)
|
|
||||||
private String thumbnailURL;
|
|
||||||
|
|
||||||
@ColumnInfo(name = UPLOADER)
|
|
||||||
private String uploader;
|
|
||||||
|
|
||||||
@ColumnInfo(name = DURATION)
|
|
||||||
private long duration;
|
|
||||||
|
|
||||||
public WatchHistoryEntry(Date creationDate, int serviceId, String title, String url, String streamId, String thumbnailURL, String uploader, long duration) {
|
|
||||||
super(creationDate, serviceId);
|
|
||||||
this.title = title;
|
|
||||||
this.url = url;
|
|
||||||
this.streamId = streamId;
|
|
||||||
this.thumbnailURL = thumbnailURL;
|
|
||||||
this.uploader = uploader;
|
|
||||||
this.duration = duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WatchHistoryEntry(StreamInfo streamInfo) {
|
|
||||||
this(new Date(), streamInfo.getServiceId(), streamInfo.getName(), streamInfo.getUrl(),
|
|
||||||
streamInfo.id, streamInfo.thumbnail_url, streamInfo.uploader_name, streamInfo.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUrl(String url) {
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(String title) {
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStreamId() {
|
|
||||||
return streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStreamId(String streamId) {
|
|
||||||
this.streamId = streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getThumbnailURL() {
|
|
||||||
return thumbnailURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setThumbnailURL(String thumbnailURL) {
|
|
||||||
this.thumbnailURL = thumbnailURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUploader() {
|
|
||||||
return uploader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUploader(String uploader) {
|
|
||||||
this.uploader = uploader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getDuration() {
|
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDuration(int duration) {
|
|
||||||
this.duration = duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Override
|
|
||||||
public boolean hasEqualValues(HistoryEntry otherEntry) {
|
|
||||||
return otherEntry instanceof WatchHistoryEntry && super.hasEqualValues(otherEntry)
|
|
||||||
&& getUrl().equals(((WatchHistoryEntry) otherEntry).getUrl());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,7 @@
|
|||||||
|
package org.schabi.newpipe.database.playlist;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.LocalItem;
|
||||||
|
|
||||||
|
public interface PlaylistLocalItem extends LocalItem {
|
||||||
|
String getOrderingName();
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
package org.schabi.newpipe.database.playlist;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
|
||||||
|
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_THUMBNAIL_URL;
|
||||||
|
|
||||||
|
public class PlaylistMetadataEntry implements PlaylistLocalItem {
|
||||||
|
final public static String PLAYLIST_STREAM_COUNT = "streamCount";
|
||||||
|
|
||||||
|
@ColumnInfo(name = PLAYLIST_ID)
|
||||||
|
final public long uid;
|
||||||
|
@ColumnInfo(name = PLAYLIST_NAME)
|
||||||
|
final public String name;
|
||||||
|
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
|
||||||
|
final public String thumbnailUrl;
|
||||||
|
@ColumnInfo(name = PLAYLIST_STREAM_COUNT)
|
||||||
|
final public long streamCount;
|
||||||
|
|
||||||
|
public PlaylistMetadataEntry(long uid, String name, String thumbnailUrl, long streamCount) {
|
||||||
|
this.uid = uid;
|
||||||
|
this.name = name;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
this.streamCount = streamCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalItemType getLocalItemType() {
|
||||||
|
return LocalItemType.PLAYLIST_LOCAL_ITEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOrderingName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,60 @@
|
|||||||
|
package org.schabi.newpipe.database.playlist;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.LocalItem;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
|
||||||
|
public class PlaylistStreamEntry implements LocalItem {
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_ID)
|
||||||
|
final public long uid;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_SERVICE_ID)
|
||||||
|
final public int serviceId;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_URL)
|
||||||
|
final public String url;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_TITLE)
|
||||||
|
final public String title;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
|
||||||
|
final public StreamType streamType;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
|
||||||
|
final public long duration;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_UPLOADER)
|
||||||
|
final public String uploader;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_THUMBNAIL_URL)
|
||||||
|
final public String thumbnailUrl;
|
||||||
|
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
|
||||||
|
final public long streamId;
|
||||||
|
@ColumnInfo(name = PlaylistStreamEntity.JOIN_INDEX)
|
||||||
|
final public int joinIndex;
|
||||||
|
|
||||||
|
public PlaylistStreamEntry(long uid, int serviceId, String url, String title,
|
||||||
|
StreamType streamType, long duration, String uploader,
|
||||||
|
String thumbnailUrl, long streamId, int joinIndex) {
|
||||||
|
this.uid = uid;
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
this.url = url;
|
||||||
|
this.title = title;
|
||||||
|
this.streamType = streamType;
|
||||||
|
this.duration = duration;
|
||||||
|
this.uploader = uploader;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
this.streamId = streamId;
|
||||||
|
this.joinIndex = joinIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StreamInfoItem toStreamInfoItem() throws IllegalArgumentException {
|
||||||
|
StreamInfoItem item = new StreamInfoItem(serviceId, url, title, streamType);
|
||||||
|
item.setThumbnailUrl(thumbnailUrl);
|
||||||
|
item.setUploaderName(uploader);
|
||||||
|
item.setDuration(duration);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalItemType getLocalItemType() {
|
||||||
|
return LocalItemType.PLAYLIST_STREAM_ITEM;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
public abstract class PlaylistDAO implements BasicDAO<PlaylistEntity> {
|
||||||
|
@Override
|
||||||
|
@Query("SELECT * FROM " + PLAYLIST_TABLE)
|
||||||
|
public abstract Flowable<List<PlaylistEntity>> getAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("DELETE FROM " + PLAYLIST_TABLE)
|
||||||
|
public abstract int deleteAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flowable<List<PlaylistEntity>> listByService(int serviceId) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
|
||||||
|
public abstract Flowable<List<PlaylistEntity>> getPlaylist(final long playlistId);
|
||||||
|
|
||||||
|
@Query("DELETE FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
|
||||||
|
public abstract int deletePlaylist(final long playlistId);
|
||||||
|
}
|
@@ -0,0 +1,60 @@
|
|||||||
|
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.PlaylistRemoteEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.Flowable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_ID;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_SERVICE_ID;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_TABLE;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public abstract class PlaylistRemoteDAO implements BasicDAO<PlaylistRemoteEntity> {
|
||||||
|
@Override
|
||||||
|
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE)
|
||||||
|
public abstract Flowable<List<PlaylistRemoteEntity>> getAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE)
|
||||||
|
public abstract int deleteAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE +
|
||||||
|
" WHERE " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
|
||||||
|
public abstract Flowable<List<PlaylistRemoteEntity>> listByService(int serviceId);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " +
|
||||||
|
REMOTE_PLAYLIST_URL + " = :url AND " +
|
||||||
|
REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
|
||||||
|
public abstract Flowable<List<PlaylistRemoteEntity>> getPlaylist(long serviceId, String url);
|
||||||
|
|
||||||
|
@Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE +
|
||||||
|
" WHERE " +
|
||||||
|
REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
|
||||||
|
abstract Long getPlaylistIdInternal(long serviceId, String url);
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
public long upsert(PlaylistRemoteEntity playlist) {
|
||||||
|
final Long playlistId = getPlaylistIdInternal(playlist.getServiceId(), playlist.getUrl());
|
||||||
|
|
||||||
|
if (playlistId == null) {
|
||||||
|
return insert(playlist);
|
||||||
|
} else {
|
||||||
|
playlist.setUid(playlistId);
|
||||||
|
update(playlist);
|
||||||
|
return playlistId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE +
|
||||||
|
" WHERE " + REMOTE_PLAYLIST_ID + " = :playlistId")
|
||||||
|
public abstract int deletePlaylist(final long playlistId);
|
||||||
|
}
|
@@ -0,0 +1,69 @@
|
|||||||
|
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.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;
|
||||||
|
|
||||||
|
import io.reactivex.Flowable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.*;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.*;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.*;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity> {
|
||||||
|
@Override
|
||||||
|
@Query("SELECT * FROM " + PLAYLIST_STREAM_JOIN_TABLE)
|
||||||
|
public abstract Flowable<List<PlaylistStreamEntity>> getAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE)
|
||||||
|
public abstract int deleteAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flowable<List<PlaylistStreamEntity>> listByService(int serviceId) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE +
|
||||||
|
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
||||||
|
public abstract void deleteBatch(final long playlistId);
|
||||||
|
|
||||||
|
@Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)" +
|
||||||
|
" FROM " + PLAYLIST_STREAM_JOIN_TABLE +
|
||||||
|
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
||||||
|
public abstract Flowable<Integer> getMaximumIndexOf(final long playlistId);
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN " +
|
||||||
|
// get ids of streams of the given playlist
|
||||||
|
"(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX +
|
||||||
|
" FROM " + PLAYLIST_STREAM_JOIN_TABLE +
|
||||||
|
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)" +
|
||||||
|
|
||||||
|
// then merge with the stream metadata
|
||||||
|
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID +
|
||||||
|
" ORDER BY " + JOIN_INDEX + " ASC")
|
||||||
|
public abstract Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " +
|
||||||
|
PLAYLIST_THUMBNAIL_URL + ", " +
|
||||||
|
"COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT +
|
||||||
|
|
||||||
|
" FROM " + PLAYLIST_TABLE +
|
||||||
|
" LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE +
|
||||||
|
" ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID +
|
||||||
|
" GROUP BY " + JOIN_PLAYLIST_ID +
|
||||||
|
" ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
|
||||||
|
public abstract Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
|
||||||
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
package org.schabi.newpipe.database.playlist.model;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Entity(tableName = PLAYLIST_TABLE,
|
||||||
|
indices = {@Index(value = {PLAYLIST_NAME})})
|
||||||
|
public class PlaylistEntity {
|
||||||
|
final public static String PLAYLIST_TABLE = "playlists";
|
||||||
|
final public static String PLAYLIST_ID = "uid";
|
||||||
|
final public static String PLAYLIST_NAME = "name";
|
||||||
|
final public static String PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ColumnInfo(name = PLAYLIST_ID)
|
||||||
|
private long uid = 0;
|
||||||
|
|
||||||
|
@ColumnInfo(name = PLAYLIST_NAME)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
|
||||||
|
private String thumbnailUrl;
|
||||||
|
|
||||||
|
public PlaylistEntity(String name, String thumbnailUrl) {
|
||||||
|
this.name = name;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUid() {
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUid(long uid) {
|
||||||
|
this.uid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getThumbnailUrl() {
|
||||||
|
return thumbnailUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThumbnailUrl(String thumbnailUrl) {
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user