mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-23 08:30:44 +00:00
Merge remote-tracking branch 'upstream/dev' into unsupported-url-dialog
This commit is contained in:
commit
eeba9c0a5f
1
.github/CONTRIBUTING.md
vendored
1
.github/CONTRIBUTING.md
vendored
@ -33,6 +33,7 @@ with your GitHub account.
|
|||||||
|
|
||||||
## Code contribution
|
## Code contribution
|
||||||
|
|
||||||
|
* If you want to add a feature or change one, please open an issue describing your change. This gives the team and community a chance to give feedback before you spend any time on something that could be done differently or not done at all. It also prevents two contributors from working on the same thing and one being disappointed when only one user's code can be added.
|
||||||
* Stick to NewPipe's style conventions: follow [checkStyle](https://github.com/checkstyle/checkstyle). It will run each time you build the project.
|
* Stick to NewPipe's style conventions: follow [checkStyle](https://github.com/checkstyle/checkstyle). It will run each time you build the project.
|
||||||
* Do not bring non-free software (e.g. binary blobs) into the project. Also, make sure you do not introduce Google
|
* Do not bring non-free software (e.g. binary blobs) into the project. Also, make sure you do not introduce Google
|
||||||
libraries.
|
libraries.
|
||||||
|
@ -13,8 +13,8 @@ android {
|
|||||||
resValue "string", "app_name", "NewPipe"
|
resValue "string", "app_name", "NewPipe"
|
||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 950
|
versionCode 953
|
||||||
versionName "0.19.5"
|
versionName "0.19.8"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@ -50,7 +50,7 @@ android {
|
|||||||
// TODO: update Gradle version
|
// TODO: update Gradle version
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources false // disabled to fix F-Droid's reproducible build
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
archivesBaseName = 'app'
|
archivesBaseName = 'app'
|
||||||
}
|
}
|
||||||
@ -84,11 +84,17 @@ ext {
|
|||||||
checkstyleVersion = '8.32'
|
checkstyleVersion = '8.32'
|
||||||
stethoVersion = '1.5.1'
|
stethoVersion = '1.5.1'
|
||||||
leakCanaryVersion = '2.2'
|
leakCanaryVersion = '2.2'
|
||||||
exoPlayerVersion = '2.11.6'
|
exoPlayerVersion = '2.11.8'
|
||||||
androidxLifecycleVersion = '2.2.0'
|
androidxLifecycleVersion = '2.2.0'
|
||||||
androidxRoomVersion = '2.2.5'
|
androidxRoomVersion = '2.2.5'
|
||||||
groupieVersion = '2.8.0'
|
groupieVersion = '2.8.0'
|
||||||
markwonVersion = '4.3.1'
|
markwonVersion = '4.3.1'
|
||||||
|
googleAutoServiceVersion = '1.0-rc7'
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
checkstyle
|
||||||
|
ktlint
|
||||||
}
|
}
|
||||||
|
|
||||||
checkstyle {
|
checkstyle {
|
||||||
@ -106,8 +112,7 @@ task runCheckstyle(type: Checkstyle) {
|
|||||||
exclude '**/BuildConfig.java'
|
exclude '**/BuildConfig.java'
|
||||||
exclude 'main/java/us/shandian/giga/**'
|
exclude 'main/java/us/shandian/giga/**'
|
||||||
|
|
||||||
// empty classpath
|
classpath = configurations.checkstyle
|
||||||
classpath = files()
|
|
||||||
|
|
||||||
showViolations true
|
showViolations true
|
||||||
|
|
||||||
@ -117,10 +122,6 @@ task runCheckstyle(type: Checkstyle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
|
||||||
ktlint
|
|
||||||
}
|
|
||||||
|
|
||||||
task runKtlint(type: JavaExec) {
|
task runKtlint(type: JavaExec) {
|
||||||
main = "com.pinterest.ktlint.Main"
|
main = "com.pinterest.ktlint.Main"
|
||||||
classpath = configurations.ktlint
|
classpath = configurations.ktlint
|
||||||
@ -138,12 +139,12 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
|
|
||||||
implementation "frankiesardo:icepick:${icepickVersion}"
|
implementation "frankiesardo:icepick:${icepickVersion}"
|
||||||
kapt "frankiesardo:icepick-processor:${icepickVersion}"
|
kapt "frankiesardo:icepick-processor:${icepickVersion}"
|
||||||
|
|
||||||
debugImplementation "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||||
ktlint "com.pinterest:ktlint:0.35.0"
|
ktlint "com.pinterest:ktlint:0.35.0"
|
||||||
|
|
||||||
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
|
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
|
||||||
@ -163,18 +164,21 @@ dependencies {
|
|||||||
exclude module: 'support-annotations'
|
exclude module: 'support-annotations'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:bda83fe6a5b9a8a0751669fbc444fa49d72d0d2f'
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:6633f26ec5a73a8e932de575b7a0643b6ad6c890'
|
||||||
|
|
||||||
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
||||||
implementation "org.jsoup:jsoup:1.13.1"
|
implementation "org.jsoup:jsoup:1.13.1"
|
||||||
|
|
||||||
implementation "com.squareup.okhttp3:okhttp:3.12.11"
|
implementation "com.squareup.okhttp3:okhttp:3.12.12"
|
||||||
|
|
||||||
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
|
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
|
||||||
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
|
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
|
||||||
|
|
||||||
implementation "com.google.android.material:material:1.1.0"
|
implementation "com.google.android.material:material:1.1.0"
|
||||||
|
|
||||||
|
compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}"
|
||||||
|
kapt "com.google.auto.service:auto-service:${googleAutoServiceVersion}"
|
||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||||
implementation "androidx.preference:preference:1.1.1"
|
implementation "androidx.preference:preference:1.1.1"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||||
@ -183,7 +187,6 @@ dependencies {
|
|||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
|
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
|
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:${androidxLifecycleVersion}"
|
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
||||||
implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
|
implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
|
||||||
|
@ -21,13 +21,13 @@ public class ErrorInfoTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void errorInfoTestParcelable() {
|
public void errorInfoTestParcelable() {
|
||||||
ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request",
|
final ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request",
|
||||||
R.string.general_error);
|
R.string.general_error);
|
||||||
// Obtain a Parcel object and write the parcelable object to it:
|
// Obtain a Parcel object and write the parcelable object to it:
|
||||||
Parcel parcel = Parcel.obtain();
|
final Parcel parcel = Parcel.obtain();
|
||||||
info.writeToParcel(parcel, 0);
|
info.writeToParcel(parcel, 0);
|
||||||
parcel.setDataPosition(0);
|
parcel.setDataPosition(0);
|
||||||
ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
|
final ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
|
||||||
|
|
||||||
assertEquals(UserAction.USER_REPORT, infoFromParcel.userAction);
|
assertEquals(UserAction.USER_REPORT, infoFromParcel.userAction);
|
||||||
assertEquals("youtube", infoFromParcel.serviceName);
|
assertEquals("youtube", infoFromParcel.serviceName);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.schabi.newpipe
|
package org.schabi.newpipe
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.facebook.stetho.Stetho
|
import com.facebook.stetho.Stetho
|
||||||
@ -11,11 +10,6 @@ import okhttp3.OkHttpClient
|
|||||||
import org.schabi.newpipe.extractor.downloader.Downloader
|
import org.schabi.newpipe.extractor.downloader.Downloader
|
||||||
|
|
||||||
class DebugApp : App() {
|
class DebugApp : App() {
|
||||||
override fun attachBaseContext(base: Context) {
|
|
||||||
super.attachBaseContext(base)
|
|
||||||
MultiDex.install(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
initStetho()
|
initStetho()
|
||||||
@ -34,6 +28,12 @@ class DebugApp : App() {
|
|||||||
return downloader
|
return downloader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun initACRA() {
|
||||||
|
// install MultiDex before initializing ACRA
|
||||||
|
MultiDex.install(this)
|
||||||
|
super.initACRA()
|
||||||
|
}
|
||||||
|
|
||||||
private fun initStetho() {
|
private fun initStetho() {
|
||||||
// Create an InitializerBuilder
|
// Create an InitializerBuilder
|
||||||
val initializerBuilder = Stetho.newInitializerBuilder(this)
|
val initializerBuilder = Stetho.newInitializerBuilder(this)
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
android:logo="@mipmap/ic_launcher"
|
android:logo="@mipmap/ic_launcher"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:theme="@style/OpeningTheme"
|
android:theme="@style/OpeningTheme"
|
||||||
|
android:resizeableActivity="true"
|
||||||
tools:ignore="AllowBackup">
|
tools:ignore="AllowBackup">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@ -43,8 +44,8 @@
|
|||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".player.BackgroundPlayer"
|
android:name=".player.MainPlayer"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@ -52,25 +53,9 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".player.BackgroundPlayerActivity"
|
android:name=".player.BackgroundPlayerActivity"
|
||||||
android:label="@string/title_activity_background_player"
|
android:label="@string/title_activity_play_queue"
|
||||||
android:launchMode="singleTask" />
|
android:launchMode="singleTask" />
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".player.PopupVideoPlayerActivity"
|
|
||||||
android:label="@string/title_activity_popup_player"
|
|
||||||
android:launchMode="singleTask" />
|
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".player.PopupVideoPlayer"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".player.MainVideoPlayer"
|
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:theme="@style/VideoPlayerTheme" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".settings.SettingsActivity"
|
android:name=".settings.SettingsActivity"
|
||||||
android:label="@string/settings" />
|
android:label="@string/settings" />
|
||||||
@ -242,17 +227,19 @@
|
|||||||
<data android:host="dev.invidio.us" />
|
<data android:host="dev.invidio.us" />
|
||||||
<data android:host="www.invidio.us" />
|
<data android:host="www.invidio.us" />
|
||||||
<data android:host="invidious.snopyta.org" />
|
<data android:host="invidious.snopyta.org" />
|
||||||
<data android:host="de.invidious.snopyta.org" />
|
|
||||||
<data android:host="fi.invidious.snopyta.org" />
|
<data android:host="fi.invidious.snopyta.org" />
|
||||||
<data android:host="vid.wxzm.sx" />
|
<data android:host="yewtu.be" />
|
||||||
<data android:host="invidious.kabi.tk" />
|
<data android:host="invidious.ggc-project.de" />
|
||||||
<data android:host="invidiou.sh" />
|
<data android:host="yt.maisputain.ovh" />
|
||||||
<data android:host="www.invidiou.sh" />
|
|
||||||
<data android:host="no.invidiou.sh" />
|
|
||||||
<data android:host="invidious.enkirton.net" />
|
|
||||||
<data android:host="tube.poal.co" />
|
|
||||||
<data android:host="invidious.13ad.de" />
|
<data android:host="invidious.13ad.de" />
|
||||||
<data android:host="yt.elukerio.org" />
|
<data android:host="invidious.toot.koeln" />
|
||||||
|
<data android:host="invidious.fdn.fr" />
|
||||||
|
<data android:host="watch.nettohikari.com" />
|
||||||
|
<data android:host="invidious.snwmds.net" />
|
||||||
|
<data android:host="invidious.snwmds.org" />
|
||||||
|
<data android:host="invidious.snwmds.com" />
|
||||||
|
<data android:host="invidious.sunsetravens.com" />
|
||||||
|
<data android:host="invidious.gachirangers.com" />
|
||||||
<data android:pathPrefix="/" />
|
<data android:pathPrefix="/" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
@ -319,6 +306,7 @@
|
|||||||
<data android:host="peertube.fr" />
|
<data android:host="peertube.fr" />
|
||||||
<data android:host="peertube.live" />
|
<data android:host="peertube.live" />
|
||||||
<data android:host="peertube.video" />
|
<data android:host="peertube.video" />
|
||||||
|
<data android:host="tube.privacytools.io" />
|
||||||
<data android:host="video.ploud.fr" />
|
<data android:host="video.ploud.fr" />
|
||||||
<data android:host="video.lqdn.fr" />
|
<data android:host="video.lqdn.fr" />
|
||||||
<data android:host="skeptikon.fr" />
|
<data android:host="skeptikon.fr" />
|
||||||
@ -331,5 +319,11 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".RouterActivity$FetcherService"
|
android:name=".RouterActivity$FetcherService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<!-- see https://github.com/TeamNewPipe/NewPipe/issues/3947 -->
|
||||||
|
<!-- Version < 3.0. DeX Mode and Screen Mirroring support -->
|
||||||
|
<meta-data android:name="com.samsung.android.keepalive.density" android:value="true"/>
|
||||||
|
<!-- Version >= 3.0. DeX Dual Mode support -->
|
||||||
|
<meta-data android:name="com.samsung.android.multidisplay.keep_process_alive" android:value="true"/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -150,7 +150,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
// from its saved state, where the fragment manager has already
|
// from its saved state, where the fragment manager has already
|
||||||
// taken care of restoring the fragments we previously had instantiated.
|
// taken care of restoring the fragments we previously had instantiated.
|
||||||
if (mFragments.size() > position) {
|
if (mFragments.size() > position) {
|
||||||
Fragment f = mFragments.get(position);
|
final Fragment f = mFragments.get(position);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
@ -160,12 +160,12 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
mCurTransaction = mFragmentManager.beginTransaction();
|
mCurTransaction = mFragmentManager.beginTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
Fragment fragment = getItem(position);
|
final Fragment fragment = getItem(position);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
|
Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
|
||||||
}
|
}
|
||||||
if (mSavedState.size() > position) {
|
if (mSavedState.size() > position) {
|
||||||
Fragment.SavedState fss = mSavedState.get(position);
|
final Fragment.SavedState fss = mSavedState.get(position);
|
||||||
if (fss != null) {
|
if (fss != null) {
|
||||||
fragment.setInitialSavedState(fss);
|
fragment.setInitialSavedState(fss);
|
||||||
}
|
}
|
||||||
@ -191,7 +191,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
@Override
|
@Override
|
||||||
public void destroyItem(@NonNull final ViewGroup container, final int position,
|
public void destroyItem(@NonNull final ViewGroup container, final int position,
|
||||||
@NonNull final Object object) {
|
@NonNull final Object object) {
|
||||||
Fragment fragment = (Fragment) object;
|
final Fragment fragment = (Fragment) object;
|
||||||
|
|
||||||
if (mCurTransaction == null) {
|
if (mCurTransaction == null) {
|
||||||
mCurTransaction = mFragmentManager.beginTransaction();
|
mCurTransaction = mFragmentManager.beginTransaction();
|
||||||
@ -217,7 +217,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
||||||
public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
|
public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
|
||||||
@NonNull final Object object) {
|
@NonNull final Object object) {
|
||||||
Fragment fragment = (Fragment) object;
|
final Fragment fragment = (Fragment) object;
|
||||||
if (fragment != mCurrentPrimaryItem) {
|
if (fragment != mCurrentPrimaryItem) {
|
||||||
if (mCurrentPrimaryItem != null) {
|
if (mCurrentPrimaryItem != null) {
|
||||||
mCurrentPrimaryItem.setMenuVisibility(false);
|
mCurrentPrimaryItem.setMenuVisibility(false);
|
||||||
@ -267,17 +267,17 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
Bundle state = null;
|
Bundle state = null;
|
||||||
if (mSavedState.size() > 0) {
|
if (mSavedState.size() > 0) {
|
||||||
state = new Bundle();
|
state = new Bundle();
|
||||||
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
|
final Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
|
||||||
mSavedState.toArray(fss);
|
mSavedState.toArray(fss);
|
||||||
state.putParcelableArray("states", fss);
|
state.putParcelableArray("states", fss);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < mFragments.size(); i++) {
|
for (int i = 0; i < mFragments.size(); i++) {
|
||||||
Fragment f = mFragments.get(i);
|
final Fragment f = mFragments.get(i);
|
||||||
if (f != null && f.isAdded()) {
|
if (f != null && f.isAdded()) {
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
state = new Bundle();
|
state = new Bundle();
|
||||||
}
|
}
|
||||||
String key = "f" + i;
|
final String key = "f" + i;
|
||||||
mFragmentManager.putFragment(state, key, f);
|
mFragmentManager.putFragment(state, key, f);
|
||||||
|
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
@ -294,21 +294,21 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
@Override
|
@Override
|
||||||
public void restoreState(@Nullable final Parcelable state, @Nullable final ClassLoader loader) {
|
public void restoreState(@Nullable final Parcelable state, @Nullable final ClassLoader loader) {
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
Bundle bundle = (Bundle) state;
|
final Bundle bundle = (Bundle) state;
|
||||||
bundle.setClassLoader(loader);
|
bundle.setClassLoader(loader);
|
||||||
Parcelable[] fss = bundle.getParcelableArray("states");
|
final Parcelable[] fss = bundle.getParcelableArray("states");
|
||||||
mSavedState.clear();
|
mSavedState.clear();
|
||||||
mFragments.clear();
|
mFragments.clear();
|
||||||
if (fss != null) {
|
if (fss != null) {
|
||||||
for (int i = 0; i < fss.length; i++) {
|
for (final Parcelable parcelable : fss) {
|
||||||
mSavedState.add((Fragment.SavedState) fss[i]);
|
mSavedState.add((Fragment.SavedState) parcelable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Iterable<String> keys = bundle.keySet();
|
final Iterable<String> keys = bundle.keySet();
|
||||||
for (String key: keys) {
|
for (final String key : keys) {
|
||||||
if (key.startsWith("f")) {
|
if (key.startsWith("f")) {
|
||||||
int index = Integer.parseInt(key.substring(1));
|
final int index = Integer.parseInt(key.substring(1));
|
||||||
Fragment f = mFragmentManager.getFragment(bundle, key);
|
final Fragment f = mFragmentManager.getFragment(bundle, key);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
while (mFragments.size() <= index) {
|
while (mFragments.size() <= index) {
|
||||||
mFragments.add(null);
|
mFragments.add(null);
|
||||||
|
@ -4,11 +4,14 @@ import android.content.Context;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.OverScroller;
|
import android.widget.OverScroller;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
@ -20,23 +23,25 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean allowScroll = true;
|
||||||
|
private final Rect globalRect = new Rect();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onRequestChildRectangleOnScreen(
|
public boolean onRequestChildRectangleOnScreen(
|
||||||
@NonNull final CoordinatorLayout coordinatorLayout, @NonNull final AppBarLayout child,
|
@NonNull final CoordinatorLayout coordinatorLayout, @NonNull final AppBarLayout child,
|
||||||
@NonNull final Rect rectangle, final boolean immediate) {
|
@NonNull final Rect rectangle, final boolean immediate) {
|
||||||
|
|
||||||
focusScrollRect.set(rectangle);
|
focusScrollRect.set(rectangle);
|
||||||
|
|
||||||
coordinatorLayout.offsetDescendantRectToMyCoords(child, focusScrollRect);
|
coordinatorLayout.offsetDescendantRectToMyCoords(child, focusScrollRect);
|
||||||
|
|
||||||
int height = coordinatorLayout.getHeight();
|
final int height = coordinatorLayout.getHeight();
|
||||||
|
|
||||||
if (focusScrollRect.top <= 0 && focusScrollRect.bottom >= height) {
|
if (focusScrollRect.top <= 0 && focusScrollRect.bottom >= height) {
|
||||||
// the child is too big to fit inside ourselves completely, ignore request
|
// the child is too big to fit inside ourselves completely, ignore request
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int dy;
|
final int dy;
|
||||||
|
|
||||||
if (focusScrollRect.bottom > height) {
|
if (focusScrollRect.bottom > height) {
|
||||||
dy = focusScrollRect.top;
|
dy = focusScrollRect.top;
|
||||||
@ -48,13 +53,30 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0);
|
final int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0);
|
||||||
|
|
||||||
return consumed == dy;
|
return consumed == dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child,
|
public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child,
|
||||||
final MotionEvent ev) {
|
final MotionEvent ev) {
|
||||||
|
final ViewGroup playQueue = child.findViewById(R.id.playQueuePanel);
|
||||||
|
if (playQueue != null) {
|
||||||
|
final boolean visible = playQueue.getGlobalVisibleRect(globalRect);
|
||||||
|
if (visible && globalRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
|
||||||
|
allowScroll = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final View seekBar = child.findViewById(R.id.playbackSeekBar);
|
||||||
|
if (seekBar != null) {
|
||||||
|
final boolean visible = seekBar.getGlobalVisibleRect(globalRect);
|
||||||
|
if (visible && globalRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
|
||||||
|
allowScroll = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allowScroll = true;
|
||||||
switch (ev.getActionMasked()) {
|
switch (ev.getActionMasked()) {
|
||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
// remove reference to old nested scrolling child
|
// remove reference to old nested scrolling child
|
||||||
@ -68,17 +90,37 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||||||
return super.onInterceptTouchEvent(parent, child, ev);
|
return super.onInterceptTouchEvent(parent, child, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onStartNestedScroll(@NonNull final CoordinatorLayout parent,
|
||||||
|
@NonNull final AppBarLayout child,
|
||||||
|
@NonNull final View directTargetChild,
|
||||||
|
final View target,
|
||||||
|
final int nestedScrollAxes,
|
||||||
|
final int type) {
|
||||||
|
return allowScroll && super.onStartNestedScroll(
|
||||||
|
parent, child, directTargetChild, target, nestedScrollAxes, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onNestedFling(@NonNull final CoordinatorLayout coordinatorLayout,
|
||||||
|
@NonNull final AppBarLayout child,
|
||||||
|
@NonNull final View target, final float velocityX,
|
||||||
|
final float velocityY, final boolean consumed) {
|
||||||
|
return allowScroll && super.onNestedFling(
|
||||||
|
coordinatorLayout, child, target, velocityX, velocityY, consumed);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private OverScroller getScrollerField() {
|
private OverScroller getScrollerField() {
|
||||||
try {
|
try {
|
||||||
Class<?> headerBehaviorType = this.getClass()
|
final Class<?> headerBehaviorType = this.getClass()
|
||||||
.getSuperclass().getSuperclass().getSuperclass();
|
.getSuperclass().getSuperclass().getSuperclass();
|
||||||
if (headerBehaviorType != null) {
|
if (headerBehaviorType != null) {
|
||||||
Field field = headerBehaviorType.getDeclaredField("scroller");
|
final Field field = headerBehaviorType.getDeclaredField("scroller");
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
return ((OverScroller) field.get(this));
|
return ((OverScroller) field.get(this));
|
||||||
}
|
}
|
||||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
} catch (final NoSuchFieldException | IllegalAccessException e) {
|
||||||
// ?
|
// ?
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -87,34 +129,35 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private Field getLastNestedScrollingChildRefField() {
|
private Field getLastNestedScrollingChildRefField() {
|
||||||
try {
|
try {
|
||||||
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass();
|
final Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass();
|
||||||
if (headerBehaviorType != null) {
|
if (headerBehaviorType != null) {
|
||||||
Field field = headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef");
|
final Field field
|
||||||
|
= headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef");
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
} catch (NoSuchFieldException e) {
|
} catch (final NoSuchFieldException e) {
|
||||||
// ?
|
// ?
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetNestedScrollingChild() {
|
private void resetNestedScrollingChild() {
|
||||||
Field field = getLastNestedScrollingChildRefField();
|
final Field field = getLastNestedScrollingChildRefField();
|
||||||
if (field != null) {
|
if (field != null) {
|
||||||
try {
|
try {
|
||||||
Object value = field.get(this);
|
final Object value = field.get(this);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
field.set(this, null);
|
field.set(this, null);
|
||||||
}
|
}
|
||||||
} catch (IllegalAccessException e) {
|
} catch (final IllegalAccessException e) {
|
||||||
// ?
|
// ?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopAppBarLayoutFling() {
|
private void stopAppBarLayoutFling() {
|
||||||
OverScroller scroller = getScrollerField();
|
final OverScroller scroller = getScrollerField();
|
||||||
if (scroller != null) {
|
if (scroller != null) {
|
||||||
scroller.forceFinished(true);
|
scroller.forceFinished(true);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import android.content.SharedPreferences;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
|
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
|
||||||
@ -19,10 +20,8 @@ import org.acra.ACRA;
|
|||||||
import org.acra.config.ACRAConfigurationException;
|
import org.acra.config.ACRAConfigurationException;
|
||||||
import org.acra.config.CoreConfiguration;
|
import org.acra.config.CoreConfiguration;
|
||||||
import org.acra.config.CoreConfigurationBuilder;
|
import org.acra.config.CoreConfigurationBuilder;
|
||||||
import org.acra.sender.ReportSenderFactory;
|
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
import org.schabi.newpipe.report.AcraReportSenderFactory;
|
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
import org.schabi.newpipe.settings.SettingsActivity;
|
import org.schabi.newpipe.settings.SettingsActivity;
|
||||||
@ -37,7 +36,6 @@ import java.net.SocketException;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.annotations.NonNull;
|
|
||||||
import io.reactivex.exceptions.CompositeException;
|
import io.reactivex.exceptions.CompositeException;
|
||||||
import io.reactivex.exceptions.MissingBackpressureException;
|
import io.reactivex.exceptions.MissingBackpressureException;
|
||||||
import io.reactivex.exceptions.OnErrorNotImplementedException;
|
import io.reactivex.exceptions.OnErrorNotImplementedException;
|
||||||
@ -65,9 +63,6 @@ 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();
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static final Class<? extends ReportSenderFactory>[]
|
|
||||||
REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
|
|
||||||
private static App app;
|
private static App app;
|
||||||
|
|
||||||
public static App getApp() {
|
public static App getApp() {
|
||||||
@ -77,7 +72,6 @@ public class App extends Application {
|
|||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(final Context base) {
|
protected void attachBaseContext(final Context base) {
|
||||||
super.attachBaseContext(base);
|
super.attachBaseContext(base);
|
||||||
|
|
||||||
initACRA();
|
initACRA();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +104,7 @@ public class App extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Downloader getDownloader() {
|
protected Downloader getDownloader() {
|
||||||
DownloaderImpl downloader = DownloaderImpl.init(null);
|
final DownloaderImpl downloader = DownloaderImpl.init(null);
|
||||||
setCookiesToDownloader(downloader);
|
setCookiesToDownloader(downloader);
|
||||||
return downloader;
|
return downloader;
|
||||||
}
|
}
|
||||||
@ -200,14 +194,21 @@ public class App extends Application {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initACRA() {
|
/**
|
||||||
|
* Called in {@link #attachBaseContext(Context)} after calling the {@code super} method.
|
||||||
|
* Should be overridden if MultiDex is enabled, since it has to be initialized before ACRA.
|
||||||
|
*/
|
||||||
|
protected void initACRA() {
|
||||||
|
if (ACRA.isACRASenderServiceProcess()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
|
final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
|
||||||
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
|
|
||||||
.setBuildConfigClass(BuildConfig.class)
|
.setBuildConfigClass(BuildConfig.class)
|
||||||
.build();
|
.build();
|
||||||
ACRA.init(this, acraConfig);
|
ACRA.init(this, acraConfig);
|
||||||
} catch (ACRAConfigurationException ace) {
|
} catch (final ACRAConfigurationException ace) {
|
||||||
ace.printStackTrace();
|
ace.printStackTrace();
|
||||||
ErrorActivity.reportError(this,
|
ErrorActivity.reportError(this,
|
||||||
ace,
|
ace,
|
||||||
@ -219,7 +220,7 @@ public class App extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void initNotificationChannel() {
|
public void initNotificationChannel() {
|
||||||
if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,10 +231,10 @@ public class App extends Application {
|
|||||||
// Keep this below DEFAULT to avoid making noise on every notification update
|
// Keep this below DEFAULT to avoid making noise on every notification update
|
||||||
final int importance = NotificationManager.IMPORTANCE_LOW;
|
final int importance = NotificationManager.IMPORTANCE_LOW;
|
||||||
|
|
||||||
NotificationChannel mChannel = new NotificationChannel(id, name, importance);
|
final NotificationChannel mChannel = new NotificationChannel(id, name, importance);
|
||||||
mChannel.setDescription(description);
|
mChannel.setDescription(description);
|
||||||
|
|
||||||
NotificationManager mNotificationManager =
|
final NotificationManager mNotificationManager =
|
||||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
mNotificationManager.createNotificationChannel(mChannel);
|
mNotificationManager.createNotificationChannel(mChannel);
|
||||||
|
|
||||||
@ -254,11 +255,11 @@ public class App extends Application {
|
|||||||
final String appUpdateDescription
|
final String appUpdateDescription
|
||||||
= getString(R.string.app_update_notification_channel_description);
|
= getString(R.string.app_update_notification_channel_description);
|
||||||
|
|
||||||
NotificationChannel appUpdateChannel
|
final NotificationChannel appUpdateChannel
|
||||||
= new NotificationChannel(appUpdateId, appUpdateName, importance);
|
= new NotificationChannel(appUpdateId, appUpdateName, importance);
|
||||||
appUpdateChannel.setDescription(appUpdateDescription);
|
appUpdateChannel.setDescription(appUpdateDescription);
|
||||||
|
|
||||||
NotificationManager appUpdateNotificationManager
|
final NotificationManager appUpdateNotificationManager
|
||||||
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import android.content.pm.Signature;
|
|||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
@ -62,7 +62,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
packageInfo = pm.getPackageInfo(packageName, flags);
|
packageInfo = pm.getPackageInfo(packageName, flags);
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (final PackageManager.NameNotFoundException e) {
|
||||||
ErrorActivity.reportError(APP, e, null, null,
|
ErrorActivity.reportError(APP, e, null, null,
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||||
"Could not find package info", R.string.app_ui_crash));
|
"Could not find package info", R.string.app_ui_crash));
|
||||||
@ -77,7 +77,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
|||||||
try {
|
try {
|
||||||
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
||||||
c = (X509Certificate) cf.generateCertificate(input);
|
c = (X509Certificate) cf.generateCertificate(input);
|
||||||
} catch (CertificateException e) {
|
} catch (final CertificateException e) {
|
||||||
ErrorActivity.reportError(APP, e, null, null,
|
ErrorActivity.reportError(APP, e, null, null,
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||||
"Certificate error", R.string.app_ui_crash));
|
"Certificate error", R.string.app_ui_crash));
|
||||||
@ -86,7 +86,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
|||||||
String hexString = null;
|
String hexString = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
final MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||||
final byte[] publicKey = md.digest(c.getEncoded());
|
final byte[] publicKey = md.digest(c.getEncoded());
|
||||||
hexString = byte2HexFormatted(publicKey);
|
hexString = byte2HexFormatted(publicKey);
|
||||||
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
||||||
@ -167,7 +167,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
|||||||
|
|
||||||
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
|
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
|
||||||
|
|
||||||
} catch (JsonParserException e) {
|
} catch (final JsonParserException e) {
|
||||||
// connectivity problems, do not alarm user and fail silently
|
// connectivity problems, do not alarm user and fail silently
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.w(TAG, Log.getStackTraceString(e));
|
Log.w(TAG, Log.getStackTraceString(e));
|
||||||
@ -187,7 +187,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
|||||||
private void compareAppVersionAndShowNotification(final String versionName,
|
private void compareAppVersionAndShowNotification(final String versionName,
|
||||||
final String apkLocationUrl,
|
final String apkLocationUrl,
|
||||||
final int versionCode) {
|
final int versionCode) {
|
||||||
int notificationId = 2000;
|
final int notificationId = 2000;
|
||||||
|
|
||||||
if (BuildConfig.VERSION_CODE < versionCode) {
|
if (BuildConfig.VERSION_CODE < versionCode) {
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ package org.schabi.newpipe;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -94,18 +94,18 @@ public final class DownloaderImpl extends Downloader {
|
|||||||
private static void enableModernTLS(final OkHttpClient.Builder builder) {
|
private static void enableModernTLS(final OkHttpClient.Builder builder) {
|
||||||
try {
|
try {
|
||||||
// get the default TrustManager
|
// get the default TrustManager
|
||||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||||
TrustManagerFactory.getDefaultAlgorithm());
|
TrustManagerFactory.getDefaultAlgorithm());
|
||||||
trustManagerFactory.init((KeyStore) null);
|
trustManagerFactory.init((KeyStore) null);
|
||||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
||||||
throw new IllegalStateException("Unexpected default trust managers:"
|
throw new IllegalStateException("Unexpected default trust managers:"
|
||||||
+ Arrays.toString(trustManagers));
|
+ Arrays.toString(trustManagers));
|
||||||
}
|
}
|
||||||
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
final X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
||||||
|
|
||||||
// insert our own TLSSocketFactory
|
// insert our own TLSSocketFactory
|
||||||
SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
|
final SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
|
||||||
|
|
||||||
builder.sslSocketFactory(sslSocketFactory, trustManager);
|
builder.sslSocketFactory(sslSocketFactory, trustManager);
|
||||||
|
|
||||||
@ -114,16 +114,16 @@ public final class DownloaderImpl extends Downloader {
|
|||||||
// Necessary because some servers (e.g. Framatube.org)
|
// Necessary because some servers (e.g. Framatube.org)
|
||||||
// don't support the old cipher suites.
|
// don't support the old cipher suites.
|
||||||
// https://github.com/square/okhttp/issues/4053#issuecomment-402579554
|
// https://github.com/square/okhttp/issues/4053#issuecomment-402579554
|
||||||
List<CipherSuite> cipherSuites = new ArrayList<>();
|
final List<CipherSuite> cipherSuites =
|
||||||
cipherSuites.addAll(ConnectionSpec.MODERN_TLS.cipherSuites());
|
new ArrayList<>(ConnectionSpec.MODERN_TLS.cipherSuites());
|
||||||
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
|
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
|
||||||
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
|
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
|
||||||
ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
final ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||||
.cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
|
.cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT));
|
builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT));
|
||||||
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
} catch (final KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@ -131,15 +131,15 @@ public final class DownloaderImpl extends Downloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getCookies(final String url) {
|
public String getCookies(final String url) {
|
||||||
List<String> resultCookies = new ArrayList<>();
|
final List<String> resultCookies = new ArrayList<>();
|
||||||
if (url.contains(YOUTUBE_DOMAIN)) {
|
if (url.contains(YOUTUBE_DOMAIN)) {
|
||||||
String youtubeCookie = getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
|
final String youtubeCookie = getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
|
||||||
if (youtubeCookie != null) {
|
if (youtubeCookie != null) {
|
||||||
resultCookies.add(youtubeCookie);
|
resultCookies.add(youtubeCookie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Recaptcha cookie is always added TODO: not sure if this is necessary
|
// Recaptcha cookie is always added TODO: not sure if this is necessary
|
||||||
String recaptchaCookie = getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY);
|
final String recaptchaCookie = getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY);
|
||||||
if (recaptchaCookie != null) {
|
if (recaptchaCookie != null) {
|
||||||
resultCookies.add(recaptchaCookie);
|
resultCookies.add(recaptchaCookie);
|
||||||
}
|
}
|
||||||
@ -159,9 +159,9 @@ public final class DownloaderImpl extends Downloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updateYoutubeRestrictedModeCookies(final Context context) {
|
public void updateYoutubeRestrictedModeCookies(final Context context) {
|
||||||
String restrictedModeEnabledKey =
|
final String restrictedModeEnabledKey =
|
||||||
context.getString(R.string.youtube_restricted_mode_enabled);
|
context.getString(R.string.youtube_restricted_mode_enabled);
|
||||||
boolean restrictedModeEnabled = PreferenceManager.getDefaultSharedPreferences(context)
|
final boolean restrictedModeEnabled = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
.getBoolean(restrictedModeEnabledKey, false);
|
.getBoolean(restrictedModeEnabledKey, false);
|
||||||
updateYoutubeRestrictedModeCookies(restrictedModeEnabled);
|
updateYoutubeRestrictedModeCookies(restrictedModeEnabled);
|
||||||
}
|
}
|
||||||
@ -186,9 +186,9 @@ public final class DownloaderImpl extends Downloader {
|
|||||||
try {
|
try {
|
||||||
final Response response = head(url);
|
final Response response = head(url);
|
||||||
return Long.parseLong(response.getHeader("Content-Length"));
|
return Long.parseLong(response.getHeader("Content-Length"));
|
||||||
} catch (NumberFormatException e) {
|
} catch (final NumberFormatException e) {
|
||||||
throw new IOException("Invalid content length", e);
|
throw new IOException("Invalid content length", e);
|
||||||
} catch (ReCaptchaException e) {
|
} catch (final ReCaptchaException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,7 +199,7 @@ public final class DownloaderImpl extends Downloader {
|
|||||||
.method("GET", null).url(siteUrl)
|
.method("GET", null).url(siteUrl)
|
||||||
.addHeader("User-Agent", USER_AGENT);
|
.addHeader("User-Agent", USER_AGENT);
|
||||||
|
|
||||||
String cookies = getCookies(siteUrl);
|
final String cookies = getCookies(siteUrl);
|
||||||
if (!cookies.isEmpty()) {
|
if (!cookies.isEmpty()) {
|
||||||
requestBuilder.addHeader("Cookie", cookies);
|
requestBuilder.addHeader("Cookie", cookies);
|
||||||
}
|
}
|
||||||
@ -218,7 +218,7 @@ public final class DownloaderImpl extends Downloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return body.byteStream();
|
return body.byteStream();
|
||||||
} catch (ReCaptchaException e) {
|
} catch (final ReCaptchaException e) {
|
||||||
throw new IOException(e.getMessage(), e.getCause());
|
throw new IOException(e.getMessage(), e.getCause());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,18 +240,18 @@ public final class DownloaderImpl extends Downloader {
|
|||||||
.method(httpMethod, requestBody).url(url)
|
.method(httpMethod, requestBody).url(url)
|
||||||
.addHeader("User-Agent", USER_AGENT);
|
.addHeader("User-Agent", USER_AGENT);
|
||||||
|
|
||||||
String cookies = getCookies(url);
|
final String cookies = getCookies(url);
|
||||||
if (!cookies.isEmpty()) {
|
if (!cookies.isEmpty()) {
|
||||||
requestBuilder.addHeader("Cookie", cookies);
|
requestBuilder.addHeader("Cookie", cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, List<String>> pair : headers.entrySet()) {
|
for (final Map.Entry<String, List<String>> pair : headers.entrySet()) {
|
||||||
final String headerName = pair.getKey();
|
final String headerName = pair.getKey();
|
||||||
final List<String> headerValueList = pair.getValue();
|
final List<String> headerValueList = pair.getValue();
|
||||||
|
|
||||||
if (headerValueList.size() > 1) {
|
if (headerValueList.size() > 1) {
|
||||||
requestBuilder.removeHeader(headerName);
|
requestBuilder.removeHeader(headerName);
|
||||||
for (String headerValue : headerValueList) {
|
for (final String headerValue : headerValueList) {
|
||||||
requestBuilder.addHeader(headerName, headerValue);
|
requestBuilder.addHeader(headerName, headerValue);
|
||||||
}
|
}
|
||||||
} else if (headerValueList.size() == 1) {
|
} else if (headerValueList.size() == 1) {
|
||||||
|
@ -27,7 +27,7 @@ import android.os.Bundle;
|
|||||||
public class ExitActivity extends Activity {
|
public class ExitActivity extends Activity {
|
||||||
|
|
||||||
public static void exitAndRemoveFromRecentApps(final Activity activity) {
|
public static void exitAndRemoveFromRecentApps(final Activity activity) {
|
||||||
Intent intent = new Intent(activity, ExitActivity.class);
|
final Intent intent = new Intent(activity, ExitActivity.class);
|
||||||
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
|
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
|
||||||
@ -42,7 +42,7 @@ public class ExitActivity extends Activity {
|
|||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
finishAndRemoveTask();
|
finishAndRemoveTask();
|
||||||
} else {
|
} else {
|
||||||
finish();
|
finish();
|
||||||
|
@ -4,7 +4,7 @@ import android.annotation.SuppressLint;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
|
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
|
||||||
|
|
||||||
|
@ -27,22 +27,22 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.Window;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||||
@ -53,6 +53,7 @@ import androidx.drawerlayout.widget.DrawerLayout;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||||
import com.google.android.material.navigation.NavigationView;
|
import com.google.android.material.navigation.NavigationView;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
@ -63,14 +64,18 @@ import org.schabi.newpipe.fragments.BackPressable;
|
|||||||
import org.schabi.newpipe.fragments.MainFragment;
|
import org.schabi.newpipe.fragments.MainFragment;
|
||||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
||||||
|
import org.schabi.newpipe.player.VideoPlayer;
|
||||||
|
import org.schabi.newpipe.player.event.OnKeyDownListener;
|
||||||
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.KioskTranslator;
|
import org.schabi.newpipe.util.KioskTranslator;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.PeertubeHelper;
|
import org.schabi.newpipe.util.PeertubeHelper;
|
||||||
import org.schabi.newpipe.util.PermissionHelper;
|
import org.schabi.newpipe.util.PermissionHelper;
|
||||||
|
import org.schabi.newpipe.util.SerializedCache;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
import org.schabi.newpipe.util.StateSaver;
|
import org.schabi.newpipe.util.StateSaver;
|
||||||
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
|
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
|
||||||
@ -127,12 +132,6 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
Window w = getWindow();
|
|
||||||
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
|
|
||||||
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getSupportFragmentManager() != null
|
if (getSupportFragmentManager() != null
|
||||||
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||||
initFragments();
|
initFragments();
|
||||||
@ -141,11 +140,11 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
try {
|
try {
|
||||||
setupDrawer();
|
setupDrawer();
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiError(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AndroidTvUtils.isTv(this)) {
|
if (DeviceUtils.isTv(this)) {
|
||||||
FocusOverlayView.setupFocusObserver(this);
|
FocusOverlayView.setupFocusObserver(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,8 +155,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
drawerItems = findViewById(R.id.navigation);
|
drawerItems = findViewById(R.id.navigation);
|
||||||
|
|
||||||
//Tabs
|
//Tabs
|
||||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||||
StreamingService service = NewPipe.getService(currentServiceId);
|
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||||
|
|
||||||
int kioskId = 0;
|
int kioskId = 0;
|
||||||
|
|
||||||
@ -229,7 +228,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
case R.id.menu_tabs_group:
|
case R.id.menu_tabs_group:
|
||||||
try {
|
try {
|
||||||
tabSelected(item);
|
tabSelected(item);
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiError(this, e);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -270,8 +269,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
|
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||||
StreamingService service = NewPipe.getService(currentServiceId);
|
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||||
String serviceName = "";
|
String serviceName = "";
|
||||||
|
|
||||||
int kioskId = 0;
|
int kioskId = 0;
|
||||||
@ -300,8 +299,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupDrawerHeader() {
|
private void setupDrawerHeader() {
|
||||||
NavigationView navigationView = findViewById(R.id.navigation);
|
final NavigationView navigationView = findViewById(R.id.navigation);
|
||||||
View hView = navigationView.getHeaderView(0);
|
final View hView = navigationView.getHeaderView(0);
|
||||||
|
|
||||||
serviceArrow = hView.findViewById(R.id.drawer_arrow);
|
serviceArrow = hView.findViewById(R.id.drawer_arrow);
|
||||||
headerServiceIcon = hView.findViewById(R.id.drawer_header_service_icon);
|
headerServiceIcon = hView.findViewById(R.id.drawer_header_service_icon);
|
||||||
@ -336,7 +335,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
showTabs();
|
showTabs();
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiError(this, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,11 +344,11 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
private void showServices() {
|
private void showServices() {
|
||||||
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
|
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
|
||||||
|
|
||||||
for (StreamingService s : NewPipe.getServices()) {
|
for (final StreamingService s : NewPipe.getServices()) {
|
||||||
final String title = s.getServiceInfo().getName()
|
final String title = s.getServiceInfo().getName()
|
||||||
+ (ServiceHelper.isBeta(s) ? " (beta)" : "");
|
+ (ServiceHelper.isBeta(s) ? " (beta)" : "");
|
||||||
|
|
||||||
MenuItem menuItem = drawerItems.getMenu()
|
final MenuItem menuItem = drawerItems.getMenu()
|
||||||
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
|
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
|
||||||
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
|
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
|
||||||
|
|
||||||
@ -363,20 +362,20 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void enhancePeertubeMenu(final StreamingService s, final MenuItem menuItem) {
|
private void enhancePeertubeMenu(final StreamingService s, final MenuItem menuItem) {
|
||||||
PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance();
|
final PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance();
|
||||||
menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : ""));
|
menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : ""));
|
||||||
Spinner spinner = (Spinner) LayoutInflater.from(this)
|
final Spinner spinner = (Spinner) LayoutInflater.from(this)
|
||||||
.inflate(R.layout.instance_spinner_layout, null);
|
.inflate(R.layout.instance_spinner_layout, null);
|
||||||
List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this);
|
final List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this);
|
||||||
List<String> items = new ArrayList<>();
|
final List<String> items = new ArrayList<>();
|
||||||
int defaultSelect = 0;
|
int defaultSelect = 0;
|
||||||
for (PeertubeInstance instance : instances) {
|
for (final PeertubeInstance instance : instances) {
|
||||||
items.add(instance.getName());
|
items.add(instance.getName());
|
||||||
if (instance.getUrl().equals(currentInstace.getUrl())) {
|
if (instance.getUrl().equals(currentInstace.getUrl())) {
|
||||||
defaultSelect = items.size() - 1;
|
defaultSelect = items.size() - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
|
final ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
|
||||||
R.layout.instance_spinner_item, items);
|
R.layout.instance_spinner_item, items);
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
spinner.setAdapter(adapter);
|
spinner.setAdapter(adapter);
|
||||||
@ -385,7 +384,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onItemSelected(final AdapterView<?> parent, final View view,
|
public void onItemSelected(final AdapterView<?> parent, final View view,
|
||||||
final int position, final long id) {
|
final int position, final long id) {
|
||||||
PeertubeInstance newInstance = instances.get(position);
|
final PeertubeInstance newInstance = instances.get(position);
|
||||||
if (newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) {
|
if (newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -411,8 +410,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp);
|
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp);
|
||||||
|
|
||||||
//Tabs
|
//Tabs
|
||||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||||
StreamingService service = NewPipe.getService(currentServiceId);
|
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||||
|
|
||||||
int kioskId = 0;
|
int kioskId = 0;
|
||||||
|
|
||||||
@ -477,11 +476,12 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
headerServiceView.post(() -> headerServiceView.setSelected(true));
|
headerServiceView.post(() -> headerServiceView.setSelected(true));
|
||||||
toggleServiceButton.setContentDescription(
|
toggleServiceButton.setContentDescription(
|
||||||
getString(R.string.drawer_header_description) + selectedServiceName);
|
getString(R.string.drawer_header_description) + selectedServiceName);
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiError(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
final SharedPreferences sharedPreferences
|
||||||
|
= PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
|
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Theme has changed, recreating activity...");
|
Log.d(TAG, "Theme has changed, recreating activity...");
|
||||||
@ -514,7 +514,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
|
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
|
||||||
// to not destroy the already created backstack
|
// to not destroy the already created backstack
|
||||||
String action = intent.getAction();
|
final String action = intent.getAction();
|
||||||
if ((action != null && action.equals(Intent.ACTION_MAIN))
|
if ((action != null && action.equals(Intent.ACTION_MAIN))
|
||||||
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
|
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
|
||||||
return;
|
return;
|
||||||
@ -526,25 +526,60 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
handleIntent(intent);
|
handleIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(final int keyCode, final KeyEvent event) {
|
||||||
|
final Fragment fragment = getSupportFragmentManager()
|
||||||
|
.findFragmentById(R.id.fragment_player_holder);
|
||||||
|
if (fragment instanceof OnKeyDownListener
|
||||||
|
&& !bottomSheetHiddenOrCollapsed()) {
|
||||||
|
// Provide keyDown event to fragment which then sends this event
|
||||||
|
// to the main player service
|
||||||
|
return ((OnKeyDownListener) fragment).onKeyDown(keyCode)
|
||||||
|
|| super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onBackPressed() called");
|
Log.d(TAG, "onBackPressed() called");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AndroidTvUtils.isTv(this)) {
|
if (DeviceUtils.isTv(this)) {
|
||||||
View drawerPanel = findViewById(R.id.navigation);
|
final View drawerPanel = findViewById(R.id.navigation);
|
||||||
if (drawer.isDrawerOpen(drawerPanel)) {
|
if (drawer.isDrawerOpen(drawerPanel)) {
|
||||||
drawer.closeDrawers();
|
drawer.closeDrawers();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
// In case bottomSheet is not visible on the screen or collapsed we can assume that the user
|
||||||
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
|
// interacts with a fragment inside fragment_holder so all back presses should be
|
||||||
// delegate the back press to it
|
// handled by it
|
||||||
if (fragment instanceof BackPressable) {
|
if (bottomSheetHiddenOrCollapsed()) {
|
||||||
if (((BackPressable) fragment).onBackPressed()) {
|
final Fragment fragment = getSupportFragmentManager()
|
||||||
|
.findFragmentById(R.id.fragment_holder);
|
||||||
|
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
|
||||||
|
// delegate the back press to it
|
||||||
|
if (fragment instanceof BackPressable) {
|
||||||
|
if (((BackPressable) fragment).onBackPressed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
final Fragment fragmentPlayer = getSupportFragmentManager()
|
||||||
|
.findFragmentById(R.id.fragment_player_holder);
|
||||||
|
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
|
||||||
|
// delegate the back press to it
|
||||||
|
if (fragmentPlayer instanceof BackPressable) {
|
||||||
|
if (!((BackPressable) fragmentPlayer).onBackPressed()) {
|
||||||
|
final FrameLayout bottomSheetLayout =
|
||||||
|
findViewById(R.id.fragment_player_holder);
|
||||||
|
BottomSheetBehavior.from(bottomSheetLayout)
|
||||||
|
.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -560,7 +595,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
public void onRequestPermissionsResult(final int requestCode,
|
public void onRequestPermissionsResult(final int requestCode,
|
||||||
@NonNull final String[] permissions,
|
@NonNull final String[] permissions,
|
||||||
@NonNull final int[] grantResults) {
|
@NonNull final int[] grantResults) {
|
||||||
for (int i : grantResults) {
|
for (final int i : grantResults) {
|
||||||
if (i == PackageManager.PERMISSION_DENIED) {
|
if (i == PackageManager.PERMISSION_DENIED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -570,8 +605,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
NavigationHelper.openDownloads(this);
|
NavigationHelper.openDownloads(this);
|
||||||
break;
|
break;
|
||||||
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
|
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
|
||||||
Fragment fragment = getSupportFragmentManager()
|
final Fragment fragment = getSupportFragmentManager()
|
||||||
.findFragmentById(R.id.fragment_holder);
|
.findFragmentById(R.id.fragment_player_holder);
|
||||||
if (fragment instanceof VideoDetailFragment) {
|
if (fragment instanceof VideoDetailFragment) {
|
||||||
((VideoDetailFragment) fragment).openDownloadDialog();
|
((VideoDetailFragment) fragment).openDownloadDialog();
|
||||||
}
|
}
|
||||||
@ -622,17 +657,14 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
|
|
||||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
final Fragment fragment
|
||||||
if (!(fragment instanceof VideoDetailFragment)) {
|
= getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||||
findViewById(R.id.toolbar).findViewById(R.id.toolbar_spinner).setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(fragment instanceof SearchFragment)) {
|
if (!(fragment instanceof SearchFragment)) {
|
||||||
findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container)
|
findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container)
|
||||||
.setVisibility(View.GONE);
|
.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
final ActionBar actionBar = getSupportActionBar();
|
||||||
if (actionBar != null) {
|
if (actionBar != null) {
|
||||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||||
}
|
}
|
||||||
@ -647,7 +679,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
|
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
|
||||||
}
|
}
|
||||||
int id = item.getItemId();
|
final int id = item.getItemId();
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case android.R.id.home:
|
case android.R.id.home:
|
||||||
@ -668,6 +700,13 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
StateSaver.clearStateFiles();
|
StateSaver.clearStateFiles();
|
||||||
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
|
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
|
||||||
|
// When user watch a video inside popup and then tries to open the video in main player
|
||||||
|
// while the app is closed he will see a blank fragment on place of kiosk.
|
||||||
|
// Let's open it first
|
||||||
|
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||||
|
NavigationHelper.openMainFragment(getSupportFragmentManager());
|
||||||
|
}
|
||||||
|
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
} else {
|
} else {
|
||||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||||
@ -708,16 +747,22 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
|
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
|
||||||
String url = intent.getStringExtra(Constants.KEY_URL);
|
final String url = intent.getStringExtra(Constants.KEY_URL);
|
||||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||||
String title = intent.getStringExtra(Constants.KEY_TITLE);
|
final String title = intent.getStringExtra(Constants.KEY_TITLE);
|
||||||
switch (((StreamingService.LinkType) intent
|
switch (((StreamingService.LinkType) intent
|
||||||
.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
|
.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
|
||||||
case STREAM:
|
case STREAM:
|
||||||
boolean autoPlay = intent
|
final boolean autoPlay = intent
|
||||||
.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
|
.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
|
||||||
|
final String intentCacheKey = intent
|
||||||
|
.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY);
|
||||||
|
final PlayQueue playQueue = intentCacheKey != null
|
||||||
|
? SerializedCache.getInstance()
|
||||||
|
.take(intentCacheKey, PlayQueue.class)
|
||||||
|
: null;
|
||||||
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(),
|
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(),
|
||||||
serviceId, url, title, autoPlay);
|
serviceId, url, title, autoPlay, playQueue);
|
||||||
break;
|
break;
|
||||||
case CHANNEL:
|
case CHANNEL:
|
||||||
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
|
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
|
||||||
@ -737,7 +782,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
if (searchString == null) {
|
if (searchString == null) {
|
||||||
searchString = "";
|
searchString = "";
|
||||||
}
|
}
|
||||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||||
NavigationHelper.openSearchFragment(
|
NavigationHelper.openSearchFragment(
|
||||||
getSupportFragmentManager(),
|
getSupportFragmentManager(),
|
||||||
serviceId,
|
serviceId,
|
||||||
@ -746,8 +791,21 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
} else {
|
} else {
|
||||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiError(this, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* Utils
|
||||||
|
* */
|
||||||
|
|
||||||
|
private boolean bottomSheetHiddenOrCollapsed() {
|
||||||
|
final FrameLayout bottomSheetLayout = findViewById(R.id.fragment_player_holder);
|
||||||
|
final BottomSheetBehavior<FrameLayout> bottomSheetBehavior =
|
||||||
|
BottomSheetBehavior.from(bottomSheetLayout);
|
||||||
|
|
||||||
|
final int sheetState = bottomSheetBehavior.getState();
|
||||||
|
return sheetState == BottomSheetBehavior.STATE_HIDDEN
|
||||||
|
|| sheetState == BottomSheetBehavior.STATE_COLLAPSED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ public final class NewPipeDatabase {
|
|||||||
if (databaseInstance == null) {
|
if (databaseInstance == null) {
|
||||||
throw new IllegalStateException("database is not initialized");
|
throw new IllegalStateException("database is not initialized");
|
||||||
}
|
}
|
||||||
Cursor c = databaseInstance.query("pragma wal_checkpoint(full)", null);
|
final Cursor c = databaseInstance.query("pragma wal_checkpoint(full)", null);
|
||||||
if (c.moveToFirst() && c.getInt(0) == 1) {
|
if (c.moveToFirst() && c.getInt(0) == 1) {
|
||||||
throw new RuntimeException("Checkpoint was blocked from completing");
|
throw new RuntimeException("Checkpoint was blocked from completing");
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ public class PanicResponderActivity extends Activity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
||||||
// TODO: Explicitly clear the search results
|
// TODO: Explicitly clear the search results
|
||||||
// once they are restored when the app restarts
|
// once they are restored when the app restarts
|
||||||
@ -40,7 +40,7 @@ public class PanicResponderActivity extends Activity {
|
|||||||
ExitActivity.exitAndRemoveFromRecentApps(this);
|
ExitActivity.exitAndRemoveFromRecentApps(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
finishAndRemoveTask();
|
finishAndRemoveTask();
|
||||||
} else {
|
} else {
|
||||||
finish();
|
finish();
|
||||||
|
@ -61,7 +61,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
ThemeHelper.setTheme(this);
|
ThemeHelper.setTheme(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_recaptcha);
|
setContentView(R.layout.activity_recaptcha);
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
String url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
|
String url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
|
||||||
@ -76,7 +76,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
webView = findViewById(R.id.reCaptchaWebView);
|
webView = findViewById(R.id.reCaptchaWebView);
|
||||||
|
|
||||||
// enable Javascript
|
// enable Javascript
|
||||||
WebSettings webSettings = webView.getSettings();
|
final WebSettings webSettings = webView.getSettings();
|
||||||
webSettings.setJavaScriptEnabled(true);
|
webSettings.setJavaScriptEnabled(true);
|
||||||
|
|
||||||
webView.setWebViewClient(new WebViewClient() {
|
webView.setWebViewClient(new WebViewClient() {
|
||||||
@ -84,7 +84,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
public boolean shouldOverrideUrlLoading(final WebView view,
|
public boolean shouldOverrideUrlLoading(final WebView view,
|
||||||
final WebResourceRequest request) {
|
final WebResourceRequest request) {
|
||||||
String url = request.getUrl().toString();
|
final String url = request.getUrl().toString();
|
||||||
if (MainActivity.DEBUG) {
|
if (MainActivity.DEBUG) {
|
||||||
Log.d(TAG, "shouldOverrideUrlLoading: request.url=" + url);
|
Log.d(TAG, "shouldOverrideUrlLoading: request.url=" + url);
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
// cleaning cache, history and cookies from webView
|
// cleaning cache, history and cookies from webView
|
||||||
webView.clearCache(true);
|
webView.clearCache(true);
|
||||||
webView.clearHistory();
|
webView.clearHistory();
|
||||||
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
final android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
cookieManager.removeAllCookies(aBoolean -> {
|
cookieManager.removeAllCookies(aBoolean -> {
|
||||||
});
|
});
|
||||||
@ -128,7 +128,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
|
getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
|
||||||
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
final ActionBar actionBar = getSupportActionBar();
|
||||||
if (actionBar != null) {
|
if (actionBar != null) {
|
||||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||||
actionBar.setTitle(R.string.title_activity_recaptcha);
|
actionBar.setTitle(R.string.title_activity_recaptcha);
|
||||||
@ -145,7 +145,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
int id = item.getItemId();
|
final int id = item.getItemId();
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case R.id.menu_item_done:
|
case R.id.menu_item_done:
|
||||||
saveCookiesAndFinish();
|
saveCookiesAndFinish();
|
||||||
@ -173,7 +173,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
|
final Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
NavUtils.navigateUpTo(this, intent);
|
NavUtils.navigateUpTo(this, intent);
|
||||||
}
|
}
|
||||||
@ -188,13 +188,13 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String cookies = CookieManager.getInstance().getCookie(url);
|
final String cookies = CookieManager.getInstance().getCookie(url);
|
||||||
handleCookies(cookies);
|
handleCookies(cookies);
|
||||||
|
|
||||||
// sometimes cookies are inside the url
|
// sometimes cookies are inside the url
|
||||||
int abuseStart = url.indexOf("google_abuse=");
|
final int abuseStart = url.indexOf("google_abuse=");
|
||||||
if (abuseStart != -1) {
|
if (abuseStart != -1) {
|
||||||
int abuseEnd = url.indexOf("+path");
|
final int abuseEnd = url.indexOf("+path");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String abuseCookie = url.substring(abuseStart + 13, abuseEnd);
|
String abuseCookie = url.substring(abuseStart + 13, abuseEnd);
|
||||||
|
@ -8,7 +8,7 @@ import android.content.Intent;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.ContextThemeWrapper;
|
import android.view.ContextThemeWrapper;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -44,7 +44,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
|
|||||||
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
@ -320,7 +320,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
};
|
};
|
||||||
|
|
||||||
int id = 12345;
|
int id = 12345;
|
||||||
for (AdapterChoiceItem item : choices) {
|
for (final AdapterChoiceItem item : choices) {
|
||||||
final RadioButton radioButton
|
final RadioButton radioButton
|
||||||
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
|
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
|
||||||
radioButton.setText(item.description);
|
radioButton.setText(item.description);
|
||||||
@ -340,7 +340,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
getString(R.string.preferred_open_action_last_selected_key), null);
|
getString(R.string.preferred_open_action_last_selected_key), null);
|
||||||
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
|
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
|
||||||
for (int i = 0; i < choices.size(); i++) {
|
for (int i = 0; i < choices.size(); i++) {
|
||||||
AdapterChoiceItem c = choices.get(i);
|
final AdapterChoiceItem c = choices.get(i);
|
||||||
if (lastSelectedPlayer.equals(c.key)) {
|
if (lastSelectedPlayer.equals(c.key)) {
|
||||||
selectedRadioPosition = i;
|
selectedRadioPosition = i;
|
||||||
break;
|
break;
|
||||||
@ -357,7 +357,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
alertDialog.show();
|
alertDialog.show();
|
||||||
|
|
||||||
if (AndroidTvUtils.isTv(this)) {
|
if (DeviceUtils.isTv(this)) {
|
||||||
FocusOverlayView.setupFocusObserver(alertDialog);
|
FocusOverlayView.setupFocusObserver(alertDialog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,9 +372,9 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
final SharedPreferences preferences = PreferenceManager
|
final SharedPreferences preferences = PreferenceManager
|
||||||
.getDefaultSharedPreferences(this);
|
.getDefaultSharedPreferences(this);
|
||||||
boolean isExtVideoEnabled = preferences.getBoolean(
|
final boolean isExtVideoEnabled = preferences.getBoolean(
|
||||||
getString(R.string.use_external_video_player_key), false);
|
getString(R.string.use_external_video_player_key), false);
|
||||||
boolean isExtAudioEnabled = preferences.getBoolean(
|
final boolean isExtAudioEnabled = preferences.getBoolean(
|
||||||
getString(R.string.use_external_audio_player_key), false);
|
getString(R.string.use_external_audio_player_key), false);
|
||||||
|
|
||||||
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key),
|
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key),
|
||||||
@ -420,9 +420,9 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleText() {
|
private void handleText() {
|
||||||
String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
final String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||||
int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
final int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||||
Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
|
final Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
NavigationHelper.openSearch(getThemeWrapperContext(), serviceId, searchString);
|
NavigationHelper.openSearch(getThemeWrapperContext(), serviceId, searchString);
|
||||||
@ -489,14 +489,14 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe((@NonNull StreamInfo result) -> {
|
.subscribe((@NonNull StreamInfo result) -> {
|
||||||
List<VideoStream> sortedVideoStreams = ListHelper
|
final List<VideoStream> sortedVideoStreams = ListHelper
|
||||||
.getSortedStreamVideosList(this, result.getVideoStreams(),
|
.getSortedStreamVideosList(this, result.getVideoStreams(),
|
||||||
result.getVideoOnlyStreams(), false);
|
result.getVideoOnlyStreams(), false);
|
||||||
int selectedVideoStreamIndex = ListHelper
|
final int selectedVideoStreamIndex = ListHelper
|
||||||
.getDefaultResolutionIndex(this, sortedVideoStreams);
|
.getDefaultResolutionIndex(this, sortedVideoStreams);
|
||||||
|
|
||||||
FragmentManager fm = getSupportFragmentManager();
|
final FragmentManager fm = getSupportFragmentManager();
|
||||||
DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
|
final DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
|
||||||
downloadDialog.setVideoStreams(sortedVideoStreams);
|
downloadDialog.setVideoStreams(sortedVideoStreams);
|
||||||
downloadDialog.setAudioStreams(result.getAudioStreams());
|
downloadDialog.setAudioStreams(result.getAudioStreams());
|
||||||
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
|
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
|
||||||
@ -512,7 +512,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
public void onRequestPermissionsResult(final int requestCode,
|
public void onRequestPermissionsResult(final int requestCode,
|
||||||
@NonNull final String[] permissions,
|
@NonNull final String[] permissions,
|
||||||
@NonNull final int[] grantResults) {
|
@NonNull final int[] grantResults) {
|
||||||
for (int i : grantResults) {
|
for (final int i : grantResults) {
|
||||||
if (i == PackageManager.PERMISSION_DENIED) {
|
if (i == PackageManager.PERMISSION_DENIED) {
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
@ -580,7 +580,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result.toArray(new String[result.size()]);
|
return result.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class AdapterChoiceItem {
|
private static class AdapterChoiceItem {
|
||||||
@ -642,7 +642,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
if (!(serializable instanceof Choice)) {
|
if (!(serializable instanceof Choice)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Choice playerChoice = (Choice) serializable;
|
final Choice playerChoice = (Choice) serializable;
|
||||||
handleChoice(playerChoice);
|
handleChoice(playerChoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -690,13 +690,13 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
final SharedPreferences preferences = PreferenceManager
|
final SharedPreferences preferences = PreferenceManager
|
||||||
.getDefaultSharedPreferences(this);
|
.getDefaultSharedPreferences(this);
|
||||||
boolean isExtVideoEnabled = preferences.getBoolean(
|
final boolean isExtVideoEnabled = preferences.getBoolean(
|
||||||
getString(R.string.use_external_video_player_key), false);
|
getString(R.string.use_external_video_player_key), false);
|
||||||
boolean isExtAudioEnabled = preferences.getBoolean(
|
final boolean isExtAudioEnabled = preferences.getBoolean(
|
||||||
getString(R.string.use_external_audio_player_key), false);
|
getString(R.string.use_external_audio_player_key), false);
|
||||||
|
|
||||||
PlayQueue playQueue;
|
PlayQueue playQueue;
|
||||||
String playerChoice = choice.playerChoice;
|
final String playerChoice = choice.playerChoice;
|
||||||
|
|
||||||
if (info instanceof StreamInfo) {
|
if (info instanceof StreamInfo) {
|
||||||
if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) {
|
if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) {
|
||||||
@ -709,7 +709,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
playQueue = new SinglePlayQueue((StreamInfo) info);
|
playQueue = new SinglePlayQueue((StreamInfo) info);
|
||||||
|
|
||||||
if (playerChoice.equals(videoPlayerKey)) {
|
if (playerChoice.equals(videoPlayerKey)) {
|
||||||
NavigationHelper.playOnMainPlayer(this, playQueue, true);
|
openMainPlayer(playQueue, choice);
|
||||||
} else if (playerChoice.equals(backgroundPlayerKey)) {
|
} else if (playerChoice.equals(backgroundPlayerKey)) {
|
||||||
NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
|
NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
|
||||||
} else if (playerChoice.equals(popupPlayerKey)) {
|
} else if (playerChoice.equals(popupPlayerKey)) {
|
||||||
@ -724,7 +724,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
: new PlaylistPlayQueue((PlaylistInfo) info);
|
: new PlaylistPlayQueue((PlaylistInfo) info);
|
||||||
|
|
||||||
if (playerChoice.equals(videoPlayerKey)) {
|
if (playerChoice.equals(videoPlayerKey)) {
|
||||||
NavigationHelper.playOnMainPlayer(this, playQueue, true);
|
openMainPlayer(playQueue, choice);
|
||||||
} else if (playerChoice.equals(backgroundPlayerKey)) {
|
} else if (playerChoice.equals(backgroundPlayerKey)) {
|
||||||
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
|
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
|
||||||
} else if (playerChoice.equals(popupPlayerKey)) {
|
} else if (playerChoice.equals(popupPlayerKey)) {
|
||||||
@ -734,6 +734,11 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void openMainPlayer(final PlayQueue playQueue, final Choice choice) {
|
||||||
|
NavigationHelper.playOnMainPlayer(this, playQueue, choice.linkType,
|
||||||
|
choice.url, "", true, true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
@ -88,7 +88,7 @@ public class AboutActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
setContentView(R.layout.activity_about);
|
setContentView(R.layout.activity_about);
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
// Create the adapter that will return a fragment for each of the three
|
// Create the adapter that will return a fragment for each of the three
|
||||||
@ -99,13 +99,13 @@ public class AboutActivity extends AppCompatActivity {
|
|||||||
mViewPager = findViewById(R.id.container);
|
mViewPager = findViewById(R.id.container);
|
||||||
mViewPager.setAdapter(mSectionsPagerAdapter);
|
mViewPager.setAdapter(mSectionsPagerAdapter);
|
||||||
|
|
||||||
TabLayout tabLayout = findViewById(R.id.tabs);
|
final TabLayout tabLayout = findViewById(R.id.tabs);
|
||||||
tabLayout.setupWithViewPager(mViewPager);
|
tabLayout.setupWithViewPager(mViewPager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
int id = item.getItemId();
|
final int id = item.getItemId();
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case android.R.id.home:
|
case android.R.id.home:
|
||||||
@ -134,25 +134,25 @@ public class AboutActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||||
final Bundle savedInstanceState) {
|
final Bundle savedInstanceState) {
|
||||||
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
|
final View rootView = inflater.inflate(R.layout.fragment_about, container, false);
|
||||||
Context context = this.getContext();
|
final Context context = this.getContext();
|
||||||
|
|
||||||
TextView version = rootView.findViewById(R.id.app_version);
|
final TextView version = rootView.findViewById(R.id.app_version);
|
||||||
version.setText(BuildConfig.VERSION_NAME);
|
version.setText(BuildConfig.VERSION_NAME);
|
||||||
|
|
||||||
View githubLink = rootView.findViewById(R.id.github_link);
|
final View githubLink = rootView.findViewById(R.id.github_link);
|
||||||
githubLink.setOnClickListener(nv ->
|
githubLink.setOnClickListener(nv ->
|
||||||
openUrlInBrowser(context, context.getString(R.string.github_url)));
|
openUrlInBrowser(context, context.getString(R.string.github_url)));
|
||||||
|
|
||||||
View donationLink = rootView.findViewById(R.id.donation_link);
|
final View donationLink = rootView.findViewById(R.id.donation_link);
|
||||||
donationLink.setOnClickListener(v ->
|
donationLink.setOnClickListener(v ->
|
||||||
openUrlInBrowser(context, context.getString(R.string.donation_url)));
|
openUrlInBrowser(context, context.getString(R.string.donation_url)));
|
||||||
|
|
||||||
View websiteLink = rootView.findViewById(R.id.website_link);
|
final View websiteLink = rootView.findViewById(R.id.website_link);
|
||||||
websiteLink.setOnClickListener(nv ->
|
websiteLink.setOnClickListener(nv ->
|
||||||
openUrlInBrowser(context, context.getString(R.string.website_url)));
|
openUrlInBrowser(context, context.getString(R.string.website_url)));
|
||||||
|
|
||||||
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
|
final View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
|
||||||
privacyPolicyLink.setOnClickListener(v ->
|
privacyPolicyLink.setOnClickListener(v ->
|
||||||
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url)));
|
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url)));
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ public class AboutActivity extends AppCompatActivity {
|
|||||||
*/
|
*/
|
||||||
public class SectionsPagerAdapter extends FragmentPagerAdapter {
|
public class SectionsPagerAdapter extends FragmentPagerAdapter {
|
||||||
public SectionsPagerAdapter(final FragmentManager fm) {
|
public SectionsPagerAdapter(final FragmentManager fm) {
|
||||||
super(fm);
|
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -4,10 +4,12 @@ import android.net.Uri;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for storing information about a software license.
|
* Class for storing information about a software license.
|
||||||
*/
|
*/
|
||||||
public class License implements Parcelable {
|
public class License implements Parcelable, Serializable {
|
||||||
public static final Creator<License> CREATOR = new Creator<License>() {
|
public static final Creator<License> CREATOR = new Creator<License>() {
|
||||||
@Override
|
@Override
|
||||||
public License createFromParcel(final Parcel source) {
|
public License createFromParcel(final Parcel source) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.schabi.newpipe.about;
|
package org.schabi.newpipe.about;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -11,12 +10,14 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.util.ShareUtils;
|
import org.schabi.newpipe.util.ShareUtils;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,13 +27,15 @@ public class LicenseFragment extends Fragment {
|
|||||||
private static final String ARG_COMPONENTS = "components";
|
private static final String ARG_COMPONENTS = "components";
|
||||||
private SoftwareComponent[] softwareComponents;
|
private SoftwareComponent[] softwareComponents;
|
||||||
private SoftwareComponent componentForContextMenu;
|
private SoftwareComponent componentForContextMenu;
|
||||||
|
private License activeLicense;
|
||||||
|
private static final String LICENSE_KEY = "ACTIVE_LICENSE";
|
||||||
|
|
||||||
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
|
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
|
||||||
if (softwareComponents == null) {
|
if (softwareComponents == null) {
|
||||||
throw new NullPointerException("softwareComponents is null");
|
throw new NullPointerException("softwareComponents is null");
|
||||||
}
|
}
|
||||||
LicenseFragment fragment = new LicenseFragment();
|
final LicenseFragment fragment = new LicenseFragment();
|
||||||
Bundle bundle = new Bundle();
|
final Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArray(ARG_COMPONENTS, softwareComponents);
|
bundle.putParcelableArray(ARG_COMPONENTS, softwareComponents);
|
||||||
fragment.setArguments(bundle);
|
fragment.setArguments(bundle);
|
||||||
return fragment;
|
return fragment;
|
||||||
@ -44,8 +47,8 @@ public class LicenseFragment extends Fragment {
|
|||||||
* @param context the context to use
|
* @param context the context to use
|
||||||
* @param license the license to show
|
* @param license the license to show
|
||||||
*/
|
*/
|
||||||
private static void showLicense(final Context context, final License license) {
|
private static void showLicense(final Activity context, final License license) {
|
||||||
new LicenseFragmentHelper((Activity) context).execute(license);
|
new LicenseFragmentHelper(context).execute(license);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -54,6 +57,12 @@ public class LicenseFragment extends Fragment {
|
|||||||
softwareComponents = (SoftwareComponent[]) getArguments()
|
softwareComponents = (SoftwareComponent[]) getArguments()
|
||||||
.getParcelableArray(ARG_COMPONENTS);
|
.getParcelableArray(ARG_COMPONENTS);
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
final Serializable license = savedInstanceState.getSerializable(LICENSE_KEY);
|
||||||
|
if (license != null) {
|
||||||
|
activeLicense = (License) license;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Sort components by name
|
// Sort components by name
|
||||||
Arrays.sort(softwareComponents, (o1, o2) -> o1.getName().compareTo(o2.getName()));
|
Arrays.sort(softwareComponents, (o1, o2) -> o1.getName().compareTo(o2.getName()));
|
||||||
}
|
}
|
||||||
@ -66,8 +75,10 @@ public class LicenseFragment extends Fragment {
|
|||||||
final ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
|
final ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
|
||||||
|
|
||||||
final View licenseLink = rootView.findViewById(R.id.app_read_license);
|
final View licenseLink = rootView.findViewById(R.id.app_read_license);
|
||||||
licenseLink.setOnClickListener(v ->
|
licenseLink.setOnClickListener(v -> {
|
||||||
showLicense(getActivity(), StandardLicenses.GPL3));
|
activeLicense = StandardLicenses.GPL3;
|
||||||
|
showLicense(getActivity(), StandardLicenses.GPL3);
|
||||||
|
});
|
||||||
|
|
||||||
for (final SoftwareComponent component : softwareComponents) {
|
for (final SoftwareComponent component : softwareComponents) {
|
||||||
final View componentView = inflater
|
final View componentView = inflater
|
||||||
@ -81,11 +92,16 @@ public class LicenseFragment extends Fragment {
|
|||||||
component.getLicense().getAbbreviation()));
|
component.getLicense().getAbbreviation()));
|
||||||
|
|
||||||
componentView.setTag(component);
|
componentView.setTag(component);
|
||||||
componentView.setOnClickListener(v ->
|
componentView.setOnClickListener(v -> {
|
||||||
showLicense(getActivity(), component.getLicense()));
|
activeLicense = component.getLicense();
|
||||||
|
showLicense(getActivity(), component.getLicense());
|
||||||
|
});
|
||||||
softwareComponentsView.addView(componentView);
|
softwareComponentsView.addView(componentView);
|
||||||
registerForContextMenu(componentView);
|
registerForContextMenu(componentView);
|
||||||
}
|
}
|
||||||
|
if (activeLicense != null) {
|
||||||
|
showLicense(getActivity(), activeLicense);
|
||||||
|
}
|
||||||
return rootView;
|
return rootView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +117,7 @@ public class LicenseFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onContextItemSelected(final MenuItem item) {
|
public boolean onContextItemSelected(@NonNull final MenuItem item) {
|
||||||
// item.getMenuInfo() is null so we use the tag of the view
|
// item.getMenuInfo() is null so we use the tag of the view
|
||||||
final SoftwareComponent component = componentForContextMenu;
|
final SoftwareComponent component = componentForContextMenu;
|
||||||
if (component == null) {
|
if (component == null) {
|
||||||
@ -116,4 +132,12 @@ public class LicenseFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||||
|
super.onSaveInstanceState(savedInstanceState);
|
||||||
|
if (activeLicense != null) {
|
||||||
|
savedInstanceState.putSerializable(LICENSE_KEY, activeLicense);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
|||||||
// split the HTML file and insert the stylesheet into the HEAD of the file
|
// split the HTML file and insert the stylesheet into the HEAD of the file
|
||||||
webViewData = licenseContent.toString().replace("</head>",
|
webViewData = licenseContent.toString().replace("</head>",
|
||||||
"<style>" + getLicenseStylesheet(context) + "</style></head>");
|
"<style>" + getLicenseStylesheet(context) + "</style></head>");
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Could not get license file: " + license.getFilename(), e);
|
"Could not get license file: " + license.getFilename(), e);
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,13 @@ import io.reactivex.Flowable;
|
|||||||
@Dao
|
@Dao
|
||||||
public interface BasicDAO<Entity> {
|
public interface BasicDAO<Entity> {
|
||||||
/* Inserts */
|
/* Inserts */
|
||||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||||
long insert(Entity entity);
|
long insert(Entity entity);
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||||
List<Long> insertAll(Entity... entities);
|
List<Long> insertAll(Entity... entities);
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||||
List<Long> insertAll(Collection<Entity> entities);
|
List<Long> insertAll(Collection<Entity> entities);
|
||||||
|
|
||||||
/* Searches */
|
/* Searches */
|
||||||
|
@ -49,7 +49,7 @@ public final class Converters {
|
|||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static FeedGroupIcon feedGroupIconOf(final Integer id) {
|
public static FeedGroupIcon feedGroupIconOf(final Integer id) {
|
||||||
for (FeedGroupIcon icon : FeedGroupIcon.values()) {
|
for (final FeedGroupIcon icon : FeedGroupIcon.values()) {
|
||||||
if (icon.getId() == id) {
|
if (icon.getId() == id) {
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.room.migration.Migration;
|
import androidx.room.migration.Migration;
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||||
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.MainActivity;
|
||||||
|
|
||||||
public final class Migrations {
|
public final class Migrations {
|
||||||
public static final int DB_VER_1 = 1;
|
public static final int DB_VER_1 = 1;
|
||||||
@ -14,7 +14,7 @@ public final class Migrations {
|
|||||||
public static final int DB_VER_3 = 3;
|
public static final int DB_VER_3 = 3;
|
||||||
|
|
||||||
private static final String TAG = Migrations.class.getName();
|
private static final String TAG = Migrations.class.getName();
|
||||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
public static final boolean DEBUG = MainActivity.DEBUG;
|
||||||
|
|
||||||
public static final Migration MIGRATION_1_2 = new Migration(DB_VER_1, DB_VER_2) {
|
public static final Migration MIGRATION_1_2 = new Migration(DB_VER_1, DB_VER_2) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,7 +1,33 @@
|
|||||||
package org.schabi.newpipe.database.playlist;
|
package org.schabi.newpipe.database.playlist;
|
||||||
|
|
||||||
import org.schabi.newpipe.database.LocalItem;
|
import org.schabi.newpipe.database.LocalItem;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface PlaylistLocalItem extends LocalItem {
|
public interface PlaylistLocalItem extends LocalItem {
|
||||||
String getOrderingName();
|
String getOrderingName();
|
||||||
|
|
||||||
|
static List<PlaylistLocalItem> merge(
|
||||||
|
final List<PlaylistMetadataEntry> localPlaylists,
|
||||||
|
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||||
|
final List<PlaylistLocalItem> items = new ArrayList<>(
|
||||||
|
localPlaylists.size() + remotePlaylists.size());
|
||||||
|
items.addAll(localPlaylists);
|
||||||
|
items.addAll(remotePlaylists);
|
||||||
|
|
||||||
|
Collections.sort(items, (left, right) -> {
|
||||||
|
final String on1 = left.getOrderingName();
|
||||||
|
final String on2 = right.getOrderingName();
|
||||||
|
if (on1 == null) {
|
||||||
|
return on2 == null ? 0 : 1;
|
||||||
|
} else {
|
||||||
|
return on2 == null ? -1 : on1.compareToIgnoreCase(on2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,45 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
|||||||
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
|
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
|
||||||
abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
|
abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT * FROM subscriptions
|
||||||
|
|
||||||
|
WHERE name LIKE '%' || :filter || '%'
|
||||||
|
|
||||||
|
ORDER BY name COLLATE NOCASE ASC
|
||||||
|
""")
|
||||||
|
abstract fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT * FROM subscriptions s
|
||||||
|
|
||||||
|
LEFT JOIN feed_group_subscription_join fgs
|
||||||
|
ON s.uid = fgs.subscription_id
|
||||||
|
|
||||||
|
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
|
||||||
|
|
||||||
|
ORDER BY name COLLATE NOCASE ASC
|
||||||
|
""")
|
||||||
|
abstract fun getSubscriptionsOnlyUngrouped(
|
||||||
|
currentGroupId: Long
|
||||||
|
): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT * FROM subscriptions s
|
||||||
|
|
||||||
|
LEFT JOIN feed_group_subscription_join fgs
|
||||||
|
ON s.uid = fgs.subscription_id
|
||||||
|
|
||||||
|
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
|
||||||
|
AND s.name LIKE '%' || :filter || '%'
|
||||||
|
|
||||||
|
ORDER BY name COLLATE NOCASE ASC
|
||||||
|
""")
|
||||||
|
abstract fun getSubscriptionsOnlyUngroupedFiltered(
|
||||||
|
currentGroupId: Long,
|
||||||
|
filter: String
|
||||||
|
): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
||||||
abstract fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>
|
abstract fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
@ -52,7 +91,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
|||||||
entity.uid = uidFromInsert
|
entity.uid = uidFromInsert
|
||||||
} else {
|
} else {
|
||||||
val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url)
|
val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url)
|
||||||
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
||||||
entity.uid = subscriptionIdFromDb
|
entity.uid = subscriptionIdFromDb
|
||||||
|
|
||||||
update(entity)
|
update(entity)
|
||||||
|
@ -50,7 +50,7 @@ public class SubscriptionEntity {
|
|||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
public static SubscriptionEntity from(@NonNull final ChannelInfo info) {
|
public static SubscriptionEntity from(@NonNull final ChannelInfo info) {
|
||||||
SubscriptionEntity result = new SubscriptionEntity();
|
final SubscriptionEntity result = new SubscriptionEntity();
|
||||||
result.setServiceId(info.getServiceId());
|
result.setServiceId(info.getServiceId());
|
||||||
result.setUrl(info.getUrl());
|
result.setUrl(info.getUrl());
|
||||||
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(),
|
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(),
|
||||||
@ -124,10 +124,61 @@ public class SubscriptionEntity {
|
|||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
public ChannelInfoItem toChannelInfoItem() {
|
public ChannelInfoItem toChannelInfoItem() {
|
||||||
ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
|
final ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
|
||||||
item.setThumbnailUrl(getAvatarUrl());
|
item.setThumbnailUrl(getAvatarUrl());
|
||||||
item.setSubscriberCount(getSubscriberCount());
|
item.setSubscriberCount(getSubscriberCount());
|
||||||
item.setDescription(getDescription());
|
item.setDescription(getDescription());
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Remove these generated methods by migrating this class to a data class from Kotlin.
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("EqualsReplaceableByObjectsCall")
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final SubscriptionEntity that = (SubscriptionEntity) o;
|
||||||
|
|
||||||
|
if (uid != that.uid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (serviceId != that.serviceId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!url.equals(that.url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (name != null ? !name.equals(that.name) : that.name != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (avatarUrl != null ? !avatarUrl.equals(that.avatarUrl) : that.avatarUrl != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (subscriberCount != null
|
||||||
|
? !subscriberCount.equals(that.subscriberCount)
|
||||||
|
: that.subscriberCount != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return description != null
|
||||||
|
? description.equals(that.description)
|
||||||
|
: that.description == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = (int) (uid ^ (uid >>> 32));
|
||||||
|
result = 31 * result + serviceId;
|
||||||
|
result = 31 * result + url.hashCode();
|
||||||
|
result = 31 * result + (name != null ? name.hashCode() : 0);
|
||||||
|
result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0);
|
||||||
|
result = 31 * result + (subscriberCount != null ? subscriberCount.hashCode() : 0);
|
||||||
|
result = 31 * result + (description != null ? description.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.schabi.newpipe.download;
|
package org.schabi.newpipe.download;
|
||||||
|
|
||||||
import android.app.FragmentTransaction;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@ -11,9 +10,10 @@ import android.view.ViewTreeObserver;
|
|||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
import org.schabi.newpipe.views.FocusOverlayView;
|
import org.schabi.newpipe.views.FocusOverlayView;
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ public class DownloadActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
// Service
|
// Service
|
||||||
Intent i = new Intent();
|
final Intent i = new Intent();
|
||||||
i.setClass(this, DownloadManagerService.class);
|
i.setClass(this, DownloadManagerService.class);
|
||||||
startService(i);
|
startService(i);
|
||||||
|
|
||||||
@ -38,10 +38,10 @@ public class DownloadActivity extends AppCompatActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_downloader);
|
setContentView(R.layout.activity_downloader);
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
final ActionBar actionBar = getSupportActionBar();
|
||||||
if (actionBar != null) {
|
if (actionBar != null) {
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
actionBar.setTitle(R.string.downloads_title);
|
actionBar.setTitle(R.string.downloads_title);
|
||||||
@ -57,13 +57,13 @@ public class DownloadActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (AndroidTvUtils.isTv(this)) {
|
if (DeviceUtils.isTv(this)) {
|
||||||
FocusOverlayView.setupFocusObserver(this);
|
FocusOverlayView.setupFocusObserver(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateFragments() {
|
private void updateFragments() {
|
||||||
MissionsFragment fragment = new MissionsFragment();
|
final MissionsFragment fragment = new MissionsFragment();
|
||||||
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG)
|
.replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG)
|
||||||
@ -74,7 +74,7 @@ public class DownloadActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
MenuInflater inflater = getMenuInflater();
|
final MenuInflater inflater = getMenuInflater();
|
||||||
|
|
||||||
inflater.inflate(R.menu.download_menu, menu);
|
inflater.inflate(R.menu.download_menu, menu);
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import android.net.Uri;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -124,7 +124,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
|
|
||||||
public static DownloadDialog newInstance(final StreamInfo info) {
|
public static DownloadDialog newInstance(final StreamInfo info) {
|
||||||
DownloadDialog dialog = new DownloadDialog();
|
final DownloadDialog dialog = new DownloadDialog();
|
||||||
dialog.setInfo(info);
|
dialog.setInfo(info);
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
@ -208,14 +208,15 @@ public class DownloadDialog extends DialogFragment
|
|||||||
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
|
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
|
||||||
Icepick.restoreInstanceState(this, savedInstanceState);
|
Icepick.restoreInstanceState(this, savedInstanceState);
|
||||||
|
|
||||||
SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams = new SparseArray<>(4);
|
final SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams
|
||||||
List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
= new SparseArray<>(4);
|
||||||
|
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
||||||
|
|
||||||
for (int i = 0; i < videoStreams.size(); i++) {
|
for (int i = 0; i < videoStreams.size(); i++) {
|
||||||
if (!videoStreams.get(i).isVideoOnly()) {
|
if (!videoStreams.get(i).isVideoOnly()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
AudioStream audioStream = SecondaryStreamHelper
|
final AudioStream audioStream = SecondaryStreamHelper
|
||||||
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
|
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
|
||||||
|
|
||||||
if (audioStream != null) {
|
if (audioStream != null) {
|
||||||
@ -232,13 +233,13 @@ public class DownloadDialog extends DialogFragment
|
|||||||
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
|
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
|
||||||
this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
|
this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
|
||||||
|
|
||||||
Intent intent = new Intent(context, DownloadManagerService.class);
|
final Intent intent = new Intent(context, DownloadManagerService.class);
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
|
|
||||||
context.bindService(intent, new ServiceConnection() {
|
context.bindService(intent, new ServiceConnection() {
|
||||||
@Override
|
@Override
|
||||||
public void onServiceConnected(final ComponentName cname, final IBinder service) {
|
public void onServiceConnected(final ComponentName cname, final IBinder service) {
|
||||||
DownloadManagerBinder mgr = (DownloadManagerBinder) service;
|
final DownloadManagerBinder mgr = (DownloadManagerBinder) service;
|
||||||
|
|
||||||
mainStorageAudio = mgr.getMainStorageAudio();
|
mainStorageAudio = mgr.getMainStorageAudio();
|
||||||
mainStorageVideo = mgr.getMainStorageVideo();
|
mainStorageVideo = mgr.getMainStorageVideo();
|
||||||
@ -294,9 +295,9 @@ public class DownloadDialog extends DialogFragment
|
|||||||
initToolbar(view.findViewById(R.id.toolbar));
|
initToolbar(view.findViewById(R.id.toolbar));
|
||||||
setupDownloadOptions();
|
setupDownloadOptions();
|
||||||
|
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
|
||||||
|
|
||||||
int threads = prefs.getInt(getString(R.string.default_download_threads), 3);
|
final int threads = prefs.getInt(getString(R.string.default_download_threads), 3);
|
||||||
threadsCountTextView.setText(String.valueOf(threads));
|
threadsCountTextView.setText(String.valueOf(threads));
|
||||||
threadsSeekBar.setProgress(threads - 1);
|
threadsSeekBar.setProgress(threads - 1);
|
||||||
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||||
@ -373,13 +374,13 @@ public class DownloadDialog extends DialogFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) {
|
if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) {
|
||||||
File file = Utils.getFileForUri(data.getData());
|
final File file = Utils.getFileForUri(data.getData());
|
||||||
checkSelectedDownload(null, Uri.fromFile(file), file.getName(),
|
checkSelectedDownload(null, Uri.fromFile(file), file.getName(),
|
||||||
StoredFileHelper.DEFAULT_MIME);
|
StoredFileHelper.DEFAULT_MIME);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentFile docFile = DocumentFile.fromSingleUri(context, data.getData());
|
final DocumentFile docFile = DocumentFile.fromSingleUri(context, data.getData());
|
||||||
if (docFile == null) {
|
if (docFile == null) {
|
||||||
showFailedDialog(R.string.general_error);
|
showFailedDialog(R.string.general_error);
|
||||||
return;
|
return;
|
||||||
@ -515,7 +516,23 @@ public class DownloadDialog extends DialogFragment
|
|||||||
videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE);
|
videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE);
|
||||||
subtitleButton.setVisibility(isSubtitleStreamsAvailable ? View.VISIBLE : View.GONE);
|
subtitleButton.setVisibility(isSubtitleStreamsAvailable ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (isVideoStreamsAvailable) {
|
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||||
|
final String defaultMedia = prefs.getString(getString(R.string.last_used_download_type),
|
||||||
|
getString(R.string.last_download_type_video_key));
|
||||||
|
|
||||||
|
if (isVideoStreamsAvailable
|
||||||
|
&& (defaultMedia.equals(getString(R.string.last_download_type_video_key)))) {
|
||||||
|
videoButton.setChecked(true);
|
||||||
|
setupVideoSpinner();
|
||||||
|
} else if (isAudioStreamsAvailable
|
||||||
|
&& (defaultMedia.equals(getString(R.string.last_download_type_audio_key)))) {
|
||||||
|
audioButton.setChecked(true);
|
||||||
|
setupAudioSpinner();
|
||||||
|
} else if (isSubtitleStreamsAvailable
|
||||||
|
&& (defaultMedia.equals(getString(R.string.last_download_type_subtitle_key)))) {
|
||||||
|
subtitleButton.setChecked(true);
|
||||||
|
setupSubtitleSpinner();
|
||||||
|
} else if (isVideoStreamsAvailable) {
|
||||||
videoButton.setChecked(true);
|
videoButton.setChecked(true);
|
||||||
setupVideoSpinner();
|
setupVideoSpinner();
|
||||||
} else if (isAudioStreamsAvailable) {
|
} else if (isAudioStreamsAvailable) {
|
||||||
@ -564,7 +581,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getNameEditText() {
|
private String getNameEditText() {
|
||||||
String str = nameEditText.getText().toString().trim();
|
final String str = nameEditText.getText().toString().trim();
|
||||||
|
|
||||||
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
|
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
|
||||||
}
|
}
|
||||||
@ -591,9 +608,10 @@ public class DownloadDialog extends DialogFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void prepareSelectedDownload() {
|
private void prepareSelectedDownload() {
|
||||||
StoredDirectoryHelper mainStorage;
|
final StoredDirectoryHelper mainStorage;
|
||||||
MediaFormat format;
|
final MediaFormat format;
|
||||||
String mime;
|
final String mime;
|
||||||
|
final String selectedMediaType;
|
||||||
|
|
||||||
// first, build the filename and get the output folder (if possible)
|
// first, build the filename and get the output folder (if possible)
|
||||||
// later, run a very very very large file checking logic
|
// later, run a very very very large file checking logic
|
||||||
@ -602,6 +620,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
|
|
||||||
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
|
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
|
||||||
case R.id.audio_button:
|
case R.id.audio_button:
|
||||||
|
selectedMediaType = getString(R.string.last_download_type_audio_key);
|
||||||
mainStorage = mainStorageAudio;
|
mainStorage = mainStorageAudio;
|
||||||
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
|
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
|
||||||
switch (format) {
|
switch (format) {
|
||||||
@ -616,12 +635,14 @@ public class DownloadDialog extends DialogFragment
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case R.id.video_button:
|
case R.id.video_button:
|
||||||
|
selectedMediaType = getString(R.string.last_download_type_video_key);
|
||||||
mainStorage = mainStorageVideo;
|
mainStorage = mainStorageVideo;
|
||||||
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
|
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
|
||||||
mime = format.mimeType;
|
mime = format.mimeType;
|
||||||
filename += format.suffix;
|
filename += format.suffix;
|
||||||
break;
|
break;
|
||||||
case R.id.subtitle_button:
|
case R.id.subtitle_button:
|
||||||
|
selectedMediaType = getString(R.string.last_download_type_subtitle_key);
|
||||||
mainStorage = mainStorageVideo; // subtitle & video files go together
|
mainStorage = mainStorageVideo; // subtitle & video files go together
|
||||||
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
|
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
|
||||||
mime = format.mimeType;
|
mime = format.mimeType;
|
||||||
@ -663,6 +684,11 @@ public class DownloadDialog extends DialogFragment
|
|||||||
|
|
||||||
// check for existing file with the same name
|
// check for existing file with the same name
|
||||||
checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime);
|
checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime);
|
||||||
|
|
||||||
|
// remember the last media type downloaded by the user
|
||||||
|
prefs.edit()
|
||||||
|
.putString(getString(R.string.last_used_download_type), selectedMediaType)
|
||||||
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
|
private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
|
||||||
@ -683,15 +709,17 @@ public class DownloadDialog extends DialogFragment
|
|||||||
storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile,
|
storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile,
|
||||||
mainStorage.getTag());
|
mainStorage.getTag());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
showErrorActivity(e);
|
showErrorActivity(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if is our file
|
// check if is our file
|
||||||
MissionState state = downloadManager.checkForExistingMission(storage);
|
final MissionState state = downloadManager.checkForExistingMission(storage);
|
||||||
@StringRes int msgBtn;
|
@StringRes
|
||||||
@StringRes int msgBody;
|
final int msgBtn;
|
||||||
|
@StringRes
|
||||||
|
final int msgBody;
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case Finished:
|
case Finished:
|
||||||
@ -744,8 +772,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
|
||||||
AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
|
|
||||||
.setTitle(R.string.download_dialog_title)
|
.setTitle(R.string.download_dialog_title)
|
||||||
.setMessage(msgBody)
|
.setMessage(msgBody)
|
||||||
.setNegativeButton(android.R.string.cancel, null);
|
.setNegativeButton(android.R.string.cancel, null);
|
||||||
@ -787,7 +814,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
// try take (or steal) the file
|
// try take (or steal) the file
|
||||||
storageNew = new StoredFileHelper(context, mainStorage.getUri(),
|
storageNew = new StoredFileHelper(context, mainStorage.getUri(),
|
||||||
targetFile, mainStorage.getTag());
|
targetFile, mainStorage.getTag());
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
Log.e(TAG, "Failed to take (or steal) the file in "
|
Log.e(TAG, "Failed to take (or steal) the file in "
|
||||||
+ targetFile.toString());
|
+ targetFile.toString());
|
||||||
storageNew = null;
|
storageNew = null;
|
||||||
@ -825,18 +852,18 @@ public class DownloadDialog extends DialogFragment
|
|||||||
if (storage.length() > 0) {
|
if (storage.length() > 0) {
|
||||||
storage.truncate();
|
storage.truncate();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e);
|
Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e);
|
||||||
showFailedDialog(R.string.overwrite_failed);
|
showFailedDialog(R.string.overwrite_failed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream selectedStream;
|
final Stream selectedStream;
|
||||||
Stream secondaryStream = null;
|
Stream secondaryStream = null;
|
||||||
char kind;
|
final char kind;
|
||||||
int threads = threadsSeekBar.getProgress() + 1;
|
int threads = threadsSeekBar.getProgress() + 1;
|
||||||
String[] urls;
|
final String[] urls;
|
||||||
MissionRecoveryInfo[] recoveryInfo;
|
final MissionRecoveryInfo[] recoveryInfo;
|
||||||
String psName = null;
|
String psName = null;
|
||||||
String[] psArgs = null;
|
String[] psArgs = null;
|
||||||
long nearLength = 0;
|
long nearLength = 0;
|
||||||
@ -857,7 +884,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
kind = 'v';
|
kind = 'v';
|
||||||
selectedStream = videoStreamsAdapter.getItem(selectedVideoIndex);
|
selectedStream = videoStreamsAdapter.getItem(selectedVideoIndex);
|
||||||
|
|
||||||
SecondaryStreamHelper<AudioStream> secondary = videoStreamsAdapter
|
final SecondaryStreamHelper<AudioStream> secondary = videoStreamsAdapter
|
||||||
.getAllSecondary()
|
.getAllSecondary()
|
||||||
.get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream));
|
.get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream));
|
||||||
|
|
||||||
@ -871,7 +898,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
psArgs = null;
|
psArgs = null;
|
||||||
long videoSize = wrappedVideoStreams
|
final long videoSize = wrappedVideoStreams
|
||||||
.getSizeInBytes((VideoStream) selectedStream);
|
.getSizeInBytes((VideoStream) selectedStream);
|
||||||
|
|
||||||
// set nearLength, only, if both sizes are fetched or known. This probably
|
// set nearLength, only, if both sizes are fetched or known. This probably
|
||||||
|
@ -230,7 +230,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||||||
}
|
}
|
||||||
Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
||||||
// Starting ReCaptcha Challenge Activity
|
// Starting ReCaptcha Challenge Activity
|
||||||
Intent intent = new Intent(activity, ReCaptchaActivity.class);
|
final Intent intent = new Intent(activity, ReCaptchaActivity.class);
|
||||||
intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, exception.getUrl());
|
intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, exception.getUrl());
|
||||||
startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST);
|
startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST);
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ package org.schabi.newpipe.fragments;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@ -74,7 +74,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
|
|
||||||
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
|
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
|
||||||
previousYoutubeRestrictedModeEnabled =
|
previousYoutubeRestrictedModeEnabled =
|
||||||
PreferenceManager.getDefaultSharedPreferences(getContext())
|
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,8 +104,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
boolean youtubeRestrictedModeEnabled =
|
final boolean youtubeRestrictedModeEnabled =
|
||||||
PreferenceManager.getDefaultSharedPreferences(getContext())
|
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
||||||
if (previousYoutubeRestrictedModeEnabled != youtubeRestrictedModeEnabled) {
|
if (previousYoutubeRestrictedModeEnabled != youtubeRestrictedModeEnabled) {
|
||||||
previousYoutubeRestrictedModeEnabled = youtubeRestrictedModeEnabled;
|
previousYoutubeRestrictedModeEnabled = youtubeRestrictedModeEnabled;
|
||||||
@ -137,7 +137,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
}
|
}
|
||||||
inflater.inflate(R.menu.main_fragment_menu, menu);
|
inflater.inflate(R.menu.main_fragment_menu, menu);
|
||||||
|
|
||||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
if (supportActionBar != null) {
|
if (supportActionBar != null) {
|
||||||
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
||||||
}
|
}
|
||||||
@ -148,11 +148,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_search:
|
case R.id.action_search:
|
||||||
try {
|
try {
|
||||||
NavigationHelper.openSearchFragment(
|
NavigationHelper.openSearchFragment(getFM(),
|
||||||
getFragmentManager(),
|
ServiceHelper.getSelectedServiceId(activity), "");
|
||||||
ServiceHelper.getSelectedServiceId(activity),
|
} catch (final Exception e) {
|
||||||
"");
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -239,7 +237,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
Fragment fragment = null;
|
Fragment fragment = null;
|
||||||
try {
|
try {
|
||||||
fragment = tab.getFragment(context);
|
fragment = tab.getFragment(context);
|
||||||
} catch (ExtractionException e) {
|
} catch (final ExtractionException e) {
|
||||||
throwable = e;
|
throwable = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,9 +14,9 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
|
|||||||
super.onScrolled(recyclerView, dx, dy);
|
super.onScrolled(recyclerView, dx, dy);
|
||||||
if (dy > 0) {
|
if (dy > 0) {
|
||||||
int pastVisibleItems = 0;
|
int pastVisibleItems = 0;
|
||||||
int visibleItemCount;
|
final int visibleItemCount;
|
||||||
int totalItemCount;
|
final int totalItemCount;
|
||||||
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||||
|
|
||||||
visibleItemCount = layoutManager.getChildCount();
|
visibleItemCount = layoutManager.getChildCount();
|
||||||
totalItemCount = layoutManager.getItemCount();
|
totalItemCount = layoutManager.getItemCount();
|
||||||
@ -26,7 +26,7 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
|
|||||||
pastVisibleItems = ((LinearLayoutManager) layoutManager)
|
pastVisibleItems = ((LinearLayoutManager) layoutManager)
|
||||||
.findFirstVisibleItemPosition();
|
.findFirstVisibleItemPosition();
|
||||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||||
int[] positions = ((StaggeredGridLayoutManager) layoutManager)
|
final int[] positions = ((StaggeredGridLayoutManager) layoutManager)
|
||||||
.findFirstVisibleItemPositions(null);
|
.findFirstVisibleItemPositions(null);
|
||||||
if (positions != null && positions.length > 0) {
|
if (positions != null && positions.length > 0) {
|
||||||
pastVisibleItems = positions[0];
|
pastVisibleItems = positions[0];
|
||||||
|
@ -1,16 +1,29 @@
|
|||||||
package org.schabi.newpipe.fragments.detail;
|
package org.schabi.newpipe.fragments.detail;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
class StackItem implements Serializable {
|
class StackItem implements Serializable {
|
||||||
private final int serviceId;
|
private final int serviceId;
|
||||||
private final String url;
|
private String url;
|
||||||
private String title;
|
private String title;
|
||||||
|
private PlayQueue playQueue;
|
||||||
|
|
||||||
StackItem(final int serviceId, final String url, final String title) {
|
StackItem(final int serviceId, final String url,
|
||||||
|
final String title, final PlayQueue playQueue) {
|
||||||
this.serviceId = serviceId;
|
this.serviceId = serviceId;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
|
this.playQueue = playQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(final String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlayQueue(final PlayQueue queue) {
|
||||||
|
this.playQueue = queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getServiceId() {
|
public int getServiceId() {
|
||||||
@ -29,6 +42,10 @@ class StackItem implements Serializable {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PlayQueue getPlayQueue() {
|
||||||
|
return playQueue;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getServiceId() + ":" + getUrl() + " > " + getTitle();
|
return getServiceId() + ":" + getUrl() + " > " + getTitle();
|
||||||
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.fragments.detail;
|
|||||||
|
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
@ -10,16 +11,20 @@ import androidx.fragment.app.FragmentPagerAdapter;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class TabAdaptor extends FragmentPagerAdapter {
|
public class TabAdapter extends FragmentPagerAdapter {
|
||||||
private final List<Fragment> mFragmentList = new ArrayList<>();
|
private final List<Fragment> mFragmentList = new ArrayList<>();
|
||||||
private final List<String> mFragmentTitleList = new ArrayList<>();
|
private final List<String> mFragmentTitleList = new ArrayList<>();
|
||||||
private final FragmentManager fragmentManager;
|
private final FragmentManager fragmentManager;
|
||||||
|
|
||||||
public TabAdaptor(final FragmentManager fm) {
|
public TabAdapter(final FragmentManager fm) {
|
||||||
super(fm);
|
// if changed to BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT => crash if enqueueing stream in
|
||||||
|
// the background and then clicking on it to open VideoDetailFragment:
|
||||||
|
// "Cannot setMaxLifecycle for Fragment not attached to FragmentManager"
|
||||||
|
super(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
|
||||||
this.fragmentManager = fm;
|
this.fragmentManager = fm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Fragment getItem(final int position) {
|
public Fragment getItem(final int position) {
|
||||||
return mFragmentList.get(position);
|
return mFragmentList.get(position);
|
||||||
@ -50,14 +55,14 @@ public class TabAdaptor extends FragmentPagerAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updateItem(final String title, final Fragment fragment) {
|
public void updateItem(final String title, final Fragment fragment) {
|
||||||
int index = mFragmentTitleList.indexOf(title);
|
final int index = mFragmentTitleList.indexOf(title);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
updateItem(index, fragment);
|
updateItem(index, fragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemPosition(final Object object) {
|
public int getItemPosition(@NonNull final Object object) {
|
||||||
if (mFragmentList.contains(object)) {
|
if (mFragmentList.contains(object)) {
|
||||||
return mFragmentList.indexOf(object);
|
return mFragmentList.indexOf(object);
|
||||||
} else {
|
} else {
|
||||||
@ -82,7 +87,9 @@ public class TabAdaptor extends FragmentPagerAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyItem(final ViewGroup container, final int position, final Object object) {
|
public void destroyItem(@NonNull final ViewGroup container,
|
||||||
|
final int position,
|
||||||
|
@NonNull final Object object) {
|
||||||
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
|
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@ import android.content.SharedPreferences;
|
|||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@ -136,7 +136,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
final RecyclerView.ViewHolder itemHolder =
|
final RecyclerView.ViewHolder itemHolder =
|
||||||
itemsList.findContainingViewHolder(focusedItem);
|
itemsList.findContainingViewHolder(focusedItem);
|
||||||
return itemHolder.getAdapterPosition();
|
return itemHolder.getAdapterPosition();
|
||||||
} catch (NullPointerException e) {
|
} catch (final NullPointerException e) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
}
|
}
|
||||||
|
|
||||||
itemsList.post(() -> {
|
itemsList.post(() -> {
|
||||||
RecyclerView.ViewHolder focusedHolder =
|
final RecyclerView.ViewHolder focusedHolder =
|
||||||
itemsList.findViewHolderForAdapterPosition(position);
|
itemsList.findViewHolderForAdapterPosition(position);
|
||||||
|
|
||||||
if (focusedHolder != null) {
|
if (focusedHolder != null) {
|
||||||
@ -279,7 +279,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
selectedItem.getServiceId(),
|
selectedItem.getServiceId(),
|
||||||
selectedItem.getUrl(),
|
selectedItem.getUrl(),
|
||||||
selectedItem.getName());
|
selectedItem.getName());
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -294,7 +294,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
selectedItem.getServiceId(),
|
selectedItem.getServiceId(),
|
||||||
selectedItem.getUrl(),
|
selectedItem.getUrl(),
|
||||||
selectedItem.getName());
|
selectedItem.getName());
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -367,7 +367,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
|
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
|
||||||
}
|
}
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
if (supportActionBar != null) {
|
if (supportActionBar != null) {
|
||||||
supportActionBar.setDisplayShowTitleEnabled(true);
|
supportActionBar.setDisplayShowTitleEnabled(true);
|
||||||
if (useAsFrontPage) {
|
if (useAsFrontPage) {
|
||||||
|
@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.ListInfo;
|
import org.schabi.newpipe.extractor.ListInfo;
|
||||||
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.views.NewPipeRecyclerView;
|
import org.schabi.newpipe.views.NewPipeRecyclerView;
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
protected String url;
|
protected String url;
|
||||||
|
|
||||||
protected I currentInfo;
|
protected I currentInfo;
|
||||||
protected String currentNextPageUrl;
|
protected Page currentNextPage;
|
||||||
protected Disposable currentWorker;
|
protected Disposable currentWorker;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -78,7 +79,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
public void writeTo(final Queue<Object> objectsToSave) {
|
public void writeTo(final Queue<Object> objectsToSave) {
|
||||||
super.writeTo(objectsToSave);
|
super.writeTo(objectsToSave);
|
||||||
objectsToSave.add(currentInfo);
|
objectsToSave.add(currentInfo);
|
||||||
objectsToSave.add(currentNextPageUrl);
|
objectsToSave.add(currentNextPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -86,7 +87,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||||
super.readFrom(savedObjects);
|
super.readFrom(savedObjects);
|
||||||
currentInfo = (I) savedObjects.poll();
|
currentInfo = (I) savedObjects.poll();
|
||||||
currentNextPageUrl = (String) savedObjects.poll();
|
currentNextPage = (Page) savedObjects.poll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -130,9 +131,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
.subscribe((@NonNull I result) -> {
|
.subscribe((@NonNull I result) -> {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
currentInfo = result;
|
currentInfo = result;
|
||||||
currentNextPageUrl = result.getNextPageUrl();
|
currentNextPage = result.getNextPage();
|
||||||
handleResult(result);
|
handleResult(result);
|
||||||
}, (@NonNull Throwable throwable) -> onError(throwable));
|
}, this::onError);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -157,11 +158,10 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doFinally(this::allowDownwardFocusScroll)
|
.doFinally(this::allowDownwardFocusScroll)
|
||||||
.subscribe((@io.reactivex.annotations.NonNull
|
.subscribe((@NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
||||||
ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
handleNextItems(InfoItemsPage);
|
handleNextItems(InfoItemsPage);
|
||||||
}, (@io.reactivex.annotations.NonNull Throwable throwable) -> {
|
}, (@NonNull Throwable throwable) -> {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
onError(throwable);
|
onError(throwable);
|
||||||
});
|
});
|
||||||
@ -182,7 +182,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
@Override
|
@Override
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
currentNextPageUrl = result.getNextPageUrl();
|
currentNextPage = result.getNextPage();
|
||||||
infoListAdapter.addInfoItemList(result.getItems());
|
infoListAdapter.addInfoItemList(result.getItems());
|
||||||
|
|
||||||
showListFooter(hasMoreItems());
|
showListFooter(hasMoreItems());
|
||||||
@ -190,7 +190,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean hasMoreItems() {
|
protected boolean hasMoreItems() {
|
||||||
return !TextUtils.isEmpty(currentNextPageUrl);
|
return Page.isValid(currentNextPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -98,7 +98,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
|
|
||||||
public static ChannelFragment getInstance(final int serviceId, final String url,
|
public static ChannelFragment getInstance(final int serviceId, final String url,
|
||||||
final String name) {
|
final String name) {
|
||||||
ChannelFragment instance = new ChannelFragment();
|
final ChannelFragment instance = new ChannelFragment();
|
||||||
instance.setInitialData(serviceId, url, name);
|
instance.setInitialData(serviceId, url, name);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@ -189,7 +189,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
if (useAsFrontPage && supportActionBar != null) {
|
if (useAsFrontPage && supportActionBar != null) {
|
||||||
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
||||||
} else {
|
} else {
|
||||||
@ -206,7 +206,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
private void openRssFeed() {
|
private void openRssFeed() {
|
||||||
final ChannelInfo info = currentInfo;
|
final ChannelInfo info = currentInfo;
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl()));
|
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl()));
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,7 +345,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "No subscription to this channel!");
|
Log.d(TAG, "No subscription to this channel!");
|
||||||
}
|
}
|
||||||
SubscriptionEntity channel = new SubscriptionEntity();
|
final SubscriptionEntity channel = new SubscriptionEntity();
|
||||||
channel.setServiceId(info.getServiceId());
|
channel.setServiceId(info.getServiceId());
|
||||||
channel.setUrl(info.getUrl());
|
channel.setUrl(info.getUrl());
|
||||||
channel.setData(info.getName(),
|
channel.setData(info.getName(),
|
||||||
@ -371,16 +371,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
+ "isSubscribed = [" + isSubscribed + "]");
|
+ "isSubscribed = [" + isSubscribed + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
|
final boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
|
||||||
int backgroundDuration = isButtonVisible ? 300 : 0;
|
final int backgroundDuration = isButtonVisible ? 300 : 0;
|
||||||
int textDuration = isButtonVisible ? 200 : 0;
|
final int textDuration = isButtonVisible ? 200 : 0;
|
||||||
|
|
||||||
int subscribeBackground = ThemeHelper
|
final int subscribeBackground = ThemeHelper
|
||||||
.resolveColorFromAttr(activity, R.attr.colorPrimary);
|
.resolveColorFromAttr(activity, R.attr.colorPrimary);
|
||||||
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
|
final int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
|
||||||
int subscribedBackground = ContextCompat
|
final int subscribedBackground = ContextCompat
|
||||||
.getColor(activity, R.color.subscribed_background_color);
|
.getColor(activity, R.color.subscribed_background_color);
|
||||||
int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
|
final int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
|
||||||
|
|
||||||
if (!isSubscribed) {
|
if (!isSubscribed) {
|
||||||
headerSubscribeButton.setText(R.string.subscribe_button_title);
|
headerSubscribeButton.setText(R.string.subscribe_button_title);
|
||||||
@ -403,7 +403,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||||
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPageUrl);
|
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -426,10 +426,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
case R.id.sub_channel_title_view:
|
case R.id.sub_channel_title_view:
|
||||||
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
|
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
|
||||||
try {
|
try {
|
||||||
NavigationHelper.openChannelFragment(getFragmentManager(),
|
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
|
||||||
currentInfo.getServiceId(), currentInfo.getParentChannelUrl(),
|
currentInfo.getParentChannelUrl(),
|
||||||
currentInfo.getParentChannelName());
|
currentInfo.getParentChannelName());
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||||
}
|
}
|
||||||
} else if (DEBUG) {
|
} else if (DEBUG) {
|
||||||
@ -490,13 +490,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
|
|
||||||
playlistCtrl.setVisibility(View.VISIBLE);
|
playlistCtrl.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
List<Throwable> errors = new ArrayList<>(result.getErrors());
|
final List<Throwable> errors = new ArrayList<>(result.getErrors());
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
|
|
||||||
// handling ContentNotSupportedException not to show the error but an appropriate string
|
// handling ContentNotSupportedException not to show the error but an appropriate string
|
||||||
// so that crashes won't be sent uselessly and the user will understand what happened
|
// so that crashes won't be sent uselessly and the user will understand what happened
|
||||||
for (Iterator<Throwable> it = errors.iterator(); it.hasNext();) {
|
for (Iterator<Throwable> it = errors.iterator(); it.hasNext();) {
|
||||||
Throwable throwable = it.next();
|
final Throwable throwable = it.next();
|
||||||
if (throwable instanceof ContentNotSupportedException) {
|
if (throwable instanceof ContentNotSupportedException) {
|
||||||
showContentNotSupported();
|
showContentNotSupported();
|
||||||
it.remove();
|
it.remove();
|
||||||
@ -519,7 +519,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
monitorSubscription(result);
|
monitorSubscription(result);
|
||||||
|
|
||||||
headerPlayAllButton.setOnClickListener(view -> NavigationHelper
|
headerPlayAllButton.setOnClickListener(view -> NavigationHelper
|
||||||
.playOnMainPlayer(activity, getPlayQueue(), false));
|
.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||||
headerPopupButton.setOnClickListener(view -> NavigationHelper
|
headerPopupButton.setOnClickListener(view -> NavigationHelper
|
||||||
.playOnPopupPlayer(activity, getPlayQueue(), false));
|
.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||||
headerBackgroundButton.setOnClickListener(view -> NavigationHelper
|
headerBackgroundButton.setOnClickListener(view -> NavigationHelper
|
||||||
@ -549,13 +549,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
|
|
||||||
private PlayQueue getPlayQueue(final int index) {
|
private PlayQueue getPlayQueue(final int index) {
|
||||||
final List<StreamInfoItem> streamItems = new ArrayList<>();
|
final List<StreamInfoItem> streamItems = new ArrayList<>();
|
||||||
for (InfoItem i : infoListAdapter.getItemsList()) {
|
for (final InfoItem i : infoListAdapter.getItemsList()) {
|
||||||
if (i instanceof StreamInfoItem) {
|
if (i instanceof StreamInfoItem) {
|
||||||
streamItems.add((StreamInfoItem) i);
|
streamItems.add((StreamInfoItem) i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
|
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
|
||||||
currentInfo.getNextPageUrl(), streamItems, index);
|
currentInfo.getNextPage(), streamItems, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -581,7 +581,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int errorId = exception instanceof ExtractionException
|
final int errorId = exception instanceof ExtractionException
|
||||||
? R.string.parsing_error : R.string.general_error;
|
? R.string.parsing_error : R.string.general_error;
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL,
|
onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL,
|
||||||
|
@ -26,11 +26,9 @@ import io.reactivex.disposables.CompositeDisposable;
|
|||||||
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||||
private CompositeDisposable disposables = new CompositeDisposable();
|
private CompositeDisposable disposables = new CompositeDisposable();
|
||||||
|
|
||||||
private boolean mIsVisibleToUser = false;
|
|
||||||
|
|
||||||
public static CommentsFragment getInstance(final int serviceId, final String url,
|
public static CommentsFragment getInstance(final int serviceId, final String url,
|
||||||
final String name) {
|
final String name) {
|
||||||
CommentsFragment instance = new CommentsFragment();
|
final CommentsFragment instance = new CommentsFragment();
|
||||||
instance.setInitialData(serviceId, url, name);
|
instance.setInitialData(serviceId, url, name);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@ -39,12 +37,6 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
|||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
|
||||||
mIsVisibleToUser = isVisibleToUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(final Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
@ -71,7 +63,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
|
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -92,7 +84,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
|||||||
public void handleResult(@NonNull final CommentsInfo result) {
|
public void handleResult(@NonNull final CommentsInfo result) {
|
||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
|
|
||||||
AnimationUtils.slideUp(getView(), 120, 150, 0.06f);
|
AnimationUtils.slideUp(requireView(), 120, 150, 0.06f);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
|
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
|
||||||
|
@ -44,8 +44,8 @@ public class DefaultKioskFragment extends KioskFragment {
|
|||||||
name = kioskTranslatedName;
|
name = kioskTranslatedName;
|
||||||
|
|
||||||
currentInfo = null;
|
currentInfo = null;
|
||||||
currentNextPageUrl = null;
|
currentNextPage = null;
|
||||||
} catch (ExtractionException e) {
|
} catch (final ExtractionException e) {
|
||||||
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
|
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
|
||||||
"Loading default kiosk from selected service", 0);
|
"Loading default kiosk from selected service", 0);
|
||||||
}
|
}
|
||||||
|
@ -72,9 +72,9 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||||||
|
|
||||||
public static KioskFragment getInstance(final int serviceId, final String kioskId)
|
public static KioskFragment getInstance(final int serviceId, final String kioskId)
|
||||||
throws ExtractionException {
|
throws ExtractionException {
|
||||||
KioskFragment instance = new KioskFragment();
|
final KioskFragment instance = new KioskFragment();
|
||||||
StreamingService service = NewPipe.getService(serviceId);
|
final StreamingService service = NewPipe.getService(serviceId);
|
||||||
ListLinkHandlerFactory kioskLinkHandlerFactory = service.getKioskList()
|
final ListLinkHandlerFactory kioskLinkHandlerFactory = service.getKioskList()
|
||||||
.getListLinkHandlerFactoryByType(kioskId);
|
.getListLinkHandlerFactoryByType(kioskId);
|
||||||
instance.setInitialData(serviceId,
|
instance.setInitialData(serviceId,
|
||||||
kioskLinkHandlerFactory.fromId(kioskId).getUrl(), kioskId);
|
kioskLinkHandlerFactory.fromId(kioskId).getUrl(), kioskId);
|
||||||
@ -101,7 +101,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||||||
if (useAsFrontPage && isVisibleToUser && activity != null) {
|
if (useAsFrontPage && isVisibleToUser && activity != null) {
|
||||||
try {
|
try {
|
||||||
setTitle(kioskTranslatedName);
|
setTitle(kioskTranslatedName);
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
onUnrecoverableError(e, UserAction.UI_ERROR,
|
onUnrecoverableError(e, UserAction.UI_ERROR,
|
||||||
"none",
|
"none",
|
||||||
"none", R.string.app_ui_crash);
|
"none", R.string.app_ui_crash);
|
||||||
@ -132,7 +132,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
if (supportActionBar != null && useAsFrontPage) {
|
if (supportActionBar != null && useAsFrontPage) {
|
||||||
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
||||||
}
|
}
|
||||||
@ -150,7 +150,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||||
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPageUrl);
|
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -85,7 +85,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
|
|
||||||
public static PlaylistFragment getInstance(final int serviceId, final String url,
|
public static PlaylistFragment getInstance(final int serviceId, final String url,
|
||||||
final String name) {
|
final String name) {
|
||||||
PlaylistFragment instance = new PlaylistFragment();
|
final PlaylistFragment instance = new PlaylistFragment();
|
||||||
instance.setInitialData(serviceId, url, name);
|
instance.setInitialData(serviceId, url, name);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@ -229,7 +229,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||||
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPageUrl);
|
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -286,11 +286,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
|
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
|
||||||
headerUploaderLayout.setOnClickListener(v -> {
|
headerUploaderLayout.setOnClickListener(v -> {
|
||||||
try {
|
try {
|
||||||
NavigationHelper.openChannelFragment(getFragmentManager(),
|
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
|
||||||
result.getServiceId(),
|
result.getUploaderUrl(), result.getUploaderName());
|
||||||
result.getUploaderUrl(),
|
} catch (final Exception e) {
|
||||||
result.getUploaderName());
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -318,7 +316,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
.subscribe(getPlaylistBookmarkSubscriber());
|
.subscribe(getPlaylistBookmarkSubscriber());
|
||||||
|
|
||||||
headerPlayAllButton.setOnClickListener(view ->
|
headerPlayAllButton.setOnClickListener(view ->
|
||||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||||
headerPopupButton.setOnClickListener(view ->
|
headerPopupButton.setOnClickListener(view ->
|
||||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||||
headerBackgroundButton.setOnClickListener(view ->
|
headerBackgroundButton.setOnClickListener(view ->
|
||||||
@ -341,7 +339,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
|
|
||||||
private PlayQueue getPlayQueue(final int index) {
|
private PlayQueue getPlayQueue(final int index) {
|
||||||
final List<StreamInfoItem> infoItems = new ArrayList<>();
|
final List<StreamInfoItem> infoItems = new ArrayList<>();
|
||||||
for (InfoItem i : infoListAdapter.getItemsList()) {
|
for (final InfoItem i : infoListAdapter.getItemsList()) {
|
||||||
if (i instanceof StreamInfoItem) {
|
if (i instanceof StreamInfoItem) {
|
||||||
infoItems.add((StreamInfoItem) i);
|
infoItems.add((StreamInfoItem) i);
|
||||||
}
|
}
|
||||||
@ -349,7 +347,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
return new PlaylistPlayQueue(
|
return new PlaylistPlayQueue(
|
||||||
currentInfo.getServiceId(),
|
currentInfo.getServiceId(),
|
||||||
currentInfo.getUrl(),
|
currentInfo.getUrl(),
|
||||||
currentInfo.getNextPageUrl(),
|
currentInfo.getNextPage(),
|
||||||
infoItems,
|
infoItems,
|
||||||
index
|
index
|
||||||
);
|
);
|
||||||
@ -375,7 +373,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int errorId = exception instanceof ExtractionException
|
final int errorId = exception instanceof ExtractionException
|
||||||
? R.string.parsing_error : R.string.general_error;
|
? R.string.parsing_error : R.string.general_error;
|
||||||
onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST,
|
onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST,
|
||||||
NewPipe.getNameOfService(serviceId), url, errorId);
|
NewPipe.getNameOfService(serviceId), url, errorId);
|
||||||
|
@ -5,8 +5,10 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.core.text.HtmlCompat;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
import android.text.Html;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -37,6 +39,7 @@ import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
|||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||||
@ -46,7 +49,7 @@ import org.schabi.newpipe.fragments.list.BaseListFragment;
|
|||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
@ -118,13 +121,18 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
@State
|
@State
|
||||||
String lastSearchedString;
|
String lastSearchedString;
|
||||||
|
|
||||||
|
@State
|
||||||
|
String searchSuggestion;
|
||||||
|
|
||||||
|
@State
|
||||||
|
boolean isCorrectedSearch;
|
||||||
|
|
||||||
@State
|
@State
|
||||||
boolean wasSearchFocused = false;
|
boolean wasSearchFocused = false;
|
||||||
|
|
||||||
private Map<Integer, String> menuItemToFilterName;
|
private Map<Integer, String> menuItemToFilterName;
|
||||||
private StreamingService service;
|
private StreamingService service;
|
||||||
private String currentPageUrl;
|
private Page nextPage;
|
||||||
private String nextPageUrl;
|
|
||||||
private String contentCountry;
|
private String contentCountry;
|
||||||
private boolean isSuggestionsEnabled = true;
|
private boolean isSuggestionsEnabled = true;
|
||||||
|
|
||||||
@ -143,6 +151,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
private EditText searchEditText;
|
private EditText searchEditText;
|
||||||
private View searchClear;
|
private View searchClear;
|
||||||
|
|
||||||
|
private TextView correctSuggestion;
|
||||||
|
|
||||||
private View suggestionsPanel;
|
private View suggestionsPanel;
|
||||||
private RecyclerView suggestionsRecyclerView;
|
private RecyclerView suggestionsRecyclerView;
|
||||||
|
|
||||||
@ -151,7 +161,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
private TextWatcher textWatcher;
|
private TextWatcher textWatcher;
|
||||||
|
|
||||||
public static SearchFragment getInstance(final int serviceId, final String searchString) {
|
public static SearchFragment getInstance(final int serviceId, final String searchString) {
|
||||||
SearchFragment searchFragment = new SearchFragment();
|
final SearchFragment searchFragment = new SearchFragment();
|
||||||
searchFragment.setQuery(serviceId, searchString, new String[0], "");
|
searchFragment.setQuery(serviceId, searchString, new String[0], "");
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(searchString)) {
|
if (!TextUtils.isEmpty(searchString)) {
|
||||||
@ -177,8 +187,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
|
|
||||||
suggestionListAdapter = new SuggestionListAdapter(activity);
|
suggestionListAdapter = new SuggestionListAdapter(activity);
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
final SharedPreferences preferences
|
||||||
boolean isSearchHistoryEnabled = preferences
|
= PreferenceManager.getDefaultSharedPreferences(activity);
|
||||||
|
final boolean isSearchHistoryEnabled = preferences
|
||||||
.getBoolean(getString(R.string.enable_search_history_key), true);
|
.getBoolean(getString(R.string.enable_search_history_key), true);
|
||||||
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
|
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
|
||||||
|
|
||||||
@ -189,7 +200,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
public void onCreate(final Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
final SharedPreferences preferences
|
||||||
|
= PreferenceManager.getDefaultSharedPreferences(activity);
|
||||||
isSuggestionsEnabled = preferences
|
isSuggestionsEnabled = preferences
|
||||||
.getBoolean(getString(R.string.show_search_suggestions_key), true);
|
.getBoolean(getString(R.string.show_search_suggestions_key), true);
|
||||||
contentCountry = preferences.getString(getString(R.string.content_country_key),
|
contentCountry = preferences.getString(getString(R.string.content_country_key),
|
||||||
@ -236,7 +248,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
service = NewPipe.getService(serviceId);
|
service = NewPipe.getService(serviceId);
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportError(getActivity(), e, getActivity().getClass(),
|
ErrorActivity.reportError(getActivity(), e, getActivity().getClass(),
|
||||||
getActivity().findViewById(android.R.id.content),
|
getActivity().findViewById(android.R.id.content),
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
||||||
@ -257,6 +269,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSearchSuggestion();
|
||||||
|
|
||||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||||
initSuggestionObserver();
|
initSuggestionObserver();
|
||||||
}
|
}
|
||||||
@ -345,6 +359,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||||
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
||||||
|
|
||||||
|
correctSuggestion = rootView.findViewById(R.id.correct_suggestion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -354,15 +370,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
@Override
|
@Override
|
||||||
public void writeTo(final Queue<Object> objectsToSave) {
|
public void writeTo(final Queue<Object> objectsToSave) {
|
||||||
super.writeTo(objectsToSave);
|
super.writeTo(objectsToSave);
|
||||||
objectsToSave.add(currentPageUrl);
|
objectsToSave.add(nextPage);
|
||||||
objectsToSave.add(nextPageUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||||
super.readFrom(savedObjects);
|
super.readFrom(savedObjects);
|
||||||
currentPageUrl = (String) savedObjects.poll();
|
nextPage = (Page) savedObjects.poll();
|
||||||
nextPageUrl = (String) savedObjects.poll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -401,7 +415,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
|
||||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
if (supportActionBar != null) {
|
if (supportActionBar != null) {
|
||||||
supportActionBar.setDisplayShowTitleEnabled(false);
|
supportActionBar.setDisplayShowTitleEnabled(false);
|
||||||
supportActionBar.setDisplayHomeAsUpEnabled(true);
|
supportActionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
@ -412,16 +426,16 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
int itemId = 0;
|
int itemId = 0;
|
||||||
boolean isFirstItem = true;
|
boolean isFirstItem = true;
|
||||||
final Context c = getContext();
|
final Context c = getContext();
|
||||||
for (String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
|
for (final String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
|
||||||
if (filter.equals("music_songs")) {
|
if (filter.equals("music_songs")) {
|
||||||
MenuItem musicItem = menu.add(2,
|
final MenuItem musicItem = menu.add(2,
|
||||||
itemId++,
|
itemId++,
|
||||||
0,
|
0,
|
||||||
"YouTube Music");
|
"YouTube Music");
|
||||||
musicItem.setEnabled(false);
|
musicItem.setEnabled(false);
|
||||||
}
|
}
|
||||||
menuItemToFilterName.put(itemId, filter);
|
menuItemToFilterName.put(itemId, filter);
|
||||||
MenuItem item = menu.add(1,
|
final MenuItem item = menu.add(1,
|
||||||
itemId++,
|
itemId++,
|
||||||
0,
|
0,
|
||||||
ServiceHelper.getTranslatedFilterString(filter, c));
|
ServiceHelper.getTranslatedFilterString(filter, c));
|
||||||
@ -437,7 +451,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
List<String> cf = new ArrayList<>(1);
|
final List<String> cf = new ArrayList<>(1);
|
||||||
cf.add(menuItemToFilterName.get(item.getItemId()));
|
cf.add(menuItemToFilterName.get(item.getItemId()));
|
||||||
changeContentFilter(item, cf);
|
changeContentFilter(item, cf);
|
||||||
|
|
||||||
@ -446,7 +460,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
|
|
||||||
private void restoreFilterChecked(final Menu menu, final int itemId) {
|
private void restoreFilterChecked(final Menu menu, final int itemId) {
|
||||||
if (itemId != -1) {
|
if (itemId != -1) {
|
||||||
MenuItem item = menu.findItem(itemId);
|
final MenuItem item = menu.findItem(itemId);
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -470,16 +484,16 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
|
|
||||||
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
|
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
|
||||||
searchToolbarContainer.setTranslationX(100);
|
searchToolbarContainer.setTranslationX(100);
|
||||||
searchToolbarContainer.setAlpha(0f);
|
searchToolbarContainer.setAlpha(0.0f);
|
||||||
searchToolbarContainer.setVisibility(View.VISIBLE);
|
searchToolbarContainer.setVisibility(View.VISIBLE);
|
||||||
searchToolbarContainer.animate()
|
searchToolbarContainer.animate()
|
||||||
.translationX(0)
|
.translationX(0)
|
||||||
.alpha(1f)
|
.alpha(1.0f)
|
||||||
.setDuration(200)
|
.setDuration(200)
|
||||||
.setInterpolator(new DecelerateInterpolator()).start();
|
.setInterpolator(new DecelerateInterpolator()).start();
|
||||||
} else {
|
} else {
|
||||||
searchToolbarContainer.setTranslationX(0);
|
searchToolbarContainer.setTranslationX(0);
|
||||||
searchToolbarContainer.setAlpha(1f);
|
searchToolbarContainer.setAlpha(1.0f);
|
||||||
searchToolbarContainer.setVisibility(View.VISIBLE);
|
searchToolbarContainer.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -493,10 +507,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||||
}
|
}
|
||||||
if (TextUtils.isEmpty(searchEditText.getText())) {
|
if (TextUtils.isEmpty(searchEditText.getText())) {
|
||||||
NavigationHelper.gotoMainFragment(getFragmentManager());
|
NavigationHelper.gotoMainFragment(getFM());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
correctSuggestion.setVisibility(View.GONE);
|
||||||
|
|
||||||
searchEditText.setText("");
|
searchEditText.setText("");
|
||||||
suggestionListAdapter.setItems(new ArrayList<>());
|
suggestionListAdapter.setItems(new ArrayList<>());
|
||||||
showKeyboardSearch();
|
showKeyboardSearch();
|
||||||
@ -511,7 +527,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||||
showSuggestionsPanel();
|
showSuggestionsPanel();
|
||||||
}
|
}
|
||||||
if (AndroidTvUtils.isTv(getContext())) {
|
if (DeviceUtils.isTv(getContext())) {
|
||||||
showKeyboardSearch();
|
showKeyboardSearch();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -554,15 +570,17 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
textWatcher = new TextWatcher() {
|
textWatcher = new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(final CharSequence s, final int start,
|
public void beforeTextChanged(final CharSequence s, final int start,
|
||||||
final int count, final int after) { }
|
final int count, final int after) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(final CharSequence s, final int start,
|
public void onTextChanged(final CharSequence s, final int start,
|
||||||
final int before, final int count) { }
|
final int before, final int count) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(final Editable s) {
|
public void afterTextChanged(final Editable s) {
|
||||||
String newText = searchEditText.getText().toString();
|
final String newText = searchEditText.getText().toString();
|
||||||
suggestionPublisher.onNext(newText);
|
suggestionPublisher.onNext(newText);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -628,7 +646,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (searchEditText.requestFocus()) {
|
if (searchEditText.requestFocus()) {
|
||||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
final InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||||
Context.INPUT_METHOD_SERVICE);
|
Context.INPUT_METHOD_SERVICE);
|
||||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
|
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
|
||||||
}
|
}
|
||||||
@ -642,7 +660,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
InputMethodManager imm = (InputMethodManager) activity
|
final InputMethodManager imm = (InputMethodManager) activity
|
||||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
|
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
|
||||||
InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
||||||
@ -688,10 +706,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void giveSearchEditTextFocus() {
|
|
||||||
showKeyboardSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initSuggestionObserver() {
|
private void initSuggestionObserver() {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "initSuggestionObserver() called");
|
Log.d(TAG, "initSuggestionObserver() called");
|
||||||
@ -713,8 +727,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
.getRelatedSearches(query, 3, 25);
|
.getRelatedSearches(query, 3, 25);
|
||||||
final Observable<List<SuggestionItem>> local = flowable.toObservable()
|
final Observable<List<SuggestionItem>> local = flowable.toObservable()
|
||||||
.map(searchHistoryEntries -> {
|
.map(searchHistoryEntries -> {
|
||||||
List<SuggestionItem> result = new ArrayList<>();
|
final List<SuggestionItem> result = new ArrayList<>();
|
||||||
for (SearchHistoryEntry entry : searchHistoryEntries) {
|
for (final SearchHistoryEntry entry : searchHistoryEntries) {
|
||||||
result.add(new SuggestionItem(true, entry.getSearch()));
|
result.add(new SuggestionItem(true, entry.getSearch()));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -730,15 +744,15 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
.suggestionsFor(serviceId, query)
|
.suggestionsFor(serviceId, query)
|
||||||
.toObservable()
|
.toObservable()
|
||||||
.map(strings -> {
|
.map(strings -> {
|
||||||
List<SuggestionItem> result = new ArrayList<>();
|
final List<SuggestionItem> result = new ArrayList<>();
|
||||||
for (String entry : strings) {
|
for (final String entry : strings) {
|
||||||
result.add(new SuggestionItem(false, entry));
|
result.add(new SuggestionItem(false, entry));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
return Observable.zip(local, network, (localResult, networkResult) -> {
|
return Observable.zip(local, network, (localResult, networkResult) -> {
|
||||||
List<SuggestionItem> result = new ArrayList<>();
|
final List<SuggestionItem> result = new ArrayList<>();
|
||||||
if (localResult.size() > 0) {
|
if (localResult.size() > 0) {
|
||||||
result.addAll(localResult);
|
result.addAll(localResult);
|
||||||
}
|
}
|
||||||
@ -747,7 +761,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
final Iterator<SuggestionItem> iterator = networkResult.iterator();
|
final Iterator<SuggestionItem> iterator = networkResult.iterator();
|
||||||
while (iterator.hasNext() && localResult.size() > 0) {
|
while (iterator.hasNext() && localResult.size() > 0) {
|
||||||
final SuggestionItem next = iterator.next();
|
final SuggestionItem next = iterator.next();
|
||||||
for (SuggestionItem item : localResult) {
|
for (final SuggestionItem item : localResult) {
|
||||||
if (item.query.equals(next.query)) {
|
if (item.query.equals(next.query)) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
break;
|
break;
|
||||||
@ -795,13 +809,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(intent -> {
|
.subscribe(intent -> {
|
||||||
getFragmentManager().popBackStackImmediate();
|
getFM().popBackStackImmediate();
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
}, throwable ->
|
}, throwable ->
|
||||||
showError(getString(R.string.unsupported_url), false)));
|
showError(getString(R.string.unsupported_url), false)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
// Exception occurred, it's not a url
|
// Exception occurred, it's not a url
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -845,7 +859,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void loadMoreItems() {
|
protected void loadMoreItems() {
|
||||||
if (nextPageUrl == null || nextPageUrl.isEmpty()) {
|
if (!Page.isValid(nextPage)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isLoading.set(true);
|
isLoading.set(true);
|
||||||
@ -858,7 +872,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
searchString,
|
searchString,
|
||||||
asList(contentFilter),
|
asList(contentFilter),
|
||||||
sortFilter,
|
sortFilter,
|
||||||
nextPageUrl)
|
nextPage)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
||||||
@ -923,7 +937,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int errorId = exception instanceof ParsingException
|
final int errorId = exception instanceof ParsingException
|
||||||
? R.string.parsing_error
|
? R.string.parsing_error
|
||||||
: R.string.general_error;
|
: R.string.general_error;
|
||||||
onUnrecoverableError(exception, UserAction.GET_SUGGESTIONS,
|
onUnrecoverableError(exception, UserAction.GET_SUGGESTIONS,
|
||||||
@ -961,9 +975,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
NewPipe.getNameOfService(serviceId), searchString, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchSuggestion = result.getSearchSuggestion();
|
||||||
|
isCorrectedSearch = result.isCorrectedSearch();
|
||||||
|
|
||||||
|
handleSearchSuggestion();
|
||||||
|
|
||||||
lastSearchedString = searchString;
|
lastSearchedString = searchString;
|
||||||
nextPageUrl = result.getNextPageUrl();
|
nextPage = result.getNextPage();
|
||||||
currentPageUrl = result.getUrl();
|
|
||||||
|
|
||||||
if (infoListAdapter.getItemsList().size() == 0) {
|
if (infoListAdapter.getItemsList().size() == 0) {
|
||||||
if (!result.getRelatedItems().isEmpty()) {
|
if (!result.getRelatedItems().isEmpty()) {
|
||||||
@ -978,17 +996,48 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleSearchSuggestion() {
|
||||||
|
if (TextUtils.isEmpty(searchSuggestion)) {
|
||||||
|
correctSuggestion.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
final String helperText = getString(isCorrectedSearch
|
||||||
|
? R.string.search_showing_result_for
|
||||||
|
: R.string.did_you_mean);
|
||||||
|
|
||||||
|
final String highlightedSearchSuggestion =
|
||||||
|
"<b><i>" + Html.escapeHtml(searchSuggestion) + "</i></b>";
|
||||||
|
final String text = String.format(helperText, highlightedSearchSuggestion);
|
||||||
|
correctSuggestion.setText(HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_LEGACY));
|
||||||
|
|
||||||
|
correctSuggestion.setOnClickListener(v -> {
|
||||||
|
correctSuggestion.setVisibility(View.GONE);
|
||||||
|
search(searchSuggestion, contentFilter, sortFilter);
|
||||||
|
searchEditText.setText(searchSuggestion);
|
||||||
|
});
|
||||||
|
|
||||||
|
correctSuggestion.setOnLongClickListener(v -> {
|
||||||
|
searchEditText.setText(searchSuggestion);
|
||||||
|
searchEditText.setSelection(searchSuggestion.length());
|
||||||
|
showKeyboardSearch();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
correctSuggestion.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||||
showListFooter(false);
|
showListFooter(false);
|
||||||
currentPageUrl = result.getNextPageUrl();
|
|
||||||
infoListAdapter.addInfoItemList(result.getItems());
|
infoListAdapter.addInfoItemList(result.getItems());
|
||||||
nextPageUrl = result.getNextPageUrl();
|
nextPage = result.getNextPage();
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||||
NewPipe.getNameOfService(serviceId),
|
NewPipe.getNameOfService(serviceId),
|
||||||
"\"" + searchString + "\" → page: " + nextPageUrl, 0);
|
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
|
||||||
|
+ "pageIds: " + nextPage.getIds() + ", "
|
||||||
|
+ "pageCookies: " + nextPage.getCookies(), 0);
|
||||||
}
|
}
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
}
|
}
|
||||||
@ -1003,7 +1052,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
infoListAdapter.clearStreamItemList();
|
infoListAdapter.clearStreamItemList();
|
||||||
showEmptyState();
|
showEmptyState();
|
||||||
} else {
|
} else {
|
||||||
int errorId = exception instanceof ParsingException
|
final int errorId = exception instanceof ParsingException
|
||||||
? R.string.parsing_error
|
? R.string.parsing_error
|
||||||
: R.string.general_error;
|
: R.string.general_error;
|
||||||
onUnrecoverableError(exception, UserAction.SEARCHED,
|
onUnrecoverableError(exception, UserAction.SEARCHED,
|
||||||
|
@ -33,7 +33,7 @@ public class SuggestionListAdapter
|
|||||||
this.items.addAll(items);
|
this.items.addAll(items);
|
||||||
} else {
|
} else {
|
||||||
// remove history items if history is disabled
|
// remove history items if history is disabled
|
||||||
for (SuggestionItem item : items) {
|
for (final SuggestionItem item : items) {
|
||||||
if (!item.fromHistory) {
|
if (!item.fromHistory) {
|
||||||
this.items.add(item);
|
this.items.add(item);
|
||||||
}
|
}
|
||||||
@ -123,8 +123,8 @@ public class SuggestionListAdapter
|
|||||||
|
|
||||||
private static int resolveResourceIdFromAttr(final Context context,
|
private static int resolveResourceIdFromAttr(final Context context,
|
||||||
@AttrRes final int attr) {
|
@AttrRes final int attr) {
|
||||||
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
final TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
||||||
int attributeResourceId = a.getResourceId(0, 0);
|
final int attributeResourceId = a.getResourceId(0, 0);
|
||||||
a.recycle();
|
a.recycle();
|
||||||
return attributeResourceId;
|
return attributeResourceId;
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,12 @@ package org.schabi.newpipe.fragments.list.videos;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.Switch;
|
import android.widget.Switch;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -40,22 +39,14 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private View headerRootLayout;
|
private View headerRootLayout;
|
||||||
private Switch aSwitch;
|
private Switch autoplaySwitch;
|
||||||
|
|
||||||
private boolean mIsVisibleToUser = false;
|
|
||||||
|
|
||||||
public static RelatedVideosFragment getInstance(final StreamInfo info) {
|
public static RelatedVideosFragment getInstance(final StreamInfo info) {
|
||||||
RelatedVideosFragment instance = new RelatedVideosFragment();
|
final RelatedVideosFragment instance = new RelatedVideosFragment();
|
||||||
instance.setInitialData(info);
|
instance.setInitialData(info);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
|
||||||
mIsVisibleToUser = isVisibleToUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -81,22 +72,18 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected View getListHeader() {
|
protected View getListHeader() {
|
||||||
if (relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null) {
|
if (relatedStreamInfo != null && relatedStreamInfo.getRelatedItems() != null) {
|
||||||
headerRootLayout = activity.getLayoutInflater()
|
headerRootLayout = activity.getLayoutInflater()
|
||||||
.inflate(R.layout.related_streams_header, itemsList, false);
|
.inflate(R.layout.related_streams_header, itemsList, false);
|
||||||
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
|
autoplaySwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
|
||||||
|
|
||||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
final SharedPreferences pref = PreferenceManager
|
||||||
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
.getDefaultSharedPreferences(requireContext());
|
||||||
aSwitch.setChecked(autoplay);
|
final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||||
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
autoplaySwitch.setChecked(autoplay);
|
||||||
@Override
|
autoplaySwitch.setOnCheckedChangeListener((compoundButton, b) ->
|
||||||
public void onCheckedChanged(final CompoundButton compoundButton,
|
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit()
|
||||||
final boolean b) {
|
.putBoolean(getString(R.string.auto_queue_key), b).apply());
|
||||||
PreferenceManager.getDefaultSharedPreferences(getContext()).edit()
|
|
||||||
.putBoolean(getString(R.string.auto_queue_key), b).apply();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return headerRootLayout;
|
return headerRootLayout;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@ -105,7 +92,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||||
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
|
return Single.fromCallable(ListExtractor.InfoItemsPage::emptyPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -179,12 +166,10 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTitle(final String title) {
|
public void setTitle(final String title) {
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setInitialData(final StreamInfo info) {
|
private void setInitialData(final StreamInfo info) {
|
||||||
@ -204,7 +189,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||||||
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
|
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
|
||||||
super.onRestoreInstanceState(savedState);
|
super.onRestoreInstanceState(savedState);
|
||||||
if (savedState != null) {
|
if (savedState != null) {
|
||||||
Serializable serializable = savedState.getSerializable(INFO_KEY);
|
final Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||||
if (serializable instanceof RelatedStreamInfo) {
|
if (serializable instanceof RelatedStreamInfo) {
|
||||||
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
||||||
}
|
}
|
||||||
@ -214,10 +199,11 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
||||||
final String s) {
|
final String s) {
|
||||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
final SharedPreferences pref =
|
||||||
boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
PreferenceManager.getDefaultSharedPreferences(requireContext());
|
||||||
if (null != aSwitch) {
|
final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||||
aSwitch.setChecked(autoplay);
|
if (autoplaySwitch != null) {
|
||||||
|
autoplaySwitch.setChecked(autoplay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,8 @@ public class InfoItemBuilder {
|
|||||||
public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
|
public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
|
||||||
final HistoryRecordManager historyRecordManager,
|
final HistoryRecordManager historyRecordManager,
|
||||||
final boolean useMiniVariant) {
|
final boolean useMiniVariant) {
|
||||||
InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
|
final InfoItemHolder holder
|
||||||
|
= holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
|
||||||
holder.updateFromItem(infoItem, historyRecordManager);
|
holder.updateFromItem(infoItem, historyRecordManager);
|
||||||
return holder.itemView;
|
return holder.itemView;
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,10 @@ public class InfoItemDialog {
|
|||||||
final View bannerView = View.inflate(activity, R.layout.dialog_title, null);
|
final View bannerView = View.inflate(activity, R.layout.dialog_title, null);
|
||||||
bannerView.setSelected(true);
|
bannerView.setSelected(true);
|
||||||
|
|
||||||
TextView titleView = bannerView.findViewById(R.id.itemTitleView);
|
final TextView titleView = bannerView.findViewById(R.id.itemTitleView);
|
||||||
titleView.setText(title);
|
titleView.setText(title);
|
||||||
|
|
||||||
TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
|
final TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
|
||||||
if (additionalDetail != null) {
|
if (additionalDetail != null) {
|
||||||
detailsView.setText(additionalDetail);
|
detailsView.setText(additionalDetail);
|
||||||
detailsView.setVisibility(View.VISIBLE);
|
detailsView.setVisibility(View.VISIBLE);
|
||||||
|
@ -123,7 +123,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||||||
+ infoItemList.size() + ", data.size() = " + data.size());
|
+ infoItemList.size() + ", data.size() = " + data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
int offsetStart = sizeConsideringHeaderOffset();
|
final int offsetStart = sizeConsideringHeaderOffset();
|
||||||
infoItemList.addAll(data);
|
infoItemList.addAll(data);
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -135,7 +135,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||||||
notifyItemRangeInserted(offsetStart, data.size());
|
notifyItemRangeInserted(offsetStart, data.size());
|
||||||
|
|
||||||
if (footer != null && showFooter) {
|
if (footer != null && showFooter) {
|
||||||
int footerNow = sizeConsideringHeaderOffset();
|
final int footerNow = sizeConsideringHeaderOffset();
|
||||||
notifyItemMoved(offsetStart, footerNow);
|
notifyItemMoved(offsetStart, footerNow);
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -160,7 +160,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||||||
+ infoItemList.size() + ", thread = " + Thread.currentThread());
|
+ infoItemList.size() + ", thread = " + Thread.currentThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
int positionInserted = sizeConsideringHeaderOffset();
|
final int positionInserted = sizeConsideringHeaderOffset();
|
||||||
infoItemList.add(data);
|
infoItemList.add(data);
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -172,7 +172,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||||||
notifyItemInserted(positionInserted);
|
notifyItemInserted(positionInserted);
|
||||||
|
|
||||||
if (footer != null && showFooter) {
|
if (footer != null && showFooter) {
|
||||||
int footerNow = sizeConsideringHeaderOffset();
|
final int footerNow = sizeConsideringHeaderOffset();
|
||||||
notifyItemMoved(positionInserted, footerNow);
|
notifyItemMoved(positionInserted, footerNow);
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -191,7 +191,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setHeader(final View header) {
|
public void setHeader(final View header) {
|
||||||
boolean changed = header != this.header;
|
final boolean changed = header != this.header;
|
||||||
this.header = header;
|
this.header = header;
|
||||||
if (changed) {
|
if (changed) {
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
@ -219,7 +219,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int sizeConsideringHeaderOffset() {
|
private int sizeConsideringHeaderOffset() {
|
||||||
int i = infoItemList.size() + (header != null ? 1 : 0);
|
final int i = infoItemList.size() + (header != null ? 1 : 0);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i);
|
Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i);
|
||||||
}
|
}
|
||||||
@ -347,7 +347,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
|
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
|
||||||
@NonNull final List<Object> payloads) {
|
@NonNull final List<Object> payloads) {
|
||||||
if (!payloads.isEmpty() && holder instanceof InfoItemHolder) {
|
if (!payloads.isEmpty() && holder instanceof InfoItemHolder) {
|
||||||
for (Object payload : payloads) {
|
for (final Object payload : payloads) {
|
||||||
if (payload instanceof StreamStateEntity) {
|
if (payload instanceof StreamStateEntity) {
|
||||||
((InfoItemHolder) holder).updateState(infoItemList
|
((InfoItemHolder) holder).updateState(infoItemList
|
||||||
.get(header == null ? position : position - 1), recordManager);
|
.get(header == null ? position : position - 1), recordManager);
|
||||||
|
@ -56,8 +56,8 @@ public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder {
|
|||||||
String details = super.getDetailLine(item);
|
String details = super.getDetailLine(item);
|
||||||
|
|
||||||
if (item.getStreamCount() >= 0) {
|
if (item.getStreamCount() >= 0) {
|
||||||
String formattedVideoAmount = Localization.localizeStreamCount(itemBuilder.getContext(),
|
final String formattedVideoAmount = Localization.localizeStreamCount(
|
||||||
item.getStreamCount());
|
itemBuilder.getContext(), item.getStreamCount());
|
||||||
|
|
||||||
if (!details.isEmpty()) {
|
if (!details.isEmpty()) {
|
||||||
details += " • " + formattedVideoAmount;
|
details += " • " + formattedVideoAmount;
|
||||||
|
@ -15,7 +15,7 @@ import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
|||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.CommentTextOnTouchListener;
|
import org.schabi.newpipe.util.CommentTextOnTouchListener;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
@ -45,9 +45,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||||||
@Override
|
@Override
|
||||||
public String transformUrl(final Matcher match, final String url) {
|
public String transformUrl(final Matcher match, final String url) {
|
||||||
int timestamp = 0;
|
int timestamp = 0;
|
||||||
String hours = match.group(1);
|
final String hours = match.group(1);
|
||||||
String minutes = match.group(2);
|
final String minutes = match.group(2);
|
||||||
String seconds = match.group(3);
|
final String seconds = match.group(3);
|
||||||
if (hours != null) {
|
if (hours != null) {
|
||||||
timestamp += (Integer.parseInt(hours.replace(":", "")) * 3600);
|
timestamp += (Integer.parseInt(hours.replace(":", "")) * 3600);
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||||||
|
|
||||||
|
|
||||||
itemView.setOnLongClickListener(view -> {
|
itemView.setOnLongClickListener(view -> {
|
||||||
if (AndroidTvUtils.isTv(itemBuilder.getContext())) {
|
if (DeviceUtils.isTv(itemBuilder.getContext())) {
|
||||||
openCommentAuthor(item);
|
openCommentAuthor(item);
|
||||||
} else {
|
} else {
|
||||||
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
|
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
|
||||||
@ -146,7 +146,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||||||
item.getServiceId(),
|
item.getServiceId(),
|
||||||
item.getUploaderUrl(),
|
item.getUploaderUrl(),
|
||||||
item.getUploaderName());
|
item.getUploaderName());
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,7 +164,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
URLSpan[] urls = itemContentView.getUrls();
|
final URLSpan[] urls = itemContentView.getUrls();
|
||||||
|
|
||||||
return urls != null && urls.length != 0;
|
return urls != null && urls.length != 0;
|
||||||
}
|
}
|
||||||
@ -181,12 +181,13 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||||||
boolean hasEllipsis = false;
|
boolean hasEllipsis = false;
|
||||||
|
|
||||||
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
|
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
|
||||||
int endOfLastLine = itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1);
|
final int endOfLastLine
|
||||||
|
= itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1);
|
||||||
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
|
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
|
||||||
if (end == -1) {
|
if (end == -1) {
|
||||||
end = Math.max(endOfLastLine - 2, 0);
|
end = Math.max(endOfLastLine - 2, 0);
|
||||||
}
|
}
|
||||||
String newVal = itemContentView.getText().subSequence(0, end) + " …";
|
final String newVal = itemContentView.getText().subSequence(0, end) + " …";
|
||||||
itemContentView.setText(newVal);
|
itemContentView.setText(newVal);
|
||||||
hasEllipsis = true;
|
hasEllipsis = true;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.schabi.newpipe.info_list.holder;
|
package org.schabi.newpipe.info_list.holder;
|
||||||
|
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
@ -60,7 +60,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
|||||||
R.color.duration_background_color));
|
R.color.duration_background_color));
|
||||||
itemDurationView.setVisibility(View.VISIBLE);
|
itemDurationView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
|
final StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
|
||||||
.blockingGet()[0];
|
.blockingGet()[0];
|
||||||
if (state2 != null) {
|
if (state2 != null) {
|
||||||
itemProgressView.setVisibility(View.VISIBLE);
|
itemProgressView.setVisibility(View.VISIBLE);
|
||||||
@ -113,7 +113,8 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
|||||||
final HistoryRecordManager historyRecordManager) {
|
final HistoryRecordManager historyRecordManager) {
|
||||||
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
||||||
|
|
||||||
StreamStateEntity state = historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
|
final StreamStateEntity state
|
||||||
|
= historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
|
||||||
if (state != null && item.getDuration() > 0
|
if (state != null && item.getDuration() > 0
|
||||||
&& item.getStreamType() != StreamType.LIVE_STREAM) {
|
&& item.getStreamType() != StreamType.LIVE_STREAM) {
|
||||||
itemProgressView.setMax((int) item.getDuration());
|
itemProgressView.setMax((int) item.getDuration());
|
||||||
|
@ -4,7 +4,7 @@ import android.content.SharedPreferences;
|
|||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
@ -101,7 +101,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||||||
+ localItems.size() + ", data.size() = " + data.size());
|
+ localItems.size() + ", data.size() = " + data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
int offsetStart = sizeConsideringHeader();
|
final int offsetStart = sizeConsideringHeader();
|
||||||
localItems.addAll(data);
|
localItems.addAll(data);
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -113,7 +113,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||||||
notifyItemRangeInserted(offsetStart, data.size());
|
notifyItemRangeInserted(offsetStart, data.size());
|
||||||
|
|
||||||
if (footer != null && showFooter) {
|
if (footer != null && showFooter) {
|
||||||
int footerNow = sizeConsideringHeader();
|
final int footerNow = sizeConsideringHeader();
|
||||||
notifyItemMoved(offsetStart, footerNow);
|
notifyItemMoved(offsetStart, footerNow);
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -158,7 +158,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setHeader(final View header) {
|
public void setHeader(final View header) {
|
||||||
boolean changed = header != this.header;
|
final boolean changed = header != this.header;
|
||||||
this.header = header;
|
this.header = header;
|
||||||
if (changed) {
|
if (changed) {
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
@ -316,7 +316,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
|
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
|
||||||
@NonNull final List<Object> payloads) {
|
@NonNull final List<Object> payloads) {
|
||||||
if (!payloads.isEmpty() && holder instanceof LocalItemHolder) {
|
if (!payloads.isEmpty() && holder instanceof LocalItemHolder) {
|
||||||
for (Object payload : payloads) {
|
for (final Object payload : payloads) {
|
||||||
if (payload instanceof StreamStateEntity) {
|
if (payload instanceof StreamStateEntity) {
|
||||||
((LocalItemHolder) holder).updateState(localItems
|
((LocalItemHolder) holder).updateState(localItems
|
||||||
.get(header == null ? position : position - 1), recordManager);
|
.get(header == null ? position : position - 1), recordManager);
|
||||||
|
@ -30,8 +30,6 @@ import org.schabi.newpipe.report.UserAction;
|
|||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.OnClickGesture;
|
import org.schabi.newpipe.util.OnClickGesture;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
@ -54,31 +52,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||||||
// Fragment LifeCycle - Creation
|
// Fragment LifeCycle - Creation
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private static List<PlaylistLocalItem> merge(
|
|
||||||
final List<PlaylistMetadataEntry> localPlaylists,
|
|
||||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
|
||||||
List<PlaylistLocalItem> items = new ArrayList<>(
|
|
||||||
localPlaylists.size() + remotePlaylists.size());
|
|
||||||
items.addAll(localPlaylists);
|
|
||||||
items.addAll(remotePlaylists);
|
|
||||||
|
|
||||||
Collections.sort(items, (left, right) -> {
|
|
||||||
String on1 = left.getOrderingName();
|
|
||||||
String on2 = right.getOrderingName();
|
|
||||||
if (on1 == null && on2 == null) {
|
|
||||||
return 0;
|
|
||||||
} else if (on1 != null && on2 == null) {
|
|
||||||
return -1;
|
|
||||||
} else if (on1 == null && on2 != null) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return on1.compareToIgnoreCase(on2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(final Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@ -164,7 +137,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||||||
super.startLoading(forceLoad);
|
super.startLoading(forceLoad);
|
||||||
|
|
||||||
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
|
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
|
||||||
remotePlaylistManager.getPlaylists(), BookmarkFragment::merge)
|
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
|
||||||
.onBackpressureLatest()
|
.onBackpressureLatest()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(getPlaylistsSubscriber());
|
.subscribe(getPlaylistsSubscriber());
|
||||||
@ -292,15 +265,14 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showLocalDialog(final PlaylistMetadataEntry selectedItem) {
|
private void showLocalDialog(final PlaylistMetadataEntry selectedItem) {
|
||||||
View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null);
|
final View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null);
|
||||||
EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
|
final EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
|
||||||
editText.setText(selectedItem.name);
|
editText.setText(selectedItem.name);
|
||||||
|
|
||||||
Builder builder = new AlertDialog.Builder(activity);
|
final Builder builder = new AlertDialog.Builder(activity);
|
||||||
builder.setView(dialogView)
|
builder.setView(dialogView)
|
||||||
.setPositiveButton(R.string.rename_playlist, (dialog, which) -> {
|
.setPositiveButton(R.string.rename_playlist, (dialog, which) ->
|
||||||
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString());
|
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString()))
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.setNeutralButton(R.string.delete, (dialog, which) -> {
|
.setNeutralButton(R.string.delete, (dialog, which) -> {
|
||||||
showDeleteDialog(selectedItem.name,
|
showDeleteDialog(selectedItem.name,
|
||||||
|
@ -39,14 +39,14 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||||||
private CompositeDisposable playlistDisposables = new CompositeDisposable();
|
private CompositeDisposable playlistDisposables = new CompositeDisposable();
|
||||||
|
|
||||||
public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) {
|
public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) {
|
||||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||||
dialog.setInfo(Collections.singletonList(new StreamEntity(info)));
|
dialog.setInfo(Collections.singletonList(new StreamEntity(info)));
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PlaylistAppendDialog fromStreamInfoItems(final List<StreamInfoItem> items) {
|
public static PlaylistAppendDialog fromStreamInfoItems(final List<StreamInfoItem> items) {
|
||||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||||
List<StreamEntity> entities = new ArrayList<>(items.size());
|
final List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||||
for (final StreamInfoItem item : items) {
|
for (final StreamInfoItem item : items) {
|
||||||
entities.add(new StreamEntity(item));
|
entities.add(new StreamEntity(item));
|
||||||
}
|
}
|
||||||
@ -55,8 +55,8 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static PlaylistAppendDialog fromPlayQueueItems(final List<PlayQueueItem> items) {
|
public static PlaylistAppendDialog fromPlayQueueItems(final List<PlayQueueItem> items) {
|
||||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||||
List<StreamEntity> entities = new ArrayList<>(items.size());
|
final List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||||
for (final PlayQueueItem item : items) {
|
for (final PlayQueueItem item : items) {
|
||||||
entities.add(new StreamEntity(item));
|
entities.add(new StreamEntity(item));
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
|||||||
|
|
||||||
public final class PlaylistCreationDialog extends PlaylistDialog {
|
public final class PlaylistCreationDialog extends PlaylistDialog {
|
||||||
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
|
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
|
||||||
PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
final PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
||||||
dialog.setInfo(streams);
|
dialog.setInfo(streams);
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
@ -37,8 +37,8 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
|
|||||||
return super.onCreateDialog(savedInstanceState);
|
return super.onCreateDialog(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
||||||
EditText nameInput = dialogView.findViewById(R.id.playlist_name);
|
final EditText nameInput = dialogView.findViewById(R.id.playlist_name);
|
||||||
|
|
||||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext())
|
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext())
|
||||||
.setTitle(R.string.create_playlist)
|
.setTitle(R.string.create_playlist)
|
||||||
|
@ -30,7 +30,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import icepick.State
|
import icepick.State
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
@ -82,7 +82,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
|||||||
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(rootView, savedInstanceState)
|
super.onViewCreated(rootView, savedInstanceState)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, FeedViewModel.Factory(requireContext(), groupId)).get(FeedViewModel::class.java)
|
viewModel = ViewModelProvider(this, FeedViewModel.Factory(requireContext(), groupId)).get(FeedViewModel::class.java)
|
||||||
viewModel.stateLiveData.observe(viewLifecycleOwner, Observer { it?.let(::handleResult) })
|
viewModel.stateLiveData.observe(viewLifecycleOwner, Observer { it?.let(::handleResult) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,10 +27,10 @@ import android.content.Intent
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.preference.PreferenceManager
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.Notification
|
import io.reactivex.Notification
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
@ -20,7 +20,7 @@ package org.schabi.newpipe.local.history;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ public class HistoryRecordManager {
|
|||||||
final Date currentTime = new Date();
|
final Date currentTime = new Date();
|
||||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||||
final long streamId = streamTable.upsert(new StreamEntity(info));
|
final long streamId = streamTable.upsert(new StreamEntity(info));
|
||||||
StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
||||||
|
|
||||||
if (latestEntry != null) {
|
if (latestEntry != null) {
|
||||||
streamHistoryTable.delete(latestEntry);
|
streamHistoryTable.delete(latestEntry);
|
||||||
@ -129,7 +129,7 @@ public class HistoryRecordManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Single<List<Long>> insertStreamHistory(final Collection<StreamHistoryEntry> entries) {
|
public Single<List<Long>> insertStreamHistory(final Collection<StreamHistoryEntry> entries) {
|
||||||
List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
|
final List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
|
||||||
for (final StreamHistoryEntry entry : entries) {
|
for (final StreamHistoryEntry entry : entries) {
|
||||||
entities.add(entry.toStreamHistoryEntity());
|
entities.add(entry.toStreamHistoryEntity());
|
||||||
}
|
}
|
||||||
@ -138,7 +138,7 @@ public class HistoryRecordManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Single<Integer> deleteStreamHistory(final Collection<StreamHistoryEntry> entries) {
|
public Single<Integer> deleteStreamHistory(final Collection<StreamHistoryEntry> entries) {
|
||||||
List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
|
final List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
|
||||||
for (final StreamHistoryEntry entry : entries) {
|
for (final StreamHistoryEntry entry : entries) {
|
||||||
entities.add(entry.toStreamHistoryEntity());
|
entities.add(entry.toStreamHistoryEntity());
|
||||||
}
|
}
|
||||||
@ -163,7 +163,7 @@ public class HistoryRecordManager {
|
|||||||
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
|
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
|
||||||
|
|
||||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||||
SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry();
|
final SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry();
|
||||||
if (latestEntry != null && latestEntry.hasEqualValues(newEntry)) {
|
if (latestEntry != null && latestEntry.hasEqualValues(newEntry)) {
|
||||||
latestEntry.setCreationDate(currentTime);
|
latestEntry.setCreationDate(currentTime);
|
||||||
return (long) searchHistoryTable.update(latestEntry);
|
return (long) searchHistoryTable.update(latestEntry);
|
||||||
@ -256,7 +256,7 @@ public class HistoryRecordManager {
|
|||||||
public Single<List<StreamStateEntity>> loadStreamStateBatch(final List<InfoItem> infos) {
|
public Single<List<StreamStateEntity>> loadStreamStateBatch(final List<InfoItem> infos) {
|
||||||
return Single.fromCallable(() -> {
|
return Single.fromCallable(() -> {
|
||||||
final List<StreamStateEntity> result = new ArrayList<>(infos.size());
|
final List<StreamStateEntity> result = new ArrayList<>(infos.size());
|
||||||
for (InfoItem info : infos) {
|
for (final InfoItem info : infos) {
|
||||||
final List<StreamEntity> entities = streamTable
|
final List<StreamEntity> entities = streamTable
|
||||||
.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
|
.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
|
||||||
if (entities.isEmpty()) {
|
if (entities.isEmpty()) {
|
||||||
@ -279,8 +279,8 @@ public class HistoryRecordManager {
|
|||||||
final List<? extends LocalItem> items) {
|
final List<? extends LocalItem> items) {
|
||||||
return Single.fromCallable(() -> {
|
return Single.fromCallable(() -> {
|
||||||
final List<StreamStateEntity> result = new ArrayList<>(items.size());
|
final List<StreamStateEntity> result = new ArrayList<>(items.size());
|
||||||
for (LocalItem item : items) {
|
for (final LocalItem item : items) {
|
||||||
long streamId;
|
final long streamId;
|
||||||
if (item instanceof StreamStatisticsEntry) {
|
if (item instanceof StreamStatisticsEntry) {
|
||||||
streamId = ((StreamStatisticsEntry) item).getStreamId();
|
streamId = ((StreamStatisticsEntry) item).getStreamId();
|
||||||
} else if (item instanceof PlaylistStreamEntity) {
|
} else if (item instanceof PlaylistStreamEntity) {
|
||||||
|
@ -321,7 +321,7 @@ public class StatisticsPlaylistFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
headerPlayAllButton.setOnClickListener(view ->
|
headerPlayAllButton.setOnClickListener(view ->
|
||||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||||
headerPopupButton.setOnClickListener(view ->
|
headerPopupButton.setOnClickListener(view ->
|
||||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||||
headerBackgroundButton.setOnClickListener(view ->
|
headerBackgroundButton.setOnClickListener(view ->
|
||||||
@ -455,7 +455,7 @@ public class StatisticsPlaylistFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
|
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
|
||||||
List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
final List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
||||||
for (final LocalItem item : infoItems) {
|
for (final LocalItem item : infoItems) {
|
||||||
if (item instanceof StreamStatisticsEntry) {
|
if (item instanceof StreamStatisticsEntry) {
|
||||||
streamInfoItems.add(((StreamStatisticsEntry) item).toStreamInfoItem());
|
streamInfoItems.add(((StreamStatisticsEntry) item).toStreamInfoItem());
|
||||||
|
@ -70,7 +70,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
|||||||
R.color.duration_background_color));
|
R.color.duration_background_color));
|
||||||
itemDurationView.setVisibility(View.VISIBLE);
|
itemDurationView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
StreamStateEntity state = historyRecordManager
|
final StreamStateEntity state = historyRecordManager
|
||||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||||
add(localItem);
|
add(localItem);
|
||||||
}}).blockingGet().get(0);
|
}}).blockingGet().get(0);
|
||||||
@ -116,7 +116,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
|||||||
}
|
}
|
||||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
||||||
|
|
||||||
StreamStateEntity state = historyRecordManager
|
final StreamStateEntity state = historyRecordManager
|
||||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||||
add(localItem);
|
add(localItem);
|
||||||
}}).blockingGet().get(0);
|
}}).blockingGet().get(0);
|
||||||
|
@ -98,7 +98,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
|||||||
R.color.duration_background_color));
|
R.color.duration_background_color));
|
||||||
itemDurationView.setVisibility(View.VISIBLE);
|
itemDurationView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
StreamStateEntity state = historyRecordManager
|
final StreamStateEntity state = historyRecordManager
|
||||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||||
add(localItem);
|
add(localItem);
|
||||||
}}).blockingGet().get(0);
|
}}).blockingGet().get(0);
|
||||||
@ -146,7 +146,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
|||||||
}
|
}
|
||||||
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
|
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
|
||||||
|
|
||||||
StreamStateEntity state = historyRecordManager
|
final StreamStateEntity state = historyRecordManager
|
||||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||||
add(localItem);
|
add(localItem);
|
||||||
}}).blockingGet().get(0);
|
}}).blockingGet().get(0);
|
||||||
|
@ -98,7 +98,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
private boolean isRemovingWatched = false;
|
private boolean isRemovingWatched = false;
|
||||||
|
|
||||||
public static LocalPlaylistFragment getInstance(final long playlistId, final String name) {
|
public static LocalPlaylistFragment getInstance(final long playlistId, final String name) {
|
||||||
LocalPlaylistFragment instance = new LocalPlaylistFragment();
|
final LocalPlaylistFragment instance = new LocalPlaylistFragment();
|
||||||
instance.setInitialData(playlistId, name);
|
instance.setInitialData(playlistId, name);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@ -177,7 +177,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
public void selected(final LocalItem selectedItem) {
|
public void selected(final LocalItem selectedItem) {
|
||||||
if (selectedItem instanceof PlaylistStreamEntry) {
|
if (selectedItem instanceof PlaylistStreamEntry) {
|
||||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) selectedItem;
|
final PlaylistStreamEntry item = (PlaylistStreamEntry) selectedItem;
|
||||||
NavigationHelper.openVideoDetailFragment(getFragmentManager(),
|
NavigationHelper.openVideoDetailFragment(getFM(),
|
||||||
item.getStreamEntity().getServiceId(), item.getStreamEntity().getUrl(),
|
item.getStreamEntity().getServiceId(), item.getStreamEntity().getUrl(),
|
||||||
item.getStreamEntity().getTitle());
|
item.getStreamEntity().getTitle());
|
||||||
}
|
}
|
||||||
@ -411,7 +411,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
if (removePartiallyWatched) {
|
if (removePartiallyWatched) {
|
||||||
while (playlistIter.hasNext()) {
|
while (playlistIter.hasNext()) {
|
||||||
final PlaylistStreamEntry playlistItem = playlistIter.next();
|
final PlaylistStreamEntry playlistItem = playlistIter.next();
|
||||||
int indexInHistory = Collections.binarySearch(historyStreamIds,
|
final int indexInHistory = Collections.binarySearch(historyStreamIds,
|
||||||
playlistItem.getStreamId());
|
playlistItem.getStreamId());
|
||||||
|
|
||||||
if (indexInHistory < 0) {
|
if (indexInHistory < 0) {
|
||||||
@ -427,7 +427,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
.loadLocalStreamStateBatch(playlist).blockingGet().iterator();
|
.loadLocalStreamStateBatch(playlist).blockingGet().iterator();
|
||||||
|
|
||||||
while (playlistIter.hasNext()) {
|
while (playlistIter.hasNext()) {
|
||||||
PlaylistStreamEntry playlistItem = playlistIter.next();
|
final PlaylistStreamEntry playlistItem = playlistIter.next();
|
||||||
final int indexInHistory = Collections.binarySearch(historyStreamIds,
|
final int indexInHistory = Collections.binarySearch(historyStreamIds,
|
||||||
playlistItem.getStreamId());
|
playlistItem.getStreamId());
|
||||||
|
|
||||||
@ -492,7 +492,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
setVideoCount(itemListAdapter.getItemsList().size());
|
setVideoCount(itemListAdapter.getItemsList().size());
|
||||||
|
|
||||||
headerPlayAllButton.setOnClickListener(view ->
|
headerPlayAllButton.setOnClickListener(view ->
|
||||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||||
headerPopupButton.setOnClickListener(view ->
|
headerPopupButton.setOnClickListener(view ->
|
||||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||||
headerBackgroundButton.setOnClickListener(view ->
|
headerBackgroundButton.setOnClickListener(view ->
|
||||||
@ -544,7 +544,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
||||||
EditText nameEdit = dialogView.findViewById(R.id.playlist_name);
|
final EditText nameEdit = dialogView.findViewById(R.id.playlist_name);
|
||||||
nameEdit.setText(name);
|
nameEdit.setText(name);
|
||||||
nameEdit.setSelection(nameEdit.getText().length());
|
nameEdit.setSelection(nameEdit.getText().length());
|
||||||
|
|
||||||
@ -553,9 +553,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
.setView(dialogView)
|
.setView(dialogView)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.setPositiveButton(R.string.rename, (dialogInterface, i) -> {
|
.setPositiveButton(R.string.rename, (dialogInterface, i) ->
|
||||||
changePlaylistName(nameEdit.getText().toString());
|
changePlaylistName(nameEdit.getText().toString()));
|
||||||
});
|
|
||||||
|
|
||||||
dialogBuilder.show();
|
dialogBuilder.show();
|
||||||
}
|
}
|
||||||
@ -601,7 +600,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateThumbnailUrl() {
|
private void updateThumbnailUrl() {
|
||||||
String newThumbnailUrl;
|
final String newThumbnailUrl;
|
||||||
|
|
||||||
if (!itemListAdapter.getItemsList().isEmpty()) {
|
if (!itemListAdapter.getItemsList().isEmpty()) {
|
||||||
newThumbnailUrl = ((PlaylistStreamEntry) itemListAdapter.getItemsList().get(0))
|
newThumbnailUrl = ((PlaylistStreamEntry) itemListAdapter.getItemsList().get(0))
|
||||||
@ -662,7 +661,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
final List<LocalItem> items = itemListAdapter.getItemsList();
|
final List<LocalItem> items = itemListAdapter.getItemsList();
|
||||||
List<Long> streamIds = new ArrayList<>(items.size());
|
final List<Long> streamIds = new ArrayList<>(items.size());
|
||||||
for (final LocalItem item : items) {
|
for (final LocalItem item : items) {
|
||||||
if (item instanceof PlaylistStreamEntry) {
|
if (item instanceof PlaylistStreamEntry) {
|
||||||
streamIds.add(((PlaylistStreamEntry) item).getStreamId());
|
streamIds.add(((PlaylistStreamEntry) item).getStreamId());
|
||||||
@ -815,7 +814,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
|
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
|
||||||
List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
final List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
||||||
for (final LocalItem item : infoItems) {
|
for (final LocalItem item : infoItems) {
|
||||||
if (item instanceof PlaylistStreamEntry) {
|
if (item instanceof PlaylistStreamEntry) {
|
||||||
streamInfoItems.add(((PlaylistStreamEntry) item).toStreamInfoItem());
|
streamInfoItems.add(((PlaylistStreamEntry) item).toStreamInfoItem());
|
||||||
|
@ -61,7 +61,7 @@ public class LocalPlaylistManager {
|
|||||||
final List<StreamEntity> streams,
|
final List<StreamEntity> streams,
|
||||||
final int indexOffset) {
|
final int indexOffset) {
|
||||||
|
|
||||||
List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size());
|
final List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size());
|
||||||
final List<Long> streamIds = streamTable.upsertAll(streams);
|
final List<Long> streamIds = streamTable.upsertAll(streams);
|
||||||
for (int index = 0; index < streamIds.size(); index++) {
|
for (int index = 0; index < streamIds.size(); index++) {
|
||||||
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(index),
|
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(index),
|
||||||
@ -71,7 +71,7 @@ public class LocalPlaylistManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Completable updateJoin(final long playlistId, final List<Long> streamIds) {
|
public Completable updateJoin(final long playlistId, final List<Long> streamIds) {
|
||||||
List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streamIds.size());
|
final List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streamIds.size());
|
||||||
for (int i = 0; i < streamIds.size(); i++) {
|
for (int i = 0; i < streamIds.size(); i++) {
|
||||||
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(i), i));
|
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(i), i));
|
||||||
}
|
}
|
||||||
@ -115,7 +115,7 @@ public class LocalPlaylistManager {
|
|||||||
.firstElement()
|
.firstElement()
|
||||||
.filter(playlistEntities -> !playlistEntities.isEmpty())
|
.filter(playlistEntities -> !playlistEntities.isEmpty())
|
||||||
.map(playlistEntities -> {
|
.map(playlistEntities -> {
|
||||||
PlaylistEntity playlist = playlistEntities.get(0);
|
final PlaylistEntity playlist = playlistEntities.get(0);
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
playlist.setName(name);
|
playlist.setName(name);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ public class RemotePlaylistManager {
|
|||||||
|
|
||||||
public Single<Integer> onUpdate(final long playlistId, final PlaylistInfo playlistInfo) {
|
public Single<Integer> onUpdate(final long playlistId, final PlaylistInfo playlistInfo) {
|
||||||
return Single.fromCallable(() -> {
|
return Single.fromCallable(() -> {
|
||||||
PlaylistRemoteEntity playlist = new PlaylistRemoteEntity(playlistInfo);
|
final PlaylistRemoteEntity playlist = new PlaylistRemoteEntity(playlistInfo);
|
||||||
playlist.setUid(playlistId);
|
playlist.setUid(playlistId);
|
||||||
return playlistRemoteTable.update(playlist);
|
return playlistRemoteTable.update(playlist);
|
||||||
}).subscribeOn(Schedulers.io());
|
}).subscribeOn(Schedulers.io());
|
||||||
|
@ -11,15 +11,15 @@ import android.content.res.Configuration
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.preference.PreferenceManager
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.nononsenseapps.filepicker.Utils
|
import com.nononsenseapps.filepicker.Utils
|
||||||
import com.xwray.groupie.Group
|
import com.xwray.groupie.Group
|
||||||
@ -277,7 +277,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||||||
}
|
}
|
||||||
items_list.adapter = groupAdapter
|
items_list.adapter = groupAdapter
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this).get(SubscriptionViewModel::class.java)
|
viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java)
|
||||||
viewModel.stateLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer { it?.let(this::handleResult) })
|
viewModel.stateLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer { it?.let(this::handleResult) })
|
||||||
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer { it?.let(this::handleFeedGroups) })
|
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer { it?.let(this::handleFeedGroups) })
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@ package org.schabi.newpipe.local.subscription
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import org.schabi.newpipe.NewPipeDatabase
|
import org.schabi.newpipe.NewPipeDatabase
|
||||||
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
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 org.schabi.newpipe.extractor.ListInfo
|
import org.schabi.newpipe.extractor.ListInfo
|
||||||
@ -21,9 +23,28 @@ class SubscriptionManager(context: Context) {
|
|||||||
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
|
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
|
||||||
fun subscriptions() = subscriptionTable.all
|
fun subscriptions() = subscriptionTable.all
|
||||||
|
|
||||||
|
fun getSubscriptions(
|
||||||
|
currentGroupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||||
|
filterQuery: String = "",
|
||||||
|
showOnlyUngrouped: Boolean = false
|
||||||
|
): Flowable<List<SubscriptionEntity>> {
|
||||||
|
return when {
|
||||||
|
filterQuery.isNotEmpty() -> {
|
||||||
|
return if (showOnlyUngrouped) {
|
||||||
|
subscriptionTable.getSubscriptionsOnlyUngroupedFiltered(
|
||||||
|
currentGroupId, filterQuery)
|
||||||
|
} else {
|
||||||
|
subscriptionTable.getSubscriptionsFiltered(filterQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showOnlyUngrouped -> subscriptionTable.getSubscriptionsOnlyUngrouped(currentGroupId)
|
||||||
|
else -> subscriptionTable.all
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> {
|
fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> {
|
||||||
val listEntities = subscriptionTable.upsertAll(
|
val listEntities = subscriptionTable.upsertAll(
|
||||||
infoList.map { SubscriptionEntity.from(it) })
|
infoList.map { SubscriptionEntity.from(it) })
|
||||||
|
|
||||||
database.runInTransaction {
|
database.runInTransaction {
|
||||||
infoList.forEachIndexed { index, info ->
|
infoList.forEachIndexed { index, info ->
|
||||||
@ -35,13 +56,13 @@ class SubscriptionManager(context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url)
|
fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url)
|
||||||
.flatMapCompletable {
|
.flatMapCompletable {
|
||||||
Completable.fromRunnable {
|
Completable.fromRunnable {
|
||||||
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
|
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
|
||||||
subscriptionTable.update(it)
|
subscriptionTable.update(it)
|
||||||
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
|
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateFromInfo(subscriptionId: Long, info: ListInfo<StreamInfoItem>) {
|
fun updateFromInfo(subscriptionId: Long, info: ListInfo<StreamInfoItem>) {
|
||||||
val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId)
|
val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId)
|
||||||
@ -57,8 +78,8 @@ class SubscriptionManager(context: Context) {
|
|||||||
|
|
||||||
fun deleteSubscription(serviceId: Int, url: String): Completable {
|
fun deleteSubscription(serviceId: Int, url: String): Completable {
|
||||||
return Completable.fromCallable { subscriptionTable.deleteSubscription(serviceId, url) }
|
return Completable.fromCallable { subscriptionTable.deleteSubscription(serviceId, url) }
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun insertSubscription(subscriptionEntity: SubscriptionEntity, info: ChannelInfo) {
|
fun insertSubscription(subscriptionEntity: SubscriptionEntity, info: ChannelInfo) {
|
||||||
|
@ -64,7 +64,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
|||||||
private Button inputButton;
|
private Button inputButton;
|
||||||
|
|
||||||
public static SubscriptionsImportFragment getInstance(final int serviceId) {
|
public static SubscriptionsImportFragment getInstance(final int serviceId) {
|
||||||
SubscriptionsImportFragment instance = new SubscriptionsImportFragment();
|
final SubscriptionsImportFragment instance = new SubscriptionsImportFragment();
|
||||||
instance.setInitialData(serviceId);
|
instance.setInitialData(serviceId);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@ -140,7 +140,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
|||||||
setInfoText("");
|
setInfoText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
if (supportActionBar != null) {
|
if (supportActionBar != null) {
|
||||||
supportActionBar.setDisplayShowTitleEnabled(true);
|
supportActionBar.setDisplayShowTitleEnabled(true);
|
||||||
setTitle(getString(R.string.import_title));
|
setTitle(getString(R.string.import_title));
|
||||||
@ -206,7 +206,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
|||||||
relatedUrl = extractor.getRelatedUrl();
|
relatedUrl = extractor.getRelatedUrl();
|
||||||
instructionsString = ServiceHelper.getImportInstructions(currentServiceId);
|
instructionsString = ServiceHelper.getImportInstructions(currentServiceId);
|
||||||
return;
|
return;
|
||||||
} catch (ExtractionException ignored) {
|
} catch (final ExtractionException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import android.content.Context
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
|
import android.text.TextUtils
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -13,34 +14,22 @@ import android.view.inputmethod.InputMethodManager
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.xwray.groupie.GroupAdapter
|
import com.xwray.groupie.GroupAdapter
|
||||||
|
import com.xwray.groupie.OnItemClickListener
|
||||||
import com.xwray.groupie.Section
|
import com.xwray.groupie.Section
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import icepick.Icepick
|
import icepick.Icepick
|
||||||
import icepick.State
|
import icepick.State
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.cancel_button
|
import kotlin.collections.contains
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.confirm_button
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.*
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.delete_button
|
import kotlinx.android.synthetic.main.toolbar_search_layout.*
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.delete_screen_message
|
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.group_name_input
|
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.group_name_input_container
|
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.icon_preview
|
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.icon_selector
|
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.options_root
|
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.select_channel_button
|
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.selected_subscription_count_view
|
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.separator
|
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector
|
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector_header_info
|
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector_list
|
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
import org.schabi.newpipe.fragments.BackPressable
|
||||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.DeleteScreen
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.DeleteScreen
|
||||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.IconPickerScreen
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.IconPickerScreen
|
||||||
@ -51,9 +40,10 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.Dia
|
|||||||
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
||||||
import org.schabi.newpipe.local.subscription.item.PickerIconItem
|
import org.schabi.newpipe.local.subscription.item.PickerIconItem
|
||||||
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||||
|
import org.schabi.newpipe.util.DeviceUtils
|
||||||
import org.schabi.newpipe.util.ThemeHelper
|
import org.schabi.newpipe.util.ThemeHelper
|
||||||
|
|
||||||
class FeedGroupDialog : DialogFragment() {
|
class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||||
private lateinit var viewModel: FeedGroupDialogViewModel
|
private lateinit var viewModel: FeedGroupDialogViewModel
|
||||||
private var groupId: Long = NO_GROUP_SELECTED
|
private var groupId: Long = NO_GROUP_SELECTED
|
||||||
private var groupIcon: FeedGroupIcon? = null
|
private var groupIcon: FeedGroupIcon? = null
|
||||||
@ -66,22 +56,20 @@ class FeedGroupDialog : DialogFragment() {
|
|||||||
object DeleteScreen : ScreenState()
|
object DeleteScreen : ScreenState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@State
|
@State @JvmField var selectedIcon: FeedGroupIcon? = null
|
||||||
@JvmField
|
@State @JvmField var selectedSubscriptions: HashSet<Long> = HashSet()
|
||||||
var selectedIcon: FeedGroupIcon? = null
|
@State @JvmField var wasSubscriptionSelectionChanged: Boolean = false
|
||||||
@State
|
@State @JvmField var currentScreen: ScreenState = InitialScreen
|
||||||
@JvmField
|
|
||||||
var selectedSubscriptions: HashSet<Long> = HashSet()
|
|
||||||
@State
|
|
||||||
@JvmField
|
|
||||||
var currentScreen: ScreenState = InitialScreen
|
|
||||||
|
|
||||||
@State
|
@State @JvmField var subscriptionsListState: Parcelable? = null
|
||||||
@JvmField
|
@State @JvmField var iconsListState: Parcelable? = null
|
||||||
var subscriptionsListState: Parcelable? = null
|
@State @JvmField var wasSearchSubscriptionsVisible = false
|
||||||
@State
|
@State @JvmField var subscriptionsCurrentSearchQuery = ""
|
||||||
@JvmField
|
@State @JvmField var subscriptionsShowOnlyUngrouped = false
|
||||||
var iconsListState: Parcelable? = null
|
|
||||||
|
private val subscriptionMainSection = Section()
|
||||||
|
private val subscriptionEmptyFooter = Section()
|
||||||
|
private lateinit var subscriptionGroupAdapter: GroupAdapter<GroupieViewHolder>
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -91,22 +79,30 @@ class FeedGroupDialog : DialogFragment() {
|
|||||||
groupId = arguments?.getLong(KEY_GROUP_ID, NO_GROUP_SELECTED) ?: NO_GROUP_SELECTED
|
groupId = arguments?.getLong(KEY_GROUP_ID, NO_GROUP_SELECTED) ?: NO_GROUP_SELECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
return inflater.inflate(R.layout.dialog_feed_group_create, container)
|
return inflater.inflate(R.layout.dialog_feed_group_create, container)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
return object : Dialog(requireActivity(), theme) {
|
return object : Dialog(requireActivity(), theme) {
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (currentScreen !is InitialScreen) {
|
if (!this@FeedGroupDialog.onBackPressed()) {
|
||||||
showScreen(InitialScreen)
|
|
||||||
} else {
|
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
|
||||||
|
wasSearchSubscriptionsVisible = isSearchVisible()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
|
|
||||||
@ -119,11 +115,15 @@ class FeedGroupDialog : DialogFragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, FeedGroupDialogViewModel.Factory(requireContext(), groupId))
|
viewModel = ViewModelProvider(this,
|
||||||
.get(FeedGroupDialogViewModel::class.java)
|
FeedGroupDialogViewModel.Factory(requireContext(),
|
||||||
|
groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped)
|
||||||
|
).get(FeedGroupDialogViewModel::class.java)
|
||||||
|
|
||||||
viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup))
|
viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup))
|
||||||
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer { setupSubscriptionPicker(it.first, it.second) })
|
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer {
|
||||||
|
setupSubscriptionPicker(it.first, it.second)
|
||||||
|
})
|
||||||
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
|
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
|
||||||
when (it) {
|
when (it) {
|
||||||
ProcessingEvent -> disableInput()
|
ProcessingEvent -> disableInput()
|
||||||
@ -131,15 +131,54 @@ class FeedGroupDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
subscriptionGroupAdapter = GroupAdapter<GroupieViewHolder>().apply {
|
||||||
|
add(subscriptionMainSection)
|
||||||
|
add(subscriptionEmptyFooter)
|
||||||
|
spanCount = 4
|
||||||
|
}
|
||||||
|
subscriptions_selector_list.apply {
|
||||||
|
// Disable animations, too distracting.
|
||||||
|
itemAnimator = null
|
||||||
|
adapter = subscriptionGroupAdapter
|
||||||
|
layoutManager = GridLayoutManager(requireContext(), subscriptionGroupAdapter.spanCount,
|
||||||
|
RecyclerView.VERTICAL, false).apply {
|
||||||
|
spanSizeLookup = subscriptionGroupAdapter.spanSizeLookup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setupIconPicker()
|
setupIconPicker()
|
||||||
setupListeners()
|
setupListeners()
|
||||||
|
|
||||||
showScreen(currentScreen)
|
showScreen(currentScreen)
|
||||||
|
|
||||||
|
if (currentScreen == SubscriptionsPickerScreen && wasSearchSubscriptionsVisible) {
|
||||||
|
showSearch()
|
||||||
|
} else if (currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED) {
|
||||||
|
showKeyboard()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
subscriptions_selector_list?.adapter = null
|
||||||
|
icon_selector?.adapter = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/*///////////////////////////////////////////////////////////////////////////
|
||||||
// Setup
|
// Setup
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////// */
|
||||||
|
|
||||||
|
override fun onBackPressed(): Boolean {
|
||||||
|
if (currentScreen is SubscriptionsPickerScreen && isSearchVisible()) {
|
||||||
|
hideSearch()
|
||||||
|
return true
|
||||||
|
} else if (currentScreen !is InitialScreen) {
|
||||||
|
showScreen(InitialScreen)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupListeners() {
|
private fun setupListeners() {
|
||||||
delete_button.setOnClickListener { showScreen(DeleteScreen) }
|
delete_button.setOnClickListener { showScreen(DeleteScreen) }
|
||||||
@ -163,13 +202,64 @@ class FeedGroupDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
confirm_button.setOnClickListener {
|
confirm_button.setOnClickListener { handlePositiveButton() }
|
||||||
when (currentScreen) {
|
|
||||||
InitialScreen -> handlePositiveButtonInitialScreen()
|
select_channel_button.setOnClickListener {
|
||||||
DeleteScreen -> viewModel.deleteGroup()
|
subscriptions_selector_list.scrollToPosition(0)
|
||||||
else -> showScreen(InitialScreen)
|
showScreen(SubscriptionsPickerScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
val headerMenu = subscriptions_header_toolbar.menu
|
||||||
|
requireActivity().menuInflater.inflate(R.menu.menu_feed_group_dialog, headerMenu)
|
||||||
|
|
||||||
|
headerMenu.findItem(R.id.action_search).setOnMenuItemClickListener {
|
||||||
|
showSearch()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
headerMenu.findItem(R.id.feed_group_toggle_show_only_ungrouped_subscriptions).apply {
|
||||||
|
isChecked = subscriptionsShowOnlyUngrouped
|
||||||
|
setOnMenuItemClickListener {
|
||||||
|
subscriptionsShowOnlyUngrouped = !subscriptionsShowOnlyUngrouped
|
||||||
|
it.isChecked = subscriptionsShowOnlyUngrouped
|
||||||
|
viewModel.toggleShowOnlyUngrouped(subscriptionsShowOnlyUngrouped)
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toolbar_search_clear.setOnClickListener {
|
||||||
|
if (TextUtils.isEmpty(toolbar_search_edit_text.text)) {
|
||||||
|
hideSearch()
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
resetSearch()
|
||||||
|
showKeyboardSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
toolbar_search_edit_text.setOnClickListener {
|
||||||
|
if (DeviceUtils.isTv(context)) {
|
||||||
|
showKeyboardSearch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toolbar_search_edit_text.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
||||||
|
override fun afterTextChanged(s: Editable) = Unit
|
||||||
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
|
val newQuery: String = toolbar_search_edit_text.text.toString()
|
||||||
|
subscriptionsCurrentSearchQuery = newQuery
|
||||||
|
viewModel.filterSubscriptionsBy(newQuery)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
subscriptionGroupAdapter.setOnItemClickListener(subscriptionPickerItemListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePositiveButton() = when {
|
||||||
|
currentScreen is InitialScreen -> handlePositiveButtonInitialScreen()
|
||||||
|
currentScreen is DeleteScreen -> viewModel.deleteGroup()
|
||||||
|
currentScreen is SubscriptionsPickerScreen && isSearchVisible() -> hideSearch()
|
||||||
|
else -> showScreen(InitialScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePositiveButtonInitialScreen() {
|
private fun handlePositiveButtonInitialScreen() {
|
||||||
@ -202,80 +292,73 @@ class FeedGroupDialog : DialogFragment() {
|
|||||||
groupIcon = feedGroupEntity?.icon
|
groupIcon = feedGroupEntity?.icon
|
||||||
groupSortOrder = feedGroupEntity?.sortOrder ?: -1
|
groupSortOrder = feedGroupEntity?.sortOrder ?: -1
|
||||||
|
|
||||||
icon_preview.setImageResource((if (selectedIcon == null) icon else selectedIcon!!).getDrawableRes(requireContext()))
|
val feedGroupIcon = if (selectedIcon == null) icon else selectedIcon!!
|
||||||
|
icon_preview.setImageResource(feedGroupIcon.getDrawableRes(requireContext()))
|
||||||
|
|
||||||
if (group_name_input.text.isNullOrBlank()) {
|
if (group_name_input.text.isNullOrBlank()) {
|
||||||
group_name_input.setText(name)
|
group_name_input.setText(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSubscriptionPicker(subscriptions: List<SubscriptionEntity>, selectedSubscriptions: Set<Long>) {
|
private val subscriptionPickerItemListener = OnItemClickListener { item, view ->
|
||||||
this.selectedSubscriptions.addAll(selectedSubscriptions)
|
if (item is PickerSubscriptionItem) {
|
||||||
val useGridLayout = subscriptions.isNotEmpty()
|
val subscriptionId = item.subscriptionEntity.uid
|
||||||
|
wasSubscriptionSelectionChanged = true
|
||||||
|
|
||||||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
|
||||||
groupAdapter.spanCount = if (useGridLayout) 4 else 1
|
this.selectedSubscriptions.remove(subscriptionId)
|
||||||
|
false
|
||||||
val subscriptionsCount = this.selectedSubscriptions.size
|
|
||||||
val selectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
|
|
||||||
selected_subscription_count_view.text = selectedCountText
|
|
||||||
subscriptions_selector_header_info.text = selectedCountText
|
|
||||||
|
|
||||||
Section().apply {
|
|
||||||
addAll(subscriptions.map {
|
|
||||||
val isSelected = this@FeedGroupDialog.selectedSubscriptions.contains(it.uid)
|
|
||||||
PickerSubscriptionItem(it, isSelected)
|
|
||||||
})
|
|
||||||
setPlaceholder(EmptyPlaceholderItem())
|
|
||||||
|
|
||||||
groupAdapter.add(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriptions_selector_list.apply {
|
|
||||||
layoutManager = if (useGridLayout) {
|
|
||||||
GridLayoutManager(requireContext(), groupAdapter.spanCount, RecyclerView.VERTICAL, false)
|
|
||||||
} else {
|
} else {
|
||||||
LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
|
this.selectedSubscriptions.add(subscriptionId)
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter = groupAdapter
|
item.updateSelected(view, isSelected)
|
||||||
|
updateSubscriptionSelectedCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (subscriptionsListState != null) {
|
private fun setupSubscriptionPicker(
|
||||||
layoutManager?.onRestoreInstanceState(subscriptionsListState)
|
subscriptions: List<PickerSubscriptionItem>,
|
||||||
subscriptionsListState = null
|
selectedSubscriptions: Set<Long>
|
||||||
}
|
) {
|
||||||
|
if (!wasSubscriptionSelectionChanged) {
|
||||||
|
this.selectedSubscriptions.addAll(selectedSubscriptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
groupAdapter.setOnItemClickListener { item, _ ->
|
updateSubscriptionSelectedCount()
|
||||||
when (item) {
|
|
||||||
is PickerSubscriptionItem -> {
|
|
||||||
val subscriptionId = item.subscriptionEntity.uid
|
|
||||||
|
|
||||||
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
|
if (subscriptions.isEmpty()) {
|
||||||
this.selectedSubscriptions.remove(subscriptionId)
|
subscriptionEmptyFooter.clear()
|
||||||
false
|
subscriptionEmptyFooter.add(EmptyPlaceholderItem())
|
||||||
} else {
|
} else {
|
||||||
this.selectedSubscriptions.add(subscriptionId)
|
subscriptionEmptyFooter.clear()
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
item.isSelected = isSelected
|
|
||||||
item.notifyChanged(PickerSubscriptionItem.UPDATE_SELECTED)
|
|
||||||
|
|
||||||
val subscriptionsCount = this.selectedSubscriptions.size
|
|
||||||
val updateSelectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
|
|
||||||
selected_subscription_count_view.text = updateSelectedCountText
|
|
||||||
subscriptions_selector_header_info.text = updateSelectedCountText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select_channel_button.setOnClickListener {
|
subscriptions.forEach {
|
||||||
|
it.isSelected = this@FeedGroupDialog.selectedSubscriptions
|
||||||
|
.contains(it.subscriptionEntity.uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionMainSection.update(subscriptions, false)
|
||||||
|
|
||||||
|
if (subscriptionsListState != null) {
|
||||||
|
subscriptions_selector_list.layoutManager?.onRestoreInstanceState(subscriptionsListState)
|
||||||
|
subscriptionsListState = null
|
||||||
|
} else {
|
||||||
subscriptions_selector_list.scrollToPosition(0)
|
subscriptions_selector_list.scrollToPosition(0)
|
||||||
showScreen(SubscriptionsPickerScreen)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateSubscriptionSelectedCount() {
|
||||||
|
val selectedCount = this.selectedSubscriptions.size
|
||||||
|
val selectedCountText = resources.getQuantityString(
|
||||||
|
R.plurals.feed_group_dialog_selection_count,
|
||||||
|
selectedCount, selectedCount)
|
||||||
|
selected_subscription_count_view.text = selectedCountText
|
||||||
|
subscriptions_header_info.text = selectedCountText
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupIconPicker() {
|
private fun setupIconPicker() {
|
||||||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||||
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(requireContext(), it) })
|
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(requireContext(), it) })
|
||||||
@ -311,9 +394,9 @@ class FeedGroupDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
/*///////////////////////////////////////////////////////////////////////////
|
||||||
// Screen Selector
|
// Screen Selector
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////// */
|
||||||
|
|
||||||
private fun showScreen(screen: ScreenState) {
|
private fun showScreen(screen: ScreenState) {
|
||||||
currentScreen = screen
|
currentScreen = screen
|
||||||
@ -337,7 +420,8 @@ class FeedGroupDialog : DialogFragment() {
|
|||||||
else -> View.VISIBLE
|
else -> View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentScreen != InitialScreen) hideKeyboard()
|
hideKeyboard()
|
||||||
|
hideSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun View.onlyVisibleIn(vararg screens: ScreenState) {
|
private fun View.onlyVisibleIn(vararg screens: ScreenState) {
|
||||||
@ -347,13 +431,58 @@ class FeedGroupDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
/*///////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////// */
|
||||||
|
|
||||||
|
private fun isSearchVisible() = subscriptions_header_search_container?.visibility == View.VISIBLE
|
||||||
|
|
||||||
|
private fun resetSearch() {
|
||||||
|
toolbar_search_edit_text.setText("")
|
||||||
|
subscriptionsCurrentSearchQuery = ""
|
||||||
|
viewModel.clearSubscriptionsFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideSearch() {
|
||||||
|
resetSearch()
|
||||||
|
subscriptions_header_search_container.visibility = View.GONE
|
||||||
|
subscriptions_header_info_container.visibility = View.VISIBLE
|
||||||
|
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = true
|
||||||
|
hideKeyboardSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSearch() {
|
||||||
|
subscriptions_header_search_container.visibility = View.VISIBLE
|
||||||
|
subscriptions_header_info_container.visibility = View.GONE
|
||||||
|
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = false
|
||||||
|
showKeyboardSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val inputMethodManager by lazy {
|
||||||
|
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showKeyboardSearch() {
|
||||||
|
if (toolbar_search_edit_text.requestFocus()) {
|
||||||
|
inputMethodManager.showSoftInput(toolbar_search_edit_text, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideKeyboardSearch() {
|
||||||
|
inputMethodManager.hideSoftInputFromWindow(toolbar_search_edit_text.windowToken,
|
||||||
|
InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||||
|
toolbar_search_edit_text.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showKeyboard() {
|
||||||
|
if (group_name_input.requestFocus()) {
|
||||||
|
inputMethodManager.showSoftInput(group_name_input, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun hideKeyboard() {
|
private fun hideKeyboard() {
|
||||||
val inputMethodManager = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken,
|
||||||
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||||
group_name_input.clearFocus()
|
group_name_input.clearFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,42 +9,56 @@ import io.reactivex.Completable
|
|||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
||||||
|
import io.reactivex.processors.BehaviorProcessor
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
|
||||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||||
|
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||||
|
|
||||||
class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
|
class FeedGroupDialogViewModel(
|
||||||
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
applicationContext: Context,
|
||||||
@Suppress("UNCHECKED_CAST")
|
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
initialQuery: String = "",
|
||||||
return FeedGroupDialogViewModel(context.applicationContext, groupId) as T
|
initialShowOnlyUngrouped: Boolean = false
|
||||||
}
|
) : ViewModel() {
|
||||||
}
|
|
||||||
|
|
||||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext)
|
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext)
|
||||||
private var subscriptionManager = SubscriptionManager(applicationContext)
|
private var subscriptionManager = SubscriptionManager(applicationContext)
|
||||||
|
|
||||||
|
private var filterSubscriptions = BehaviorProcessor.create<String>()
|
||||||
|
private var toggleShowOnlyUngrouped = BehaviorProcessor.create<Boolean>()
|
||||||
|
|
||||||
|
private var subscriptionsFlowable = Flowable
|
||||||
|
.combineLatest(
|
||||||
|
filterSubscriptions.startWith(initialQuery),
|
||||||
|
toggleShowOnlyUngrouped.startWith(initialShowOnlyUngrouped),
|
||||||
|
BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
|
||||||
|
)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.switchMap { filter ->
|
||||||
|
subscriptionManager.getSubscriptions(groupId, filter.query, filter.showOnlyUngrouped)
|
||||||
|
}.map { list -> list.map { PickerSubscriptionItem(it) } }
|
||||||
|
|
||||||
private val mutableGroupLiveData = MutableLiveData<FeedGroupEntity>()
|
private val mutableGroupLiveData = MutableLiveData<FeedGroupEntity>()
|
||||||
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<SubscriptionEntity>, Set<Long>>>()
|
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>>()
|
||||||
private val mutableDialogEventLiveData = MutableLiveData<DialogEvent>()
|
private val mutableDialogEventLiveData = MutableLiveData<DialogEvent>()
|
||||||
val groupLiveData: LiveData<FeedGroupEntity> = mutableGroupLiveData
|
val groupLiveData: LiveData<FeedGroupEntity> = mutableGroupLiveData
|
||||||
val subscriptionsLiveData: LiveData<Pair<List<SubscriptionEntity>, Set<Long>>> = mutableSubscriptionsLiveData
|
val subscriptionsLiveData: LiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>> = mutableSubscriptionsLiveData
|
||||||
val dialogEventLiveData: LiveData<DialogEvent> = mutableDialogEventLiveData
|
val dialogEventLiveData: LiveData<DialogEvent> = mutableDialogEventLiveData
|
||||||
|
|
||||||
private var actionProcessingDisposable: Disposable? = null
|
private var actionProcessingDisposable: Disposable? = null
|
||||||
|
|
||||||
private var feedGroupDisposable = feedDatabaseManager.getGroup(groupId)
|
private var feedGroupDisposable = feedDatabaseManager.getGroup(groupId)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe(mutableGroupLiveData::postValue)
|
.subscribe(mutableGroupLiveData::postValue)
|
||||||
|
|
||||||
private var subscriptionsDisposable = Flowable
|
private var subscriptionsDisposable = Flowable
|
||||||
.combineLatest(subscriptionManager.subscriptions(), feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
.combineLatest(subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
||||||
BiFunction { t1: List<SubscriptionEntity>, t2: List<Long> -> t1 to t2.toSet() })
|
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() })
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe(mutableSubscriptionsLiveData::postValue)
|
.subscribe(mutableSubscriptionsLiveData::postValue)
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
@ -55,14 +69,14 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
|||||||
|
|
||||||
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
|
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
|
||||||
doAction(feedDatabaseManager.createGroup(name, selectedIcon)
|
doAction(feedDatabaseManager.createGroup(name, selectedIcon)
|
||||||
.flatMapCompletable {
|
.flatMapCompletable {
|
||||||
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
|
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
|
||||||
doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
|
doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
|
||||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteGroup() {
|
fun deleteGroup() {
|
||||||
@ -74,13 +88,40 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
|||||||
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
|
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
|
||||||
|
|
||||||
actionProcessingDisposable = completable
|
actionProcessingDisposable = completable
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
|
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun filterSubscriptionsBy(query: String) {
|
||||||
|
filterSubscriptions.onNext(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearSubscriptionsFilter() {
|
||||||
|
filterSubscriptions.onNext("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleShowOnlyUngrouped(showOnlyUngrouped: Boolean) {
|
||||||
|
toggleShowOnlyUngrouped.onNext(showOnlyUngrouped)
|
||||||
|
}
|
||||||
|
|
||||||
sealed class DialogEvent {
|
sealed class DialogEvent {
|
||||||
object ProcessingEvent : DialogEvent()
|
object ProcessingEvent : DialogEvent()
|
||||||
object SuccessEvent : DialogEvent()
|
object SuccessEvent : DialogEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class Filter(val query: String, val showOnlyUngrouped: Boolean)
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val context: Context,
|
||||||
|
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||||
|
private val initialQuery: String = "",
|
||||||
|
private val initialShowOnlyUngrouped: Boolean = false
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
|
return FeedGroupDialogViewModel(context.applicationContext,
|
||||||
|
groupId, initialQuery, initialShowOnlyUngrouped) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback
|
import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@ -49,7 +49,7 @@ class FeedGroupReorderDialog : DialogFragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this).get(FeedGroupReorderDialogViewModel::class.java)
|
viewModel = ViewModelProvider(this).get(FeedGroupReorderDialogViewModel::class.java)
|
||||||
viewModel.groupsLiveData.observe(viewLifecycleOwner, Observer(::handleGroups))
|
viewModel.groupsLiveData.observe(viewLifecycleOwner, Observer(::handleGroups))
|
||||||
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
|
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
|
||||||
when (it) {
|
when (it) {
|
||||||
|
@ -7,4 +7,5 @@ import org.schabi.newpipe.R
|
|||||||
class EmptyPlaceholderItem : Item() {
|
class EmptyPlaceholderItem : Item() {
|
||||||
override fun getLayout(): Int = R.layout.list_empty_view
|
override fun getLayout(): Int = R.layout.list_empty_view
|
||||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
||||||
|
override fun getSpanSize(spanCount: Int, position: Int): Int = spanCount
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,28 @@
|
|||||||
package org.schabi.newpipe.local.subscription.item
|
package org.schabi.newpipe.local.subscription.item
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.nostra13.universalimageloader.core.DisplayImageOptions
|
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader
|
import com.nostra13.universalimageloader.core.ImageLoader
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||||
import kotlinx.android.synthetic.main.picker_subscription_item.selected_highlight
|
import kotlinx.android.synthetic.main.picker_subscription_item.*
|
||||||
import kotlinx.android.synthetic.main.picker_subscription_item.thumbnail_view
|
import kotlinx.android.synthetic.main.picker_subscription_item.view.*
|
||||||
import kotlinx.android.synthetic.main.picker_subscription_item.title_view
|
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
import org.schabi.newpipe.util.AnimationUtils
|
import org.schabi.newpipe.util.AnimationUtils
|
||||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants
|
import org.schabi.newpipe.util.ImageDisplayConstants
|
||||||
|
|
||||||
data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, var isSelected: Boolean = false) : Item() {
|
data class PickerSubscriptionItem(
|
||||||
companion object {
|
val subscriptionEntity: SubscriptionEntity,
|
||||||
const val UPDATE_SELECTED = 123
|
var isSelected: Boolean = false
|
||||||
|
) : Item() {
|
||||||
val IMAGE_LOADING_OPTIONS: DisplayImageOptions = ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
|
override fun getId(): Long = subscriptionEntity.uid
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLayout(): Int = R.layout.picker_subscription_item
|
override fun getLayout(): Int = R.layout.picker_subscription_item
|
||||||
|
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
|
||||||
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
|
|
||||||
if (payloads.contains(UPDATE_SELECTED)) {
|
|
||||||
animateView(viewHolder.selected_highlight, AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
super.bind(viewHolder, position, payloads)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
||||||
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl, viewHolder.thumbnail_view, IMAGE_LOADING_OPTIONS)
|
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl,
|
||||||
|
viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS)
|
||||||
|
|
||||||
viewHolder.title_view.text = subscriptionEntity.name
|
viewHolder.title_view.text = subscriptionEntity.name
|
||||||
viewHolder.selected_highlight.visibility = if (isSelected) View.VISIBLE else View.GONE
|
viewHolder.selected_highlight.visibility = if (isSelected) View.VISIBLE else View.GONE
|
||||||
@ -47,7 +36,9 @@ data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, va
|
|||||||
viewHolder.selected_highlight.alpha = 1F
|
viewHolder.selected_highlight.alpha = 1F
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getId(): Long {
|
fun updateSelected(containerView: View, isSelected: Boolean) {
|
||||||
return subscriptionEntity.uid
|
this.isSelected = isSelected
|
||||||
|
animateView(containerView.selected_highlight,
|
||||||
|
AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,12 +86,12 @@ public final class ImportExportJsonHelper {
|
|||||||
eventListener.onSizeReceived(channelsArray.size());
|
eventListener.onSizeReceived(channelsArray.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Object o : channelsArray) {
|
for (final Object o : channelsArray) {
|
||||||
if (o instanceof JsonObject) {
|
if (o instanceof JsonObject) {
|
||||||
JsonObject itemObject = (JsonObject) o;
|
final JsonObject itemObject = (JsonObject) o;
|
||||||
int serviceId = itemObject.getInt(JSON_SERVICE_ID_KEY, 0);
|
final int serviceId = itemObject.getInt(JSON_SERVICE_ID_KEY, 0);
|
||||||
String url = itemObject.getString(JSON_URL_KEY);
|
final String url = itemObject.getString(JSON_URL_KEY);
|
||||||
String name = itemObject.getString(JSON_NAME_KEY);
|
final String name = itemObject.getString(JSON_NAME_KEY);
|
||||||
|
|
||||||
if (url != null && name != null && !url.isEmpty() && !name.isEmpty()) {
|
if (url != null && name != null && !url.isEmpty() && !name.isEmpty()) {
|
||||||
channels.add(new SubscriptionItem(serviceId, url, name));
|
channels.add(new SubscriptionItem(serviceId, url, name));
|
||||||
@ -101,7 +101,7 @@ public final class ImportExportJsonHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (final Throwable e) {
|
||||||
throw new InvalidSourceException("Couldn't parse json", e);
|
throw new InvalidSourceException("Couldn't parse json", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ public final class ImportExportJsonHelper {
|
|||||||
*/
|
*/
|
||||||
public static void writeTo(final List<SubscriptionItem> items, final OutputStream out,
|
public static void writeTo(final List<SubscriptionItem> items, final OutputStream out,
|
||||||
@Nullable final ImportExportEventListener eventListener) {
|
@Nullable final ImportExportEventListener eventListener) {
|
||||||
JsonAppendableWriter writer = JsonWriter.on(out);
|
final JsonAppendableWriter writer = JsonWriter.on(out);
|
||||||
writeTo(items, writer, eventListener);
|
writeTo(items, writer, eventListener);
|
||||||
writer.done();
|
writer.done();
|
||||||
}
|
}
|
||||||
@ -140,7 +140,7 @@ public final class ImportExportJsonHelper {
|
|||||||
writer.value(JSON_APP_VERSION_INT_KEY, BuildConfig.VERSION_CODE);
|
writer.value(JSON_APP_VERSION_INT_KEY, BuildConfig.VERSION_CODE);
|
||||||
|
|
||||||
writer.array(JSON_SUBSCRIPTIONS_ARRAY_KEY);
|
writer.array(JSON_SUBSCRIPTIONS_ARRAY_KEY);
|
||||||
for (SubscriptionItem item : items) {
|
for (final SubscriptionItem item : items) {
|
||||||
writer.object();
|
writer.object();
|
||||||
writer.value(JSON_SERVICE_ID_KEY, item.getServiceId());
|
writer.value(JSON_SERVICE_ID_KEY, item.getServiceId());
|
||||||
writer.value(JSON_URL_KEY, item.getUrl());
|
writer.value(JSON_URL_KEY, item.getUrl());
|
||||||
|
@ -74,7 +74,7 @@ public class SubscriptionsExportService extends BaseImportExportService {
|
|||||||
try {
|
try {
|
||||||
outFile = new File(path);
|
outFile = new File(path);
|
||||||
outputStream = new FileOutputStream(outFile);
|
outputStream = new FileOutputStream(outFile);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (final FileNotFoundException e) {
|
||||||
handleError(e);
|
handleError(e);
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ public class SubscriptionsExportService extends BaseImportExportService {
|
|||||||
.map(subscriptionEntities -> {
|
.map(subscriptionEntities -> {
|
||||||
final List<SubscriptionItem> result
|
final List<SubscriptionItem> result
|
||||||
= new ArrayList<>(subscriptionEntities.size());
|
= new ArrayList<>(subscriptionEntities.size());
|
||||||
for (SubscriptionEntity entity : subscriptionEntities) {
|
for (final SubscriptionEntity entity : subscriptionEntities) {
|
||||||
result.add(new SubscriptionItem(entity.getServiceId(), entity.getUrl(),
|
result.add(new SubscriptionItem(entity.getServiceId(), entity.getUrl(),
|
||||||
entity.getName()));
|
entity.getName()));
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
inputStream = new FileInputStream(new File(filePath));
|
inputStream = new FileInputStream(new File(filePath));
|
||||||
} catch (FileNotFoundException e) {
|
} catch (final FileNotFoundException e) {
|
||||||
handleError(e);
|
handleError(e);
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
@ -187,7 +187,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
|
|||||||
.getChannelInfo(subscriptionItem.getServiceId(),
|
.getChannelInfo(subscriptionItem.getServiceId(),
|
||||||
subscriptionItem.getUrl(), true)
|
subscriptionItem.getUrl(), true)
|
||||||
.blockingGet());
|
.blockingGet());
|
||||||
} catch (Throwable e) {
|
} catch (final Throwable e) {
|
||||||
return Notification.createOnError(e);
|
return Notification.createOnError(e);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -239,7 +239,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
|
|||||||
private Consumer<Notification<ChannelInfo>> getNotificationsConsumer() {
|
private Consumer<Notification<ChannelInfo>> getNotificationsConsumer() {
|
||||||
return notification -> {
|
return notification -> {
|
||||||
if (notification.isOnNext()) {
|
if (notification.isOnNext()) {
|
||||||
String name = notification.getValue().getName();
|
final String name = notification.getValue().getName();
|
||||||
eventListener.onItemCompleted(!TextUtils.isEmpty(name) ? name : "");
|
eventListener.onItemCompleted(!TextUtils.isEmpty(name) ? name : "");
|
||||||
} else if (notification.isOnError()) {
|
} else if (notification.isOnError()) {
|
||||||
final Throwable error = notification.getError();
|
final Throwable error = notification.getError();
|
||||||
@ -260,7 +260,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
|
|||||||
private Function<List<Notification<ChannelInfo>>, List<SubscriptionEntity>> upsertBatch() {
|
private Function<List<Notification<ChannelInfo>>, List<SubscriptionEntity>> upsertBatch() {
|
||||||
return notificationList -> {
|
return notificationList -> {
|
||||||
final List<ChannelInfo> infoList = new ArrayList<>(notificationList.size());
|
final List<ChannelInfo> infoList = new ArrayList<>(notificationList.size());
|
||||||
for (Notification<ChannelInfo> n : notificationList) {
|
for (final Notification<ChannelInfo> n : notificationList) {
|
||||||
if (n.isOnNext()) {
|
if (n.isOnNext()) {
|
||||||
infoList.add(n.getValue());
|
infoList.add(n.getValue());
|
||||||
}
|
}
|
||||||
|
@ -1,684 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
|
|
||||||
* BackgroundPlayer.java is part of NewPipe
|
|
||||||
*
|
|
||||||
* License: GPL-3.0+
|
|
||||||
* This program 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.
|
|
||||||
*
|
|
||||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.schabi.newpipe.player;
|
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.RemoteViews;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
|
||||||
import com.google.android.exoplayer2.Player;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
|
||||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|
||||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
|
||||||
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
|
||||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
|
||||||
import org.schabi.newpipe.util.BitmapUtils;
|
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service Background Player implementing {@link VideoPlayer}.
|
|
||||||
*
|
|
||||||
* @author mauriciocolli
|
|
||||||
*/
|
|
||||||
public final class BackgroundPlayer extends Service {
|
|
||||||
public static final String ACTION_CLOSE
|
|
||||||
= "org.schabi.newpipe.player.BackgroundPlayer.CLOSE";
|
|
||||||
public static final String ACTION_PLAY_PAUSE
|
|
||||||
= "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE";
|
|
||||||
public static final String ACTION_REPEAT
|
|
||||||
= "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
|
|
||||||
public static final String ACTION_PLAY_NEXT
|
|
||||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT";
|
|
||||||
public static final String ACTION_PLAY_PREVIOUS
|
|
||||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS";
|
|
||||||
public static final String ACTION_FAST_REWIND
|
|
||||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
|
|
||||||
public static final String ACTION_FAST_FORWARD
|
|
||||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
|
|
||||||
|
|
||||||
public static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
|
|
||||||
private static final String TAG = "BackgroundPlayer";
|
|
||||||
private static final boolean DEBUG = BasePlayer.DEBUG;
|
|
||||||
private static final int NOTIFICATION_ID = 123789;
|
|
||||||
private static final int NOTIFICATION_UPDATES_BEFORE_RESET = 60;
|
|
||||||
private BasePlayerImpl basePlayerImpl;
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Service-Activity Binder
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Notification
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
private PlayerEventListener activityListener;
|
|
||||||
private IBinder mBinder;
|
|
||||||
private NotificationManager notificationManager;
|
|
||||||
private NotificationCompat.Builder notBuilder;
|
|
||||||
private RemoteViews notRemoteView;
|
|
||||||
private RemoteViews bigNotRemoteView;
|
|
||||||
private boolean shouldUpdateOnProgress;
|
|
||||||
private int timesNotificationUpdated;
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Service's LifeCycle
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onCreate() called");
|
|
||||||
}
|
|
||||||
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
|
||||||
assureCorrectAppLanguage(this);
|
|
||||||
ThemeHelper.setTheme(this);
|
|
||||||
basePlayerImpl = new BasePlayerImpl(this);
|
|
||||||
basePlayerImpl.setup();
|
|
||||||
|
|
||||||
mBinder = new PlayerServiceBinder(basePlayerImpl);
|
|
||||||
shouldUpdateOnProgress = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int onStartCommand(final Intent intent, final int flags, final int startId) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], "
|
|
||||||
+ "flags = [" + flags + "], startId = [" + startId + "]");
|
|
||||||
}
|
|
||||||
basePlayerImpl.handleIntent(intent);
|
|
||||||
if (basePlayerImpl.mediaSessionManager != null) {
|
|
||||||
basePlayerImpl.mediaSessionManager.handleMediaButtonIntent(intent);
|
|
||||||
}
|
|
||||||
return START_NOT_STICKY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "destroy() called");
|
|
||||||
}
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void attachBaseContext(final Context base) {
|
|
||||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(final Intent intent) {
|
|
||||||
return mBinder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Actions
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
private void onClose() {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onClose() called");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (basePlayerImpl != null) {
|
|
||||||
basePlayerImpl.savePlaybackState();
|
|
||||||
basePlayerImpl.stopActivityBinding();
|
|
||||||
basePlayerImpl.destroy();
|
|
||||||
}
|
|
||||||
if (notificationManager != null) {
|
|
||||||
notificationManager.cancel(NOTIFICATION_ID);
|
|
||||||
}
|
|
||||||
mBinder = null;
|
|
||||||
basePlayerImpl = null;
|
|
||||||
|
|
||||||
stopForeground(true);
|
|
||||||
stopSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onScreenOnOff(final boolean on) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
|
|
||||||
}
|
|
||||||
shouldUpdateOnProgress = on;
|
|
||||||
basePlayerImpl.triggerProgressUpdate();
|
|
||||||
if (on) {
|
|
||||||
basePlayerImpl.startProgressLoop();
|
|
||||||
} else {
|
|
||||||
basePlayerImpl.stopProgressLoop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Notification
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
private void resetNotification() {
|
|
||||||
notBuilder = createNotification();
|
|
||||||
timesNotificationUpdated = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NotificationCompat.Builder createNotification() {
|
|
||||||
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
|
|
||||||
R.layout.player_background_notification);
|
|
||||||
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
|
|
||||||
R.layout.player_background_notification_expanded);
|
|
||||||
|
|
||||||
setupNotification(notRemoteView);
|
|
||||||
setupNotification(bigNotRemoteView);
|
|
||||||
|
|
||||||
NotificationCompat.Builder builder = new NotificationCompat
|
|
||||||
.Builder(this, getString(R.string.notification_channel_id))
|
|
||||||
.setOngoing(true)
|
|
||||||
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
||||||
.setCustomContentView(notRemoteView)
|
|
||||||
.setCustomBigContentView(bigNotRemoteView);
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
setLockScreenThumbnail(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
|
||||||
builder.setPriority(NotificationCompat.PRIORITY_MAX);
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private void setLockScreenThumbnail(final NotificationCompat.Builder builder) {
|
|
||||||
boolean isLockScreenThumbnailEnabled = sharedPreferences.getBoolean(
|
|
||||||
getString(R.string.enable_lock_screen_video_thumbnail_key), true);
|
|
||||||
|
|
||||||
if (isLockScreenThumbnailEnabled) {
|
|
||||||
basePlayerImpl.mediaSessionManager.setLockScreenArt(
|
|
||||||
builder,
|
|
||||||
getCenteredThumbnailBitmap()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
basePlayerImpl.mediaSessionManager.clearLockScreenArt(builder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private Bitmap getCenteredThumbnailBitmap() {
|
|
||||||
final int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
|
|
||||||
final int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
|
|
||||||
|
|
||||||
return BitmapUtils.centerCrop(basePlayerImpl.getThumbnail(), screenWidth, screenHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupNotification(final RemoteViews remoteViews) {
|
|
||||||
if (basePlayerImpl == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle());
|
|
||||||
remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName());
|
|
||||||
|
|
||||||
remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause,
|
|
||||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
|
||||||
new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
|
|
||||||
remoteViews.setOnClickPendingIntent(R.id.notificationStop,
|
|
||||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
|
||||||
new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
|
|
||||||
remoteViews.setOnClickPendingIntent(R.id.notificationRepeat,
|
|
||||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
|
||||||
new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
|
|
||||||
|
|
||||||
// Starts background player activity -- attempts to unlock lockscreen
|
|
||||||
final Intent intent = NavigationHelper.getBackgroundPlayerActivityIntent(this);
|
|
||||||
remoteViews.setOnClickPendingIntent(R.id.notificationContent,
|
|
||||||
PendingIntent.getActivity(this, NOTIFICATION_ID, intent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT));
|
|
||||||
|
|
||||||
if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) {
|
|
||||||
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
|
|
||||||
R.drawable.exo_controls_previous);
|
|
||||||
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
|
|
||||||
R.drawable.exo_controls_next);
|
|
||||||
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
|
|
||||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
|
||||||
new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
|
|
||||||
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
|
|
||||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
|
||||||
new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
|
|
||||||
} else {
|
|
||||||
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
|
|
||||||
R.drawable.exo_controls_rewind);
|
|
||||||
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
|
|
||||||
R.drawable.exo_controls_fastforward);
|
|
||||||
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
|
|
||||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
|
||||||
new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
|
|
||||||
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
|
|
||||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
|
||||||
new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
|
|
||||||
}
|
|
||||||
|
|
||||||
setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the notification, and the play/pause button in it.
|
|
||||||
* Used for changes on the remoteView
|
|
||||||
*
|
|
||||||
* @param drawableId if != -1, sets the drawable with that id on the play/pause button
|
|
||||||
*/
|
|
||||||
private synchronized void updateNotification(final int drawableId) {
|
|
||||||
// if (DEBUG) {
|
|
||||||
// Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
|
|
||||||
// }
|
|
||||||
if (notBuilder == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (drawableId != -1) {
|
|
||||||
if (notRemoteView != null) {
|
|
||||||
notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
|
||||||
}
|
|
||||||
if (bigNotRemoteView != null) {
|
|
||||||
bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
|
|
||||||
timesNotificationUpdated++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Utils
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) {
|
|
||||||
switch (repeatMode) {
|
|
||||||
case Player.REPEAT_MODE_OFF:
|
|
||||||
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
|
|
||||||
R.drawable.exo_controls_repeat_off);
|
|
||||||
break;
|
|
||||||
case Player.REPEAT_MODE_ONE:
|
|
||||||
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
|
|
||||||
R.drawable.exo_controls_repeat_one);
|
|
||||||
break;
|
|
||||||
case Player.REPEAT_MODE_ALL:
|
|
||||||
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
|
|
||||||
R.drawable.exo_controls_repeat_all);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
protected class BasePlayerImpl extends BasePlayer {
|
|
||||||
@NonNull
|
|
||||||
private final AudioPlaybackResolver resolver;
|
|
||||||
private int cachedDuration;
|
|
||||||
private String cachedDurationString;
|
|
||||||
|
|
||||||
BasePlayerImpl(final Context context) {
|
|
||||||
super(context);
|
|
||||||
this.resolver = new AudioPlaybackResolver(context, dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initPlayer(final boolean playOnReady) {
|
|
||||||
super.initPlayer(playOnReady);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleIntent(final Intent intent) {
|
|
||||||
super.handleIntent(intent);
|
|
||||||
|
|
||||||
resetNotification();
|
|
||||||
if (bigNotRemoteView != null) {
|
|
||||||
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
|
|
||||||
}
|
|
||||||
if (notRemoteView != null) {
|
|
||||||
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
|
|
||||||
}
|
|
||||||
startForeground(NOTIFICATION_ID, notBuilder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Thumbnail Loading
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
private void updateNotificationThumbnail() {
|
|
||||||
if (basePlayerImpl == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (notRemoteView != null) {
|
|
||||||
notRemoteView.setImageViewBitmap(R.id.notificationCover,
|
|
||||||
basePlayerImpl.getThumbnail());
|
|
||||||
}
|
|
||||||
if (bigNotRemoteView != null) {
|
|
||||||
bigNotRemoteView.setImageViewBitmap(R.id.notificationCover,
|
|
||||||
basePlayerImpl.getThumbnail());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadingComplete(final String imageUri, final View view,
|
|
||||||
final Bitmap loadedImage) {
|
|
||||||
super.onLoadingComplete(imageUri, view, loadedImage);
|
|
||||||
resetNotification();
|
|
||||||
updateNotificationThumbnail();
|
|
||||||
updateNotification(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadingFailed(final String imageUri, final View view,
|
|
||||||
final FailReason failReason) {
|
|
||||||
super.onLoadingFailed(imageUri, view, failReason);
|
|
||||||
resetNotification();
|
|
||||||
updateNotificationThumbnail();
|
|
||||||
updateNotification(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// States Implementation
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPrepared(final boolean playWhenReady) {
|
|
||||||
super.onPrepared(playWhenReady);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onShuffleClicked() {
|
|
||||||
super.onShuffleClicked();
|
|
||||||
updatePlayback();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMuteUnmuteButtonClicked() {
|
|
||||||
super.onMuteUnmuteButtonClicked();
|
|
||||||
updatePlayback();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpdateProgress(final int currentProgress, final int duration,
|
|
||||||
final int bufferPercent) {
|
|
||||||
updateProgress(currentProgress, duration, bufferPercent);
|
|
||||||
|
|
||||||
if (!shouldUpdateOnProgress) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (timesNotificationUpdated > NOTIFICATION_UPDATES_BEFORE_RESET) {
|
|
||||||
resetNotification();
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Oreo*/) {
|
|
||||||
updateNotificationThumbnail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bigNotRemoteView != null) {
|
|
||||||
if (cachedDuration != duration) {
|
|
||||||
cachedDuration = duration;
|
|
||||||
cachedDurationString = getTimeString(duration);
|
|
||||||
}
|
|
||||||
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration,
|
|
||||||
currentProgress, false);
|
|
||||||
bigNotRemoteView.setTextViewText(R.id.notificationTime,
|
|
||||||
getTimeString(currentProgress) + " / " + cachedDurationString);
|
|
||||||
}
|
|
||||||
if (notRemoteView != null) {
|
|
||||||
notRemoteView.setProgressBar(R.id.notificationProgressBar, duration,
|
|
||||||
currentProgress, false);
|
|
||||||
}
|
|
||||||
updateNotification(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayPrevious() {
|
|
||||||
super.onPlayPrevious();
|
|
||||||
triggerProgressUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayNext() {
|
|
||||||
super.onPlayNext();
|
|
||||||
triggerProgressUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
super.destroy();
|
|
||||||
if (notRemoteView != null) {
|
|
||||||
notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
|
|
||||||
}
|
|
||||||
if (bigNotRemoteView != null) {
|
|
||||||
bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// ExoPlayer Listener
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) {
|
|
||||||
super.onPlaybackParametersChanged(playbackParameters);
|
|
||||||
updatePlayback();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadingChanged(final boolean isLoading) {
|
|
||||||
// Disable default behavior
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRepeatModeChanged(final int i) {
|
|
||||||
resetNotification();
|
|
||||||
updateNotification(-1);
|
|
||||||
updatePlayback();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Playback Listener
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
|
||||||
super.onMetadataChanged(tag);
|
|
||||||
resetNotification();
|
|
||||||
updateNotificationThumbnail();
|
|
||||||
updateNotification(-1);
|
|
||||||
updateMetadata();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
|
||||||
return resolver.resolve(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlaybackShutdown() {
|
|
||||||
super.onPlaybackShutdown();
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Activity Event Listener
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
/*package-private*/ void setActivityListener(final PlayerEventListener listener) {
|
|
||||||
activityListener = listener;
|
|
||||||
updateMetadata();
|
|
||||||
updatePlayback();
|
|
||||||
triggerProgressUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*package-private*/ void removeActivityListener(final PlayerEventListener listener) {
|
|
||||||
if (activityListener == listener) {
|
|
||||||
activityListener = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateMetadata() {
|
|
||||||
if (activityListener != null && getCurrentMetadata() != null) {
|
|
||||||
activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePlayback() {
|
|
||||||
if (activityListener != null && simpleExoPlayer != null && playQueue != null) {
|
|
||||||
activityListener.onPlaybackUpdate(currentState, getRepeatMode(),
|
|
||||||
playQueue.isShuffled(), getPlaybackParameters());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateProgress(final int currentProgress, final int duration,
|
|
||||||
final int bufferPercent) {
|
|
||||||
if (activityListener != null) {
|
|
||||||
activityListener.onProgressUpdate(currentProgress, duration, bufferPercent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopActivityBinding() {
|
|
||||||
if (activityListener != null) {
|
|
||||||
activityListener.onServiceStopped();
|
|
||||||
activityListener = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Broadcast Receiver
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setupBroadcastReceiver(final IntentFilter intentFltr) {
|
|
||||||
super.setupBroadcastReceiver(intentFltr);
|
|
||||||
intentFltr.addAction(ACTION_CLOSE);
|
|
||||||
intentFltr.addAction(ACTION_PLAY_PAUSE);
|
|
||||||
intentFltr.addAction(ACTION_REPEAT);
|
|
||||||
intentFltr.addAction(ACTION_PLAY_PREVIOUS);
|
|
||||||
intentFltr.addAction(ACTION_PLAY_NEXT);
|
|
||||||
intentFltr.addAction(ACTION_FAST_REWIND);
|
|
||||||
intentFltr.addAction(ACTION_FAST_FORWARD);
|
|
||||||
|
|
||||||
intentFltr.addAction(Intent.ACTION_SCREEN_ON);
|
|
||||||
intentFltr.addAction(Intent.ACTION_SCREEN_OFF);
|
|
||||||
|
|
||||||
intentFltr.addAction(Intent.ACTION_HEADSET_PLUG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBroadcastReceived(final Intent intent) {
|
|
||||||
super.onBroadcastReceived(intent);
|
|
||||||
if (intent == null || intent.getAction() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
|
|
||||||
}
|
|
||||||
switch (intent.getAction()) {
|
|
||||||
case ACTION_CLOSE:
|
|
||||||
onClose();
|
|
||||||
break;
|
|
||||||
case ACTION_PLAY_PAUSE:
|
|
||||||
onPlayPause();
|
|
||||||
break;
|
|
||||||
case ACTION_REPEAT:
|
|
||||||
onRepeatClicked();
|
|
||||||
break;
|
|
||||||
case ACTION_PLAY_NEXT:
|
|
||||||
onPlayNext();
|
|
||||||
break;
|
|
||||||
case ACTION_PLAY_PREVIOUS:
|
|
||||||
onPlayPrevious();
|
|
||||||
break;
|
|
||||||
case ACTION_FAST_FORWARD:
|
|
||||||
onFastForward();
|
|
||||||
break;
|
|
||||||
case ACTION_FAST_REWIND:
|
|
||||||
onFastRewind();
|
|
||||||
break;
|
|
||||||
case Intent.ACTION_SCREEN_ON:
|
|
||||||
onScreenOnOff(true);
|
|
||||||
break;
|
|
||||||
case Intent.ACTION_SCREEN_OFF:
|
|
||||||
onScreenOnOff(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// States
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void changeState(final int state) {
|
|
||||||
super.changeState(state);
|
|
||||||
updatePlayback();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlaying() {
|
|
||||||
super.onPlaying();
|
|
||||||
resetNotification();
|
|
||||||
updateNotificationThumbnail();
|
|
||||||
updateNotification(R.drawable.exo_controls_pause);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPaused() {
|
|
||||||
super.onPaused();
|
|
||||||
resetNotification();
|
|
||||||
updateNotificationThumbnail();
|
|
||||||
updateNotification(R.drawable.exo_controls_play);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
super.onCompleted();
|
|
||||||
resetNotification();
|
|
||||||
if (bigNotRemoteView != null) {
|
|
||||||
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
|
|
||||||
}
|
|
||||||
if (notRemoteView != null) {
|
|
||||||
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
|
|
||||||
}
|
|
||||||
updateNotificationThumbnail();
|
|
||||||
updateNotification(R.drawable.ic_replay_white_24dp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,13 @@
|
|||||||
package org.schabi.newpipe.player;
|
package org.schabi.newpipe.player;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.PermissionHelper;
|
import org.schabi.newpipe.util.PermissionHelper;
|
||||||
|
|
||||||
import static org.schabi.newpipe.player.BackgroundPlayer.ACTION_CLOSE;
|
|
||||||
|
|
||||||
public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
||||||
|
|
||||||
private static final String TAG = "BackgroundPlayerActivity";
|
private static final String TAG = "BackgroundPlayerActivity";
|
||||||
@ -19,25 +19,25 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSupportActionTitle() {
|
public String getSupportActionTitle() {
|
||||||
return getResources().getString(R.string.title_activity_background_player);
|
return getResources().getString(R.string.title_activity_play_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Intent getBindIntent() {
|
public Intent getBindIntent() {
|
||||||
return new Intent(this, BackgroundPlayer.class);
|
return new Intent(this, MainPlayer.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startPlayerListener() {
|
public void startPlayerListener() {
|
||||||
if (player != null && player instanceof BackgroundPlayer.BasePlayerImpl) {
|
if (player instanceof VideoPlayerImpl) {
|
||||||
((BackgroundPlayer.BasePlayerImpl) player).setActivityListener(this);
|
((VideoPlayerImpl) player).setActivityListener(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopPlayerListener() {
|
public void stopPlayerListener() {
|
||||||
if (player != null && player instanceof BackgroundPlayer.BasePlayerImpl) {
|
if (player instanceof VideoPlayerImpl) {
|
||||||
((BackgroundPlayer.BasePlayerImpl) player).removeActivityListener(this);
|
((VideoPlayerImpl) player).removeActivityListener(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,18 +56,30 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.player.setRecovery();
|
this.player.setRecovery();
|
||||||
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
NavigationHelper.playOnPopupPlayer(
|
||||||
getApplicationContext().startService(
|
getApplicationContext(), player.playQueue, this.player.isPlaying());
|
||||||
getSwitchIntent(PopupVideoPlayer.class)
|
|
||||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.action_switch_background) {
|
||||||
|
this.player.setRecovery();
|
||||||
|
NavigationHelper.playOnBackgroundPlayer(
|
||||||
|
getApplicationContext(), player.playQueue, this.player.isPlaying());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Intent getPlayerShutdownIntent() {
|
public void setupMenu(final Menu menu) {
|
||||||
return new Intent(ACTION_CLOSE);
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.findItem(R.id.action_switch_popup)
|
||||||
|
.setVisible(!((VideoPlayerImpl) player).popupPlayerSelected());
|
||||||
|
menu.findItem(R.id.action_switch_background)
|
||||||
|
.setVisible(!((VideoPlayerImpl) player).audioPlayerSelected());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ import android.content.SharedPreferences;
|
|||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
@ -54,8 +54,8 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
|||||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||||
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
|
||||||
import org.schabi.newpipe.DownloaderImpl;
|
import org.schabi.newpipe.DownloaderImpl;
|
||||||
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
@ -78,6 +78,7 @@ import org.schabi.newpipe.util.SerializedCache;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.disposables.SerialDisposable;
|
import io.reactivex.disposables.SerialDisposable;
|
||||||
@ -97,7 +98,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|||||||
@SuppressWarnings({"WeakerAccess"})
|
@SuppressWarnings({"WeakerAccess"})
|
||||||
public abstract class BasePlayer implements
|
public abstract class BasePlayer implements
|
||||||
Player.EventListener, PlaybackListener, ImageLoadingListener {
|
Player.EventListener, PlaybackListener, ImageLoadingListener {
|
||||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
public static final boolean DEBUG = MainActivity.DEBUG;
|
||||||
@NonNull
|
@NonNull
|
||||||
public static final String TAG = "BasePlayer";
|
public static final String TAG = "BasePlayer";
|
||||||
|
|
||||||
@ -128,13 +129,15 @@ public abstract class BasePlayer implements
|
|||||||
@NonNull
|
@NonNull
|
||||||
public static final String SELECT_ON_APPEND = "select_on_append";
|
public static final String SELECT_ON_APPEND = "select_on_append";
|
||||||
@NonNull
|
@NonNull
|
||||||
|
public static final String PLAYER_TYPE = "player_type";
|
||||||
|
@NonNull
|
||||||
public static final String IS_MUTED = "is_muted";
|
public static final String IS_MUTED = "is_muted";
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Playback
|
// Playback
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f};
|
protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
|
||||||
|
|
||||||
protected PlayQueue playQueue;
|
protected PlayQueue playQueue;
|
||||||
protected PlayQueueAdapter playQueueAdapter;
|
protected PlayQueueAdapter playQueueAdapter;
|
||||||
@ -159,6 +162,10 @@ public abstract class BasePlayer implements
|
|||||||
protected static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds
|
protected static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds
|
||||||
protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
|
protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
|
||||||
|
|
||||||
|
public static final int PLAYER_TYPE_VIDEO = 0;
|
||||||
|
public static final int PLAYER_TYPE_AUDIO = 1;
|
||||||
|
public static final int PLAYER_TYPE_POPUP = 2;
|
||||||
|
|
||||||
protected SimpleExoPlayer simpleExoPlayer;
|
protected SimpleExoPlayer simpleExoPlayer;
|
||||||
protected AudioReactor audioReactor;
|
protected AudioReactor audioReactor;
|
||||||
protected MediaSessionManager mediaSessionManager;
|
protected MediaSessionManager mediaSessionManager;
|
||||||
@ -223,7 +230,7 @@ public abstract class BasePlayer implements
|
|||||||
|
|
||||||
public void setup() {
|
public void setup() {
|
||||||
if (simpleExoPlayer == null) {
|
if (simpleExoPlayer == null) {
|
||||||
initPlayer(/*playOnInit=*/true);
|
initPlayer(true);
|
||||||
}
|
}
|
||||||
initListeners();
|
initListeners();
|
||||||
}
|
}
|
||||||
@ -250,7 +257,8 @@ public abstract class BasePlayer implements
|
|||||||
registerBroadcastReceiver();
|
registerBroadcastReceiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initListeners() { }
|
public void initListeners() {
|
||||||
|
}
|
||||||
|
|
||||||
public void handleIntent(final Intent intent) {
|
public void handleIntent(final Intent intent) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -272,7 +280,7 @@ public abstract class BasePlayer implements
|
|||||||
|
|
||||||
// Resolve append intents
|
// Resolve append intents
|
||||||
if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) {
|
if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) {
|
||||||
int sizeBeforeAppend = playQueue.size();
|
final int sizeBeforeAppend = playQueue.size();
|
||||||
playQueue.append(queue.getStreams());
|
playQueue.append(queue.getStreams());
|
||||||
|
|
||||||
if ((intent.getBooleanExtra(SELECT_ON_APPEND, false)
|
if ((intent.getBooleanExtra(SELECT_ON_APPEND, false)
|
||||||
@ -288,34 +296,72 @@ public abstract class BasePlayer implements
|
|||||||
final float playbackPitch = savedParameters.pitch;
|
final float playbackPitch = savedParameters.pitch;
|
||||||
final boolean playbackSkipSilence = savedParameters.skipSilence;
|
final boolean playbackSkipSilence = savedParameters.skipSilence;
|
||||||
|
|
||||||
|
final boolean samePlayQueue = playQueue != null && playQueue.equals(queue);
|
||||||
|
|
||||||
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
|
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
|
||||||
final boolean isMuted = intent
|
final boolean isMuted = intent
|
||||||
.getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted());
|
.getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There are 3 situations when playback shouldn't be started from scratch (zero timestamp):
|
||||||
|
* 1. User pressed on a timestamp link and the same video should be rewound to the timestamp
|
||||||
|
* 2. User changed a player from, for example. main to popup, or from audio to main, etc
|
||||||
|
* 3. User chose to resume a video based on a saved timestamp from history of played videos
|
||||||
|
* In those cases time will be saved because re-init of the play queue is a not an instant
|
||||||
|
* task and requires network calls
|
||||||
|
* */
|
||||||
// seek to timestamp if stream is already playing
|
// seek to timestamp if stream is already playing
|
||||||
if (simpleExoPlayer != null
|
if (simpleExoPlayer != null
|
||||||
&& queue.size() == 1
|
&& queue.size() == 1
|
||||||
&& playQueue != null
|
&& playQueue != null
|
||||||
|
&& playQueue.size() == 1
|
||||||
&& playQueue.getItem() != null
|
&& playQueue.getItem() != null
|
||||||
&& queue.getItem().getUrl().equals(playQueue.getItem().getUrl())
|
&& queue.getItem().getUrl().equals(playQueue.getItem().getUrl())
|
||||||
&& queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET
|
&& queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
|
||||||
) {
|
// Player can have state = IDLE when playback is stopped or failed
|
||||||
|
// and we should retry() in this case
|
||||||
|
if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) {
|
||||||
|
simpleExoPlayer.retry();
|
||||||
|
}
|
||||||
simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
|
simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
|
||||||
return;
|
return;
|
||||||
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false) && isPlaybackResumeEnabled()) {
|
|
||||||
|
} else if (samePlayQueue && !playQueue.isDisposed() && simpleExoPlayer != null) {
|
||||||
|
// Do not re-init the same PlayQueue. Save time
|
||||||
|
// Player can have state = IDLE when playback is stopped or failed
|
||||||
|
// and we should retry() in this case
|
||||||
|
if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) {
|
||||||
|
simpleExoPlayer.retry();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false)
|
||||||
|
&& isPlaybackResumeEnabled()
|
||||||
|
&& !samePlayQueue) {
|
||||||
final PlayQueueItem item = queue.getItem();
|
final PlayQueueItem item = queue.getItem();
|
||||||
if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
|
if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
|
||||||
stateLoader = recordManager.loadStreamState(item)
|
stateLoader = recordManager.loadStreamState(item)
|
||||||
.observeOn(mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doFinally(() -> initPlayback(queue, repeatMode, playbackSpeed,
|
// Do not place initPlayback() in doFinally() because
|
||||||
playbackPitch, playbackSkipSilence, true, isMuted))
|
// it restarts playback after destroy()
|
||||||
|
//.doFinally()
|
||||||
.subscribe(
|
.subscribe(
|
||||||
state -> queue
|
state -> {
|
||||||
.setRecovery(queue.getIndex(), state.getProgressTime()),
|
queue.setRecovery(queue.getIndex(), state.getProgressTime());
|
||||||
|
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
|
||||||
|
playbackSkipSilence, true, isMuted);
|
||||||
|
},
|
||||||
error -> {
|
error -> {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
error.printStackTrace();
|
error.printStackTrace();
|
||||||
}
|
}
|
||||||
|
// In case any error we can start playback without history
|
||||||
|
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
|
||||||
|
playbackSkipSilence, true, isMuted);
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
// Completed but not found in history
|
||||||
|
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
|
||||||
|
playbackSkipSilence, true, isMuted);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
databaseUpdateReactor.add(stateLoader);
|
databaseUpdateReactor.add(stateLoader);
|
||||||
@ -323,21 +369,23 @@ public abstract class BasePlayer implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Good to go...
|
// Good to go...
|
||||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
|
// In a case of equal PlayQueues we can re-init old one but only when it is disposed
|
||||||
/*playOnInit=*/!intent.getBooleanExtra(START_PAUSED, false), isMuted);
|
initPlayback(samePlayQueue ? playQueue : queue, repeatMode,
|
||||||
|
playbackSpeed, playbackPitch, playbackSkipSilence,
|
||||||
|
!intent.getBooleanExtra(START_PAUSED, false),
|
||||||
|
isMuted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlaybackParameters retrievePlaybackParametersFromPreferences() {
|
private PlaybackParameters retrievePlaybackParametersFromPreferences() {
|
||||||
final SharedPreferences preferences =
|
final SharedPreferences preferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(context);
|
PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
|
||||||
final float speed = preferences
|
final float speed = preferences.getFloat(
|
||||||
.getFloat(context.getString(R.string.playback_speed_key), getPlaybackSpeed());
|
context.getString(R.string.playback_speed_key), getPlaybackSpeed());
|
||||||
final float pitch = preferences.getFloat(context.getString(R.string.playback_pitch_key),
|
final float pitch = preferences.getFloat(
|
||||||
getPlaybackPitch());
|
context.getString(R.string.playback_pitch_key), getPlaybackPitch());
|
||||||
final boolean skipSilence = preferences
|
final boolean skipSilence = preferences.getBoolean(
|
||||||
.getBoolean(context.getString(R.string.playback_skip_silence_key),
|
context.getString(R.string.playback_skip_silence_key), getPlaybackSkipSilence());
|
||||||
getPlaybackSkipSilence());
|
|
||||||
return new PlaybackParameters(speed, pitch, skipSilence);
|
return new PlaybackParameters(speed, pitch, skipSilence);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,6 +459,7 @@ public abstract class BasePlayer implements
|
|||||||
|
|
||||||
databaseUpdateReactor.clear();
|
databaseUpdateReactor.clear();
|
||||||
progressUpdateReactor.set(null);
|
progressUpdateReactor.set(null);
|
||||||
|
ImageLoader.getInstance().stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -562,7 +611,8 @@ public abstract class BasePlayer implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPausedSeek() { }
|
public void onPausedSeek() {
|
||||||
|
}
|
||||||
|
|
||||||
public void onCompleted() {
|
public void onCompleted() {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -830,7 +880,6 @@ public abstract class BasePlayer implements
|
|||||||
}
|
}
|
||||||
setRecovery();
|
setRecovery();
|
||||||
|
|
||||||
final Throwable cause = error.getCause();
|
|
||||||
if (error instanceof BehindLiveWindowException) {
|
if (error instanceof BehindLiveWindowException) {
|
||||||
reload();
|
reload();
|
||||||
} else {
|
} else {
|
||||||
@ -1018,14 +1067,6 @@ public abstract class BasePlayer implements
|
|||||||
registerView();
|
registerView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlaybackShutdown() {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "Shutting down...");
|
|
||||||
}
|
|
||||||
destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// General Player
|
// General Player
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -1090,6 +1131,7 @@ public abstract class BasePlayer implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
simpleExoPlayer.setPlayWhenReady(true);
|
simpleExoPlayer.setPlayWhenReady(true);
|
||||||
|
savePlaybackState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
@ -1102,6 +1144,7 @@ public abstract class BasePlayer implements
|
|||||||
|
|
||||||
audioReactor.abandonAudioFocus();
|
audioReactor.abandonAudioFocus();
|
||||||
simpleExoPlayer.setPlayWhenReady(false);
|
simpleExoPlayer.setPlayWhenReady(false);
|
||||||
|
savePlaybackState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPlayPause() {
|
public void onPlayPause() {
|
||||||
@ -1296,6 +1339,11 @@ public abstract class BasePlayer implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final StreamInfo currentInfo = currentMetadata.getMetadata();
|
final StreamInfo currentInfo = currentMetadata.getMetadata();
|
||||||
|
if (playQueue != null) {
|
||||||
|
// Save current position. It will help to restore this position once a user
|
||||||
|
// wants to play prev or next stream from the queue
|
||||||
|
playQueue.setRecovery(playQueue.getIndex(), simpleExoPlayer.getContentPosition());
|
||||||
|
}
|
||||||
savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
|
savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1408,7 +1456,7 @@ public abstract class BasePlayer implements
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Timeline.Window timelineWindow = new Timeline.Window();
|
final Timeline.Window timelineWindow = new Timeline.Window();
|
||||||
currentTimeline.getWindow(currentWindowIndex, timelineWindow);
|
currentTimeline.getWindow(currentWindowIndex, timelineWindow);
|
||||||
return timelineWindow.getDefaultPositionMs() <= simpleExoPlayer.getCurrentPosition();
|
return timelineWindow.getDefaultPositionMs() <= simpleExoPlayer.getCurrentPosition();
|
||||||
}
|
}
|
||||||
@ -1419,7 +1467,7 @@ public abstract class BasePlayer implements
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return simpleExoPlayer.isCurrentWindowDynamic();
|
return simpleExoPlayer.isCurrentWindowDynamic();
|
||||||
} catch (@NonNull IndexOutOfBoundsException e) {
|
} catch (@NonNull final IndexOutOfBoundsException e) {
|
||||||
// Why would this even happen =(
|
// Why would this even happen =(
|
||||||
// But lets log it anyway. Save is save
|
// But lets log it anyway. Save is save
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -1434,6 +1482,10 @@ public abstract class BasePlayer implements
|
|||||||
return simpleExoPlayer != null && simpleExoPlayer.isPlaying();
|
return simpleExoPlayer != null && simpleExoPlayer.isPlaying();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLoading() {
|
||||||
|
return simpleExoPlayer != null && simpleExoPlayer.isLoading();
|
||||||
|
}
|
||||||
|
|
||||||
@Player.RepeatMode
|
@Player.RepeatMode
|
||||||
public int getRepeatMode() {
|
public int getRepeatMode() {
|
||||||
return simpleExoPlayer == null
|
return simpleExoPlayer == null
|
||||||
@ -1471,20 +1523,32 @@ public abstract class BasePlayer implements
|
|||||||
return parameters == null ? PlaybackParameters.DEFAULT : parameters;
|
return parameters == null ? PlaybackParameters.DEFAULT : parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the playback parameters of the player, and also saves them to shared preferences.
|
||||||
|
* Speed and pitch are rounded up to 2 decimal places before being used or saved.
|
||||||
|
*
|
||||||
|
* @param speed the playback speed, will be rounded to up to 2 decimal places
|
||||||
|
* @param pitch the playback pitch, will be rounded to up to 2 decimal places
|
||||||
|
* @param skipSilence skip silence during playback
|
||||||
|
*/
|
||||||
public void setPlaybackParameters(final float speed, final float pitch,
|
public void setPlaybackParameters(final float speed, final float pitch,
|
||||||
final boolean skipSilence) {
|
final boolean skipSilence) {
|
||||||
savePlaybackParametersToPreferences(speed, pitch, skipSilence);
|
final float roundedSpeed = Math.round(speed * 100.0f) / 100.0f;
|
||||||
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence));
|
final float roundedPitch = Math.round(pitch * 100.0f) / 100.0f;
|
||||||
|
|
||||||
|
savePlaybackParametersToPreferences(roundedSpeed, roundedPitch, skipSilence);
|
||||||
|
simpleExoPlayer.setPlaybackParameters(
|
||||||
|
new PlaybackParameters(roundedSpeed, roundedPitch, skipSilence));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void savePlaybackParametersToPreferences(final float speed, final float pitch,
|
private void savePlaybackParametersToPreferences(final float speed, final float pitch,
|
||||||
final boolean skipSilence) {
|
final boolean skipSilence) {
|
||||||
PreferenceManager.getDefaultSharedPreferences(context)
|
PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
.edit()
|
.edit()
|
||||||
.putFloat(context.getString(R.string.playback_speed_key), speed)
|
.putFloat(context.getString(R.string.playback_speed_key), speed)
|
||||||
.putFloat(context.getString(R.string.playback_pitch_key), pitch)
|
.putFloat(context.getString(R.string.playback_pitch_key), pitch)
|
||||||
.putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence)
|
.putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence)
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayQueue getPlayQueue() {
|
public PlayQueue getPlayQueue() {
|
||||||
|
484
app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
Normal file
484
app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
|
||||||
|
* Part of NewPipe
|
||||||
|
*
|
||||||
|
* License: GPL-3.0+
|
||||||
|
* This program 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.
|
||||||
|
*
|
||||||
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.schabi.newpipe.player;
|
||||||
|
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.Player;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.BuildConfig;
|
||||||
|
import org.schabi.newpipe.MainActivity;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.util.BitmapUtils;
|
||||||
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One service for all players.
|
||||||
|
*
|
||||||
|
* @author mauriciocolli
|
||||||
|
*/
|
||||||
|
public final class MainPlayer extends Service {
|
||||||
|
private static final String TAG = "MainPlayer";
|
||||||
|
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||||
|
|
||||||
|
private VideoPlayerImpl playerImpl;
|
||||||
|
private WindowManager windowManager;
|
||||||
|
private SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
|
private final IBinder mBinder = new MainPlayer.LocalBinder();
|
||||||
|
|
||||||
|
public enum PlayerType {
|
||||||
|
VIDEO,
|
||||||
|
AUDIO,
|
||||||
|
POPUP
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Notification
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
static final int NOTIFICATION_ID = 123789;
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
private NotificationCompat.Builder notBuilder;
|
||||||
|
private RemoteViews notRemoteView;
|
||||||
|
private RemoteViews bigNotRemoteView;
|
||||||
|
|
||||||
|
static final String ACTION_CLOSE =
|
||||||
|
"org.schabi.newpipe.player.MainPlayer.CLOSE";
|
||||||
|
static final String ACTION_PLAY_PAUSE =
|
||||||
|
"org.schabi.newpipe.player.MainPlayer.PLAY_PAUSE";
|
||||||
|
static final String ACTION_OPEN_CONTROLS =
|
||||||
|
"org.schabi.newpipe.player.MainPlayer.OPEN_CONTROLS";
|
||||||
|
static final String ACTION_REPEAT =
|
||||||
|
"org.schabi.newpipe.player.MainPlayer.REPEAT";
|
||||||
|
static final String ACTION_PLAY_NEXT =
|
||||||
|
"org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT";
|
||||||
|
static final String ACTION_PLAY_PREVIOUS =
|
||||||
|
"org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS";
|
||||||
|
static final String ACTION_FAST_REWIND =
|
||||||
|
"org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND";
|
||||||
|
static final String ACTION_FAST_FORWARD =
|
||||||
|
"org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD";
|
||||||
|
|
||||||
|
private static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Service's LifeCycle
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onCreate() called");
|
||||||
|
}
|
||||||
|
assureCorrectAppLanguage(this);
|
||||||
|
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
||||||
|
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||||
|
|
||||||
|
ThemeHelper.setTheme(this);
|
||||||
|
createView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createView() {
|
||||||
|
final View layout = View.inflate(this, R.layout.player, null);
|
||||||
|
|
||||||
|
playerImpl = new VideoPlayerImpl(this);
|
||||||
|
playerImpl.setup(layout);
|
||||||
|
playerImpl.shouldUpdateOnProgress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(final Intent intent, final int flags, final int startId) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onStartCommand() called with: intent = [" + intent
|
||||||
|
+ "], flags = [" + flags + "], startId = [" + startId + "]");
|
||||||
|
}
|
||||||
|
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
|
||||||
|
&& playerImpl.playQueue == null) {
|
||||||
|
// Player is not working, no need to process media button's action
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
|
||||||
|
|| intent.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY) != null) {
|
||||||
|
showNotificationAndStartForeground();
|
||||||
|
}
|
||||||
|
|
||||||
|
playerImpl.handleIntent(intent);
|
||||||
|
if (playerImpl.mediaSessionManager != null) {
|
||||||
|
playerImpl.mediaSessionManager.handleMediaButtonIntent(intent);
|
||||||
|
}
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop(final boolean autoplayEnabled) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "stop() called");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl.getPlayer() != null) {
|
||||||
|
playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady();
|
||||||
|
// Releases wifi & cpu, disables keepScreenOn, etc.
|
||||||
|
if (!autoplayEnabled) {
|
||||||
|
playerImpl.onPause();
|
||||||
|
}
|
||||||
|
// We can't just pause the player here because it will make transition
|
||||||
|
// from one stream to a new stream not smooth
|
||||||
|
playerImpl.getPlayer().stop(false);
|
||||||
|
playerImpl.setRecovery();
|
||||||
|
// Android TV will handle back button in case controls will be visible
|
||||||
|
// (one more additional unneeded click while the player is hidden)
|
||||||
|
playerImpl.hideControls(0, 0);
|
||||||
|
// Notification shows information about old stream but if a user selects
|
||||||
|
// a stream from backStack it's not actual anymore
|
||||||
|
// So we should hide the notification at all.
|
||||||
|
// When autoplay enabled such notification flashing is annoying so skip this case
|
||||||
|
if (!autoplayEnabled) {
|
||||||
|
stopForeground(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTaskRemoved(final Intent rootIntent) {
|
||||||
|
super.onTaskRemoved(rootIntent);
|
||||||
|
if (!playerImpl.videoPlayerSelected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onDestroy();
|
||||||
|
// Unload from memory completely
|
||||||
|
Runtime.getRuntime().halt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "destroy() called");
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(final Context base) {
|
||||||
|
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(final Intent intent) {
|
||||||
|
return mBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Actions
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
private void onClose() {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onClose() called");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl != null) {
|
||||||
|
removeViewFromParent();
|
||||||
|
|
||||||
|
playerImpl.setRecovery();
|
||||||
|
playerImpl.savePlaybackState();
|
||||||
|
playerImpl.stopActivityBinding();
|
||||||
|
playerImpl.removePopupFromView();
|
||||||
|
playerImpl.destroy();
|
||||||
|
}
|
||||||
|
if (notificationManager != null) {
|
||||||
|
notificationManager.cancel(NOTIFICATION_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopForeground(true);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
boolean isLandscape() {
|
||||||
|
// DisplayMetrics from activity context knows about MultiWindow feature
|
||||||
|
// while DisplayMetrics from app context doesn't
|
||||||
|
final DisplayMetrics metrics = (playerImpl != null
|
||||||
|
&& playerImpl.getParentActivity() != null)
|
||||||
|
? playerImpl.getParentActivity().getResources().getDisplayMetrics()
|
||||||
|
: getResources().getDisplayMetrics();
|
||||||
|
return metrics.heightPixels < metrics.widthPixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getView() {
|
||||||
|
if (playerImpl == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return playerImpl.getRootView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeViewFromParent() {
|
||||||
|
if (getView().getParent() != null) {
|
||||||
|
if (playerImpl.getParentActivity() != null) {
|
||||||
|
// This means view was added to fragment
|
||||||
|
final ViewGroup parent = (ViewGroup) getView().getParent();
|
||||||
|
parent.removeView(getView());
|
||||||
|
} else {
|
||||||
|
// This means view was added by windowManager for popup player
|
||||||
|
windowManager.removeViewImmediate(getView());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showNotificationAndStartForeground() {
|
||||||
|
resetNotification();
|
||||||
|
if (getBigNotRemoteView() != null) {
|
||||||
|
getBigNotRemoteView().setProgressBar(R.id.notificationProgressBar, 100, 0, false);
|
||||||
|
}
|
||||||
|
if (getNotRemoteView() != null) {
|
||||||
|
getNotRemoteView().setProgressBar(R.id.notificationProgressBar, 100, 0, false);
|
||||||
|
}
|
||||||
|
startForeground(NOTIFICATION_ID, getNotBuilder().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Notification
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
void resetNotification() {
|
||||||
|
notBuilder = createNotification();
|
||||||
|
playerImpl.timesNotificationUpdated = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationCompat.Builder createNotification() {
|
||||||
|
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
|
||||||
|
R.layout.player_notification);
|
||||||
|
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
|
||||||
|
R.layout.player_notification_expanded);
|
||||||
|
|
||||||
|
setupNotification(notRemoteView);
|
||||||
|
setupNotification(bigNotRemoteView);
|
||||||
|
|
||||||
|
final NotificationCompat.Builder builder = new NotificationCompat
|
||||||
|
.Builder(this, getString(R.string.notification_channel_id))
|
||||||
|
.setOngoing(true)
|
||||||
|
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
||||||
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
.setCustomContentView(notRemoteView)
|
||||||
|
.setCustomBigContentView(bigNotRemoteView);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
setLockScreenThumbnail(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setPriority(NotificationCompat.PRIORITY_MAX);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
private void setLockScreenThumbnail(final NotificationCompat.Builder builder) {
|
||||||
|
final boolean isLockScreenThumbnailEnabled = sharedPreferences.getBoolean(
|
||||||
|
getString(R.string.enable_lock_screen_video_thumbnail_key), true);
|
||||||
|
|
||||||
|
if (isLockScreenThumbnailEnabled) {
|
||||||
|
playerImpl.mediaSessionManager.setLockScreenArt(
|
||||||
|
builder,
|
||||||
|
getCenteredThumbnailBitmap()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
playerImpl.mediaSessionManager.clearLockScreenArt(builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Bitmap getCenteredThumbnailBitmap() {
|
||||||
|
final int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
|
||||||
|
final int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
|
||||||
|
|
||||||
|
return BitmapUtils.centerCrop(playerImpl.getThumbnail(), screenWidth, screenHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupNotification(final RemoteViews remoteViews) {
|
||||||
|
// Don't show anything until player is playing
|
||||||
|
if (playerImpl == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteViews.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
|
||||||
|
remoteViews.setTextViewText(R.id.notificationArtist, playerImpl.getUploaderName());
|
||||||
|
remoteViews.setImageViewBitmap(R.id.notificationCover, playerImpl.getThumbnail());
|
||||||
|
|
||||||
|
remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause,
|
||||||
|
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||||
|
new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||||
|
remoteViews.setOnClickPendingIntent(R.id.notificationStop,
|
||||||
|
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||||
|
new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||||
|
// Starts VideoDetailFragment or opens BackgroundPlayerActivity.
|
||||||
|
remoteViews.setOnClickPendingIntent(R.id.notificationContent,
|
||||||
|
PendingIntent.getActivity(this, NOTIFICATION_ID,
|
||||||
|
getIntentForNotification(), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||||
|
remoteViews.setOnClickPendingIntent(R.id.notificationRepeat,
|
||||||
|
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||||
|
new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||||
|
|
||||||
|
|
||||||
|
if (playerImpl.playQueue != null && playerImpl.playQueue.size() > 1) {
|
||||||
|
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
|
||||||
|
R.drawable.exo_controls_previous);
|
||||||
|
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
|
||||||
|
R.drawable.exo_controls_next);
|
||||||
|
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
|
||||||
|
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||||
|
new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||||
|
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
|
||||||
|
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||||
|
new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||||
|
} else {
|
||||||
|
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
|
||||||
|
R.drawable.exo_controls_rewind);
|
||||||
|
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
|
||||||
|
R.drawable.exo_controls_fastforward);
|
||||||
|
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
|
||||||
|
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||||
|
new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||||
|
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
|
||||||
|
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||||
|
new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
setRepeatModeIcon(remoteViews, playerImpl.getRepeatMode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the notification, and the play/pause button in it.
|
||||||
|
* Used for changes on the remoteView
|
||||||
|
*
|
||||||
|
* @param drawableId if != -1, sets the drawable with that id on the play/pause button
|
||||||
|
*/
|
||||||
|
synchronized void updateNotification(final int drawableId) {
|
||||||
|
/*if (DEBUG) {
|
||||||
|
Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
|
||||||
|
}*/
|
||||||
|
if (notBuilder == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (drawableId != -1) {
|
||||||
|
if (notRemoteView != null) {
|
||||||
|
notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
||||||
|
}
|
||||||
|
if (bigNotRemoteView != null) {
|
||||||
|
bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
|
||||||
|
playerImpl.timesNotificationUpdated++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) {
|
||||||
|
if (remoteViews == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (repeatMode) {
|
||||||
|
case Player.REPEAT_MODE_OFF:
|
||||||
|
remoteViews.setInt(R.id.notificationRepeat,
|
||||||
|
SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_off);
|
||||||
|
break;
|
||||||
|
case Player.REPEAT_MODE_ONE:
|
||||||
|
remoteViews.setInt(R.id.notificationRepeat,
|
||||||
|
SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_one);
|
||||||
|
break;
|
||||||
|
case Player.REPEAT_MODE_ALL:
|
||||||
|
remoteViews.setInt(R.id.notificationRepeat,
|
||||||
|
SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_all);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getIntentForNotification() {
|
||||||
|
final Intent intent;
|
||||||
|
if (playerImpl.audioPlayerSelected() || playerImpl.popupPlayerSelected()) {
|
||||||
|
// Means we play in popup or audio only. Let's show BackgroundPlayerActivity
|
||||||
|
intent = NavigationHelper.getBackgroundPlayerActivityIntent(getApplicationContext());
|
||||||
|
} else {
|
||||||
|
// We are playing in fragment. Don't open another activity just show fragment. That's it
|
||||||
|
intent = NavigationHelper.getPlayerIntent(this, MainActivity.class, null, true);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.setAction(Intent.ACTION_MAIN);
|
||||||
|
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
|
}
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Getters
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
NotificationCompat.Builder getNotBuilder() {
|
||||||
|
return notBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteViews getBigNotRemoteView() {
|
||||||
|
return bigNotRemoteView;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteViews getNotRemoteView() {
|
||||||
|
return notRemoteView;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class LocalBinder extends Binder {
|
||||||
|
|
||||||
|
public MainPlayer getService() {
|
||||||
|
return MainPlayer.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoPlayerImpl getPlayer() {
|
||||||
|
return MainPlayer.this.playerImpl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,66 +0,0 @@
|
|||||||
package org.schabi.newpipe.player;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.player.PopupVideoPlayer.ACTION_CLOSE;
|
|
||||||
|
|
||||||
public final class PopupVideoPlayerActivity extends ServicePlayerActivity {
|
|
||||||
|
|
||||||
private static final String TAG = "PopupVideoPlayerActivity";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTag() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSupportActionTitle() {
|
|
||||||
return getResources().getString(R.string.title_activity_popup_player);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Intent getBindIntent() {
|
|
||||||
return new Intent(this, PopupVideoPlayer.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startPlayerListener() {
|
|
||||||
if (player != null && player instanceof PopupVideoPlayer.VideoPlayerImpl) {
|
|
||||||
((PopupVideoPlayer.VideoPlayerImpl) player).setActivityListener(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopPlayerListener() {
|
|
||||||
if (player != null && player instanceof PopupVideoPlayer.VideoPlayerImpl) {
|
|
||||||
((PopupVideoPlayer.VideoPlayerImpl) player).removeActivityListener(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPlayerOptionMenuResource() {
|
|
||||||
return R.menu.menu_play_queue_popup;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPlayerOptionSelected(final MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.action_switch_background) {
|
|
||||||
this.player.setRecovery();
|
|
||||||
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
|
||||||
getApplicationContext().startService(
|
|
||||||
getSwitchIntent(BackgroundPlayer.class)
|
|
||||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Intent getPlayerShutdownIntent() {
|
|
||||||
return new Intent(ACTION_CLOSE);
|
|
||||||
}
|
|
||||||
}
|
|
@ -27,17 +27,21 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||||
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
||||||
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
|
||||||
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
@ -110,7 +114,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
|
|
||||||
public abstract boolean onPlayerOptionSelected(MenuItem item);
|
public abstract boolean onPlayerOptionSelected(MenuItem item);
|
||||||
|
|
||||||
public abstract Intent getPlayerShutdownIntent();
|
public abstract void setupMenu(Menu m);
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// Activity Lifecycle
|
// Activity Lifecycle
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@ -152,6 +156,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow to setup visibility of menuItems
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareOptionsMenu(final Menu m) {
|
||||||
|
setupMenu(m);
|
||||||
|
return super.onPrepareOptionsMenu(m);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
@ -175,11 +186,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
return true;
|
return true;
|
||||||
case R.id.action_switch_main:
|
case R.id.action_switch_main:
|
||||||
this.player.setRecovery();
|
this.player.setRecovery();
|
||||||
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
|
||||||
getApplicationContext().startActivity(
|
getApplicationContext().startActivity(
|
||||||
getSwitchIntent(MainVideoPlayer.class)
|
getSwitchIntent(MainActivity.class, MainPlayer.PlayerType.VIDEO)
|
||||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
|
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()));
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item);
|
return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item);
|
||||||
@ -191,13 +200,22 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
unbind();
|
unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Intent getSwitchIntent(final Class clazz) {
|
protected Intent getSwitchIntent(final Class clazz, final MainPlayer.PlayerType playerType) {
|
||||||
return NavigationHelper.getPlayerIntent(getApplicationContext(), clazz,
|
return NavigationHelper.getPlayerIntent(getApplicationContext(), clazz,
|
||||||
this.player.getPlayQueue(), this.player.getRepeatMode(),
|
this.player.getPlayQueue(), this.player.getRepeatMode(),
|
||||||
this.player.getPlaybackSpeed(), this.player.getPlaybackPitch(),
|
this.player.getPlaybackSpeed(), this.player.getPlaybackPitch(),
|
||||||
this.player.getPlaybackSkipSilence(), null, false, false, this.player.isMuted())
|
this.player.getPlaybackSkipSilence(),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
!this.player.isPlaying(),
|
||||||
|
this.player.isMuted())
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying());
|
.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM)
|
||||||
|
.putExtra(Constants.KEY_URL, this.player.getVideoUrl())
|
||||||
|
.putExtra(Constants.KEY_TITLE, this.player.getVideoTitle())
|
||||||
|
.putExtra(Constants.KEY_SERVICE_ID,
|
||||||
|
this.player.getCurrentMetadata().getMetadata().getServiceId())
|
||||||
|
.putExtra(VideoPlayer.PLAYER_TYPE, playerType);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@ -247,6 +265,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
|
|
||||||
if (service instanceof PlayerServiceBinder) {
|
if (service instanceof PlayerServiceBinder) {
|
||||||
player = ((PlayerServiceBinder) service).getPlayerInstance();
|
player = ((PlayerServiceBinder) service).getPlayerInstance();
|
||||||
|
} else if (service instanceof MainPlayer.LocalBinder) {
|
||||||
|
player = ((MainPlayer.LocalBinder) service).getPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player == null || player.getPlayQueue() == null
|
if (player == null || player.getPlayQueue() == null
|
||||||
@ -500,7 +520,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
|
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
|
||||||
player.getPlaybackSkipSilence()).show(getSupportFragmentManager(), getTag());
|
player.getPlaybackSkipSilence(), this).show(getSupportFragmentManager(), getTag());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -560,7 +580,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private void shareUrl(final String subject, final String url) {
|
private void shareUrl(final String subject, final String url) {
|
||||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
final Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
intent.setType("text/plain");
|
intent.setType("text/plain");
|
||||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||||
@ -571,6 +591,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
// Binding Service Listener
|
// Binding Service Listener
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onQueueUpdate(final PlayQueue queue) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackUpdate(final int state, final int repeatMode, final boolean shuffled,
|
public void onPlaybackUpdate(final int state, final int repeatMode, final boolean shuffled,
|
||||||
final PlaybackParameters parameters) {
|
final PlaybackParameters parameters) {
|
||||||
@ -610,7 +634,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMetadataUpdate(final StreamInfo info) {
|
public void onMetadataUpdate(final StreamInfo info, final PlayQueue queue) {
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
metadataTitle.setText(info.getName());
|
metadataTitle.setText(info.getName());
|
||||||
metadataArtist.setText(info.getUploaderName());
|
metadataArtist.setText(info.getUploaderName());
|
||||||
@ -710,7 +734,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
|
|
||||||
private void onMaybeMuteChanged() {
|
private void onMaybeMuteChanged() {
|
||||||
if (menu != null && player != null) {
|
if (menu != null && player != null) {
|
||||||
MenuItem item = menu.findItem(R.id.action_mute);
|
final MenuItem item = menu.findItem(R.id.action_mute);
|
||||||
|
|
||||||
//Change the mute-button item in ActionBar
|
//Change the mute-button item in ActionBar
|
||||||
//1) Text change:
|
//1) Text change:
|
||||||
|
@ -30,20 +30,21 @@ import android.content.SharedPreferences;
|
|||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.PorterDuffColorFilter;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.SurfaceView;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.SeekBar;
|
import android.widget.SeekBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.content.res.AppCompatResources;
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
@ -69,6 +70,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
|||||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
|
import org.schabi.newpipe.views.ExpandableSurfaceView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -117,8 +119,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
|
|
||||||
private View rootView;
|
private View rootView;
|
||||||
|
|
||||||
private AspectRatioFrameLayout aspectRatioFrameLayout;
|
private ExpandableSurfaceView surfaceView;
|
||||||
private SurfaceView surfaceView;
|
|
||||||
private View surfaceForeground;
|
private View surfaceForeground;
|
||||||
|
|
||||||
private View loadingPanel;
|
private View loadingPanel;
|
||||||
@ -135,7 +136,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
private TextView playbackLiveSync;
|
private TextView playbackLiveSync;
|
||||||
private TextView playbackSpeedTextView;
|
private TextView playbackSpeedTextView;
|
||||||
|
|
||||||
private View topControlsRoot;
|
private LinearLayout topControlsRoot;
|
||||||
private TextView qualityTextView;
|
private TextView qualityTextView;
|
||||||
|
|
||||||
private SubtitleView subtitleView;
|
private SubtitleView subtitleView;
|
||||||
@ -167,7 +168,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
|
|
||||||
// workaround to match normalized captions like english to English or deutsch to Deutsch
|
// workaround to match normalized captions like english to English or deutsch to Deutsch
|
||||||
private static boolean containsCaseInsensitive(final List<String> list, final String toFind) {
|
private static boolean containsCaseInsensitive(final List<String> list, final String toFind) {
|
||||||
for (String i : list) {
|
for (final String i : list) {
|
||||||
if (i.equalsIgnoreCase(toFind)) {
|
if (i.equalsIgnoreCase(toFind)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -182,7 +183,6 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
|
|
||||||
public void initViews(final View view) {
|
public void initViews(final View view) {
|
||||||
this.rootView = view;
|
this.rootView = view;
|
||||||
this.aspectRatioFrameLayout = view.findViewById(R.id.aspectRatioLayout);
|
|
||||||
this.surfaceView = view.findViewById(R.id.surfaceView);
|
this.surfaceView = view.findViewById(R.id.surfaceView);
|
||||||
this.surfaceForeground = view.findViewById(R.id.surfaceForeground);
|
this.surfaceForeground = view.findViewById(R.id.surfaceForeground);
|
||||||
this.loadingPanel = view.findViewById(R.id.loading_panel);
|
this.loadingPanel = view.findViewById(R.id.loading_panel);
|
||||||
@ -207,24 +207,22 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
|
|
||||||
this.resizeView = view.findViewById(R.id.resizeTextView);
|
this.resizeView = view.findViewById(R.id.resizeTextView);
|
||||||
resizeView.setText(PlayerHelper
|
resizeView.setText(PlayerHelper
|
||||||
.resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode()));
|
.resizeTypeOf(context, getSurfaceView().getResizeMode()));
|
||||||
|
|
||||||
this.captionTextView = view.findViewById(R.id.captionTextView);
|
this.captionTextView = view.findViewById(R.id.captionTextView);
|
||||||
|
|
||||||
//this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f);
|
playbackSeekBar.getThumb()
|
||||||
|
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
this.playbackSeekBar.getProgressDrawable()
|
||||||
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY));
|
||||||
}
|
|
||||||
this.playbackSeekBar.getProgressDrawable().
|
|
||||||
setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
|
|
||||||
|
|
||||||
this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
|
this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
|
||||||
this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeedTextView);
|
this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeedTextView);
|
||||||
this.captionPopupMenu = new PopupMenu(context, captionTextView);
|
this.captionPopupMenu = new PopupMenu(context, captionTextView);
|
||||||
|
|
||||||
((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel))
|
((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel))
|
||||||
.getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);
|
.getIndeterminateDrawable()
|
||||||
|
.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void setupSubtitleView(@NonNull SubtitleView view, float captionScale,
|
protected abstract void setupSubtitleView(@NonNull SubtitleView view, float captionScale,
|
||||||
@ -252,7 +250,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
simpleExoPlayer.addTextOutput(cues -> subtitleView.onCues(cues));
|
simpleExoPlayer.addTextOutput(cues -> subtitleView.onCues(cues));
|
||||||
|
|
||||||
// Setup audio session with onboard equalizer
|
// Setup audio session with onboard equalizer
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||||
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
|
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
|
||||||
}
|
}
|
||||||
@ -282,7 +280,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
|
|
||||||
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
||||||
for (int i = 0; i < availableStreams.size(); i++) {
|
for (int i = 0; i < availableStreams.size(); i++) {
|
||||||
VideoStream videoStream = availableStreams.get(i);
|
final VideoStream videoStream = availableStreams.get(i);
|
||||||
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat
|
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat
|
||||||
.getNameById(videoStream.getFormatId()) + " " + videoStream.resolution);
|
.getNameById(videoStream.getFormatId()) + " " + videoStream.resolution);
|
||||||
}
|
}
|
||||||
@ -314,7 +312,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
}
|
}
|
||||||
captionPopupMenu.getMenu().removeGroup(captionPopupMenuGroupId);
|
captionPopupMenu.getMenu().removeGroup(captionPopupMenuGroupId);
|
||||||
|
|
||||||
String userPreferredLanguage = PreferenceManager.getDefaultSharedPreferences(context)
|
final String userPreferredLanguage = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
.getString(context.getString(R.string.caption_user_set_key), null);
|
.getString(context.getString(R.string.caption_user_set_key), null);
|
||||||
/*
|
/*
|
||||||
* only search for autogenerated cc as fallback
|
* only search for autogenerated cc as fallback
|
||||||
@ -326,7 +324,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
&& !userPreferredLanguage.contains("(");
|
&& !userPreferredLanguage.contains("(");
|
||||||
|
|
||||||
// Add option for turning off caption
|
// Add option for turning off caption
|
||||||
MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
|
final MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
|
||||||
0, Menu.NONE, R.string.caption_none);
|
0, Menu.NONE, R.string.caption_none);
|
||||||
captionOffItem.setOnMenuItemClickListener(menuItem -> {
|
captionOffItem.setOnMenuItemClickListener(menuItem -> {
|
||||||
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
||||||
@ -342,7 +340,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
// Add all available captions
|
// Add all available captions
|
||||||
for (int i = 0; i < availableLanguages.size(); i++) {
|
for (int i = 0; i < availableLanguages.size(); i++) {
|
||||||
final String captionLanguage = availableLanguages.get(i);
|
final String captionLanguage = availableLanguages.get(i);
|
||||||
MenuItem captionItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
|
final MenuItem captionItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
|
||||||
i + 1, Menu.NONE, captionLanguage);
|
i + 1, Menu.NONE, captionLanguage);
|
||||||
captionItem.setOnMenuItemClickListener(menuItem -> {
|
captionItem.setOnMenuItemClickListener(menuItem -> {
|
||||||
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
||||||
@ -459,11 +457,8 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
animateView(controlsRoot, false, DEFAULT_CONTROLS_DURATION);
|
animateView(controlsRoot, false, DEFAULT_CONTROLS_DURATION);
|
||||||
|
|
||||||
playbackSeekBar.setEnabled(false);
|
playbackSeekBar.setEnabled(false);
|
||||||
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-,
|
playbackSeekBar.getThumb()
|
||||||
// so sets the color again
|
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
|
||||||
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingPanel.setBackgroundColor(Color.BLACK);
|
loadingPanel.setBackgroundColor(Color.BLACK);
|
||||||
animateView(loadingPanel, true, 0);
|
animateView(loadingPanel, true, 0);
|
||||||
@ -479,11 +474,8 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
showAndAnimateControl(-1, true);
|
showAndAnimateControl(-1, true);
|
||||||
|
|
||||||
playbackSeekBar.setEnabled(true);
|
playbackSeekBar.setEnabled(true);
|
||||||
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-,
|
playbackSeekBar.getThumb()
|
||||||
// so sets the color again
|
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
|
||||||
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingPanel.setVisibility(View.GONE);
|
loadingPanel.setVisibility(View.GONE);
|
||||||
|
|
||||||
@ -520,7 +512,6 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
super.onCompleted();
|
super.onCompleted();
|
||||||
|
|
||||||
showControls(500);
|
showControls(500);
|
||||||
animateView(endScreen, true, 800);
|
|
||||||
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
|
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
|
||||||
loadingPanel.setVisibility(View.GONE);
|
loadingPanel.setVisibility(View.GONE);
|
||||||
|
|
||||||
@ -555,7 +546,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
+ "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
|
+ "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
|
||||||
+ "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
|
+ "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
|
||||||
}
|
}
|
||||||
aspectRatioFrameLayout.setAspectRatio(((float) width) / height);
|
getSurfaceView().setAspectRatio(((float) width) / height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -583,7 +574,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
.getTrackGroups(textRenderer);
|
.getTrackGroups(textRenderer);
|
||||||
|
|
||||||
// Extract all loaded languages
|
// Extract all loaded languages
|
||||||
List<String> availableLanguages = new ArrayList<>(textTracks.length);
|
final List<String> availableLanguages = new ArrayList<>(textTracks.length);
|
||||||
for (int i = 0; i < textTracks.length; i++) {
|
for (int i = 0; i < textTracks.length; i++) {
|
||||||
final TrackGroup textTrack = textTracks.get(i);
|
final TrackGroup textTrack = textTracks.get(i);
|
||||||
if (textTrack.length > 0 && textTrack.getFormat(0) != null) {
|
if (textTrack.length > 0 && textTrack.getFormat(0) != null) {
|
||||||
@ -620,12 +611,6 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
|
playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
|
||||||
|
|
||||||
super.onPrepared(playWhenReady);
|
super.onPrepared(playWhenReady);
|
||||||
|
|
||||||
if (simpleExoPlayer.getCurrentPosition() != 0 && !isControlsVisible()) {
|
|
||||||
controlsVisibilityHandler.removeCallbacksAndMessages(null);
|
|
||||||
controlsVisibilityHandler
|
|
||||||
.postDelayed(this::showControlsThenHide, DEFAULT_CONTROLS_DURATION);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -675,7 +660,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onFullScreenButtonClicked() {
|
protected void toggleFullscreen() {
|
||||||
changeState(STATE_BLOCKED);
|
changeState(STATE_BLOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -739,8 +724,8 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
qualityTextView.setText(menuItem.getTitle());
|
qualityTextView.setText(menuItem.getTitle());
|
||||||
return true;
|
return true;
|
||||||
} else if (playbackSpeedPopupMenuGroupId == menuItem.getGroupId()) {
|
} else if (playbackSpeedPopupMenuGroupId == menuItem.getGroupId()) {
|
||||||
int speedIndex = menuItem.getItemId();
|
final int speedIndex = menuItem.getItemId();
|
||||||
float speed = PLAYBACK_SPEEDS[speedIndex];
|
final float speed = PLAYBACK_SPEEDS[speedIndex];
|
||||||
|
|
||||||
setPlaybackSpeed(speed);
|
setPlaybackSpeed(speed);
|
||||||
playbackSpeedTextView.setText(formatSpeed(speed));
|
playbackSpeedTextView.setText(formatSpeed(speed));
|
||||||
@ -799,16 +784,16 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
showControls(DEFAULT_CONTROLS_DURATION);
|
showControls(DEFAULT_CONTROLS_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onResizeClicked() {
|
void onResizeClicked() {
|
||||||
if (getAspectRatioFrameLayout() != null) {
|
if (getSurfaceView() != null) {
|
||||||
final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode();
|
final int currentResizeMode = getSurfaceView().getResizeMode();
|
||||||
final int newResizeMode = nextResizeMode(currentResizeMode);
|
final int newResizeMode = nextResizeMode(currentResizeMode);
|
||||||
setResizeMode(newResizeMode);
|
setResizeMode(newResizeMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
|
protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
|
||||||
getAspectRatioFrameLayout().setResizeMode(resizeMode);
|
getSurfaceView().setResizeMode(resizeMode);
|
||||||
getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode));
|
getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -916,9 +901,9 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
if (drawableId == -1) {
|
if (drawableId == -1) {
|
||||||
if (controlAnimationView.getVisibility() == View.VISIBLE) {
|
if (controlAnimationView.getVisibility() == View.VISIBLE) {
|
||||||
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
|
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
|
||||||
PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f),
|
PropertyValuesHolder.ofFloat(View.ALPHA, 1.0f, 0.0f),
|
||||||
PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1f),
|
PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1.0f),
|
||||||
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1f)
|
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1.0f)
|
||||||
).setDuration(DEFAULT_CONTROLS_DURATION);
|
).setDuration(DEFAULT_CONTROLS_DURATION);
|
||||||
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
|
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
|
||||||
@Override
|
@Override
|
||||||
@ -931,10 +916,10 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float scaleFrom = goneOnEnd ? 1f : 1f;
|
final float scaleFrom = goneOnEnd ? 1f : 1f;
|
||||||
float scaleTo = goneOnEnd ? 1.8f : 1.4f;
|
final float scaleTo = goneOnEnd ? 1.8f : 1.4f;
|
||||||
float alphaFrom = goneOnEnd ? 1f : 0f;
|
final float alphaFrom = goneOnEnd ? 1f : 0f;
|
||||||
float alphaTo = goneOnEnd ? 0f : 1f;
|
final float alphaTo = goneOnEnd ? 0f : 1f;
|
||||||
|
|
||||||
|
|
||||||
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
|
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
|
||||||
@ -1020,6 +1005,9 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
animateView(controlsRoot, false, duration);
|
animateView(controlsRoot, false, duration);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void hideSystemUIIfNeeded();
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Getters and Setters
|
// Getters and Setters
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -1033,11 +1021,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
this.resolver.setPlaybackQuality(quality);
|
this.resolver.setPlaybackQuality(quality);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
|
public ExpandableSurfaceView getSurfaceView() {
|
||||||
return aspectRatioFrameLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SurfaceView getSurfaceView() {
|
|
||||||
return surfaceView;
|
return surfaceView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1096,7 +1080,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
return playbackEndTime;
|
return playbackEndTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public View getTopControlsRoot() {
|
public LinearLayout getTopControlsRoot() {
|
||||||
return topControlsRoot;
|
return topControlsRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1108,6 +1092,10 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
return qualityPopupMenu;
|
return qualityPopupMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TextView getPlaybackSpeedTextView() {
|
||||||
|
return playbackSpeedTextView;
|
||||||
|
}
|
||||||
|
|
||||||
public PopupMenu getPlaybackSpeedPopupMenu() {
|
public PopupMenu getPlaybackSpeedPopupMenu() {
|
||||||
return playbackSpeedPopupMenu;
|
return playbackSpeedPopupMenu;
|
||||||
}
|
}
|
||||||
|
2184
app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
Normal file
2184
app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,72 @@
|
|||||||
|
package org.schabi.newpipe.player.event;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CustomBottomSheetBehavior extends BottomSheetBehavior<FrameLayout> {
|
||||||
|
|
||||||
|
public CustomBottomSheetBehavior(final Context context, final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean visible;
|
||||||
|
Rect globalRect = new Rect();
|
||||||
|
private boolean skippingInterception = false;
|
||||||
|
private final List<Integer> skipInterceptionOfElements = Arrays.asList(
|
||||||
|
R.id.detail_content_root_layout, R.id.relatedStreamsLayout,
|
||||||
|
R.id.playQueuePanel, R.id.viewpager, R.id.bottomControls);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent,
|
||||||
|
@NonNull final FrameLayout child,
|
||||||
|
final MotionEvent event) {
|
||||||
|
// Drop following when action ends
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_CANCEL
|
||||||
|
|| event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
|
skippingInterception = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found that user still swiping, continue following
|
||||||
|
if (skippingInterception) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't need to do anything if bottomSheet isn't expanded
|
||||||
|
if (getState() == BottomSheetBehavior.STATE_EXPANDED) {
|
||||||
|
// Without overriding scrolling will not work when user touches these elements
|
||||||
|
for (final Integer element : skipInterceptionOfElements) {
|
||||||
|
final ViewGroup viewGroup = child.findViewById(element);
|
||||||
|
if (viewGroup != null) {
|
||||||
|
visible = viewGroup.getGlobalVisibleRect(globalRect);
|
||||||
|
if (visible
|
||||||
|
&& globalRect.contains((int) event.getRawX(), (int) event.getRawY())) {
|
||||||
|
// Makes bottom part of the player draggable in portrait when
|
||||||
|
// playbackControlRoot is hidden
|
||||||
|
if (element == R.id.bottomControls
|
||||||
|
&& child.findViewById(R.id.playbackControlRoot)
|
||||||
|
.getVisibility() != View.VISIBLE) {
|
||||||
|
return super.onInterceptTouchEvent(parent, child, event);
|
||||||
|
}
|
||||||
|
skippingInterception = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onInterceptTouchEvent(parent, child, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package org.schabi.newpipe.player.event;
|
||||||
|
|
||||||
|
public interface OnKeyDownListener {
|
||||||
|
boolean onKeyDown(int keyCode);
|
||||||
|
}
|
@ -4,14 +4,13 @@ package org.schabi.newpipe.player.event;
|
|||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
|
|
||||||
public interface PlayerEventListener {
|
public interface PlayerEventListener {
|
||||||
|
void onQueueUpdate(PlayQueue queue);
|
||||||
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled,
|
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled,
|
||||||
PlaybackParameters parameters);
|
PlaybackParameters parameters);
|
||||||
|
|
||||||
void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
|
void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
|
||||||
|
void onMetadataUpdate(StreamInfo info, PlayQueue queue);
|
||||||
void onMetadataUpdate(StreamInfo info);
|
|
||||||
|
|
||||||
void onServiceStopped();
|
void onServiceStopped();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,623 @@
|
|||||||
|
package org.schabi.newpipe.player.event;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.player.BasePlayer;
|
||||||
|
import org.schabi.newpipe.player.MainPlayer;
|
||||||
|
import org.schabi.newpipe.player.VideoPlayerImpl;
|
||||||
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING;
|
||||||
|
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION;
|
||||||
|
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
|
||||||
|
import static org.schabi.newpipe.util.AnimationUtils.Type.SCALE_AND_ALPHA;
|
||||||
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
|
public class PlayerGestureListener
|
||||||
|
extends GestureDetector.SimpleOnGestureListener
|
||||||
|
implements View.OnTouchListener {
|
||||||
|
private static final String TAG = ".PlayerGestureListener";
|
||||||
|
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||||
|
|
||||||
|
private final VideoPlayerImpl playerImpl;
|
||||||
|
private final MainPlayer service;
|
||||||
|
|
||||||
|
private int initialPopupX;
|
||||||
|
private int initialPopupY;
|
||||||
|
|
||||||
|
private boolean isMovingInMain;
|
||||||
|
private boolean isMovingInPopup;
|
||||||
|
|
||||||
|
private boolean isResizing;
|
||||||
|
|
||||||
|
private final int tossFlingVelocity;
|
||||||
|
|
||||||
|
private final boolean isVolumeGestureEnabled;
|
||||||
|
private final boolean isBrightnessGestureEnabled;
|
||||||
|
private final int maxVolume;
|
||||||
|
private static final int MOVEMENT_THRESHOLD = 40;
|
||||||
|
|
||||||
|
// [popup] initial coordinates and distance between fingers
|
||||||
|
private double initPointerDistance = -1;
|
||||||
|
private float initFirstPointerX = -1;
|
||||||
|
private float initFirstPointerY = -1;
|
||||||
|
private float initSecPointerX = -1;
|
||||||
|
private float initSecPointerY = -1;
|
||||||
|
|
||||||
|
|
||||||
|
public PlayerGestureListener(final VideoPlayerImpl playerImpl, final MainPlayer service) {
|
||||||
|
this.playerImpl = playerImpl;
|
||||||
|
this.service = service;
|
||||||
|
this.tossFlingVelocity = PlayerHelper.getTossFlingVelocity(service);
|
||||||
|
|
||||||
|
isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service);
|
||||||
|
isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(service);
|
||||||
|
maxVolume = playerImpl.getAudioReactor().getMaxVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Helpers
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Main and popup players' gesture listeners is too different.
|
||||||
|
* So it will be better to have different implementations of them
|
||||||
|
* */
|
||||||
|
@Override
|
||||||
|
public boolean onDoubleTap(final MotionEvent e) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = "
|
||||||
|
+ e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl.popupPlayerSelected()) {
|
||||||
|
return onDoubleTapInPopup(e);
|
||||||
|
} else {
|
||||||
|
return onDoubleTapInMain(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl.popupPlayerSelected()) {
|
||||||
|
return onSingleTapConfirmedInPopup(e);
|
||||||
|
} else {
|
||||||
|
return onSingleTapConfirmedInMain(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDown(final MotionEvent e) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onDown() called with: e = [" + e + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl.popupPlayerSelected()) {
|
||||||
|
return onDownInPopup(e);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLongPress(final MotionEvent e) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl.popupPlayerSelected()) {
|
||||||
|
onLongPressInPopup(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onScroll(final MotionEvent initialEvent, final MotionEvent movingEvent,
|
||||||
|
final float distanceX, final float distanceY) {
|
||||||
|
if (playerImpl.popupPlayerSelected()) {
|
||||||
|
return onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY);
|
||||||
|
} else {
|
||||||
|
return onScrollInMain(initialEvent, movingEvent, distanceX, distanceY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onFling(final MotionEvent e1, final MotionEvent e2,
|
||||||
|
final float velocityX, final float velocityY) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onFling() called with velocity: dX=["
|
||||||
|
+ velocityX + "], dY=[" + velocityY + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl.popupPlayerSelected()) {
|
||||||
|
return onFlingInPopup(e1, e2, velocityX, velocityY);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(final View v, final MotionEvent event) {
|
||||||
|
/*if (DEBUG && false) {
|
||||||
|
Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]");
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if (playerImpl.popupPlayerSelected()) {
|
||||||
|
return onTouchInPopup(v, event);
|
||||||
|
} else {
|
||||||
|
return onTouchInMain(v, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Main player listener
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private boolean onDoubleTapInMain(final MotionEvent e) {
|
||||||
|
if (e.getX() > playerImpl.getRootView().getWidth() * 2.0 / 3.0) {
|
||||||
|
playerImpl.onFastForward();
|
||||||
|
} else if (e.getX() < playerImpl.getRootView().getWidth() / 3.0) {
|
||||||
|
playerImpl.onFastRewind();
|
||||||
|
} else {
|
||||||
|
playerImpl.getPlayPauseButton().performClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean onSingleTapConfirmedInMain(final MotionEvent e) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl.isControlsVisible()) {
|
||||||
|
playerImpl.hideControls(150, 0);
|
||||||
|
} else {
|
||||||
|
if (playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
|
||||||
|
playerImpl.showControls(0);
|
||||||
|
} else {
|
||||||
|
playerImpl.showControlsThenHide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onScrollInMain(final MotionEvent initialEvent, final MotionEvent movingEvent,
|
||||||
|
final float distanceX, final float distanceY) {
|
||||||
|
if ((!isVolumeGestureEnabled && !isBrightnessGestureEnabled)
|
||||||
|
|| !playerImpl.isFullscreen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean isTouchingStatusBar = initialEvent.getY() < getStatusBarHeight(service);
|
||||||
|
final boolean isTouchingNavigationBar = initialEvent.getY()
|
||||||
|
> playerImpl.getRootView().getHeight() - getNavigationBarHeight(service);
|
||||||
|
if (isTouchingStatusBar || isTouchingNavigationBar) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if (DEBUG && false) Log.d(TAG, "onScrollInMain = " +
|
||||||
|
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" +
|
||||||
|
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" +
|
||||||
|
", distanceXy = [" + distanceX + ", " + distanceY + "]");*/
|
||||||
|
|
||||||
|
final boolean insideThreshold =
|
||||||
|
Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD;
|
||||||
|
if (!isMovingInMain && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY))
|
||||||
|
|| playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMovingInMain = true;
|
||||||
|
|
||||||
|
final boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
|
||||||
|
final boolean acceptVolumeArea = acceptAnyArea
|
||||||
|
|| initialEvent.getX() > playerImpl.getRootView().getWidth() / 2.0;
|
||||||
|
|
||||||
|
if (isVolumeGestureEnabled && acceptVolumeArea) {
|
||||||
|
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
|
||||||
|
final float currentProgressPercent = (float) playerImpl
|
||||||
|
.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
|
||||||
|
final int currentVolume = (int) (maxVolume * currentProgressPercent);
|
||||||
|
playerImpl.getAudioReactor().setVolume(currentVolume);
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
playerImpl.getVolumeImageView().setImageDrawable(
|
||||||
|
AppCompatResources.getDrawable(service, currentProgressPercent <= 0
|
||||||
|
? R.drawable.ic_volume_off_white_24dp
|
||||||
|
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_24dp
|
||||||
|
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_24dp
|
||||||
|
: R.drawable.ic_volume_up_white_24dp)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (playerImpl.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
|
||||||
|
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
|
||||||
|
}
|
||||||
|
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||||
|
playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final Activity parent = playerImpl.getParentActivity();
|
||||||
|
if (parent == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Window window = parent.getWindow();
|
||||||
|
|
||||||
|
playerImpl.getBrightnessProgressBar().incrementProgressBy((int) distanceY);
|
||||||
|
final float currentProgressPercent = (float) playerImpl.getBrightnessProgressBar()
|
||||||
|
.getProgress() / playerImpl.getMaxGestureLength();
|
||||||
|
final WindowManager.LayoutParams layoutParams = window.getAttributes();
|
||||||
|
layoutParams.screenBrightness = currentProgressPercent;
|
||||||
|
window.setAttributes(layoutParams);
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onScroll().brightnessControl, "
|
||||||
|
+ "currentBrightness = " + currentProgressPercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
playerImpl.getBrightnessImageView().setImageDrawable(
|
||||||
|
AppCompatResources.getDrawable(service,
|
||||||
|
currentProgressPercent < 0.25
|
||||||
|
? R.drawable.ic_brightness_low_white_24dp
|
||||||
|
: currentProgressPercent < 0.75
|
||||||
|
? R.drawable.ic_brightness_medium_white_24dp
|
||||||
|
: R.drawable.ic_brightness_high_white_24dp)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
|
||||||
|
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
|
||||||
|
}
|
||||||
|
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||||
|
playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onScrollEndInMain() {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onScrollEnd() called");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||||
|
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
|
||||||
|
}
|
||||||
|
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||||
|
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
|
||||||
|
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onTouchInMain(final View v, final MotionEvent event) {
|
||||||
|
playerImpl.getGestureDetector().onTouchEvent(event);
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_UP && isMovingInMain) {
|
||||||
|
isMovingInMain = false;
|
||||||
|
onScrollEndInMain();
|
||||||
|
}
|
||||||
|
// This hack allows to stop receiving touch events on appbar
|
||||||
|
// while touching video player's view
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
v.getParent().requestDisallowInterceptTouchEvent(playerImpl.isFullscreen());
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
v.getParent().requestDisallowInterceptTouchEvent(false);
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Popup player listener
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private boolean onDoubleTapInPopup(final MotionEvent e) {
|
||||||
|
if (playerImpl == null || !playerImpl.isPlaying()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
playerImpl.hideControls(0, 0);
|
||||||
|
|
||||||
|
if (e.getX() > playerImpl.getPopupWidth() / 2) {
|
||||||
|
playerImpl.onFastForward();
|
||||||
|
} else {
|
||||||
|
playerImpl.onFastRewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onSingleTapConfirmedInPopup(final MotionEvent e) {
|
||||||
|
if (playerImpl == null || playerImpl.getPlayer() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (playerImpl.isControlsVisible()) {
|
||||||
|
playerImpl.hideControls(100, 100);
|
||||||
|
} else {
|
||||||
|
playerImpl.getPlayPauseButton().requestFocus();
|
||||||
|
playerImpl.showControlsThenHide();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onDownInPopup(final MotionEvent e) {
|
||||||
|
// Fix popup position when the user touch it, it may have the wrong one
|
||||||
|
// because the soft input is visible (the draggable area is currently resized).
|
||||||
|
playerImpl.updateScreenSize();
|
||||||
|
playerImpl.checkPopupPositionBounds();
|
||||||
|
|
||||||
|
initialPopupX = playerImpl.getPopupLayoutParams().x;
|
||||||
|
initialPopupY = playerImpl.getPopupLayoutParams().y;
|
||||||
|
playerImpl.setPopupWidth(playerImpl.getPopupLayoutParams().width);
|
||||||
|
playerImpl.setPopupHeight(playerImpl.getPopupLayoutParams().height);
|
||||||
|
return super.onDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLongPressInPopup(final MotionEvent e) {
|
||||||
|
playerImpl.updateScreenSize();
|
||||||
|
playerImpl.checkPopupPositionBounds();
|
||||||
|
playerImpl.updatePopupSize((int) playerImpl.getScreenWidth(), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onScrollInPopup(final MotionEvent initialEvent,
|
||||||
|
final MotionEvent movingEvent,
|
||||||
|
final float distanceX,
|
||||||
|
final float distanceY) {
|
||||||
|
if (isResizing || playerImpl == null) {
|
||||||
|
return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMovingInPopup) {
|
||||||
|
animateView(playerImpl.getCloseOverlayButton(), true, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
isMovingInPopup = true;
|
||||||
|
|
||||||
|
final float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX());
|
||||||
|
float posX = (int) (initialPopupX + diffX);
|
||||||
|
final float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY());
|
||||||
|
float posY = (int) (initialPopupY + diffY);
|
||||||
|
|
||||||
|
if (posX > (playerImpl.getScreenWidth() - playerImpl.getPopupWidth())) {
|
||||||
|
posX = (int) (playerImpl.getScreenWidth() - playerImpl.getPopupWidth());
|
||||||
|
} else if (posX < 0) {
|
||||||
|
posX = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (posY > (playerImpl.getScreenHeight() - playerImpl.getPopupHeight())) {
|
||||||
|
posY = (int) (playerImpl.getScreenHeight() - playerImpl.getPopupHeight());
|
||||||
|
} else if (posY < 0) {
|
||||||
|
posY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
playerImpl.getPopupLayoutParams().x = (int) posX;
|
||||||
|
playerImpl.getPopupLayoutParams().y = (int) posY;
|
||||||
|
|
||||||
|
final View closingOverlayView = playerImpl.getClosingOverlayView();
|
||||||
|
if (playerImpl.isInsideClosingRadius(movingEvent)) {
|
||||||
|
if (closingOverlayView.getVisibility() == View.GONE) {
|
||||||
|
animateView(closingOverlayView, true, 250);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (closingOverlayView.getVisibility() == View.VISIBLE) {
|
||||||
|
animateView(closingOverlayView, false, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (DEBUG) {
|
||||||
|
// Log.d(TAG, "onScrollInPopup = "
|
||||||
|
// + "e1.getRaw = [" + initialEvent.getRawX() + ", "
|
||||||
|
// + initialEvent.getRawY() + "], "
|
||||||
|
// + "e1.getX,Y = [" + initialEvent.getX() + ", "
|
||||||
|
// + initialEvent.getY() + "], "
|
||||||
|
// + "e2.getRaw = [" + movingEvent.getRawX() + ", "
|
||||||
|
// + movingEvent.getRawY() + "], "
|
||||||
|
// + "e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "], "
|
||||||
|
// + "distanceX,Y = [" + distanceX + ", " + distanceY + "], "
|
||||||
|
// + "posX,Y = [" + posX + ", " + posY + "], "
|
||||||
|
// + "popupW,H = [" + popupWidth + " x " + popupHeight + "]");
|
||||||
|
// }
|
||||||
|
playerImpl.windowManager
|
||||||
|
.updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onScrollEndInPopup(final MotionEvent event) {
|
||||||
|
if (playerImpl == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
|
||||||
|
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerImpl.isInsideClosingRadius(event)) {
|
||||||
|
playerImpl.closePopup();
|
||||||
|
} else {
|
||||||
|
animateView(playerImpl.getClosingOverlayView(), false, 0);
|
||||||
|
|
||||||
|
if (!playerImpl.isPopupClosing) {
|
||||||
|
animateView(playerImpl.getCloseOverlayButton(), false, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onFlingInPopup(final MotionEvent e1,
|
||||||
|
final MotionEvent e2,
|
||||||
|
final float velocityX,
|
||||||
|
final float velocityY) {
|
||||||
|
if (playerImpl == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final float absVelocityX = Math.abs(velocityX);
|
||||||
|
final float absVelocityY = Math.abs(velocityY);
|
||||||
|
if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
|
||||||
|
if (absVelocityX > tossFlingVelocity) {
|
||||||
|
playerImpl.getPopupLayoutParams().x = (int) velocityX;
|
||||||
|
}
|
||||||
|
if (absVelocityY > tossFlingVelocity) {
|
||||||
|
playerImpl.getPopupLayoutParams().y = (int) velocityY;
|
||||||
|
}
|
||||||
|
playerImpl.checkPopupPositionBounds();
|
||||||
|
playerImpl.windowManager
|
||||||
|
.updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onTouchInPopup(final View v, final MotionEvent event) {
|
||||||
|
if (playerImpl == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
playerImpl.getGestureDetector().onTouchEvent(event);
|
||||||
|
|
||||||
|
if (event.getPointerCount() == 2 && !isMovingInPopup && !isResizing) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
|
||||||
|
}
|
||||||
|
playerImpl.showAndAnimateControl(-1, true);
|
||||||
|
playerImpl.getLoadingPanel().setVisibility(View.GONE);
|
||||||
|
|
||||||
|
playerImpl.hideControls(0, 0);
|
||||||
|
animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
|
||||||
|
animateView(playerImpl.getResizingIndicator(), true, 200, 0);
|
||||||
|
//record coordinates of fingers
|
||||||
|
initFirstPointerX = event.getX(0);
|
||||||
|
initFirstPointerY = event.getY(0);
|
||||||
|
initSecPointerX = event.getX(1);
|
||||||
|
initSecPointerY = event.getY(1);
|
||||||
|
//record distance between fingers
|
||||||
|
initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX,
|
||||||
|
initFirstPointerY - initSecPointerY);
|
||||||
|
|
||||||
|
isResizing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], "
|
||||||
|
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
|
||||||
|
}
|
||||||
|
return handleMultiDrag(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], "
|
||||||
|
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
|
||||||
|
}
|
||||||
|
if (isMovingInPopup) {
|
||||||
|
isMovingInPopup = false;
|
||||||
|
onScrollEndInPopup(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isResizing) {
|
||||||
|
isResizing = false;
|
||||||
|
|
||||||
|
initPointerDistance = -1;
|
||||||
|
initFirstPointerX = -1;
|
||||||
|
initFirstPointerY = -1;
|
||||||
|
initSecPointerX = -1;
|
||||||
|
initSecPointerY = -1;
|
||||||
|
|
||||||
|
animateView(playerImpl.getResizingIndicator(), false, 100, 0);
|
||||||
|
playerImpl.changeState(playerImpl.getCurrentState());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playerImpl.isPopupClosing) {
|
||||||
|
playerImpl.savePositionAndSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.performClick();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleMultiDrag(final MotionEvent event) {
|
||||||
|
if (initPointerDistance != -1 && event.getPointerCount() == 2) {
|
||||||
|
// get the movements of the fingers
|
||||||
|
final double firstPointerMove = Math.hypot(event.getX(0) - initFirstPointerX,
|
||||||
|
event.getY(0) - initFirstPointerY);
|
||||||
|
final double secPointerMove = Math.hypot(event.getX(1) - initSecPointerX,
|
||||||
|
event.getY(1) - initSecPointerY);
|
||||||
|
|
||||||
|
// minimum threshold beyond which pinch gesture will work
|
||||||
|
final int minimumMove = ViewConfiguration.get(service).getScaledTouchSlop();
|
||||||
|
|
||||||
|
if (Math.max(firstPointerMove, secPointerMove) > minimumMove) {
|
||||||
|
// calculate current distance between the pointers
|
||||||
|
final double currentPointerDistance =
|
||||||
|
Math.hypot(event.getX(0) - event.getX(1),
|
||||||
|
event.getY(0) - event.getY(1));
|
||||||
|
|
||||||
|
final double popupWidth = playerImpl.getPopupWidth();
|
||||||
|
// change co-ordinates of popup so the center stays at the same position
|
||||||
|
final double newWidth = (popupWidth * currentPointerDistance / initPointerDistance);
|
||||||
|
initPointerDistance = currentPointerDistance;
|
||||||
|
playerImpl.getPopupLayoutParams().x += (popupWidth - newWidth) / 2;
|
||||||
|
|
||||||
|
playerImpl.checkPopupPositionBounds();
|
||||||
|
playerImpl.updateScreenSize();
|
||||||
|
|
||||||
|
playerImpl.updatePopupSize(
|
||||||
|
(int) Math.min(playerImpl.getScreenWidth(), newWidth),
|
||||||
|
-1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Utils
|
||||||
|
* */
|
||||||
|
|
||||||
|
private int getNavigationBarHeight(final Context context) {
|
||||||
|
final int resId = context.getResources()
|
||||||
|
.getIdentifier("navigation_bar_height", "dimen", "android");
|
||||||
|
if (resId > 0) {
|
||||||
|
return context.getResources().getDimensionPixelSize(resId);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getStatusBarHeight(final Context context) {
|
||||||
|
final int resId = context.getResources()
|
||||||
|
.getIdentifier("status_bar_height", "dimen", "android");
|
||||||
|
if (resId > 0) {
|
||||||
|
return context.getResources().getDimensionPixelSize(resId);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
|||||||
|
package org.schabi.newpipe.player.event;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
|
||||||
|
public interface PlayerServiceEventListener extends PlayerEventListener {
|
||||||
|
void onFullscreenStateChanged(boolean fullscreen);
|
||||||
|
|
||||||
|
void onScreenRotationButtonClicked();
|
||||||
|
|
||||||
|
void onMoreOptionsLongClicked();
|
||||||
|
|
||||||
|
void onPlayerError(ExoPlaybackException error);
|
||||||
|
|
||||||
|
void hideSystemUiIfNeeded();
|
||||||
|
}
|
@ -114,7 +114,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
|
|||||||
private void onAudioFocusGain() {
|
private void onAudioFocusGain() {
|
||||||
Log.d(TAG, "onAudioFocusGain() called");
|
Log.d(TAG, "onAudioFocusGain() called");
|
||||||
player.setVolume(DUCK_AUDIO_TO);
|
player.setVolume(DUCK_AUDIO_TO);
|
||||||
animateAudio(DUCK_AUDIO_TO, 1f);
|
animateAudio(DUCK_AUDIO_TO, 1.0f);
|
||||||
|
|
||||||
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
|
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
|
||||||
player.setPlayWhenReady(true);
|
player.setPlayWhenReady(true);
|
||||||
@ -133,7 +133,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void animateAudio(final float from, final float to) {
|
private void animateAudio(final float from, final float to) {
|
||||||
ValueAnimator valueAnimator = new ValueAnimator();
|
final ValueAnimator valueAnimator = new ValueAnimator();
|
||||||
valueAnimator.setFloatValues(from, to);
|
valueAnimator.setFloatValues(from, to);
|
||||||
valueAnimator.setDuration(AudioReactor.DUCK_DURATION);
|
valueAnimator.setDuration(AudioReactor.DUCK_DURATION);
|
||||||
valueAnimator.addListener(new AnimatorListenerAdapter() {
|
valueAnimator.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@ -80,13 +80,13 @@ import java.io.File;
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (File file : cacheDir.listFiles()) {
|
for (final File file : cacheDir.listFiles()) {
|
||||||
final String filePath = file.getAbsolutePath();
|
final String filePath = file.getAbsolutePath();
|
||||||
final boolean deleteSuccessful = file.delete();
|
final boolean deleteSuccessful = file.delete();
|
||||||
|
|
||||||
Log.d(TAG, "tryDeleteCacheFiles: " + filePath + " deleted = " + deleteSuccessful);
|
Log.d(TAG, "tryDeleteCacheFiles: " + filePath + " deleted = " + deleteSuccessful);
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
Log.e(TAG, "Failed to delete file.", ignored);
|
Log.e(TAG, "Failed to delete file.", ignored);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ public class LoadController implements LoadControl {
|
|||||||
final int optimalPlaybackBufferMs) {
|
final int optimalPlaybackBufferMs) {
|
||||||
this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000;
|
this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000;
|
||||||
|
|
||||||
DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
|
final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
|
||||||
builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs,
|
builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs,
|
||||||
initialPlaybackBufferMs, initialPlaybackBufferMs);
|
initialPlaybackBufferMs, initialPlaybackBufferMs);
|
||||||
internalLoadControl = builder.createDefaultLoadControl();
|
internalLoadControl = builder.createDefaultLoadControl();
|
||||||
|
@ -62,7 +62,7 @@ public class MediaSessionManager {
|
|||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
|
||||||
MediaStyle mediaStyle = new MediaStyle()
|
final MediaStyle mediaStyle = new MediaStyle()
|
||||||
.setMediaSession(mediaSession.getSessionToken());
|
.setMediaSession(mediaSession.getSessionToken());
|
||||||
|
|
||||||
builder.setStyle(mediaStyle);
|
builder.setStyle(mediaStyle);
|
||||||
@ -76,7 +76,7 @@ public class MediaSessionManager {
|
|||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
|
||||||
MediaStyle mediaStyle = new MediaStyle()
|
final MediaStyle mediaStyle = new MediaStyle()
|
||||||
.setMediaSession(mediaSession.getSessionToken());
|
.setMediaSession(mediaSession.getSessionToken());
|
||||||
|
|
||||||
builder.setStyle(mediaStyle);
|
builder.setStyle(mediaStyle);
|
||||||
|
@ -3,7 +3,7 @@ package org.schabi.newpipe.player.helper;
|
|||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
@ -92,8 +92,10 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
|
|
||||||
public static PlaybackParameterDialog newInstance(final double playbackTempo,
|
public static PlaybackParameterDialog newInstance(final double playbackTempo,
|
||||||
final double playbackPitch,
|
final double playbackPitch,
|
||||||
final boolean playbackSkipSilence) {
|
final boolean playbackSkipSilence,
|
||||||
PlaybackParameterDialog dialog = new PlaybackParameterDialog();
|
final Callback callback) {
|
||||||
|
final PlaybackParameterDialog dialog = new PlaybackParameterDialog();
|
||||||
|
dialog.callback = callback;
|
||||||
dialog.initialTempo = playbackTempo;
|
dialog.initialTempo = playbackTempo;
|
||||||
dialog.initialPitch = playbackPitch;
|
dialog.initialPitch = playbackPitch;
|
||||||
|
|
||||||
@ -111,9 +113,9 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onAttach(final Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
if (context != null && context instanceof Callback) {
|
if (context instanceof Callback) {
|
||||||
callback = (Callback) context;
|
callback = (Callback) context;
|
||||||
} else {
|
} else if (callback == null) {
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,8 +187,8 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
|
|
||||||
private void setupTempoControl(@NonNull final View rootView) {
|
private void setupTempoControl(@NonNull final View rootView) {
|
||||||
tempoSlider = rootView.findViewById(R.id.tempoSeekbar);
|
tempoSlider = rootView.findViewById(R.id.tempoSeekbar);
|
||||||
TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
|
final TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
|
||||||
TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
|
final TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
|
||||||
tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText);
|
tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText);
|
||||||
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
|
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
|
||||||
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
|
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
|
||||||
@ -210,8 +212,8 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
|
|
||||||
private void setupPitchControl(@NonNull final View rootView) {
|
private void setupPitchControl(@NonNull final View rootView) {
|
||||||
pitchSlider = rootView.findViewById(R.id.pitchSeekbar);
|
pitchSlider = rootView.findViewById(R.id.pitchSeekbar);
|
||||||
TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
|
final TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
|
||||||
TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
|
final TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
|
||||||
pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText);
|
pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText);
|
||||||
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
|
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
|
||||||
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
|
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
|
||||||
@ -237,12 +239,13 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
|
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
|
||||||
if (unhookingCheckbox != null) {
|
if (unhookingCheckbox != null) {
|
||||||
// restore whether pitch and tempo are unhooked or not
|
// restore whether pitch and tempo are unhooked or not
|
||||||
unhookingCheckbox.setChecked(PreferenceManager.getDefaultSharedPreferences(getContext())
|
unhookingCheckbox.setChecked(PreferenceManager
|
||||||
|
.getDefaultSharedPreferences(requireContext())
|
||||||
.getBoolean(getString(R.string.playback_unhook_key), true));
|
.getBoolean(getString(R.string.playback_unhook_key), true));
|
||||||
|
|
||||||
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
||||||
// save whether pitch and tempo are unhooked or not
|
// save whether pitch and tempo are unhooked or not
|
||||||
PreferenceManager.getDefaultSharedPreferences(getContext())
|
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
.edit()
|
.edit()
|
||||||
.putBoolean(getString(R.string.playback_unhook_key), isChecked)
|
.putBoolean(getString(R.string.playback_unhook_key), isChecked)
|
||||||
.apply();
|
.apply();
|
||||||
@ -267,12 +270,12 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupStepSizeSelector(@NonNull final View rootView) {
|
private void setupStepSizeSelector(@NonNull final View rootView) {
|
||||||
TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
|
final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
|
||||||
TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
|
final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
|
||||||
TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
|
final TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
|
||||||
TextView stepSizeTwentyFivePercentText = rootView
|
final TextView stepSizeTwentyFivePercentText = rootView
|
||||||
.findViewById(R.id.stepSizeTwentyFivePercent);
|
.findViewById(R.id.stepSizeTwentyFivePercent);
|
||||||
TextView stepSizeOneHundredPercentText = rootView
|
final TextView stepSizeOneHundredPercentText = rootView
|
||||||
.findViewById(R.id.stepSizeOneHundredPercent);
|
.findViewById(R.id.stepSizeOneHundredPercent);
|
||||||
|
|
||||||
if (stepSizeOnePercentText != null) {
|
if (stepSizeOnePercentText != null) {
|
||||||
|
@ -2,8 +2,8 @@ package org.schabi.newpipe.player.helper;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import androidx.preference.PreferenceManager;
|
||||||
import android.preference.PreferenceManager;
|
import android.provider.Settings;
|
||||||
import android.view.accessibility.CaptioningManager;
|
import android.view.accessibility.CaptioningManager;
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
@ -45,6 +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_FIT;
|
||||||
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
||||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||||
|
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS;
|
||||||
|
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI;
|
||||||
|
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER;
|
||||||
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_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_NONE;
|
||||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
|
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
|
||||||
@ -56,6 +59,15 @@ public final class PlayerHelper {
|
|||||||
private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x");
|
private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x");
|
||||||
private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%");
|
private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%");
|
||||||
|
|
||||||
|
@Retention(SOURCE)
|
||||||
|
@IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI,
|
||||||
|
AUTOPLAY_TYPE_NEVER})
|
||||||
|
public @interface AutoplayType {
|
||||||
|
int AUTOPLAY_TYPE_ALWAYS = 0;
|
||||||
|
int AUTOPLAY_TYPE_WIFI = 1;
|
||||||
|
int AUTOPLAY_TYPE_NEVER = 2;
|
||||||
|
}
|
||||||
|
|
||||||
private PlayerHelper() { }
|
private PlayerHelper() { }
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@ -63,10 +75,10 @@ public final class PlayerHelper {
|
|||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public static String getTimeString(final int milliSeconds) {
|
public static String getTimeString(final int milliSeconds) {
|
||||||
int seconds = (milliSeconds % 60000) / 1000;
|
final int seconds = (milliSeconds % 60000) / 1000;
|
||||||
int minutes = (milliSeconds % 3600000) / 60000;
|
final int minutes = (milliSeconds % 3600000) / 60000;
|
||||||
int hours = (milliSeconds % 86400000) / 3600000;
|
final int hours = (milliSeconds % 86400000) / 3600000;
|
||||||
int days = (milliSeconds % (86400000 * 7)) / 86400000;
|
final int days = (milliSeconds % (86400000 * 7)) / 86400000;
|
||||||
|
|
||||||
STRING_BUILDER.setLength(0);
|
STRING_BUILDER.setLength(0);
|
||||||
return days > 0
|
return days > 0
|
||||||
@ -132,17 +144,17 @@ public final class PlayerHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a {@link StreamInfo} and the existing queue items, provide the
|
* Given a {@link StreamInfo} and the existing queue items,
|
||||||
* {@link SinglePlayQueue} consisting of the next video for auto queuing.
|
* provide the {@link SinglePlayQueue} consisting of the next video for auto queueing.
|
||||||
* <p>
|
* <p>
|
||||||
* This method detects and prevents cycle by naively checking if a
|
* This method detects and prevents cycles by naively checking
|
||||||
* candidate next video's url already exists in the existing items.
|
* if a candidate next video's url already exists in the existing items.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* To select the next video, {@link StreamInfo#getNextVideo()} is first
|
* The first item in {@link StreamInfo#getRelatedStreams()} is checked first.
|
||||||
* checked. If it is nonnull and is not part of the existing items, then
|
* If it is non-null and is not part of the existing items, it will be used as the next stream.
|
||||||
* it will be used as the next video. Otherwise, an random item with
|
* Otherwise, a random item with non-repeating url will be selected
|
||||||
* non-repeating url will be selected from the {@link StreamInfo#getRelatedStreams()}.
|
* from the {@link StreamInfo#getRelatedStreams()}.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param info currently playing stream
|
* @param info currently playing stream
|
||||||
@ -152,27 +164,28 @@ public final class PlayerHelper {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public static PlayQueue autoQueueOf(@NonNull final StreamInfo info,
|
public static PlayQueue autoQueueOf(@NonNull final StreamInfo info,
|
||||||
@NonNull final List<PlayQueueItem> existingItems) {
|
@NonNull final List<PlayQueueItem> existingItems) {
|
||||||
Set<String> urls = new HashSet<>(existingItems.size());
|
final Set<String> urls = new HashSet<>(existingItems.size());
|
||||||
for (final PlayQueueItem item : existingItems) {
|
for (final PlayQueueItem item : existingItems) {
|
||||||
urls.add(item.getUrl());
|
urls.add(item.getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
final StreamInfoItem nextVideo = info.getNextVideo();
|
|
||||||
if (nextVideo != null && !urls.contains(nextVideo.getUrl())) {
|
|
||||||
return getAutoQueuedSinglePlayQueue(nextVideo);
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<InfoItem> relatedItems = info.getRelatedStreams();
|
final List<InfoItem> relatedItems = info.getRelatedStreams();
|
||||||
if (relatedItems == null) {
|
if (relatedItems == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<StreamInfoItem> autoQueueItems = new ArrayList<>();
|
if (relatedItems.get(0) != null && relatedItems.get(0) instanceof StreamInfoItem
|
||||||
for (final InfoItem item : info.getRelatedStreams()) {
|
&& !urls.contains(relatedItems.get(0).getUrl())) {
|
||||||
|
return getAutoQueuedSinglePlayQueue((StreamInfoItem) relatedItems.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<StreamInfoItem> autoQueueItems = new ArrayList<>();
|
||||||
|
for (final InfoItem item : relatedItems) {
|
||||||
if (item instanceof StreamInfoItem && !urls.contains(item.getUrl())) {
|
if (item instanceof StreamInfoItem && !urls.contains(item.getUrl())) {
|
||||||
autoQueueItems.add((StreamInfoItem) item);
|
autoQueueItems.add((StreamInfoItem) item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Collections.shuffle(autoQueueItems);
|
Collections.shuffle(autoQueueItems);
|
||||||
return autoQueueItems.isEmpty()
|
return autoQueueItems.isEmpty()
|
||||||
? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
|
? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
|
||||||
@ -202,6 +215,11 @@ public final class PlayerHelper {
|
|||||||
return isAutoQueueEnabled(context, false);
|
return isAutoQueueEnabled(context, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isClearingQueueConfirmationRequired(@NonNull final Context context) {
|
||||||
|
return getPreferences(context)
|
||||||
|
.getBoolean(context.getString(R.string.clear_queue_confirmation_key), false);
|
||||||
|
}
|
||||||
|
|
||||||
@MinimizeMode
|
@MinimizeMode
|
||||||
public static int getMinimizeOnExitAction(@NonNull final Context context) {
|
public static int getMinimizeOnExitAction(@NonNull final Context context) {
|
||||||
final String defaultAction = context.getString(R.string.minimize_on_exit_none_key);
|
final String defaultAction = context.getString(R.string.minimize_on_exit_none_key);
|
||||||
@ -218,6 +236,18 @@ public final class PlayerHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AutoplayType
|
||||||
|
public static int getAutoplayType(@NonNull final Context context) {
|
||||||
|
final String type = getAutoplayType(context, context.getString(R.string.autoplay_wifi_key));
|
||||||
|
if (type.equals(context.getString(R.string.autoplay_always_key))) {
|
||||||
|
return AUTOPLAY_TYPE_ALWAYS;
|
||||||
|
} else if (type.equals(context.getString(R.string.autoplay_never_key))) {
|
||||||
|
return AUTOPLAY_TYPE_NEVER;
|
||||||
|
} else {
|
||||||
|
return AUTOPLAY_TYPE_WIFI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static SeekParameters getSeekParameters(@NonNull final Context context) {
|
public static SeekParameters getSeekParameters(@NonNull final Context context) {
|
||||||
return isUsingInexactSeek(context) ? SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
|
return isUsingInexactSeek(context) ? SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
|
||||||
@ -272,10 +302,6 @@ public final class PlayerHelper {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) {
|
public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
|
||||||
return CaptionStyleCompat.DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
final CaptioningManager captioningManager = (CaptioningManager)
|
final CaptioningManager captioningManager = (CaptioningManager)
|
||||||
context.getSystemService(Context.CAPTIONING_SERVICE);
|
context.getSystemService(Context.CAPTIONING_SERVICE);
|
||||||
if (captioningManager == null || !captioningManager.isEnabled()) {
|
if (captioningManager == null || !captioningManager.isEnabled()) {
|
||||||
@ -300,14 +326,10 @@ public final class PlayerHelper {
|
|||||||
* @return caption scaling
|
* @return caption scaling
|
||||||
*/
|
*/
|
||||||
public static float getCaptionScale(@NonNull final Context context) {
|
public static float getCaptionScale(@NonNull final Context context) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
|
||||||
return 1f;
|
|
||||||
}
|
|
||||||
|
|
||||||
final CaptioningManager captioningManager
|
final CaptioningManager captioningManager
|
||||||
= (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
|
= (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
|
||||||
if (captioningManager == null || !captioningManager.isEnabled()) {
|
if (captioningManager == null || !captioningManager.isEnabled()) {
|
||||||
return 1f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
return captioningManager.getFontScale();
|
return captioningManager.getFontScale();
|
||||||
@ -323,6 +345,13 @@ public final class PlayerHelper {
|
|||||||
setScreenBrightness(context, setScreenBrightness, System.currentTimeMillis());
|
setScreenBrightness(context, setScreenBrightness, System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean globalScreenOrientationLocked(final Context context) {
|
||||||
|
// 1: Screen orientation changes using accelerometer
|
||||||
|
// 0: Screen orientation is locked
|
||||||
|
return android.provider.Settings.System.getInt(
|
||||||
|
context.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// Private helpers
|
// Private helpers
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@ -367,7 +396,7 @@ public final class PlayerHelper {
|
|||||||
|
|
||||||
private static void setScreenBrightness(@NonNull final Context context,
|
private static void setScreenBrightness(@NonNull final Context context,
|
||||||
final float screenBrightness, final long timestamp) {
|
final float screenBrightness, final long timestamp) {
|
||||||
SharedPreferences.Editor editor = getPreferences(context).edit();
|
final SharedPreferences.Editor editor = getPreferences(context).edit();
|
||||||
editor.putFloat(context.getString(R.string.screen_brightness_key), screenBrightness);
|
editor.putFloat(context.getString(R.string.screen_brightness_key), screenBrightness);
|
||||||
editor.putLong(context.getString(R.string.screen_brightness_timestamp_key), timestamp);
|
editor.putLong(context.getString(R.string.screen_brightness_timestamp_key), timestamp);
|
||||||
editor.apply();
|
editor.apply();
|
||||||
@ -375,8 +404,8 @@ public final class PlayerHelper {
|
|||||||
|
|
||||||
private static float getScreenBrightness(@NonNull final Context context,
|
private static float getScreenBrightness(@NonNull final Context context,
|
||||||
final float screenBrightness) {
|
final float screenBrightness) {
|
||||||
SharedPreferences sp = getPreferences(context);
|
final SharedPreferences sp = getPreferences(context);
|
||||||
long timestamp = sp
|
final long timestamp = sp
|
||||||
.getLong(context.getString(R.string.screen_brightness_timestamp_key), 0);
|
.getLong(context.getString(R.string.screen_brightness_timestamp_key), 0);
|
||||||
// Hypothesis: 4h covers a viewing block, e.g. evening.
|
// Hypothesis: 4h covers a viewing block, e.g. evening.
|
||||||
// External lightning conditions will change in the next
|
// External lightning conditions will change in the next
|
||||||
@ -395,9 +424,15 @@ public final class PlayerHelper {
|
|||||||
.getString(context.getString(R.string.minimize_on_exit_key), key);
|
.getString(context.getString(R.string.minimize_on_exit_key), key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getAutoplayType(@NonNull final Context context,
|
||||||
|
final String key) {
|
||||||
|
return getPreferences(context).getString(context.getString(R.string.autoplay_key),
|
||||||
|
key);
|
||||||
|
}
|
||||||
|
|
||||||
private static SinglePlayQueue getAutoQueuedSinglePlayQueue(
|
private static SinglePlayQueue getAutoQueuedSinglePlayQueue(
|
||||||
final StreamInfoItem streamInfoItem) {
|
final StreamInfoItem streamInfoItem) {
|
||||||
SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);
|
final SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);
|
||||||
singlePlayQueue.getItem().setAutoQueued(true);
|
singlePlayQueue.getItem().setAutoQueued(true);
|
||||||
return singlePlayQueue;
|
return singlePlayQueue;
|
||||||
}
|
}
|
||||||
|
@ -87,13 +87,13 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Yes this is almost a copypasta, got a problem with that? =\
|
// Yes this is almost a copypasta, got a problem with that? =\
|
||||||
int windowCount = callback.getQueueSize();
|
final int windowCount = callback.getQueueSize();
|
||||||
int currentWindowIndex = callback.getCurrentPlayingIndex();
|
final int currentWindowIndex = callback.getCurrentPlayingIndex();
|
||||||
int queueSize = Math.min(maxQueueSize, windowCount);
|
final int queueSize = Math.min(maxQueueSize, windowCount);
|
||||||
int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0,
|
final int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0,
|
||||||
windowCount - queueSize);
|
windowCount - queueSize);
|
||||||
|
|
||||||
List<MediaSessionCompat.QueueItem> queue = new ArrayList<>();
|
final List<MediaSessionCompat.QueueItem> queue = new ArrayList<>();
|
||||||
for (int i = startIndex; i < startIndex + queueSize; i++) {
|
for (int i = startIndex; i < startIndex + queueSize; i++) {
|
||||||
queue.add(new MediaSessionCompat.QueueItem(callback.getQueueMetadata(i), i));
|
queue.add(new MediaSessionCompat.QueueItem(callback.getQueueMetadata(i), i));
|
||||||
}
|
}
|
||||||
|
@ -57,13 +57,14 @@ public class BasePlayerMediaSession implements MediaSessionCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final PlayQueueItem item = player.getPlayQueue().getItem(index);
|
final PlayQueueItem item = player.getPlayQueue().getItem(index);
|
||||||
MediaDescriptionCompat.Builder descriptionBuilder = new MediaDescriptionCompat.Builder()
|
final MediaDescriptionCompat.Builder descriptionBuilder
|
||||||
|
= new MediaDescriptionCompat.Builder()
|
||||||
.setMediaId(String.valueOf(index))
|
.setMediaId(String.valueOf(index))
|
||||||
.setTitle(item.getTitle())
|
.setTitle(item.getTitle())
|
||||||
.setSubtitle(item.getUploader());
|
.setSubtitle(item.getUploader());
|
||||||
|
|
||||||
// set additional metadata for A2DP/AVRCP
|
// set additional metadata for A2DP/AVRCP
|
||||||
Bundle additionalMetadata = new Bundle();
|
final Bundle additionalMetadata = new Bundle();
|
||||||
additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle());
|
additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle());
|
||||||
additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader());
|
additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader());
|
||||||
additionalMetadata
|
additionalMetadata
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user