Merge remote-tracking branch 'upstream/dev' into unsupported-url-dialog
This commit is contained in:
commit
eeba9c0a5f
290 changed files with 11279 additions and 7754 deletions
|
|
@ -150,7 +150,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||
// from its saved state, where the fragment manager has already
|
||||
// taken care of restoring the fragments we previously had instantiated.
|
||||
if (mFragments.size() > position) {
|
||||
Fragment f = mFragments.get(position);
|
||||
final Fragment f = mFragments.get(position);
|
||||
if (f != null) {
|
||||
return f;
|
||||
}
|
||||
|
|
@ -160,12 +160,12 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
|
||||
Fragment fragment = getItem(position);
|
||||
final Fragment fragment = getItem(position);
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
|
||||
}
|
||||
if (mSavedState.size() > position) {
|
||||
Fragment.SavedState fss = mSavedState.get(position);
|
||||
final Fragment.SavedState fss = mSavedState.get(position);
|
||||
if (fss != null) {
|
||||
fragment.setInitialSavedState(fss);
|
||||
}
|
||||
|
|
@ -191,7 +191,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||
@Override
|
||||
public void destroyItem(@NonNull final ViewGroup container, final int position,
|
||||
@NonNull final Object object) {
|
||||
Fragment fragment = (Fragment) object;
|
||||
final Fragment fragment = (Fragment) object;
|
||||
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
|
|
@ -217,7 +217,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
||||
public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
|
||||
@NonNull final Object object) {
|
||||
Fragment fragment = (Fragment) object;
|
||||
final Fragment fragment = (Fragment) object;
|
||||
if (fragment != mCurrentPrimaryItem) {
|
||||
if (mCurrentPrimaryItem != null) {
|
||||
mCurrentPrimaryItem.setMenuVisibility(false);
|
||||
|
|
@ -267,17 +267,17 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||
Bundle state = null;
|
||||
if (mSavedState.size() > 0) {
|
||||
state = new Bundle();
|
||||
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
|
||||
final Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
|
||||
mSavedState.toArray(fss);
|
||||
state.putParcelableArray("states", fss);
|
||||
}
|
||||
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 (state == null) {
|
||||
state = new Bundle();
|
||||
}
|
||||
String key = "f" + i;
|
||||
final String key = "f" + i;
|
||||
mFragmentManager.putFragment(state, key, f);
|
||||
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
|
@ -294,21 +294,21 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||
@Override
|
||||
public void restoreState(@Nullable final Parcelable state, @Nullable final ClassLoader loader) {
|
||||
if (state != null) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
final Bundle bundle = (Bundle) state;
|
||||
bundle.setClassLoader(loader);
|
||||
Parcelable[] fss = bundle.getParcelableArray("states");
|
||||
final Parcelable[] fss = bundle.getParcelableArray("states");
|
||||
mSavedState.clear();
|
||||
mFragments.clear();
|
||||
if (fss != null) {
|
||||
for (int i = 0; i < fss.length; i++) {
|
||||
mSavedState.add((Fragment.SavedState) fss[i]);
|
||||
for (final Parcelable parcelable : fss) {
|
||||
mSavedState.add((Fragment.SavedState) parcelable);
|
||||
}
|
||||
}
|
||||
Iterable<String> keys = bundle.keySet();
|
||||
for (String key: keys) {
|
||||
final Iterable<String> keys = bundle.keySet();
|
||||
for (final String key : keys) {
|
||||
if (key.startsWith("f")) {
|
||||
int index = Integer.parseInt(key.substring(1));
|
||||
Fragment f = mFragmentManager.getFragment(bundle, key);
|
||||
final int index = Integer.parseInt(key.substring(1));
|
||||
final Fragment f = mFragmentManager.getFragment(bundle, key);
|
||||
if (f != null) {
|
||||
while (mFragments.size() <= index) {
|
||||
mFragments.add(null);
|
||||
|
|
|
|||
|
|
@ -4,11 +4,14 @@ 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.OverScroller;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
|
|
@ -20,23 +23,25 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||
super(context, attrs);
|
||||
}
|
||||
|
||||
private boolean allowScroll = true;
|
||||
private final Rect globalRect = new Rect();
|
||||
|
||||
@Override
|
||||
public boolean onRequestChildRectangleOnScreen(
|
||||
@NonNull final CoordinatorLayout coordinatorLayout, @NonNull final AppBarLayout child,
|
||||
@NonNull final Rect rectangle, final boolean immediate) {
|
||||
|
||||
focusScrollRect.set(rectangle);
|
||||
|
||||
coordinatorLayout.offsetDescendantRectToMyCoords(child, focusScrollRect);
|
||||
|
||||
int height = coordinatorLayout.getHeight();
|
||||
final int height = coordinatorLayout.getHeight();
|
||||
|
||||
if (focusScrollRect.top <= 0 && focusScrollRect.bottom >= height) {
|
||||
// the child is too big to fit inside ourselves completely, ignore request
|
||||
return false;
|
||||
}
|
||||
|
||||
int dy;
|
||||
final int dy;
|
||||
|
||||
if (focusScrollRect.bottom > height) {
|
||||
dy = focusScrollRect.top;
|
||||
|
|
@ -48,13 +53,30 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||
return false;
|
||||
}
|
||||
|
||||
int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0);
|
||||
final int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0);
|
||||
|
||||
return consumed == dy;
|
||||
}
|
||||
|
||||
public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child,
|
||||
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()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// remove reference to old nested scrolling child
|
||||
|
|
@ -68,17 +90,37 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||
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
|
||||
private OverScroller getScrollerField() {
|
||||
try {
|
||||
Class<?> headerBehaviorType = this.getClass()
|
||||
final Class<?> headerBehaviorType = this.getClass()
|
||||
.getSuperclass().getSuperclass().getSuperclass();
|
||||
if (headerBehaviorType != null) {
|
||||
Field field = headerBehaviorType.getDeclaredField("scroller");
|
||||
final Field field = headerBehaviorType.getDeclaredField("scroller");
|
||||
field.setAccessible(true);
|
||||
return ((OverScroller) field.get(this));
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
} catch (final NoSuchFieldException | IllegalAccessException e) {
|
||||
// ?
|
||||
}
|
||||
return null;
|
||||
|
|
@ -87,34 +129,35 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||
@Nullable
|
||||
private Field getLastNestedScrollingChildRefField() {
|
||||
try {
|
||||
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass();
|
||||
final Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass();
|
||||
if (headerBehaviorType != null) {
|
||||
Field field = headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef");
|
||||
final Field field
|
||||
= headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef");
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
} catch (final NoSuchFieldException e) {
|
||||
// ?
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void resetNestedScrollingChild() {
|
||||
Field field = getLastNestedScrollingChildRefField();
|
||||
final Field field = getLastNestedScrollingChildRefField();
|
||||
if (field != null) {
|
||||
try {
|
||||
Object value = field.get(this);
|
||||
final Object value = field.get(this);
|
||||
if (value != null) {
|
||||
field.set(this, null);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
} catch (final IllegalAccessException e) {
|
||||
// ?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopAppBarLayoutFling() {
|
||||
OverScroller scroller = getScrollerField();
|
||||
final OverScroller scroller = getScrollerField();
|
||||
if (scroller != null) {
|
||||
scroller.forceFinished(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.content.SharedPreferences;
|
|||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
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.CoreConfiguration;
|
||||
import org.acra.config.CoreConfigurationBuilder;
|
||||
import org.acra.sender.ReportSenderFactory;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.report.AcraReportSenderFactory;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
|
|
@ -37,7 +36,6 @@ import java.net.SocketException;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.exceptions.CompositeException;
|
||||
import io.reactivex.exceptions.MissingBackpressureException;
|
||||
import io.reactivex.exceptions.OnErrorNotImplementedException;
|
||||
|
|
@ -65,9 +63,6 @@ import io.reactivex.plugins.RxJavaPlugins;
|
|||
|
||||
public class App extends Application {
|
||||
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;
|
||||
|
||||
public static App getApp() {
|
||||
|
|
@ -77,7 +72,6 @@ public class App extends Application {
|
|||
@Override
|
||||
protected void attachBaseContext(final Context base) {
|
||||
super.attachBaseContext(base);
|
||||
|
||||
initACRA();
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +104,7 @@ public class App extends Application {
|
|||
}
|
||||
|
||||
protected Downloader getDownloader() {
|
||||
DownloaderImpl downloader = DownloaderImpl.init(null);
|
||||
final DownloaderImpl downloader = DownloaderImpl.init(null);
|
||||
setCookiesToDownloader(downloader);
|
||||
return downloader;
|
||||
}
|
||||
|
|
@ -200,14 +194,21 @@ public class App extends Application {
|
|||
.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 {
|
||||
final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
|
||||
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
|
||||
.setBuildConfigClass(BuildConfig.class)
|
||||
.build();
|
||||
ACRA.init(this, acraConfig);
|
||||
} catch (ACRAConfigurationException ace) {
|
||||
} catch (final ACRAConfigurationException ace) {
|
||||
ace.printStackTrace();
|
||||
ErrorActivity.reportError(this,
|
||||
ace,
|
||||
|
|
@ -219,7 +220,7 @@ public class App extends Application {
|
|||
}
|
||||
|
||||
public void initNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -230,10 +231,10 @@ public class App extends Application {
|
|||
// Keep this below DEFAULT to avoid making noise on every notification update
|
||||
final int importance = NotificationManager.IMPORTANCE_LOW;
|
||||
|
||||
NotificationChannel mChannel = new NotificationChannel(id, name, importance);
|
||||
final NotificationChannel mChannel = new NotificationChannel(id, name, importance);
|
||||
mChannel.setDescription(description);
|
||||
|
||||
NotificationManager mNotificationManager =
|
||||
final NotificationManager mNotificationManager =
|
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mNotificationManager.createNotificationChannel(mChannel);
|
||||
|
||||
|
|
@ -254,11 +255,11 @@ public class App extends Application {
|
|||
final String appUpdateDescription
|
||||
= getString(R.string.app_update_notification_channel_description);
|
||||
|
||||
NotificationChannel appUpdateChannel
|
||||
final NotificationChannel appUpdateChannel
|
||||
= new NotificationChannel(appUpdateId, appUpdateName, importance);
|
||||
appUpdateChannel.setDescription(appUpdateDescription);
|
||||
|
||||
NotificationManager appUpdateNotificationManager
|
||||
final NotificationManager appUpdateNotificationManager
|
||||
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import android.content.pm.Signature;
|
|||
import android.net.ConnectivityManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
|
@ -62,7 +62,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
|||
|
||||
try {
|
||||
packageInfo = pm.getPackageInfo(packageName, flags);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
} catch (final PackageManager.NameNotFoundException e) {
|
||||
ErrorActivity.reportError(APP, e, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not find package info", R.string.app_ui_crash));
|
||||
|
|
@ -77,7 +77,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
|||
try {
|
||||
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
||||
c = (X509Certificate) cf.generateCertificate(input);
|
||||
} catch (CertificateException e) {
|
||||
} catch (final CertificateException e) {
|
||||
ErrorActivity.reportError(APP, e, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Certificate error", R.string.app_ui_crash));
|
||||
|
|
@ -86,7 +86,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
|||
String hexString = null;
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
final MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
final byte[] publicKey = md.digest(c.getEncoded());
|
||||
hexString = byte2HexFormatted(publicKey);
|
||||
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
||||
|
|
@ -167,7 +167,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
|||
|
||||
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
|
||||
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
// connectivity problems, do not alarm user and fail silently
|
||||
if (DEBUG) {
|
||||
Log.w(TAG, Log.getStackTraceString(e));
|
||||
|
|
@ -187,7 +187,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
|||
private void compareAppVersionAndShowNotification(final String versionName,
|
||||
final String apkLocationUrl,
|
||||
final int versionCode) {
|
||||
int notificationId = 2000;
|
||||
final int notificationId = 2000;
|
||||
|
||||
if (BuildConfig.VERSION_CODE < versionCode) {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package org.schabi.newpipe;
|
|||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
|
@ -94,18 +94,18 @@ public final class DownloaderImpl extends Downloader {
|
|||
private static void enableModernTLS(final OkHttpClient.Builder builder) {
|
||||
try {
|
||||
// get the default TrustManager
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
||||
throw new IllegalStateException("Unexpected default trust managers:"
|
||||
+ Arrays.toString(trustManagers));
|
||||
}
|
||||
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
||||
final X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
||||
|
||||
// insert our own TLSSocketFactory
|
||||
SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
|
||||
final SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
|
||||
|
||||
builder.sslSocketFactory(sslSocketFactory, trustManager);
|
||||
|
||||
|
|
@ -114,16 +114,16 @@ public final class DownloaderImpl extends Downloader {
|
|||
// Necessary because some servers (e.g. Framatube.org)
|
||||
// don't support the old cipher suites.
|
||||
// https://github.com/square/okhttp/issues/4053#issuecomment-402579554
|
||||
List<CipherSuite> cipherSuites = new ArrayList<>();
|
||||
cipherSuites.addAll(ConnectionSpec.MODERN_TLS.cipherSuites());
|
||||
final List<CipherSuite> 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_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]))
|
||||
.build();
|
||||
|
||||
builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT));
|
||||
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
} catch (final KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
if (DEBUG) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
@ -131,15 +131,15 @@ public final class DownloaderImpl extends Downloader {
|
|||
}
|
||||
|
||||
public String getCookies(final String url) {
|
||||
List<String> resultCookies = new ArrayList<>();
|
||||
final List<String> resultCookies = new ArrayList<>();
|
||||
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) {
|
||||
resultCookies.add(youtubeCookie);
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
resultCookies.add(recaptchaCookie);
|
||||
}
|
||||
|
|
@ -159,9 +159,9 @@ public final class DownloaderImpl extends Downloader {
|
|||
}
|
||||
|
||||
public void updateYoutubeRestrictedModeCookies(final Context context) {
|
||||
String restrictedModeEnabledKey =
|
||||
final String restrictedModeEnabledKey =
|
||||
context.getString(R.string.youtube_restricted_mode_enabled);
|
||||
boolean restrictedModeEnabled = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
final boolean restrictedModeEnabled = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(restrictedModeEnabledKey, false);
|
||||
updateYoutubeRestrictedModeCookies(restrictedModeEnabled);
|
||||
}
|
||||
|
|
@ -186,9 +186,9 @@ public final class DownloaderImpl extends Downloader {
|
|||
try {
|
||||
final Response response = head(url);
|
||||
return Long.parseLong(response.getHeader("Content-Length"));
|
||||
} catch (NumberFormatException e) {
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new IOException("Invalid content length", e);
|
||||
} catch (ReCaptchaException e) {
|
||||
} catch (final ReCaptchaException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -199,7 +199,7 @@ public final class DownloaderImpl extends Downloader {
|
|||
.method("GET", null).url(siteUrl)
|
||||
.addHeader("User-Agent", USER_AGENT);
|
||||
|
||||
String cookies = getCookies(siteUrl);
|
||||
final String cookies = getCookies(siteUrl);
|
||||
if (!cookies.isEmpty()) {
|
||||
requestBuilder.addHeader("Cookie", cookies);
|
||||
}
|
||||
|
|
@ -218,7 +218,7 @@ public final class DownloaderImpl extends Downloader {
|
|||
}
|
||||
|
||||
return body.byteStream();
|
||||
} catch (ReCaptchaException e) {
|
||||
} catch (final ReCaptchaException e) {
|
||||
throw new IOException(e.getMessage(), e.getCause());
|
||||
}
|
||||
}
|
||||
|
|
@ -240,18 +240,18 @@ public final class DownloaderImpl extends Downloader {
|
|||
.method(httpMethod, requestBody).url(url)
|
||||
.addHeader("User-Agent", USER_AGENT);
|
||||
|
||||
String cookies = getCookies(url);
|
||||
final String cookies = getCookies(url);
|
||||
if (!cookies.isEmpty()) {
|
||||
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 List<String> headerValueList = pair.getValue();
|
||||
|
||||
if (headerValueList.size() > 1) {
|
||||
requestBuilder.removeHeader(headerName);
|
||||
for (String headerValue : headerValueList) {
|
||||
for (final String headerValue : headerValueList) {
|
||||
requestBuilder.addHeader(headerName, headerValue);
|
||||
}
|
||||
} else if (headerValueList.size() == 1) {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import android.os.Bundle;
|
|||
public class ExitActivity extends 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.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
|
||||
|
|
@ -42,7 +42,7 @@ public class ExitActivity extends Activity {
|
|||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
finishAndRemoveTask();
|
||||
} else {
|
||||
finish();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import android.annotation.SuppressLint;
|
|||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,22 +27,22 @@ import android.os.Build;
|
|||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
|
|
@ -53,6 +53,7 @@ import androidx.drawerlayout.widget.DrawerLayout;
|
|||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
|
||||
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.detail.VideoDetailFragment;
|
||||
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.util.AndroidTvUtils;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PeertubeHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
|
||||
|
|
@ -127,12 +132,6 @@ public class MainActivity extends AppCompatActivity {
|
|||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Window w = getWindow();
|
||||
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
|
||||
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
|
||||
if (getSupportFragmentManager() != null
|
||||
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
initFragments();
|
||||
|
|
@ -141,11 +140,11 @@ public class MainActivity extends AppCompatActivity {
|
|||
setSupportActionBar(findViewById(R.id.toolbar));
|
||||
try {
|
||||
setupDrawer();
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
|
||||
if (AndroidTvUtils.isTv(this)) {
|
||||
if (DeviceUtils.isTv(this)) {
|
||||
FocusOverlayView.setupFocusObserver(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -156,8 +155,8 @@ public class MainActivity extends AppCompatActivity {
|
|||
drawerItems = findViewById(R.id.navigation);
|
||||
|
||||
//Tabs
|
||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
StreamingService service = NewPipe.getService(currentServiceId);
|
||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||
|
||||
int kioskId = 0;
|
||||
|
||||
|
|
@ -229,7 +228,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
case R.id.menu_tabs_group:
|
||||
try {
|
||||
tabSelected(item);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
break;
|
||||
|
|
@ -270,8 +269,8 @@ public class MainActivity extends AppCompatActivity {
|
|||
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
|
||||
break;
|
||||
default:
|
||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
StreamingService service = NewPipe.getService(currentServiceId);
|
||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||
String serviceName = "";
|
||||
|
||||
int kioskId = 0;
|
||||
|
|
@ -300,8 +299,8 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
private void setupDrawerHeader() {
|
||||
NavigationView navigationView = findViewById(R.id.navigation);
|
||||
View hView = navigationView.getHeaderView(0);
|
||||
final NavigationView navigationView = findViewById(R.id.navigation);
|
||||
final View hView = navigationView.getHeaderView(0);
|
||||
|
||||
serviceArrow = hView.findViewById(R.id.drawer_arrow);
|
||||
headerServiceIcon = hView.findViewById(R.id.drawer_header_service_icon);
|
||||
|
|
@ -336,7 +335,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
} else {
|
||||
try {
|
||||
showTabs();
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
}
|
||||
|
|
@ -345,11 +344,11 @@ public class MainActivity extends AppCompatActivity {
|
|||
private void showServices() {
|
||||
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()
|
||||
+ (ServiceHelper.isBeta(s) ? " (beta)" : "");
|
||||
|
||||
MenuItem menuItem = drawerItems.getMenu()
|
||||
final MenuItem menuItem = drawerItems.getMenu()
|
||||
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
|
||||
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
|
||||
|
||||
|
|
@ -363,20 +362,20 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
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)" : ""));
|
||||
Spinner spinner = (Spinner) LayoutInflater.from(this)
|
||||
final Spinner spinner = (Spinner) LayoutInflater.from(this)
|
||||
.inflate(R.layout.instance_spinner_layout, null);
|
||||
List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this);
|
||||
List<String> items = new ArrayList<>();
|
||||
final List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this);
|
||||
final List<String> items = new ArrayList<>();
|
||||
int defaultSelect = 0;
|
||||
for (PeertubeInstance instance : instances) {
|
||||
for (final PeertubeInstance instance : instances) {
|
||||
items.add(instance.getName());
|
||||
if (instance.getUrl().equals(currentInstace.getUrl())) {
|
||||
defaultSelect = items.size() - 1;
|
||||
}
|
||||
}
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
|
||||
R.layout.instance_spinner_item, items);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
|
|
@ -385,7 +384,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
@Override
|
||||
public void onItemSelected(final AdapterView<?> parent, final View view,
|
||||
final int position, final long id) {
|
||||
PeertubeInstance newInstance = instances.get(position);
|
||||
final PeertubeInstance newInstance = instances.get(position);
|
||||
if (newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -411,8 +410,8 @@ public class MainActivity extends AppCompatActivity {
|
|||
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp);
|
||||
|
||||
//Tabs
|
||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
StreamingService service = NewPipe.getService(currentServiceId);
|
||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||
|
||||
int kioskId = 0;
|
||||
|
||||
|
|
@ -477,11 +476,12 @@ public class MainActivity extends AppCompatActivity {
|
|||
headerServiceView.post(() -> headerServiceView.setSelected(true));
|
||||
toggleServiceButton.setContentDescription(
|
||||
getString(R.string.drawer_header_description) + selectedServiceName);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception 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 (DEBUG) {
|
||||
Log.d(TAG, "Theme has changed, recreating activity...");
|
||||
|
|
@ -514,7 +514,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
if (intent != null) {
|
||||
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
|
||||
// to not destroy the already created backstack
|
||||
String action = intent.getAction();
|
||||
final String action = intent.getAction();
|
||||
if ((action != null && action.equals(Intent.ACTION_MAIN))
|
||||
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
|
||||
return;
|
||||
|
|
@ -526,25 +526,60 @@ public class MainActivity extends AppCompatActivity {
|
|||
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
|
||||
public void onBackPressed() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onBackPressed() called");
|
||||
}
|
||||
|
||||
if (AndroidTvUtils.isTv(this)) {
|
||||
View drawerPanel = findViewById(R.id.navigation);
|
||||
if (DeviceUtils.isTv(this)) {
|
||||
final View drawerPanel = findViewById(R.id.navigation);
|
||||
if (drawer.isDrawerOpen(drawerPanel)) {
|
||||
drawer.closeDrawers();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
// In case bottomSheet is not visible on the screen or collapsed we can assume that the user
|
||||
// interacts with a fragment inside fragment_holder so all back presses should be
|
||||
// handled by it
|
||||
if (bottomSheetHiddenOrCollapsed()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -560,7 +595,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
public void onRequestPermissionsResult(final int requestCode,
|
||||
@NonNull final String[] permissions,
|
||||
@NonNull final int[] grantResults) {
|
||||
for (int i : grantResults) {
|
||||
for (final int i : grantResults) {
|
||||
if (i == PackageManager.PERMISSION_DENIED) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -570,8 +605,8 @@ public class MainActivity extends AppCompatActivity {
|
|||
NavigationHelper.openDownloads(this);
|
||||
break;
|
||||
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
|
||||
Fragment fragment = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.fragment_holder);
|
||||
final Fragment fragment = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.fragment_player_holder);
|
||||
if (fragment instanceof VideoDetailFragment) {
|
||||
((VideoDetailFragment) fragment).openDownloadDialog();
|
||||
}
|
||||
|
|
@ -622,17 +657,14 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||
if (!(fragment instanceof VideoDetailFragment)) {
|
||||
findViewById(R.id.toolbar).findViewById(R.id.toolbar_spinner).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
final Fragment fragment
|
||||
= getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||
if (!(fragment instanceof SearchFragment)) {
|
||||
findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container)
|
||||
.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
|
|
@ -647,7 +679,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
if (DEBUG) {
|
||||
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
|
||||
}
|
||||
int id = item.getItemId();
|
||||
final int id = item.getItemId();
|
||||
|
||||
switch (id) {
|
||||
case android.R.id.home:
|
||||
|
|
@ -668,6 +700,13 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
StateSaver.clearStateFiles();
|
||||
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());
|
||||
} else {
|
||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||
|
|
@ -708,16 +747,22 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
|
||||
String url = intent.getStringExtra(Constants.KEY_URL);
|
||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
String title = intent.getStringExtra(Constants.KEY_TITLE);
|
||||
final String url = intent.getStringExtra(Constants.KEY_URL);
|
||||
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
final String title = intent.getStringExtra(Constants.KEY_TITLE);
|
||||
switch (((StreamingService.LinkType) intent
|
||||
.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
|
||||
case STREAM:
|
||||
boolean autoPlay = intent
|
||||
final boolean autoPlay = intent
|
||||
.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(),
|
||||
serviceId, url, title, autoPlay);
|
||||
serviceId, url, title, autoPlay, playQueue);
|
||||
break;
|
||||
case CHANNEL:
|
||||
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
|
||||
|
|
@ -737,7 +782,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
if (searchString == null) {
|
||||
searchString = "";
|
||||
}
|
||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
NavigationHelper.openSearchFragment(
|
||||
getSupportFragmentManager(),
|
||||
serviceId,
|
||||
|
|
@ -746,8 +791,21 @@ public class MainActivity extends AppCompatActivity {
|
|||
} else {
|
||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception 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) {
|
||||
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) {
|
||||
throw new RuntimeException("Checkpoint was blocked from completing");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class PanicResponderActivity extends Activity {
|
|||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Intent intent = getIntent();
|
||||
final Intent intent = getIntent();
|
||||
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
||||
// TODO: Explicitly clear the search results
|
||||
// once they are restored when the app restarts
|
||||
|
|
@ -40,7 +40,7 @@ public class PanicResponderActivity extends Activity {
|
|||
ExitActivity.exitAndRemoveFromRecentApps(this);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
finishAndRemoveTask();
|
||||
} else {
|
||||
finish();
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||
ThemeHelper.setTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_recaptcha);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
String url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
|
||||
|
|
@ -76,7 +76,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||
webView = findViewById(R.id.reCaptchaWebView);
|
||||
|
||||
// enable Javascript
|
||||
WebSettings webSettings = webView.getSettings();
|
||||
final WebSettings webSettings = webView.getSettings();
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
|
|
@ -84,7 +84,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||
@Override
|
||||
public boolean shouldOverrideUrlLoading(final WebView view,
|
||||
final WebResourceRequest request) {
|
||||
String url = request.getUrl().toString();
|
||||
final String url = request.getUrl().toString();
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "shouldOverrideUrlLoading: request.url=" + url);
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||
// cleaning cache, history and cookies from webView
|
||||
webView.clearCache(true);
|
||||
webView.clearHistory();
|
||||
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
||||
final android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.removeAllCookies(aBoolean -> {
|
||||
});
|
||||
|
|
@ -128,7 +128,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setTitle(R.string.title_activity_recaptcha);
|
||||
|
|
@ -145,7 +145,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
final int id = item.getItemId();
|
||||
switch (id) {
|
||||
case R.id.menu_item_done:
|
||||
saveCookiesAndFinish();
|
||||
|
|
@ -173,7 +173,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||
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);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
}
|
||||
|
|
@ -188,13 +188,13 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||
return;
|
||||
}
|
||||
|
||||
String cookies = CookieManager.getInstance().getCookie(url);
|
||||
final String cookies = CookieManager.getInstance().getCookie(url);
|
||||
handleCookies(cookies);
|
||||
|
||||
// sometimes cookies are inside the url
|
||||
int abuseStart = url.indexOf("google_abuse=");
|
||||
final int abuseStart = url.indexOf("google_abuse=");
|
||||
if (abuseStart != -1) {
|
||||
int abuseEnd = url.indexOf("+path");
|
||||
final int abuseEnd = url.indexOf("+path");
|
||||
|
||||
try {
|
||||
String abuseCookie = url.substring(abuseStart + 13, abuseEnd);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ContextThemeWrapper;
|
||||
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.SinglePlayQueue;
|
||||
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.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
|
|
@ -320,7 +320,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||
};
|
||||
|
||||
int id = 12345;
|
||||
for (AdapterChoiceItem item : choices) {
|
||||
for (final AdapterChoiceItem item : choices) {
|
||||
final RadioButton radioButton
|
||||
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
|
||||
radioButton.setText(item.description);
|
||||
|
|
@ -340,7 +340,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||
getString(R.string.preferred_open_action_last_selected_key), null);
|
||||
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
|
||||
for (int i = 0; i < choices.size(); i++) {
|
||||
AdapterChoiceItem c = choices.get(i);
|
||||
final AdapterChoiceItem c = choices.get(i);
|
||||
if (lastSelectedPlayer.equals(c.key)) {
|
||||
selectedRadioPosition = i;
|
||||
break;
|
||||
|
|
@ -357,7 +357,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||
|
||||
alertDialog.show();
|
||||
|
||||
if (AndroidTvUtils.isTv(this)) {
|
||||
if (DeviceUtils.isTv(this)) {
|
||||
FocusOverlayView.setupFocusObserver(alertDialog);
|
||||
}
|
||||
}
|
||||
|
|
@ -372,9 +372,9 @@ public class RouterActivity extends AppCompatActivity {
|
|||
|
||||
final SharedPreferences preferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(this);
|
||||
boolean isExtVideoEnabled = preferences.getBoolean(
|
||||
final boolean isExtVideoEnabled = preferences.getBoolean(
|
||||
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);
|
||||
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key),
|
||||
|
|
@ -420,9 +420,9 @@ public class RouterActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
private void handleText() {
|
||||
String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
|
||||
final String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
final int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
final Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
NavigationHelper.openSearch(getThemeWrapperContext(), serviceId, searchString);
|
||||
|
|
@ -489,14 +489,14 @@ public class RouterActivity extends AppCompatActivity {
|
|||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe((@NonNull StreamInfo result) -> {
|
||||
List<VideoStream> sortedVideoStreams = ListHelper
|
||||
final List<VideoStream> sortedVideoStreams = ListHelper
|
||||
.getSortedStreamVideosList(this, result.getVideoStreams(),
|
||||
result.getVideoOnlyStreams(), false);
|
||||
int selectedVideoStreamIndex = ListHelper
|
||||
final int selectedVideoStreamIndex = ListHelper
|
||||
.getDefaultResolutionIndex(this, sortedVideoStreams);
|
||||
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
|
||||
final FragmentManager fm = getSupportFragmentManager();
|
||||
final DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
|
||||
downloadDialog.setVideoStreams(sortedVideoStreams);
|
||||
downloadDialog.setAudioStreams(result.getAudioStreams());
|
||||
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
|
||||
|
|
@ -512,7 +512,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||
public void onRequestPermissionsResult(final int requestCode,
|
||||
@NonNull final String[] permissions,
|
||||
@NonNull final int[] grantResults) {
|
||||
for (int i : grantResults) {
|
||||
for (final int i : grantResults) {
|
||||
if (i == PackageManager.PERMISSION_DENIED) {
|
||||
finish();
|
||||
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 {
|
||||
|
|
@ -642,7 +642,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||
if (!(serializable instanceof Choice)) {
|
||||
return;
|
||||
}
|
||||
Choice playerChoice = (Choice) serializable;
|
||||
final Choice playerChoice = (Choice) serializable;
|
||||
handleChoice(playerChoice);
|
||||
}
|
||||
|
||||
|
|
@ -690,13 +690,13 @@ public class RouterActivity extends AppCompatActivity {
|
|||
|
||||
final SharedPreferences preferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(this);
|
||||
boolean isExtVideoEnabled = preferences.getBoolean(
|
||||
final boolean isExtVideoEnabled = preferences.getBoolean(
|
||||
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);
|
||||
|
||||
PlayQueue playQueue;
|
||||
String playerChoice = choice.playerChoice;
|
||||
final String playerChoice = choice.playerChoice;
|
||||
|
||||
if (info instanceof StreamInfo) {
|
||||
if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) {
|
||||
|
|
@ -709,7 +709,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||
playQueue = new SinglePlayQueue((StreamInfo) info);
|
||||
|
||||
if (playerChoice.equals(videoPlayerKey)) {
|
||||
NavigationHelper.playOnMainPlayer(this, playQueue, true);
|
||||
openMainPlayer(playQueue, choice);
|
||||
} else if (playerChoice.equals(backgroundPlayerKey)) {
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
|
||||
} else if (playerChoice.equals(popupPlayerKey)) {
|
||||
|
|
@ -724,7 +724,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||
: new PlaylistPlayQueue((PlaylistInfo) info);
|
||||
|
||||
if (playerChoice.equals(videoPlayerKey)) {
|
||||
NavigationHelper.playOnMainPlayer(this, playQueue, true);
|
||||
openMainPlayer(playQueue, choice);
|
||||
} else if (playerChoice.equals(backgroundPlayerKey)) {
|
||||
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
|
||||
} 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
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public class AboutActivity extends AppCompatActivity {
|
|||
|
||||
setContentView(R.layout.activity_about);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
// 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.setAdapter(mSectionsPagerAdapter);
|
||||
|
||||
TabLayout tabLayout = findViewById(R.id.tabs);
|
||||
final TabLayout tabLayout = findViewById(R.id.tabs);
|
||||
tabLayout.setupWithViewPager(mViewPager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
final int id = item.getItemId();
|
||||
|
||||
switch (id) {
|
||||
case android.R.id.home:
|
||||
|
|
@ -134,25 +134,25 @@ public class AboutActivity extends AppCompatActivity {
|
|||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
|
||||
Context context = this.getContext();
|
||||
final View rootView = inflater.inflate(R.layout.fragment_about, container, false);
|
||||
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);
|
||||
|
||||
View githubLink = rootView.findViewById(R.id.github_link);
|
||||
final View githubLink = rootView.findViewById(R.id.github_link);
|
||||
githubLink.setOnClickListener(nv ->
|
||||
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 ->
|
||||
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 ->
|
||||
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 ->
|
||||
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url)));
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ public class AboutActivity extends AppCompatActivity {
|
|||
*/
|
||||
public class SectionsPagerAdapter extends FragmentPagerAdapter {
|
||||
public SectionsPagerAdapter(final FragmentManager fm) {
|
||||
super(fm);
|
||||
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ import android.net.Uri;
|
|||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 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>() {
|
||||
@Override
|
||||
public License createFromParcel(final Parcel source) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package org.schabi.newpipe.about;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -11,12 +10,14 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
|
|
@ -26,13 +27,15 @@ public class LicenseFragment extends Fragment {
|
|||
private static final String ARG_COMPONENTS = "components";
|
||||
private SoftwareComponent[] softwareComponents;
|
||||
private SoftwareComponent componentForContextMenu;
|
||||
private License activeLicense;
|
||||
private static final String LICENSE_KEY = "ACTIVE_LICENSE";
|
||||
|
||||
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
|
||||
if (softwareComponents == null) {
|
||||
throw new NullPointerException("softwareComponents is null");
|
||||
}
|
||||
LicenseFragment fragment = new LicenseFragment();
|
||||
Bundle bundle = new Bundle();
|
||||
final LicenseFragment fragment = new LicenseFragment();
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArray(ARG_COMPONENTS, softwareComponents);
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
|
|
@ -44,8 +47,8 @@ public class LicenseFragment extends Fragment {
|
|||
* @param context the context to use
|
||||
* @param license the license to show
|
||||
*/
|
||||
private static void showLicense(final Context context, final License license) {
|
||||
new LicenseFragmentHelper((Activity) context).execute(license);
|
||||
private static void showLicense(final Activity context, final License license) {
|
||||
new LicenseFragmentHelper(context).execute(license);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -54,6 +57,12 @@ public class LicenseFragment extends Fragment {
|
|||
softwareComponents = (SoftwareComponent[]) getArguments()
|
||||
.getParcelableArray(ARG_COMPONENTS);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
final Serializable license = savedInstanceState.getSerializable(LICENSE_KEY);
|
||||
if (license != null) {
|
||||
activeLicense = (License) license;
|
||||
}
|
||||
}
|
||||
// Sort components by name
|
||||
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 View licenseLink = rootView.findViewById(R.id.app_read_license);
|
||||
licenseLink.setOnClickListener(v ->
|
||||
showLicense(getActivity(), StandardLicenses.GPL3));
|
||||
licenseLink.setOnClickListener(v -> {
|
||||
activeLicense = StandardLicenses.GPL3;
|
||||
showLicense(getActivity(), StandardLicenses.GPL3);
|
||||
});
|
||||
|
||||
for (final SoftwareComponent component : softwareComponents) {
|
||||
final View componentView = inflater
|
||||
|
|
@ -81,11 +92,16 @@ public class LicenseFragment extends Fragment {
|
|||
component.getLicense().getAbbreviation()));
|
||||
|
||||
componentView.setTag(component);
|
||||
componentView.setOnClickListener(v ->
|
||||
showLicense(getActivity(), component.getLicense()));
|
||||
componentView.setOnClickListener(v -> {
|
||||
activeLicense = component.getLicense();
|
||||
showLicense(getActivity(), component.getLicense());
|
||||
});
|
||||
softwareComponentsView.addView(componentView);
|
||||
registerForContextMenu(componentView);
|
||||
}
|
||||
if (activeLicense != null) {
|
||||
showLicense(getActivity(), activeLicense);
|
||||
}
|
||||
return rootView;
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +117,7 @@ public class LicenseFragment extends Fragment {
|
|||
}
|
||||
|
||||
@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
|
||||
final SoftwareComponent component = componentForContextMenu;
|
||||
if (component == null) {
|
||||
|
|
@ -116,4 +132,12 @@ public class LicenseFragment extends Fragment {
|
|||
}
|
||||
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
|
||||
webViewData = licenseContent.toString().replace("</head>",
|
||||
"<style>" + getLicenseStylesheet(context) + "</style></head>");
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not get license file: " + license.getFilename(), e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@ import io.reactivex.Flowable;
|
|||
@Dao
|
||||
public interface BasicDAO<Entity> {
|
||||
/* Inserts */
|
||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
||||
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||
long insert(Entity entity);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
||||
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||
List<Long> insertAll(Entity... entities);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
||||
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||
List<Long> insertAll(Collection<Entity> entities);
|
||||
|
||||
/* Searches */
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public final class Converters {
|
|||
|
||||
@TypeConverter
|
||||
public static FeedGroupIcon feedGroupIconOf(final Integer id) {
|
||||
for (FeedGroupIcon icon : FeedGroupIcon.values()) {
|
||||
for (final FeedGroupIcon icon : FeedGroupIcon.values()) {
|
||||
if (icon.getId() == id) {
|
||||
return icon;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.room.migration.Migration;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
|
||||
public final class Migrations {
|
||||
public static final int DB_VER_1 = 1;
|
||||
|
|
@ -14,7 +14,7 @@ public final class Migrations {
|
|||
public static final int DB_VER_3 = 3;
|
||||
|
||||
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) {
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,7 +1,33 @@
|
|||
package org.schabi.newpipe.database.playlist;
|
||||
|
||||
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 {
|
||||
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")
|
||||
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")
|
||||
abstract fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
|
|
@ -52,7 +91,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
|||
entity.uid = uidFromInsert
|
||||
} else {
|
||||
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
|
||||
|
||||
update(entity)
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public class SubscriptionEntity {
|
|||
|
||||
@Ignore
|
||||
public static SubscriptionEntity from(@NonNull final ChannelInfo info) {
|
||||
SubscriptionEntity result = new SubscriptionEntity();
|
||||
final SubscriptionEntity result = new SubscriptionEntity();
|
||||
result.setServiceId(info.getServiceId());
|
||||
result.setUrl(info.getUrl());
|
||||
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(),
|
||||
|
|
@ -124,10 +124,61 @@ public class SubscriptionEntity {
|
|||
|
||||
@Ignore
|
||||
public ChannelInfoItem toChannelInfoItem() {
|
||||
ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
|
||||
final ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
|
||||
item.setThumbnailUrl(getAvatarUrl());
|
||||
item.setSubscriberCount(getSubscriberCount());
|
||||
item.setDescription(getDescription());
|
||||
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;
|
||||
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
|
|
@ -11,9 +10,10 @@ import android.view.ViewTreeObserver;
|
|||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
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.views.FocusOverlayView;
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
// Service
|
||||
Intent i = new Intent();
|
||||
final Intent i = new Intent();
|
||||
i.setClass(this, DownloadManagerService.class);
|
||||
startService(i);
|
||||
|
||||
|
|
@ -38,10 +38,10 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_downloader);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFragments() {
|
||||
MissionsFragment fragment = new MissionsFragment();
|
||||
final MissionsFragment fragment = new MissionsFragment();
|
||||
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG)
|
||||
|
|
@ -74,7 +74,7 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
@Override
|
||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
final MenuInflater inflater = getMenuInflater();
|
||||
|
||||
inflater.inflate(R.menu.download_menu, menu);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import android.net.Uri;
|
|||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -124,7 +124,7 @@ public class DownloadDialog extends DialogFragment
|
|||
private SharedPreferences prefs;
|
||||
|
||||
public static DownloadDialog newInstance(final StreamInfo info) {
|
||||
DownloadDialog dialog = new DownloadDialog();
|
||||
final DownloadDialog dialog = new DownloadDialog();
|
||||
dialog.setInfo(info);
|
||||
return dialog;
|
||||
}
|
||||
|
|
@ -208,14 +208,15 @@ public class DownloadDialog extends DialogFragment
|
|||
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
|
||||
Icepick.restoreInstanceState(this, savedInstanceState);
|
||||
|
||||
SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams = new SparseArray<>(4);
|
||||
List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
||||
final SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams
|
||||
= new SparseArray<>(4);
|
||||
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
||||
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
if (!videoStreams.get(i).isVideoOnly()) {
|
||||
continue;
|
||||
}
|
||||
AudioStream audioStream = SecondaryStreamHelper
|
||||
final AudioStream audioStream = SecondaryStreamHelper
|
||||
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
|
||||
|
||||
if (audioStream != null) {
|
||||
|
|
@ -232,13 +233,13 @@ public class DownloadDialog extends DialogFragment
|
|||
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
|
||||
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.bindService(intent, new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(final ComponentName cname, final IBinder service) {
|
||||
DownloadManagerBinder mgr = (DownloadManagerBinder) service;
|
||||
final DownloadManagerBinder mgr = (DownloadManagerBinder) service;
|
||||
|
||||
mainStorageAudio = mgr.getMainStorageAudio();
|
||||
mainStorageVideo = mgr.getMainStorageVideo();
|
||||
|
|
@ -294,9 +295,9 @@ public class DownloadDialog extends DialogFragment
|
|||
initToolbar(view.findViewById(R.id.toolbar));
|
||||
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));
|
||||
threadsSeekBar.setProgress(threads - 1);
|
||||
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
|
|
@ -373,13 +374,13 @@ public class DownloadDialog extends DialogFragment
|
|||
}
|
||||
|
||||
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(),
|
||||
StoredFileHelper.DEFAULT_MIME);
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentFile docFile = DocumentFile.fromSingleUri(context, data.getData());
|
||||
final DocumentFile docFile = DocumentFile.fromSingleUri(context, data.getData());
|
||||
if (docFile == null) {
|
||||
showFailedDialog(R.string.general_error);
|
||||
return;
|
||||
|
|
@ -515,7 +516,23 @@ public class DownloadDialog extends DialogFragment
|
|||
videoButton.setVisibility(isVideoStreamsAvailable ? 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);
|
||||
setupVideoSpinner();
|
||||
} else if (isAudioStreamsAvailable) {
|
||||
|
|
@ -564,7 +581,7 @@ public class DownloadDialog extends DialogFragment
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -591,9 +608,10 @@ public class DownloadDialog extends DialogFragment
|
|||
}
|
||||
|
||||
private void prepareSelectedDownload() {
|
||||
StoredDirectoryHelper mainStorage;
|
||||
MediaFormat format;
|
||||
String mime;
|
||||
final StoredDirectoryHelper mainStorage;
|
||||
final MediaFormat format;
|
||||
final String mime;
|
||||
final String selectedMediaType;
|
||||
|
||||
// first, build the filename and get the output folder (if possible)
|
||||
// later, run a very very very large file checking logic
|
||||
|
|
@ -602,6 +620,7 @@ public class DownloadDialog extends DialogFragment
|
|||
|
||||
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.audio_button:
|
||||
selectedMediaType = getString(R.string.last_download_type_audio_key);
|
||||
mainStorage = mainStorageAudio;
|
||||
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
|
||||
switch (format) {
|
||||
|
|
@ -616,12 +635,14 @@ public class DownloadDialog extends DialogFragment
|
|||
}
|
||||
break;
|
||||
case R.id.video_button:
|
||||
selectedMediaType = getString(R.string.last_download_type_video_key);
|
||||
mainStorage = mainStorageVideo;
|
||||
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
|
||||
mime = format.mimeType;
|
||||
filename += format.suffix;
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
selectedMediaType = getString(R.string.last_download_type_subtitle_key);
|
||||
mainStorage = mainStorageVideo; // subtitle & video files go together
|
||||
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
|
||||
mime = format.mimeType;
|
||||
|
|
@ -663,6 +684,11 @@ public class DownloadDialog extends DialogFragment
|
|||
|
||||
// check for existing file with the same name
|
||||
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,
|
||||
|
|
@ -683,15 +709,17 @@ public class DownloadDialog extends DialogFragment
|
|||
storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile,
|
||||
mainStorage.getTag());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
showErrorActivity(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if is our file
|
||||
MissionState state = downloadManager.checkForExistingMission(storage);
|
||||
@StringRes int msgBtn;
|
||||
@StringRes int msgBody;
|
||||
final MissionState state = downloadManager.checkForExistingMission(storage);
|
||||
@StringRes
|
||||
final int msgBtn;
|
||||
@StringRes
|
||||
final int msgBody;
|
||||
|
||||
switch (state) {
|
||||
case Finished:
|
||||
|
|
@ -744,8 +772,7 @@ public class DownloadDialog extends DialogFragment
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
|
||||
final AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.download_dialog_title)
|
||||
.setMessage(msgBody)
|
||||
.setNegativeButton(android.R.string.cancel, null);
|
||||
|
|
@ -787,7 +814,7 @@ public class DownloadDialog extends DialogFragment
|
|||
// try take (or steal) the file
|
||||
storageNew = new StoredFileHelper(context, mainStorage.getUri(),
|
||||
targetFile, mainStorage.getTag());
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "Failed to take (or steal) the file in "
|
||||
+ targetFile.toString());
|
||||
storageNew = null;
|
||||
|
|
@ -825,18 +852,18 @@ public class DownloadDialog extends DialogFragment
|
|||
if (storage.length() > 0) {
|
||||
storage.truncate();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e);
|
||||
showFailedDialog(R.string.overwrite_failed);
|
||||
return;
|
||||
}
|
||||
|
||||
Stream selectedStream;
|
||||
final Stream selectedStream;
|
||||
Stream secondaryStream = null;
|
||||
char kind;
|
||||
final char kind;
|
||||
int threads = threadsSeekBar.getProgress() + 1;
|
||||
String[] urls;
|
||||
MissionRecoveryInfo[] recoveryInfo;
|
||||
final String[] urls;
|
||||
final MissionRecoveryInfo[] recoveryInfo;
|
||||
String psName = null;
|
||||
String[] psArgs = null;
|
||||
long nearLength = 0;
|
||||
|
|
@ -857,7 +884,7 @@ public class DownloadDialog extends DialogFragment
|
|||
kind = 'v';
|
||||
selectedStream = videoStreamsAdapter.getItem(selectedVideoIndex);
|
||||
|
||||
SecondaryStreamHelper<AudioStream> secondary = videoStreamsAdapter
|
||||
final SecondaryStreamHelper<AudioStream> secondary = videoStreamsAdapter
|
||||
.getAllSecondary()
|
||||
.get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream));
|
||||
|
||||
|
|
@ -871,7 +898,7 @@ public class DownloadDialog extends DialogFragment
|
|||
}
|
||||
|
||||
psArgs = null;
|
||||
long videoSize = wrappedVideoStreams
|
||||
final long videoSize = wrappedVideoStreams
|
||||
.getSizeInBytes((VideoStream) selectedStream);
|
||||
|
||||
// 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();
|
||||
// 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());
|
||||
startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package org.schabi.newpipe.fragments;
|
|||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
|
|
@ -74,7 +74,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
|
||||
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
|
||||
previousYoutubeRestrictedModeEnabled =
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
||||
}
|
||||
|
||||
|
|
@ -104,8 +104,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
boolean youtubeRestrictedModeEnabled =
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||
final boolean youtubeRestrictedModeEnabled =
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
||||
if (previousYoutubeRestrictedModeEnabled != youtubeRestrictedModeEnabled) {
|
||||
previousYoutubeRestrictedModeEnabled = youtubeRestrictedModeEnabled;
|
||||
|
|
@ -137,7 +137,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
}
|
||||
inflater.inflate(R.menu.main_fragment_menu, menu);
|
||||
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
|
|
@ -148,11 +148,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
switch (item.getItemId()) {
|
||||
case R.id.action_search:
|
||||
try {
|
||||
NavigationHelper.openSearchFragment(
|
||||
getFragmentManager(),
|
||||
ServiceHelper.getSelectedServiceId(activity),
|
||||
"");
|
||||
} catch (Exception e) {
|
||||
NavigationHelper.openSearchFragment(getFM(),
|
||||
ServiceHelper.getSelectedServiceId(activity), "");
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
return true;
|
||||
|
|
@ -239,7 +237,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
Fragment fragment = null;
|
||||
try {
|
||||
fragment = tab.getFragment(context);
|
||||
} catch (ExtractionException e) {
|
||||
} catch (final ExtractionException e) {
|
||||
throwable = e;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
|
|||
super.onScrolled(recyclerView, dx, dy);
|
||||
if (dy > 0) {
|
||||
int pastVisibleItems = 0;
|
||||
int visibleItemCount;
|
||||
int totalItemCount;
|
||||
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||
final int visibleItemCount;
|
||||
final int totalItemCount;
|
||||
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||
|
||||
visibleItemCount = layoutManager.getChildCount();
|
||||
totalItemCount = layoutManager.getItemCount();
|
||||
|
|
@ -26,7 +26,7 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
|
|||
pastVisibleItems = ((LinearLayoutManager) layoutManager)
|
||||
.findFirstVisibleItemPosition();
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
int[] positions = ((StaggeredGridLayoutManager) layoutManager)
|
||||
final int[] positions = ((StaggeredGridLayoutManager) layoutManager)
|
||||
.findFirstVisibleItemPositions(null);
|
||||
if (positions != null && positions.length > 0) {
|
||||
pastVisibleItems = positions[0];
|
||||
|
|
|
|||
|
|
@ -1,16 +1,29 @@
|
|||
package org.schabi.newpipe.fragments.detail;
|
||||
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
class StackItem implements Serializable {
|
||||
private final int serviceId;
|
||||
private final String url;
|
||||
private String url;
|
||||
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.url = url;
|
||||
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() {
|
||||
|
|
@ -29,6 +42,10 @@ class StackItem implements Serializable {
|
|||
return url;
|
||||
}
|
||||
|
||||
public PlayQueue getPlayQueue() {
|
||||
return playQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getServiceId() + ":" + getUrl() + " > " + getTitle();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.fragments.detail;
|
|||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
|
@ -10,16 +11,20 @@ import androidx.fragment.app.FragmentPagerAdapter;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TabAdaptor extends FragmentPagerAdapter {
|
||||
public class TabAdapter extends FragmentPagerAdapter {
|
||||
private final List<Fragment> mFragmentList = new ArrayList<>();
|
||||
private final List<String> mFragmentTitleList = new ArrayList<>();
|
||||
private final FragmentManager fragmentManager;
|
||||
|
||||
public TabAdaptor(final FragmentManager fm) {
|
||||
super(fm);
|
||||
public TabAdapter(final FragmentManager 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;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment getItem(final int position) {
|
||||
return mFragmentList.get(position);
|
||||
|
|
@ -50,14 +55,14 @@ public class TabAdaptor extends FragmentPagerAdapter {
|
|||
}
|
||||
|
||||
public void updateItem(final String title, final Fragment fragment) {
|
||||
int index = mFragmentTitleList.indexOf(title);
|
||||
final int index = mFragmentTitleList.indexOf(title);
|
||||
if (index != -1) {
|
||||
updateItem(index, fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemPosition(final Object object) {
|
||||
public int getItemPosition(@NonNull final Object object) {
|
||||
if (mFragmentList.contains(object)) {
|
||||
return mFragmentList.indexOf(object);
|
||||
} else {
|
||||
|
|
@ -82,7 +87,9 @@ public class TabAdaptor extends FragmentPagerAdapter {
|
|||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
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.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
|
@ -136,7 +136,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||
final RecyclerView.ViewHolder itemHolder =
|
||||
itemsList.findContainingViewHolder(focusedItem);
|
||||
return itemHolder.getAdapterPosition();
|
||||
} catch (NullPointerException e) {
|
||||
} catch (final NullPointerException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||
}
|
||||
|
||||
itemsList.post(() -> {
|
||||
RecyclerView.ViewHolder focusedHolder =
|
||||
final RecyclerView.ViewHolder focusedHolder =
|
||||
itemsList.findViewHolderForAdapterPosition(position);
|
||||
|
||||
if (focusedHolder != null) {
|
||||
|
|
@ -279,7 +279,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||
selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
}
|
||||
|
|
@ -294,7 +294,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||
selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
}
|
||||
|
|
@ -367,7 +367,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setDisplayShowTitleEnabled(true);
|
||||
if (useAsFrontPage) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.views.NewPipeRecyclerView;
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||
protected String url;
|
||||
|
||||
protected I currentInfo;
|
||||
protected String currentNextPageUrl;
|
||||
protected Page currentNextPage;
|
||||
protected Disposable currentWorker;
|
||||
|
||||
@Override
|
||||
|
|
@ -78,7 +79,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||
public void writeTo(final Queue<Object> objectsToSave) {
|
||||
super.writeTo(objectsToSave);
|
||||
objectsToSave.add(currentInfo);
|
||||
objectsToSave.add(currentNextPageUrl);
|
||||
objectsToSave.add(currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -86,7 +87,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||
super.readFrom(savedObjects);
|
||||
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) -> {
|
||||
isLoading.set(false);
|
||||
currentInfo = result;
|
||||
currentNextPageUrl = result.getNextPageUrl();
|
||||
currentNextPage = result.getNextPage();
|
||||
handleResult(result);
|
||||
}, (@NonNull Throwable throwable) -> onError(throwable));
|
||||
}, this::onError);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -157,11 +158,10 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doFinally(this::allowDownwardFocusScroll)
|
||||
.subscribe((@io.reactivex.annotations.NonNull
|
||||
ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
||||
.subscribe((@NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
||||
isLoading.set(false);
|
||||
handleNextItems(InfoItemsPage);
|
||||
}, (@io.reactivex.annotations.NonNull Throwable throwable) -> {
|
||||
}, (@NonNull Throwable throwable) -> {
|
||||
isLoading.set(false);
|
||||
onError(throwable);
|
||||
});
|
||||
|
|
@ -182,7 +182,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||
@Override
|
||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||
super.handleNextItems(result);
|
||||
currentNextPageUrl = result.getNextPageUrl();
|
||||
currentNextPage = result.getNextPage();
|
||||
infoListAdapter.addInfoItemList(result.getItems());
|
||||
|
||||
showListFooter(hasMoreItems());
|
||||
|
|
@ -190,7 +190,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||
|
||||
@Override
|
||||
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,
|
||||
final String name) {
|
||||
ChannelFragment instance = new ChannelFragment();
|
||||
final ChannelFragment instance = new ChannelFragment();
|
||||
instance.setInitialData(serviceId, url, name);
|
||||
return instance;
|
||||
}
|
||||
|
|
@ -189,7 +189,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
@Override
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (useAsFrontPage && supportActionBar != null) {
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
||||
} else {
|
||||
|
|
@ -206,7 +206,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
private void openRssFeed() {
|
||||
final ChannelInfo info = currentInfo;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -345,7 +345,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
if (DEBUG) {
|
||||
Log.d(TAG, "No subscription to this channel!");
|
||||
}
|
||||
SubscriptionEntity channel = new SubscriptionEntity();
|
||||
final SubscriptionEntity channel = new SubscriptionEntity();
|
||||
channel.setServiceId(info.getServiceId());
|
||||
channel.setUrl(info.getUrl());
|
||||
channel.setData(info.getName(),
|
||||
|
|
@ -371,16 +371,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
+ "isSubscribed = [" + isSubscribed + "]");
|
||||
}
|
||||
|
||||
boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
|
||||
int backgroundDuration = isButtonVisible ? 300 : 0;
|
||||
int textDuration = isButtonVisible ? 200 : 0;
|
||||
final boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
|
||||
final int backgroundDuration = isButtonVisible ? 300 : 0;
|
||||
final int textDuration = isButtonVisible ? 200 : 0;
|
||||
|
||||
int subscribeBackground = ThemeHelper
|
||||
final int subscribeBackground = ThemeHelper
|
||||
.resolveColorFromAttr(activity, R.attr.colorPrimary);
|
||||
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
|
||||
int subscribedBackground = ContextCompat
|
||||
final int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
|
||||
final int subscribedBackground = ContextCompat
|
||||
.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) {
|
||||
headerSubscribeButton.setText(R.string.subscribe_button_title);
|
||||
|
|
@ -403,7 +403,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPageUrl);
|
||||
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -426,10 +426,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
case R.id.sub_channel_title_view:
|
||||
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
|
||||
try {
|
||||
NavigationHelper.openChannelFragment(getFragmentManager(),
|
||||
currentInfo.getServiceId(), currentInfo.getParentChannelUrl(),
|
||||
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
|
||||
currentInfo.getParentChannelUrl(),
|
||||
currentInfo.getParentChannelName());
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
} else if (DEBUG) {
|
||||
|
|
@ -490,13 +490,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
|
||||
playlistCtrl.setVisibility(View.VISIBLE);
|
||||
|
||||
List<Throwable> errors = new ArrayList<>(result.getErrors());
|
||||
final List<Throwable> errors = new ArrayList<>(result.getErrors());
|
||||
if (!errors.isEmpty()) {
|
||||
|
||||
// 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
|
||||
for (Iterator<Throwable> it = errors.iterator(); it.hasNext();) {
|
||||
Throwable throwable = it.next();
|
||||
final Throwable throwable = it.next();
|
||||
if (throwable instanceof ContentNotSupportedException) {
|
||||
showContentNotSupported();
|
||||
it.remove();
|
||||
|
|
@ -519,7 +519,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
monitorSubscription(result);
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view -> NavigationHelper
|
||||
.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||
headerPopupButton.setOnClickListener(view -> NavigationHelper
|
||||
.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
headerBackgroundButton.setOnClickListener(view -> NavigationHelper
|
||||
|
|
@ -549,13 +549,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
|
||||
private PlayQueue getPlayQueue(final int index) {
|
||||
final List<StreamInfoItem> streamItems = new ArrayList<>();
|
||||
for (InfoItem i : infoListAdapter.getItemsList()) {
|
||||
for (final InfoItem i : infoListAdapter.getItemsList()) {
|
||||
if (i instanceof StreamInfoItem) {
|
||||
streamItems.add((StreamInfoItem) i);
|
||||
}
|
||||
}
|
||||
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
|
||||
currentInfo.getNextPageUrl(), streamItems, index);
|
||||
currentInfo.getNextPage(), streamItems, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -581,7 +581,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
return true;
|
||||
}
|
||||
|
||||
int errorId = exception instanceof ExtractionException
|
||||
final int errorId = exception instanceof ExtractionException
|
||||
? R.string.parsing_error : R.string.general_error;
|
||||
|
||||
onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL,
|
||||
|
|
|
|||
|
|
@ -26,11 +26,9 @@ import io.reactivex.disposables.CompositeDisposable;
|
|||
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private boolean mIsVisibleToUser = false;
|
||||
|
||||
public static CommentsFragment getInstance(final int serviceId, final String url,
|
||||
final String name) {
|
||||
CommentsFragment instance = new CommentsFragment();
|
||||
final CommentsFragment instance = new CommentsFragment();
|
||||
instance.setInitialData(serviceId, url, name);
|
||||
return instance;
|
||||
}
|
||||
|
|
@ -39,12 +37,6 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
|||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(final Context context) {
|
||||
super.onAttach(context);
|
||||
|
|
@ -71,7 +63,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
|||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
|
||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -92,7 +84,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
|||
public void handleResult(@NonNull final CommentsInfo result) {
|
||||
super.handleResult(result);
|
||||
|
||||
AnimationUtils.slideUp(getView(), 120, 150, 0.06f);
|
||||
AnimationUtils.slideUp(requireView(), 120, 150, 0.06f);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ public class DefaultKioskFragment extends KioskFragment {
|
|||
name = kioskTranslatedName;
|
||||
|
||||
currentInfo = null;
|
||||
currentNextPageUrl = null;
|
||||
} catch (ExtractionException e) {
|
||||
currentNextPage = null;
|
||||
} catch (final ExtractionException e) {
|
||||
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
|
||||
"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)
|
||||
throws ExtractionException {
|
||||
KioskFragment instance = new KioskFragment();
|
||||
StreamingService service = NewPipe.getService(serviceId);
|
||||
ListLinkHandlerFactory kioskLinkHandlerFactory = service.getKioskList()
|
||||
final KioskFragment instance = new KioskFragment();
|
||||
final StreamingService service = NewPipe.getService(serviceId);
|
||||
final ListLinkHandlerFactory kioskLinkHandlerFactory = service.getKioskList()
|
||||
.getListLinkHandlerFactoryByType(kioskId);
|
||||
instance.setInitialData(serviceId,
|
||||
kioskLinkHandlerFactory.fromId(kioskId).getUrl(), kioskId);
|
||||
|
|
@ -101,7 +101,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||
if (useAsFrontPage && isVisibleToUser && activity != null) {
|
||||
try {
|
||||
setTitle(kioskTranslatedName);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
onUnrecoverableError(e, UserAction.UI_ERROR,
|
||||
"none",
|
||||
"none", R.string.app_ui_crash);
|
||||
|
|
@ -132,7 +132,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||
@Override
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null && useAsFrontPage) {
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
|
|
@ -150,7 +150,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||
|
||||
@Override
|
||||
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,
|
||||
final String name) {
|
||||
PlaylistFragment instance = new PlaylistFragment();
|
||||
final PlaylistFragment instance = new PlaylistFragment();
|
||||
instance.setInitialData(serviceId, url, name);
|
||||
return instance;
|
||||
}
|
||||
|
|
@ -229,7 +229,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPageUrl);
|
||||
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -286,11 +286,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
|
||||
headerUploaderLayout.setOnClickListener(v -> {
|
||||
try {
|
||||
NavigationHelper.openChannelFragment(getFragmentManager(),
|
||||
result.getServiceId(),
|
||||
result.getUploaderUrl(),
|
||||
result.getUploaderName());
|
||||
} catch (Exception e) {
|
||||
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
|
||||
result.getUploaderUrl(), result.getUploaderName());
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
});
|
||||
|
|
@ -318,7 +316,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
.subscribe(getPlaylistBookmarkSubscriber());
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||
headerPopupButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
headerBackgroundButton.setOnClickListener(view ->
|
||||
|
|
@ -341,7 +339,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
|
||||
private PlayQueue getPlayQueue(final int index) {
|
||||
final List<StreamInfoItem> infoItems = new ArrayList<>();
|
||||
for (InfoItem i : infoListAdapter.getItemsList()) {
|
||||
for (final InfoItem i : infoListAdapter.getItemsList()) {
|
||||
if (i instanceof StreamInfoItem) {
|
||||
infoItems.add((StreamInfoItem) i);
|
||||
}
|
||||
|
|
@ -349,7 +347,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
return new PlaylistPlayQueue(
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUrl(),
|
||||
currentInfo.getNextPageUrl(),
|
||||
currentInfo.getNextPage(),
|
||||
infoItems,
|
||||
index
|
||||
);
|
||||
|
|
@ -375,7 +373,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
return true;
|
||||
}
|
||||
|
||||
int errorId = exception instanceof ExtractionException
|
||||
final int errorId = exception instanceof ExtractionException
|
||||
? R.string.parsing_error : R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST,
|
||||
NewPipe.getNameOfService(serviceId), url, errorId);
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
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.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
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.report.ErrorActivity;
|
||||
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.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
|
@ -118,13 +121,18 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
@State
|
||||
String lastSearchedString;
|
||||
|
||||
@State
|
||||
String searchSuggestion;
|
||||
|
||||
@State
|
||||
boolean isCorrectedSearch;
|
||||
|
||||
@State
|
||||
boolean wasSearchFocused = false;
|
||||
|
||||
private Map<Integer, String> menuItemToFilterName;
|
||||
private StreamingService service;
|
||||
private String currentPageUrl;
|
||||
private String nextPageUrl;
|
||||
private Page nextPage;
|
||||
private String contentCountry;
|
||||
private boolean isSuggestionsEnabled = true;
|
||||
|
||||
|
|
@ -143,6 +151,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
private EditText searchEditText;
|
||||
private View searchClear;
|
||||
|
||||
private TextView correctSuggestion;
|
||||
|
||||
private View suggestionsPanel;
|
||||
private RecyclerView suggestionsRecyclerView;
|
||||
|
||||
|
|
@ -151,7 +161,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
private TextWatcher textWatcher;
|
||||
|
||||
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], "");
|
||||
|
||||
if (!TextUtils.isEmpty(searchString)) {
|
||||
|
|
@ -177,8 +187,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
super.onAttach(context);
|
||||
|
||||
suggestionListAdapter = new SuggestionListAdapter(activity);
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
boolean isSearchHistoryEnabled = preferences
|
||||
final SharedPreferences preferences
|
||||
= PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
final boolean isSearchHistoryEnabled = preferences
|
||||
.getBoolean(getString(R.string.enable_search_history_key), true);
|
||||
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
|
||||
|
||||
|
|
@ -189,7 +200,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
final SharedPreferences preferences
|
||||
= PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
isSuggestionsEnabled = preferences
|
||||
.getBoolean(getString(R.string.show_search_suggestions_key), true);
|
||||
contentCountry = preferences.getString(getString(R.string.content_country_key),
|
||||
|
|
@ -236,7 +248,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
|
||||
try {
|
||||
service = NewPipe.getService(serviceId);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportError(getActivity(), e, getActivity().getClass(),
|
||||
getActivity().findViewById(android.R.id.content),
|
||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
||||
|
|
@ -257,6 +269,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
}
|
||||
}
|
||||
|
||||
handleSearchSuggestion();
|
||||
|
||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||
initSuggestionObserver();
|
||||
}
|
||||
|
|
@ -345,6 +359,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||
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
|
||||
public void writeTo(final Queue<Object> objectsToSave) {
|
||||
super.writeTo(objectsToSave);
|
||||
objectsToSave.add(currentPageUrl);
|
||||
objectsToSave.add(nextPageUrl);
|
||||
objectsToSave.add(nextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||
super.readFrom(savedObjects);
|
||||
currentPageUrl = (String) savedObjects.poll();
|
||||
nextPageUrl = (String) savedObjects.poll();
|
||||
nextPage = (Page) savedObjects.poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -401,7 +415,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setDisplayShowTitleEnabled(false);
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
|
@ -412,16 +426,16 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
int itemId = 0;
|
||||
boolean isFirstItem = true;
|
||||
final Context c = getContext();
|
||||
for (String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
|
||||
for (final String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
|
||||
if (filter.equals("music_songs")) {
|
||||
MenuItem musicItem = menu.add(2,
|
||||
final MenuItem musicItem = menu.add(2,
|
||||
itemId++,
|
||||
0,
|
||||
"YouTube Music");
|
||||
musicItem.setEnabled(false);
|
||||
}
|
||||
menuItemToFilterName.put(itemId, filter);
|
||||
MenuItem item = menu.add(1,
|
||||
final MenuItem item = menu.add(1,
|
||||
itemId++,
|
||||
0,
|
||||
ServiceHelper.getTranslatedFilterString(filter, c));
|
||||
|
|
@ -437,7 +451,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
|
||||
@Override
|
||||
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()));
|
||||
changeContentFilter(item, cf);
|
||||
|
||||
|
|
@ -446,7 +460,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
|
||||
private void restoreFilterChecked(final Menu menu, final int itemId) {
|
||||
if (itemId != -1) {
|
||||
MenuItem item = menu.findItem(itemId);
|
||||
final MenuItem item = menu.findItem(itemId);
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -470,16 +484,16 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
|
||||
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
|
||||
searchToolbarContainer.setTranslationX(100);
|
||||
searchToolbarContainer.setAlpha(0f);
|
||||
searchToolbarContainer.setAlpha(0.0f);
|
||||
searchToolbarContainer.setVisibility(View.VISIBLE);
|
||||
searchToolbarContainer.animate()
|
||||
.translationX(0)
|
||||
.alpha(1f)
|
||||
.alpha(1.0f)
|
||||
.setDuration(200)
|
||||
.setInterpolator(new DecelerateInterpolator()).start();
|
||||
} else {
|
||||
searchToolbarContainer.setTranslationX(0);
|
||||
searchToolbarContainer.setAlpha(1f);
|
||||
searchToolbarContainer.setAlpha(1.0f);
|
||||
searchToolbarContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
|
@ -493,10 +507,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||
}
|
||||
if (TextUtils.isEmpty(searchEditText.getText())) {
|
||||
NavigationHelper.gotoMainFragment(getFragmentManager());
|
||||
NavigationHelper.gotoMainFragment(getFM());
|
||||
return;
|
||||
}
|
||||
|
||||
correctSuggestion.setVisibility(View.GONE);
|
||||
|
||||
searchEditText.setText("");
|
||||
suggestionListAdapter.setItems(new ArrayList<>());
|
||||
showKeyboardSearch();
|
||||
|
|
@ -511,7 +527,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||
showSuggestionsPanel();
|
||||
}
|
||||
if (AndroidTvUtils.isTv(getContext())) {
|
||||
if (DeviceUtils.isTv(getContext())) {
|
||||
showKeyboardSearch();
|
||||
}
|
||||
});
|
||||
|
|
@ -554,15 +570,17 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
textWatcher = new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence s, final int start,
|
||||
final int count, final int after) { }
|
||||
final int count, final int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(final CharSequence s, final int start,
|
||||
final int before, final int count) { }
|
||||
final int before, final int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
String newText = searchEditText.getText().toString();
|
||||
final String newText = searchEditText.getText().toString();
|
||||
suggestionPublisher.onNext(newText);
|
||||
}
|
||||
};
|
||||
|
|
@ -628,7 +646,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
}
|
||||
|
||||
if (searchEditText.requestFocus()) {
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
final InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
|
||||
}
|
||||
|
|
@ -642,7 +660,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
return;
|
||||
}
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) activity
|
||||
final InputMethodManager imm = (InputMethodManager) activity
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
||||
|
|
@ -688,10 +706,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
return false;
|
||||
}
|
||||
|
||||
public void giveSearchEditTextFocus() {
|
||||
showKeyboardSearch();
|
||||
}
|
||||
|
||||
private void initSuggestionObserver() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "initSuggestionObserver() called");
|
||||
|
|
@ -713,8 +727,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
.getRelatedSearches(query, 3, 25);
|
||||
final Observable<List<SuggestionItem>> local = flowable.toObservable()
|
||||
.map(searchHistoryEntries -> {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
for (SearchHistoryEntry entry : searchHistoryEntries) {
|
||||
final List<SuggestionItem> result = new ArrayList<>();
|
||||
for (final SearchHistoryEntry entry : searchHistoryEntries) {
|
||||
result.add(new SuggestionItem(true, entry.getSearch()));
|
||||
}
|
||||
return result;
|
||||
|
|
@ -730,15 +744,15 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
.suggestionsFor(serviceId, query)
|
||||
.toObservable()
|
||||
.map(strings -> {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
for (String entry : strings) {
|
||||
final List<SuggestionItem> result = new ArrayList<>();
|
||||
for (final String entry : strings) {
|
||||
result.add(new SuggestionItem(false, entry));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
return Observable.zip(local, network, (localResult, networkResult) -> {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
final List<SuggestionItem> result = new ArrayList<>();
|
||||
if (localResult.size() > 0) {
|
||||
result.addAll(localResult);
|
||||
}
|
||||
|
|
@ -747,7 +761,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
final Iterator<SuggestionItem> iterator = networkResult.iterator();
|
||||
while (iterator.hasNext() && localResult.size() > 0) {
|
||||
final SuggestionItem next = iterator.next();
|
||||
for (SuggestionItem item : localResult) {
|
||||
for (final SuggestionItem item : localResult) {
|
||||
if (item.query.equals(next.query)) {
|
||||
iterator.remove();
|
||||
break;
|
||||
|
|
@ -795,13 +809,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(intent -> {
|
||||
getFragmentManager().popBackStackImmediate();
|
||||
getFM().popBackStackImmediate();
|
||||
activity.startActivity(intent);
|
||||
}, throwable ->
|
||||
showError(getString(R.string.unsupported_url), false)));
|
||||
return;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
} catch (final Exception ignored) {
|
||||
// Exception occurred, it's not a url
|
||||
}
|
||||
|
||||
|
|
@ -845,7 +859,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
|
||||
@Override
|
||||
protected void loadMoreItems() {
|
||||
if (nextPageUrl == null || nextPageUrl.isEmpty()) {
|
||||
if (!Page.isValid(nextPage)) {
|
||||
return;
|
||||
}
|
||||
isLoading.set(true);
|
||||
|
|
@ -858,7 +872,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
searchString,
|
||||
asList(contentFilter),
|
||||
sortFilter,
|
||||
nextPageUrl)
|
||||
nextPage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
||||
|
|
@ -923,7 +937,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
return;
|
||||
}
|
||||
|
||||
int errorId = exception instanceof ParsingException
|
||||
final int errorId = exception instanceof ParsingException
|
||||
? R.string.parsing_error
|
||||
: R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.GET_SUGGESTIONS,
|
||||
|
|
@ -961,9 +975,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
||||
}
|
||||
|
||||
searchSuggestion = result.getSearchSuggestion();
|
||||
isCorrectedSearch = result.isCorrectedSearch();
|
||||
|
||||
handleSearchSuggestion();
|
||||
|
||||
lastSearchedString = searchString;
|
||||
nextPageUrl = result.getNextPageUrl();
|
||||
currentPageUrl = result.getUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
if (infoListAdapter.getItemsList().size() == 0) {
|
||||
if (!result.getRelatedItems().isEmpty()) {
|
||||
|
|
@ -978,17 +996,48 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
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
|
||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||
showListFooter(false);
|
||||
currentPageUrl = result.getNextPageUrl();
|
||||
infoListAdapter.addInfoItemList(result.getItems());
|
||||
nextPageUrl = result.getNextPageUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
"\"" + searchString + "\" → page: " + nextPageUrl, 0);
|
||||
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
|
||||
+ "pageIds: " + nextPage.getIds() + ", "
|
||||
+ "pageCookies: " + nextPage.getCookies(), 0);
|
||||
}
|
||||
super.handleNextItems(result);
|
||||
}
|
||||
|
|
@ -1003,7 +1052,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
infoListAdapter.clearStreamItemList();
|
||||
showEmptyState();
|
||||
} else {
|
||||
int errorId = exception instanceof ParsingException
|
||||
final int errorId = exception instanceof ParsingException
|
||||
? R.string.parsing_error
|
||||
: R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.SEARCHED,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class SuggestionListAdapter
|
|||
this.items.addAll(items);
|
||||
} else {
|
||||
// remove history items if history is disabled
|
||||
for (SuggestionItem item : items) {
|
||||
for (final SuggestionItem item : items) {
|
||||
if (!item.fromHistory) {
|
||||
this.items.add(item);
|
||||
}
|
||||
|
|
@ -123,8 +123,8 @@ public class SuggestionListAdapter
|
|||
|
||||
private static int resolveResourceIdFromAttr(final Context context,
|
||||
@AttrRes final int attr) {
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
||||
int attributeResourceId = a.getResourceId(0, 0);
|
||||
final TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
||||
final int attributeResourceId = a.getResourceId(0, 0);
|
||||
a.recycle();
|
||||
return attributeResourceId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@ package org.schabi.newpipe.fragments.list.videos;
|
|||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.Switch;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
|
@ -40,22 +39,14 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private View headerRootLayout;
|
||||
private Switch aSwitch;
|
||||
|
||||
private boolean mIsVisibleToUser = false;
|
||||
private Switch autoplaySwitch;
|
||||
|
||||
public static RelatedVideosFragment getInstance(final StreamInfo info) {
|
||||
RelatedVideosFragment instance = new RelatedVideosFragment();
|
||||
final RelatedVideosFragment instance = new RelatedVideosFragment();
|
||||
instance.setInitialData(info);
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
@ -81,22 +72,18 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||
}
|
||||
|
||||
protected View getListHeader() {
|
||||
if (relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null) {
|
||||
if (relatedStreamInfo != null && relatedStreamInfo.getRelatedItems() != null) {
|
||||
headerRootLayout = activity.getLayoutInflater()
|
||||
.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());
|
||||
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
aSwitch.setChecked(autoplay);
|
||||
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton compoundButton,
|
||||
final boolean b) {
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext()).edit()
|
||||
.putBoolean(getString(R.string.auto_queue_key), b).apply();
|
||||
}
|
||||
});
|
||||
final SharedPreferences pref = PreferenceManager
|
||||
.getDefaultSharedPreferences(requireContext());
|
||||
final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
autoplaySwitch.setChecked(autoplay);
|
||||
autoplaySwitch.setOnCheckedChangeListener((compoundButton, b) ->
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit()
|
||||
.putBoolean(getString(R.string.auto_queue_key), b).apply());
|
||||
return headerRootLayout;
|
||||
} else {
|
||||
return null;
|
||||
|
|
@ -105,7 +92,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||
|
||||
@Override
|
||||
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
|
||||
public void setTitle(final String title) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void setInitialData(final StreamInfo info) {
|
||||
|
|
@ -204,7 +189,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
|
||||
super.onRestoreInstanceState(savedState);
|
||||
if (savedState != null) {
|
||||
Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||
final Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||
if (serializable instanceof RelatedStreamInfo) {
|
||||
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
||||
}
|
||||
|
|
@ -214,10 +199,11 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
||||
final String s) {
|
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
if (null != aSwitch) {
|
||||
aSwitch.setChecked(autoplay);
|
||||
final SharedPreferences pref =
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext());
|
||||
final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
if (autoplaySwitch != null) {
|
||||
autoplaySwitch.setChecked(autoplay);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ public class InfoItemBuilder {
|
|||
public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
|
||||
final HistoryRecordManager historyRecordManager,
|
||||
final boolean useMiniVariant) {
|
||||
InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
|
||||
final InfoItemHolder holder
|
||||
= holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
|
||||
holder.updateFromItem(infoItem, historyRecordManager);
|
||||
return holder.itemView;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ public class InfoItemDialog {
|
|||
final View bannerView = View.inflate(activity, R.layout.dialog_title, null);
|
||||
bannerView.setSelected(true);
|
||||
|
||||
TextView titleView = bannerView.findViewById(R.id.itemTitleView);
|
||||
final TextView titleView = bannerView.findViewById(R.id.itemTitleView);
|
||||
titleView.setText(title);
|
||||
|
||||
TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
|
||||
final TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
|
||||
if (additionalDetail != null) {
|
||||
detailsView.setText(additionalDetail);
|
||||
detailsView.setVisibility(View.VISIBLE);
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||
+ infoItemList.size() + ", data.size() = " + data.size());
|
||||
}
|
||||
|
||||
int offsetStart = sizeConsideringHeaderOffset();
|
||||
final int offsetStart = sizeConsideringHeaderOffset();
|
||||
infoItemList.addAll(data);
|
||||
|
||||
if (DEBUG) {
|
||||
|
|
@ -135,7 +135,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||
notifyItemRangeInserted(offsetStart, data.size());
|
||||
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeaderOffset();
|
||||
final int footerNow = sizeConsideringHeaderOffset();
|
||||
notifyItemMoved(offsetStart, footerNow);
|
||||
|
||||
if (DEBUG) {
|
||||
|
|
@ -160,7 +160,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||
+ infoItemList.size() + ", thread = " + Thread.currentThread());
|
||||
}
|
||||
|
||||
int positionInserted = sizeConsideringHeaderOffset();
|
||||
final int positionInserted = sizeConsideringHeaderOffset();
|
||||
infoItemList.add(data);
|
||||
|
||||
if (DEBUG) {
|
||||
|
|
@ -172,7 +172,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||
notifyItemInserted(positionInserted);
|
||||
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeaderOffset();
|
||||
final int footerNow = sizeConsideringHeaderOffset();
|
||||
notifyItemMoved(positionInserted, footerNow);
|
||||
|
||||
if (DEBUG) {
|
||||
|
|
@ -191,7 +191,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||
}
|
||||
|
||||
public void setHeader(final View header) {
|
||||
boolean changed = header != this.header;
|
||||
final boolean changed = header != this.header;
|
||||
this.header = header;
|
||||
if (changed) {
|
||||
notifyDataSetChanged();
|
||||
|
|
@ -219,7 +219,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||
}
|
||||
|
||||
private int sizeConsideringHeaderOffset() {
|
||||
int i = infoItemList.size() + (header != null ? 1 : 0);
|
||||
final int i = infoItemList.size() + (header != null ? 1 : 0);
|
||||
if (DEBUG) {
|
||||
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,
|
||||
@NonNull final List<Object> payloads) {
|
||||
if (!payloads.isEmpty() && holder instanceof InfoItemHolder) {
|
||||
for (Object payload : payloads) {
|
||||
for (final Object payload : payloads) {
|
||||
if (payload instanceof StreamStateEntity) {
|
||||
((InfoItemHolder) holder).updateState(infoItemList
|
||||
.get(header == null ? position : position - 1), recordManager);
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder {
|
|||
String details = super.getDetailLine(item);
|
||||
|
||||
if (item.getStreamCount() >= 0) {
|
||||
String formattedVideoAmount = Localization.localizeStreamCount(itemBuilder.getContext(),
|
||||
item.getStreamCount());
|
||||
final String formattedVideoAmount = Localization.localizeStreamCount(
|
||||
itemBuilder.getContext(), item.getStreamCount());
|
||||
|
||||
if (!details.isEmpty()) {
|
||||
details += " • " + formattedVideoAmount;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
|||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
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.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
|
@ -45,9 +45,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||
@Override
|
||||
public String transformUrl(final Matcher match, final String url) {
|
||||
int timestamp = 0;
|
||||
String hours = match.group(1);
|
||||
String minutes = match.group(2);
|
||||
String seconds = match.group(3);
|
||||
final String hours = match.group(1);
|
||||
final String minutes = match.group(2);
|
||||
final String seconds = match.group(3);
|
||||
if (hours != null) {
|
||||
timestamp += (Integer.parseInt(hours.replace(":", "")) * 3600);
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||
|
||||
|
||||
itemView.setOnLongClickListener(view -> {
|
||||
if (AndroidTvUtils.isTv(itemBuilder.getContext())) {
|
||||
if (DeviceUtils.isTv(itemBuilder.getContext())) {
|
||||
openCommentAuthor(item);
|
||||
} else {
|
||||
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
|
||||
|
|
@ -146,7 +146,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||
item.getServiceId(),
|
||||
item.getUploaderUrl(),
|
||||
item.getUploaderName());
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
||||
}
|
||||
}
|
||||
|
|
@ -164,7 +164,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||
return false;
|
||||
}
|
||||
|
||||
URLSpan[] urls = itemContentView.getUrls();
|
||||
final URLSpan[] urls = itemContentView.getUrls();
|
||||
|
||||
return urls != null && urls.length != 0;
|
||||
}
|
||||
|
|
@ -181,12 +181,13 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||
boolean hasEllipsis = false;
|
||||
|
||||
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);
|
||||
if (end == -1) {
|
||||
end = Math.max(endOfLastLine - 2, 0);
|
||||
}
|
||||
String newVal = itemContentView.getText().subSequence(0, end) + " …";
|
||||
final String newVal = itemContentView.getText().subSequence(0, end) + " …";
|
||||
itemContentView.setText(newVal);
|
||||
hasEllipsis = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
|||
R.color.duration_background_color));
|
||||
itemDurationView.setVisibility(View.VISIBLE);
|
||||
|
||||
StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
|
||||
final StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
|
||||
.blockingGet()[0];
|
||||
if (state2 != null) {
|
||||
itemProgressView.setVisibility(View.VISIBLE);
|
||||
|
|
@ -113,7 +113,8 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
|||
final HistoryRecordManager historyRecordManager) {
|
||||
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
|
||||
&& item.getStreamType() != StreamType.LIVE_STREAM) {
|
||||
itemProgressView.setMax((int) item.getDuration());
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import android.content.SharedPreferences;
|
|||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||
+ localItems.size() + ", data.size() = " + data.size());
|
||||
}
|
||||
|
||||
int offsetStart = sizeConsideringHeader();
|
||||
final int offsetStart = sizeConsideringHeader();
|
||||
localItems.addAll(data);
|
||||
|
||||
if (DEBUG) {
|
||||
|
|
@ -113,7 +113,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||
notifyItemRangeInserted(offsetStart, data.size());
|
||||
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeader();
|
||||
final int footerNow = sizeConsideringHeader();
|
||||
notifyItemMoved(offsetStart, footerNow);
|
||||
|
||||
if (DEBUG) {
|
||||
|
|
@ -158,7 +158,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||
}
|
||||
|
||||
public void setHeader(final View header) {
|
||||
boolean changed = header != this.header;
|
||||
final boolean changed = header != this.header;
|
||||
this.header = header;
|
||||
if (changed) {
|
||||
notifyDataSetChanged();
|
||||
|
|
@ -316,7 +316,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
|
||||
@NonNull final List<Object> payloads) {
|
||||
if (!payloads.isEmpty() && holder instanceof LocalItemHolder) {
|
||||
for (Object payload : payloads) {
|
||||
for (final Object payload : payloads) {
|
||||
if (payload instanceof StreamStateEntity) {
|
||||
((LocalItemHolder) holder).updateState(localItems
|
||||
.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.OnClickGesture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import icepick.State;
|
||||
|
|
@ -54,31 +52,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
// 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
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
|
@ -164,7 +137,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
super.startLoading(forceLoad);
|
||||
|
||||
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
|
||||
remotePlaylistManager.getPlaylists(), BookmarkFragment::merge)
|
||||
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
|
||||
.onBackpressureLatest()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getPlaylistsSubscriber());
|
||||
|
|
@ -292,15 +265,14 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
}
|
||||
|
||||
private void showLocalDialog(final PlaylistMetadataEntry selectedItem) {
|
||||
View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null);
|
||||
EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
|
||||
final View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null);
|
||||
final EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
|
||||
editText.setText(selectedItem.name);
|
||||
|
||||
Builder builder = new AlertDialog.Builder(activity);
|
||||
final Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setView(dialogView)
|
||||
.setPositiveButton(R.string.rename_playlist, (dialog, which) -> {
|
||||
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString());
|
||||
})
|
||||
.setPositiveButton(R.string.rename_playlist, (dialog, which) ->
|
||||
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString()))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setNeutralButton(R.string.delete, (dialog, which) -> {
|
||||
showDeleteDialog(selectedItem.name,
|
||||
|
|
|
|||
|
|
@ -39,14 +39,14 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||
private CompositeDisposable playlistDisposables = new CompositeDisposable();
|
||||
|
||||
public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) {
|
||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
dialog.setInfo(Collections.singletonList(new StreamEntity(info)));
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static PlaylistAppendDialog fromStreamInfoItems(final List<StreamInfoItem> items) {
|
||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
final List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
for (final StreamInfoItem item : items) {
|
||||
entities.add(new StreamEntity(item));
|
||||
}
|
||||
|
|
@ -55,8 +55,8 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||
}
|
||||
|
||||
public static PlaylistAppendDialog fromPlayQueueItems(final List<PlayQueueItem> items) {
|
||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
final List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
for (final PlayQueueItem item : items) {
|
||||
entities.add(new StreamEntity(item));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
|||
|
||||
public final class PlaylistCreationDialog extends PlaylistDialog {
|
||||
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
|
||||
PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
||||
final PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
||||
dialog.setInfo(streams);
|
||||
return dialog;
|
||||
}
|
||||
|
|
@ -37,8 +37,8 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
|
|||
return super.onCreateDialog(savedInstanceState);
|
||||
}
|
||||
|
||||
View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
||||
EditText nameInput = dialogView.findViewById(R.id.playlist_name);
|
||||
final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
||||
final EditText nameInput = dialogView.findViewById(R.id.playlist_name);
|
||||
|
||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.create_playlist)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
import icepick.State
|
||||
import java.util.Calendar
|
||||
|
|
@ -82,7 +82,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
|||
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
|
||||
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) })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@ import android.content.Intent
|
|||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Notification
|
||||
import io.reactivex.Single
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ package org.schabi.newpipe.local.history;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ public class HistoryRecordManager {
|
|||
final Date currentTime = new Date();
|
||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||
final long streamId = streamTable.upsert(new StreamEntity(info));
|
||||
StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
||||
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
||||
|
||||
if (latestEntry != null) {
|
||||
streamHistoryTable.delete(latestEntry);
|
||||
|
|
@ -129,7 +129,7 @@ public class HistoryRecordManager {
|
|||
}
|
||||
|
||||
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) {
|
||||
entities.add(entry.toStreamHistoryEntity());
|
||||
}
|
||||
|
|
@ -138,7 +138,7 @@ public class HistoryRecordManager {
|
|||
}
|
||||
|
||||
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) {
|
||||
entities.add(entry.toStreamHistoryEntity());
|
||||
}
|
||||
|
|
@ -163,7 +163,7 @@ public class HistoryRecordManager {
|
|||
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
|
||||
|
||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||
SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry();
|
||||
final SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry();
|
||||
if (latestEntry != null && latestEntry.hasEqualValues(newEntry)) {
|
||||
latestEntry.setCreationDate(currentTime);
|
||||
return (long) searchHistoryTable.update(latestEntry);
|
||||
|
|
@ -256,7 +256,7 @@ public class HistoryRecordManager {
|
|||
public Single<List<StreamStateEntity>> loadStreamStateBatch(final List<InfoItem> infos) {
|
||||
return Single.fromCallable(() -> {
|
||||
final List<StreamStateEntity> result = new ArrayList<>(infos.size());
|
||||
for (InfoItem info : infos) {
|
||||
for (final InfoItem info : infos) {
|
||||
final List<StreamEntity> entities = streamTable
|
||||
.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
|
||||
if (entities.isEmpty()) {
|
||||
|
|
@ -279,8 +279,8 @@ public class HistoryRecordManager {
|
|||
final List<? extends LocalItem> items) {
|
||||
return Single.fromCallable(() -> {
|
||||
final List<StreamStateEntity> result = new ArrayList<>(items.size());
|
||||
for (LocalItem item : items) {
|
||||
long streamId;
|
||||
for (final LocalItem item : items) {
|
||||
final long streamId;
|
||||
if (item instanceof StreamStatisticsEntry) {
|
||||
streamId = ((StreamStatisticsEntry) item).getStreamId();
|
||||
} else if (item instanceof PlaylistStreamEntity) {
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ public class StatisticsPlaylistFragment
|
|||
}
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||
headerPopupButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
headerBackgroundButton.setOnClickListener(view ->
|
||||
|
|
@ -455,7 +455,7 @@ public class StatisticsPlaylistFragment
|
|||
}
|
||||
|
||||
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) {
|
||||
if (item instanceof StreamStatisticsEntry) {
|
||||
streamInfoItems.add(((StreamStatisticsEntry) item).toStreamInfoItem());
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
|||
R.color.duration_background_color));
|
||||
itemDurationView.setVisibility(View.VISIBLE);
|
||||
|
||||
StreamStateEntity state = historyRecordManager
|
||||
final StreamStateEntity state = historyRecordManager
|
||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||
add(localItem);
|
||||
}}).blockingGet().get(0);
|
||||
|
|
@ -116,7 +116,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
|||
}
|
||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
||||
|
||||
StreamStateEntity state = historyRecordManager
|
||||
final StreamStateEntity state = historyRecordManager
|
||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||
add(localItem);
|
||||
}}).blockingGet().get(0);
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
|||
R.color.duration_background_color));
|
||||
itemDurationView.setVisibility(View.VISIBLE);
|
||||
|
||||
StreamStateEntity state = historyRecordManager
|
||||
final StreamStateEntity state = historyRecordManager
|
||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||
add(localItem);
|
||||
}}).blockingGet().get(0);
|
||||
|
|
@ -146,7 +146,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
|||
}
|
||||
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
|
||||
|
||||
StreamStateEntity state = historyRecordManager
|
||||
final StreamStateEntity state = historyRecordManager
|
||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||
add(localItem);
|
||||
}}).blockingGet().get(0);
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
private boolean isRemovingWatched = false;
|
||||
|
||||
public static LocalPlaylistFragment getInstance(final long playlistId, final String name) {
|
||||
LocalPlaylistFragment instance = new LocalPlaylistFragment();
|
||||
final LocalPlaylistFragment instance = new LocalPlaylistFragment();
|
||||
instance.setInitialData(playlistId, name);
|
||||
return instance;
|
||||
}
|
||||
|
|
@ -177,7 +177,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
public void selected(final LocalItem selectedItem) {
|
||||
if (selectedItem instanceof PlaylistStreamEntry) {
|
||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) selectedItem;
|
||||
NavigationHelper.openVideoDetailFragment(getFragmentManager(),
|
||||
NavigationHelper.openVideoDetailFragment(getFM(),
|
||||
item.getStreamEntity().getServiceId(), item.getStreamEntity().getUrl(),
|
||||
item.getStreamEntity().getTitle());
|
||||
}
|
||||
|
|
@ -411,7 +411,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
if (removePartiallyWatched) {
|
||||
while (playlistIter.hasNext()) {
|
||||
final PlaylistStreamEntry playlistItem = playlistIter.next();
|
||||
int indexInHistory = Collections.binarySearch(historyStreamIds,
|
||||
final int indexInHistory = Collections.binarySearch(historyStreamIds,
|
||||
playlistItem.getStreamId());
|
||||
|
||||
if (indexInHistory < 0) {
|
||||
|
|
@ -427,7 +427,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
.loadLocalStreamStateBatch(playlist).blockingGet().iterator();
|
||||
|
||||
while (playlistIter.hasNext()) {
|
||||
PlaylistStreamEntry playlistItem = playlistIter.next();
|
||||
final PlaylistStreamEntry playlistItem = playlistIter.next();
|
||||
final int indexInHistory = Collections.binarySearch(historyStreamIds,
|
||||
playlistItem.getStreamId());
|
||||
|
||||
|
|
@ -492,7 +492,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
setVideoCount(itemListAdapter.getItemsList().size());
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||
headerPopupButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
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);
|
||||
EditText nameEdit = dialogView.findViewById(R.id.playlist_name);
|
||||
final EditText nameEdit = dialogView.findViewById(R.id.playlist_name);
|
||||
nameEdit.setText(name);
|
||||
nameEdit.setSelection(nameEdit.getText().length());
|
||||
|
||||
|
|
@ -553,9 +553,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
.setView(dialogView)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.rename, (dialogInterface, i) -> {
|
||||
changePlaylistName(nameEdit.getText().toString());
|
||||
});
|
||||
.setPositiveButton(R.string.rename, (dialogInterface, i) ->
|
||||
changePlaylistName(nameEdit.getText().toString()));
|
||||
|
||||
dialogBuilder.show();
|
||||
}
|
||||
|
|
@ -601,7 +600,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
}
|
||||
|
||||
private void updateThumbnailUrl() {
|
||||
String newThumbnailUrl;
|
||||
final String newThumbnailUrl;
|
||||
|
||||
if (!itemListAdapter.getItemsList().isEmpty()) {
|
||||
newThumbnailUrl = ((PlaylistStreamEntry) itemListAdapter.getItemsList().get(0))
|
||||
|
|
@ -662,7 +661,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
}
|
||||
|
||||
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) {
|
||||
if (item instanceof PlaylistStreamEntry) {
|
||||
streamIds.add(((PlaylistStreamEntry) item).getStreamId());
|
||||
|
|
@ -815,7 +814,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
}
|
||||
|
||||
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) {
|
||||
if (item instanceof PlaylistStreamEntry) {
|
||||
streamInfoItems.add(((PlaylistStreamEntry) item).toStreamInfoItem());
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public class LocalPlaylistManager {
|
|||
final List<StreamEntity> streams,
|
||||
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);
|
||||
for (int index = 0; index < streamIds.size(); 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) {
|
||||
List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streamIds.size());
|
||||
final List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streamIds.size());
|
||||
for (int i = 0; i < streamIds.size(); i++) {
|
||||
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(i), i));
|
||||
}
|
||||
|
|
@ -115,7 +115,7 @@ public class LocalPlaylistManager {
|
|||
.firstElement()
|
||||
.filter(playlistEntities -> !playlistEntities.isEmpty())
|
||||
.map(playlistEntities -> {
|
||||
PlaylistEntity playlist = playlistEntities.get(0);
|
||||
final PlaylistEntity playlist = playlistEntities.get(0);
|
||||
if (name != null) {
|
||||
playlist.setName(name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public class RemotePlaylistManager {
|
|||
|
||||
public Single<Integer> onUpdate(final long playlistId, final PlaylistInfo playlistInfo) {
|
||||
return Single.fromCallable(() -> {
|
||||
PlaylistRemoteEntity playlist = new PlaylistRemoteEntity(playlistInfo);
|
||||
final PlaylistRemoteEntity playlist = new PlaylistRemoteEntity(playlistInfo);
|
||||
playlist.setUid(playlistId);
|
||||
return playlistRemoteTable.update(playlist);
|
||||
}).subscribeOn(Schedulers.io());
|
||||
|
|
|
|||
|
|
@ -11,15 +11,15 @@ import android.content.res.Configuration
|
|||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Parcelable
|
||||
import android.preference.PreferenceManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.nononsenseapps.filepicker.Utils
|
||||
import com.xwray.groupie.Group
|
||||
|
|
@ -277,7 +277,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||
}
|
||||
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.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 io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
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.SubscriptionEntity
|
||||
import org.schabi.newpipe.extractor.ListInfo
|
||||
|
|
@ -21,9 +23,28 @@ class SubscriptionManager(context: Context) {
|
|||
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
|
||||
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> {
|
||||
val listEntities = subscriptionTable.upsertAll(
|
||||
infoList.map { SubscriptionEntity.from(it) })
|
||||
infoList.map { SubscriptionEntity.from(it) })
|
||||
|
||||
database.runInTransaction {
|
||||
infoList.forEachIndexed { index, info ->
|
||||
|
|
@ -35,13 +56,13 @@ class SubscriptionManager(context: Context) {
|
|||
}
|
||||
|
||||
fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url)
|
||||
.flatMapCompletable {
|
||||
Completable.fromRunnable {
|
||||
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
|
||||
subscriptionTable.update(it)
|
||||
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
|
||||
}
|
||||
.flatMapCompletable {
|
||||
Completable.fromRunnable {
|
||||
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
|
||||
subscriptionTable.update(it)
|
||||
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateFromInfo(subscriptionId: Long, info: ListInfo<StreamInfoItem>) {
|
||||
val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId)
|
||||
|
|
@ -57,8 +78,8 @@ class SubscriptionManager(context: Context) {
|
|||
|
||||
fun deleteSubscription(serviceId: Int, url: String): Completable {
|
||||
return Completable.fromCallable { subscriptionTable.deleteSubscription(serviceId, url) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
fun insertSubscription(subscriptionEntity: SubscriptionEntity, info: ChannelInfo) {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
|||
private Button inputButton;
|
||||
|
||||
public static SubscriptionsImportFragment getInstance(final int serviceId) {
|
||||
SubscriptionsImportFragment instance = new SubscriptionsImportFragment();
|
||||
final SubscriptionsImportFragment instance = new SubscriptionsImportFragment();
|
||||
instance.setInitialData(serviceId);
|
||||
return instance;
|
||||
}
|
||||
|
|
@ -140,7 +140,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
|||
setInfoText("");
|
||||
}
|
||||
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setDisplayShowTitleEnabled(true);
|
||||
setTitle(getString(R.string.import_title));
|
||||
|
|
@ -206,7 +206,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
|||
relatedUrl = extractor.getRelatedUrl();
|
||||
instructionsString = ServiceHelper.getImportInstructions(currentServiceId);
|
||||
return;
|
||||
} catch (ExtractionException ignored) {
|
||||
} catch (final ExtractionException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.content.Context
|
|||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.text.Editable
|
||||
import android.text.TextUtils
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
|
@ -13,34 +14,22 @@ import android.view.inputmethod.InputMethodManager
|
|||
import android.widget.Toast
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.xwray.groupie.GroupAdapter
|
||||
import com.xwray.groupie.OnItemClickListener
|
||||
import com.xwray.groupie.Section
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import icepick.Icepick
|
||||
import icepick.State
|
||||
import java.io.Serializable
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.cancel_button
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.confirm_button
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.delete_button
|
||||
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 kotlin.collections.contains
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.*
|
||||
import kotlinx.android.synthetic.main.toolbar_search_layout.*
|
||||
import org.schabi.newpipe.R
|
||||
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.dialog.FeedGroupDialog.ScreenState.DeleteScreen
|
||||
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.PickerIconItem
|
||||
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||
import org.schabi.newpipe.util.DeviceUtils
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
|
||||
class FeedGroupDialog : DialogFragment() {
|
||||
class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||
private lateinit var viewModel: FeedGroupDialogViewModel
|
||||
private var groupId: Long = NO_GROUP_SELECTED
|
||||
private var groupIcon: FeedGroupIcon? = null
|
||||
|
|
@ -66,22 +56,20 @@ class FeedGroupDialog : DialogFragment() {
|
|||
object DeleteScreen : ScreenState()
|
||||
}
|
||||
|
||||
@State
|
||||
@JvmField
|
||||
var selectedIcon: FeedGroupIcon? = null
|
||||
@State
|
||||
@JvmField
|
||||
var selectedSubscriptions: HashSet<Long> = HashSet()
|
||||
@State
|
||||
@JvmField
|
||||
var currentScreen: ScreenState = InitialScreen
|
||||
@State @JvmField var selectedIcon: FeedGroupIcon? = null
|
||||
@State @JvmField var selectedSubscriptions: HashSet<Long> = HashSet()
|
||||
@State @JvmField var wasSubscriptionSelectionChanged: Boolean = false
|
||||
@State @JvmField var currentScreen: ScreenState = InitialScreen
|
||||
|
||||
@State
|
||||
@JvmField
|
||||
var subscriptionsListState: Parcelable? = null
|
||||
@State
|
||||
@JvmField
|
||||
var iconsListState: Parcelable? = null
|
||||
@State @JvmField var subscriptionsListState: Parcelable? = null
|
||||
@State @JvmField var iconsListState: Parcelable? = null
|
||||
@State @JvmField var wasSearchSubscriptionsVisible = false
|
||||
@State @JvmField var subscriptionsCurrentSearchQuery = ""
|
||||
@State @JvmField var subscriptionsShowOnlyUngrouped = false
|
||||
|
||||
private val subscriptionMainSection = Section()
|
||||
private val subscriptionEmptyFooter = Section()
|
||||
private lateinit var subscriptionGroupAdapter: GroupAdapter<GroupieViewHolder>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -91,22 +79,30 @@ class FeedGroupDialog : DialogFragment() {
|
|||
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)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return object : Dialog(requireActivity(), theme) {
|
||||
override fun onBackPressed() {
|
||||
if (currentScreen !is InitialScreen) {
|
||||
showScreen(InitialScreen)
|
||||
} else {
|
||||
if (!this@FeedGroupDialog.onBackPressed()) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
wasSearchSubscriptionsVisible = isSearchVisible()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
|
|
@ -119,11 +115,15 @@ class FeedGroupDialog : DialogFragment() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, FeedGroupDialogViewModel.Factory(requireContext(), groupId))
|
||||
.get(FeedGroupDialogViewModel::class.java)
|
||||
viewModel = ViewModelProvider(this,
|
||||
FeedGroupDialogViewModel.Factory(requireContext(),
|
||||
groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped)
|
||||
).get(FeedGroupDialogViewModel::class.java)
|
||||
|
||||
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 {
|
||||
when (it) {
|
||||
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()
|
||||
setupListeners()
|
||||
|
||||
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
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////// */
|
||||
|
||||
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() {
|
||||
delete_button.setOnClickListener { showScreen(DeleteScreen) }
|
||||
|
|
@ -163,13 +202,64 @@ class FeedGroupDialog : DialogFragment() {
|
|||
}
|
||||
})
|
||||
|
||||
confirm_button.setOnClickListener {
|
||||
when (currentScreen) {
|
||||
InitialScreen -> handlePositiveButtonInitialScreen()
|
||||
DeleteScreen -> viewModel.deleteGroup()
|
||||
else -> showScreen(InitialScreen)
|
||||
confirm_button.setOnClickListener { handlePositiveButton() }
|
||||
|
||||
select_channel_button.setOnClickListener {
|
||||
subscriptions_selector_list.scrollToPosition(0)
|
||||
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() {
|
||||
|
|
@ -202,80 +292,73 @@ class FeedGroupDialog : DialogFragment() {
|
|||
groupIcon = feedGroupEntity?.icon
|
||||
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()) {
|
||||
group_name_input.setText(name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSubscriptionPicker(subscriptions: List<SubscriptionEntity>, selectedSubscriptions: Set<Long>) {
|
||||
this.selectedSubscriptions.addAll(selectedSubscriptions)
|
||||
val useGridLayout = subscriptions.isNotEmpty()
|
||||
private val subscriptionPickerItemListener = OnItemClickListener { item, view ->
|
||||
if (item is PickerSubscriptionItem) {
|
||||
val subscriptionId = item.subscriptionEntity.uid
|
||||
wasSubscriptionSelectionChanged = true
|
||||
|
||||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||
groupAdapter.spanCount = if (useGridLayout) 4 else 1
|
||||
|
||||
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)
|
||||
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
|
||||
this.selectedSubscriptions.remove(subscriptionId)
|
||||
false
|
||||
} else {
|
||||
LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
|
||||
this.selectedSubscriptions.add(subscriptionId)
|
||||
true
|
||||
}
|
||||
|
||||
adapter = groupAdapter
|
||||
item.updateSelected(view, isSelected)
|
||||
updateSubscriptionSelectedCount()
|
||||
}
|
||||
}
|
||||
|
||||
if (subscriptionsListState != null) {
|
||||
layoutManager?.onRestoreInstanceState(subscriptionsListState)
|
||||
subscriptionsListState = null
|
||||
}
|
||||
private fun setupSubscriptionPicker(
|
||||
subscriptions: List<PickerSubscriptionItem>,
|
||||
selectedSubscriptions: Set<Long>
|
||||
) {
|
||||
if (!wasSubscriptionSelectionChanged) {
|
||||
this.selectedSubscriptions.addAll(selectedSubscriptions)
|
||||
}
|
||||
|
||||
groupAdapter.setOnItemClickListener { item, _ ->
|
||||
when (item) {
|
||||
is PickerSubscriptionItem -> {
|
||||
val subscriptionId = item.subscriptionEntity.uid
|
||||
updateSubscriptionSelectedCount()
|
||||
|
||||
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
|
||||
this.selectedSubscriptions.remove(subscriptionId)
|
||||
false
|
||||
} else {
|
||||
this.selectedSubscriptions.add(subscriptionId)
|
||||
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
|
||||
}
|
||||
}
|
||||
if (subscriptions.isEmpty()) {
|
||||
subscriptionEmptyFooter.clear()
|
||||
subscriptionEmptyFooter.add(EmptyPlaceholderItem())
|
||||
} else {
|
||||
subscriptionEmptyFooter.clear()
|
||||
}
|
||||
|
||||
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)
|
||||
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() {
|
||||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(requireContext(), it) })
|
||||
|
|
@ -311,9 +394,9 @@ class FeedGroupDialog : DialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
/*///////////////////////////////////////////////////////////////////////////
|
||||
// Screen Selector
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////// */
|
||||
|
||||
private fun showScreen(screen: ScreenState) {
|
||||
currentScreen = screen
|
||||
|
|
@ -337,7 +420,8 @@ class FeedGroupDialog : DialogFragment() {
|
|||
else -> View.VISIBLE
|
||||
}
|
||||
|
||||
if (currentScreen != InitialScreen) hideKeyboard()
|
||||
hideKeyboard()
|
||||
hideSearch()
|
||||
}
|
||||
|
||||
private fun View.onlyVisibleIn(vararg screens: ScreenState) {
|
||||
|
|
@ -347,13 +431,58 @@ class FeedGroupDialog : DialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
/*///////////////////////////////////////////////////////////////////////////
|
||||
// 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() {
|
||||
val inputMethodManager = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken,
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||
group_name_input.clearFocus()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,42 +9,56 @@ import io.reactivex.Completable
|
|||
import io.reactivex.Flowable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.processors.BehaviorProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
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.subscription.FeedGroupIcon
|
||||
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 Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return FeedGroupDialogViewModel(context.applicationContext, groupId) as T
|
||||
}
|
||||
}
|
||||
class FeedGroupDialogViewModel(
|
||||
applicationContext: Context,
|
||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
initialQuery: String = "",
|
||||
initialShowOnlyUngrouped: Boolean = false
|
||||
) : ViewModel() {
|
||||
|
||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(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 mutableSubscriptionsLiveData = MutableLiveData<Pair<List<SubscriptionEntity>, Set<Long>>>()
|
||||
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>>()
|
||||
private val mutableDialogEventLiveData = MutableLiveData<DialogEvent>()
|
||||
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
|
||||
|
||||
private var actionProcessingDisposable: Disposable? = null
|
||||
|
||||
private var feedGroupDisposable = feedDatabaseManager.getGroup(groupId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableGroupLiveData::postValue)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableGroupLiveData::postValue)
|
||||
|
||||
private var subscriptionsDisposable = Flowable
|
||||
.combineLatest(subscriptionManager.subscriptions(), feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
||||
BiFunction { t1: List<SubscriptionEntity>, t2: List<Long> -> t1 to t2.toSet() })
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableSubscriptionsLiveData::postValue)
|
||||
.combineLatest(subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
||||
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() })
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableSubscriptionsLiveData::postValue)
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
|
|
@ -55,14 +69,14 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
|||
|
||||
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
|
||||
doAction(feedDatabaseManager.createGroup(name, selectedIcon)
|
||||
.flatMapCompletable {
|
||||
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
||||
})
|
||||
.flatMapCompletable {
|
||||
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
||||
})
|
||||
}
|
||||
|
||||
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
|
||||
doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
||||
}
|
||||
|
||||
fun deleteGroup() {
|
||||
|
|
@ -74,13 +88,40 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
|||
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
|
||||
|
||||
actionProcessingDisposable = completable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.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 {
|
||||
object ProcessingEvent : 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 androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
|
@ -49,7 +49,7 @@ class FeedGroupReorderDialog : DialogFragment() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
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.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
|
||||
when (it) {
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@ import org.schabi.newpipe.R
|
|||
class EmptyPlaceholderItem : Item() {
|
||||
override fun getLayout(): Int = R.layout.list_empty_view
|
||||
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
|
||||
|
||||
import android.view.View
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions
|
||||
import com.nostra13.universalimageloader.core.ImageLoader
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.selected_highlight
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.thumbnail_view
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.title_view
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.*
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.view.*
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.util.AnimationUtils
|
||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants
|
||||
|
||||
data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, var isSelected: Boolean = false) : Item() {
|
||||
companion object {
|
||||
const val UPDATE_SELECTED = 123
|
||||
|
||||
val IMAGE_LOADING_OPTIONS: DisplayImageOptions = ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
|
||||
}
|
||||
|
||||
data class PickerSubscriptionItem(
|
||||
val subscriptionEntity: SubscriptionEntity,
|
||||
var isSelected: Boolean = false
|
||||
) : Item() {
|
||||
override fun getId(): Long = subscriptionEntity.uid
|
||||
override fun getLayout(): Int = R.layout.picker_subscription_item
|
||||
|
||||
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 getSpanSize(spanCount: Int, position: Int): Int = 1
|
||||
|
||||
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.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
|
||||
}
|
||||
|
||||
override fun getId(): Long {
|
||||
return subscriptionEntity.uid
|
||||
fun updateSelected(containerView: View, isSelected: Boolean) {
|
||||
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());
|
||||
}
|
||||
|
||||
for (Object o : channelsArray) {
|
||||
for (final Object o : channelsArray) {
|
||||
if (o instanceof JsonObject) {
|
||||
JsonObject itemObject = (JsonObject) o;
|
||||
int serviceId = itemObject.getInt(JSON_SERVICE_ID_KEY, 0);
|
||||
String url = itemObject.getString(JSON_URL_KEY);
|
||||
String name = itemObject.getString(JSON_NAME_KEY);
|
||||
final JsonObject itemObject = (JsonObject) o;
|
||||
final int serviceId = itemObject.getInt(JSON_SERVICE_ID_KEY, 0);
|
||||
final String url = itemObject.getString(JSON_URL_KEY);
|
||||
final String name = itemObject.getString(JSON_NAME_KEY);
|
||||
|
||||
if (url != null && name != null && !url.isEmpty() && !name.isEmpty()) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ public final class ImportExportJsonHelper {
|
|||
*/
|
||||
public static void writeTo(final List<SubscriptionItem> items, final OutputStream out,
|
||||
@Nullable final ImportExportEventListener eventListener) {
|
||||
JsonAppendableWriter writer = JsonWriter.on(out);
|
||||
final JsonAppendableWriter writer = JsonWriter.on(out);
|
||||
writeTo(items, writer, eventListener);
|
||||
writer.done();
|
||||
}
|
||||
|
|
@ -140,7 +140,7 @@ public final class ImportExportJsonHelper {
|
|||
writer.value(JSON_APP_VERSION_INT_KEY, BuildConfig.VERSION_CODE);
|
||||
|
||||
writer.array(JSON_SUBSCRIPTIONS_ARRAY_KEY);
|
||||
for (SubscriptionItem item : items) {
|
||||
for (final SubscriptionItem item : items) {
|
||||
writer.object();
|
||||
writer.value(JSON_SERVICE_ID_KEY, item.getServiceId());
|
||||
writer.value(JSON_URL_KEY, item.getUrl());
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ public class SubscriptionsExportService extends BaseImportExportService {
|
|||
try {
|
||||
outFile = new File(path);
|
||||
outputStream = new FileOutputStream(outFile);
|
||||
} catch (FileNotFoundException e) {
|
||||
} catch (final FileNotFoundException e) {
|
||||
handleError(e);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@ public class SubscriptionsExportService extends BaseImportExportService {
|
|||
.map(subscriptionEntities -> {
|
||||
final List<SubscriptionItem> result
|
||||
= new ArrayList<>(subscriptionEntities.size());
|
||||
for (SubscriptionEntity entity : subscriptionEntities) {
|
||||
for (final SubscriptionEntity entity : subscriptionEntities) {
|
||||
result.add(new SubscriptionItem(entity.getServiceId(), entity.getUrl(),
|
||||
entity.getName()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
|
|||
|
||||
try {
|
||||
inputStream = new FileInputStream(new File(filePath));
|
||||
} catch (FileNotFoundException e) {
|
||||
} catch (final FileNotFoundException e) {
|
||||
handleError(e);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
|
@ -187,7 +187,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
|
|||
.getChannelInfo(subscriptionItem.getServiceId(),
|
||||
subscriptionItem.getUrl(), true)
|
||||
.blockingGet());
|
||||
} catch (Throwable e) {
|
||||
} catch (final Throwable e) {
|
||||
return Notification.createOnError(e);
|
||||
}
|
||||
})
|
||||
|
|
@ -239,7 +239,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
|
|||
private Consumer<Notification<ChannelInfo>> getNotificationsConsumer() {
|
||||
return notification -> {
|
||||
if (notification.isOnNext()) {
|
||||
String name = notification.getValue().getName();
|
||||
final String name = notification.getValue().getName();
|
||||
eventListener.onItemCompleted(!TextUtils.isEmpty(name) ? name : "");
|
||||
} else if (notification.isOnError()) {
|
||||
final Throwable error = notification.getError();
|
||||
|
|
@ -260,7 +260,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
|
|||
private Function<List<Notification<ChannelInfo>>, List<SubscriptionEntity>> upsertBatch() {
|
||||
return notificationList -> {
|
||||
final List<ChannelInfo> infoList = new ArrayList<>(notificationList.size());
|
||||
for (Notification<ChannelInfo> n : notificationList) {
|
||||
for (final Notification<ChannelInfo> n : notificationList) {
|
||||
if (n.isOnNext()) {
|
||||
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;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
|
||||
import static org.schabi.newpipe.player.BackgroundPlayer.ACTION_CLOSE;
|
||||
|
||||
public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
||||
|
||||
private static final String TAG = "BackgroundPlayerActivity";
|
||||
|
|
@ -19,25 +19,25 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
|||
|
||||
@Override
|
||||
public String getSupportActionTitle() {
|
||||
return getResources().getString(R.string.title_activity_background_player);
|
||||
return getResources().getString(R.string.title_activity_play_queue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getBindIntent() {
|
||||
return new Intent(this, BackgroundPlayer.class);
|
||||
return new Intent(this, MainPlayer.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPlayerListener() {
|
||||
if (player != null && player instanceof BackgroundPlayer.BasePlayerImpl) {
|
||||
((BackgroundPlayer.BasePlayerImpl) player).setActivityListener(this);
|
||||
if (player instanceof VideoPlayerImpl) {
|
||||
((VideoPlayerImpl) player).setActivityListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopPlayerListener() {
|
||||
if (player != null && player instanceof BackgroundPlayer.BasePlayerImpl) {
|
||||
((BackgroundPlayer.BasePlayerImpl) player).removeActivityListener(this);
|
||||
if (player instanceof VideoPlayerImpl) {
|
||||
((VideoPlayerImpl) player).removeActivityListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,18 +56,30 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
|||
}
|
||||
|
||||
this.player.setRecovery();
|
||||
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
||||
getApplicationContext().startService(
|
||||
getSwitchIntent(PopupVideoPlayer.class)
|
||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
|
||||
);
|
||||
NavigationHelper.playOnPopupPlayer(
|
||||
getApplicationContext(), player.playQueue, this.player.isPlaying());
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getPlayerShutdownIntent() {
|
||||
return new Intent(ACTION_CLOSE);
|
||||
public void setupMenu(final Menu menu) {
|
||||
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.BitmapFactory;
|
||||
import android.media.AudioManager;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
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.listener.ImageLoadingListener;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.DownloaderImpl;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
|
|
@ -78,6 +78,7 @@ import org.schabi.newpipe.util.SerializedCache;
|
|||
import java.io.IOException;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.disposables.SerialDisposable;
|
||||
|
|
@ -97,7 +98,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|||
@SuppressWarnings({"WeakerAccess"})
|
||||
public abstract class BasePlayer implements
|
||||
Player.EventListener, PlaybackListener, ImageLoadingListener {
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
public static final boolean DEBUG = MainActivity.DEBUG;
|
||||
@NonNull
|
||||
public static final String TAG = "BasePlayer";
|
||||
|
||||
|
|
@ -128,13 +129,15 @@ public abstract class BasePlayer implements
|
|||
@NonNull
|
||||
public static final String SELECT_ON_APPEND = "select_on_append";
|
||||
@NonNull
|
||||
public static final String PLAYER_TYPE = "player_type";
|
||||
@NonNull
|
||||
public static final String IS_MUTED = "is_muted";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// 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 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 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 AudioReactor audioReactor;
|
||||
protected MediaSessionManager mediaSessionManager;
|
||||
|
|
@ -223,7 +230,7 @@ public abstract class BasePlayer implements
|
|||
|
||||
public void setup() {
|
||||
if (simpleExoPlayer == null) {
|
||||
initPlayer(/*playOnInit=*/true);
|
||||
initPlayer(true);
|
||||
}
|
||||
initListeners();
|
||||
}
|
||||
|
|
@ -250,7 +257,8 @@ public abstract class BasePlayer implements
|
|||
registerBroadcastReceiver();
|
||||
}
|
||||
|
||||
public void initListeners() { }
|
||||
public void initListeners() {
|
||||
}
|
||||
|
||||
public void handleIntent(final Intent intent) {
|
||||
if (DEBUG) {
|
||||
|
|
@ -272,7 +280,7 @@ public abstract class BasePlayer implements
|
|||
|
||||
// Resolve append intents
|
||||
if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) {
|
||||
int sizeBeforeAppend = playQueue.size();
|
||||
final int sizeBeforeAppend = playQueue.size();
|
||||
playQueue.append(queue.getStreams());
|
||||
|
||||
if ((intent.getBooleanExtra(SELECT_ON_APPEND, false)
|
||||
|
|
@ -288,34 +296,72 @@ public abstract class BasePlayer implements
|
|||
final float playbackPitch = savedParameters.pitch;
|
||||
final boolean playbackSkipSilence = savedParameters.skipSilence;
|
||||
|
||||
final boolean samePlayQueue = playQueue != null && playQueue.equals(queue);
|
||||
|
||||
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
|
||||
final boolean isMuted = intent
|
||||
.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
|
||||
if (simpleExoPlayer != null
|
||||
&& queue.size() == 1
|
||||
&& playQueue != null
|
||||
&& playQueue.size() == 1
|
||||
&& playQueue.getItem() != null
|
||||
&& 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());
|
||||
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();
|
||||
if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
|
||||
stateLoader = recordManager.loadStreamState(item)
|
||||
.observeOn(mainThread())
|
||||
.doFinally(() -> initPlayback(queue, repeatMode, playbackSpeed,
|
||||
playbackPitch, playbackSkipSilence, true, isMuted))
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
// Do not place initPlayback() in doFinally() because
|
||||
// it restarts playback after destroy()
|
||||
//.doFinally()
|
||||
.subscribe(
|
||||
state -> queue
|
||||
.setRecovery(queue.getIndex(), state.getProgressTime()),
|
||||
state -> {
|
||||
queue.setRecovery(queue.getIndex(), state.getProgressTime());
|
||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
|
||||
playbackSkipSilence, true, isMuted);
|
||||
},
|
||||
error -> {
|
||||
if (DEBUG) {
|
||||
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);
|
||||
|
|
@ -323,21 +369,23 @@ public abstract class BasePlayer implements
|
|||
}
|
||||
}
|
||||
// Good to go...
|
||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
|
||||
/*playOnInit=*/!intent.getBooleanExtra(START_PAUSED, false), isMuted);
|
||||
// In a case of equal PlayQueues we can re-init old one but only when it is disposed
|
||||
initPlayback(samePlayQueue ? playQueue : queue, repeatMode,
|
||||
playbackSpeed, playbackPitch, playbackSkipSilence,
|
||||
!intent.getBooleanExtra(START_PAUSED, false),
|
||||
isMuted);
|
||||
}
|
||||
|
||||
private PlaybackParameters retrievePlaybackParametersFromPreferences() {
|
||||
final SharedPreferences preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
final float speed = preferences
|
||||
.getFloat(context.getString(R.string.playback_speed_key), getPlaybackSpeed());
|
||||
final float pitch = preferences.getFloat(context.getString(R.string.playback_pitch_key),
|
||||
getPlaybackPitch());
|
||||
final boolean skipSilence = preferences
|
||||
.getBoolean(context.getString(R.string.playback_skip_silence_key),
|
||||
getPlaybackSkipSilence());
|
||||
final float speed = preferences.getFloat(
|
||||
context.getString(R.string.playback_speed_key), getPlaybackSpeed());
|
||||
final float pitch = preferences.getFloat(
|
||||
context.getString(R.string.playback_pitch_key), getPlaybackPitch());
|
||||
final boolean skipSilence = preferences.getBoolean(
|
||||
context.getString(R.string.playback_skip_silence_key), getPlaybackSkipSilence());
|
||||
return new PlaybackParameters(speed, pitch, skipSilence);
|
||||
}
|
||||
|
||||
|
|
@ -411,6 +459,7 @@ public abstract class BasePlayer implements
|
|||
|
||||
databaseUpdateReactor.clear();
|
||||
progressUpdateReactor.set(null);
|
||||
ImageLoader.getInstance().stop();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -562,7 +611,8 @@ public abstract class BasePlayer implements
|
|||
}
|
||||
}
|
||||
|
||||
public void onPausedSeek() { }
|
||||
public void onPausedSeek() {
|
||||
}
|
||||
|
||||
public void onCompleted() {
|
||||
if (DEBUG) {
|
||||
|
|
@ -830,7 +880,6 @@ public abstract class BasePlayer implements
|
|||
}
|
||||
setRecovery();
|
||||
|
||||
final Throwable cause = error.getCause();
|
||||
if (error instanceof BehindLiveWindowException) {
|
||||
reload();
|
||||
} else {
|
||||
|
|
@ -1018,14 +1067,6 @@ public abstract class BasePlayer implements
|
|||
registerView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackShutdown() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Shutting down...");
|
||||
}
|
||||
destroy();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// General Player
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
@ -1090,6 +1131,7 @@ public abstract class BasePlayer implements
|
|||
}
|
||||
|
||||
simpleExoPlayer.setPlayWhenReady(true);
|
||||
savePlaybackState();
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
|
|
@ -1102,6 +1144,7 @@ public abstract class BasePlayer implements
|
|||
|
||||
audioReactor.abandonAudioFocus();
|
||||
simpleExoPlayer.setPlayWhenReady(false);
|
||||
savePlaybackState();
|
||||
}
|
||||
|
||||
public void onPlayPause() {
|
||||
|
|
@ -1296,6 +1339,11 @@ public abstract class BasePlayer implements
|
|||
return;
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
@ -1408,7 +1456,7 @@ public abstract class BasePlayer implements
|
|||
return false;
|
||||
}
|
||||
|
||||
Timeline.Window timelineWindow = new Timeline.Window();
|
||||
final Timeline.Window timelineWindow = new Timeline.Window();
|
||||
currentTimeline.getWindow(currentWindowIndex, timelineWindow);
|
||||
return timelineWindow.getDefaultPositionMs() <= simpleExoPlayer.getCurrentPosition();
|
||||
}
|
||||
|
|
@ -1419,7 +1467,7 @@ public abstract class BasePlayer implements
|
|||
}
|
||||
try {
|
||||
return simpleExoPlayer.isCurrentWindowDynamic();
|
||||
} catch (@NonNull IndexOutOfBoundsException e) {
|
||||
} catch (@NonNull final IndexOutOfBoundsException e) {
|
||||
// Why would this even happen =(
|
||||
// But lets log it anyway. Save is save
|
||||
if (DEBUG) {
|
||||
|
|
@ -1434,6 +1482,10 @@ public abstract class BasePlayer implements
|
|||
return simpleExoPlayer != null && simpleExoPlayer.isPlaying();
|
||||
}
|
||||
|
||||
public boolean isLoading() {
|
||||
return simpleExoPlayer != null && simpleExoPlayer.isLoading();
|
||||
}
|
||||
|
||||
@Player.RepeatMode
|
||||
public int getRepeatMode() {
|
||||
return simpleExoPlayer == null
|
||||
|
|
@ -1471,20 +1523,32 @@ public abstract class BasePlayer implements
|
|||
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,
|
||||
final boolean skipSilence) {
|
||||
savePlaybackParametersToPreferences(speed, pitch, skipSilence);
|
||||
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence));
|
||||
final float roundedSpeed = Math.round(speed * 100.0f) / 100.0f;
|
||||
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,
|
||||
final boolean skipSilence) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit()
|
||||
.putFloat(context.getString(R.string.playback_speed_key), speed)
|
||||
.putFloat(context.getString(R.string.playback_pitch_key), pitch)
|
||||
.putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence)
|
||||
.apply();
|
||||
.edit()
|
||||
.putFloat(context.getString(R.string.playback_speed_key), speed)
|
||||
.putFloat(context.getString(R.string.playback_pitch_key), pitch)
|
||||
.putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence)
|
||||
.apply();
|
||||
}
|
||||
|
||||
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.Player;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
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.PlayQueueItem;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
|
@ -110,7 +114,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
|
||||
public abstract boolean onPlayerOptionSelected(MenuItem item);
|
||||
|
||||
public abstract Intent getPlayerShutdownIntent();
|
||||
public abstract void setupMenu(Menu m);
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Activity Lifecycle
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -152,6 +156,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
return true;
|
||||
}
|
||||
|
||||
// Allow to setup visibility of menuItems
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(final Menu m) {
|
||||
setupMenu(m);
|
||||
return super.onPrepareOptionsMenu(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
|
@ -175,11 +186,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
return true;
|
||||
case R.id.action_switch_main:
|
||||
this.player.setRecovery();
|
||||
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
||||
getApplicationContext().startActivity(
|
||||
getSwitchIntent(MainVideoPlayer.class)
|
||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
|
||||
);
|
||||
getSwitchIntent(MainActivity.class, MainPlayer.PlayerType.VIDEO)
|
||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()));
|
||||
return true;
|
||||
}
|
||||
return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item);
|
||||
|
|
@ -191,13 +200,22 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
unbind();
|
||||
}
|
||||
|
||||
protected Intent getSwitchIntent(final Class clazz) {
|
||||
protected Intent getSwitchIntent(final Class clazz, final MainPlayer.PlayerType playerType) {
|
||||
return NavigationHelper.getPlayerIntent(getApplicationContext(), clazz,
|
||||
this.player.getPlayQueue(), this.player.getRepeatMode(),
|
||||
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)
|
||||
.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) {
|
||||
player = ((PlayerServiceBinder) service).getPlayerInstance();
|
||||
} else if (service instanceof MainPlayer.LocalBinder) {
|
||||
player = ((MainPlayer.LocalBinder) service).getPlayer();
|
||||
}
|
||||
|
||||
if (player == null || player.getPlayQueue() == null
|
||||
|
|
@ -500,7 +520,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
return;
|
||||
}
|
||||
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
|
||||
player.getPlaybackSkipSilence()).show(getSupportFragmentManager(), getTag());
|
||||
player.getPlaybackSkipSilence(), this).show(getSupportFragmentManager(), getTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -560,7 +580,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
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.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
|
|
@ -571,6 +591,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
// Binding Service Listener
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onQueueUpdate(final PlayQueue queue) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackUpdate(final int state, final int repeatMode, final boolean shuffled,
|
||||
final PlaybackParameters parameters) {
|
||||
|
|
@ -610,7 +634,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataUpdate(final StreamInfo info) {
|
||||
public void onMetadataUpdate(final StreamInfo info, final PlayQueue queue) {
|
||||
if (info != null) {
|
||||
metadataTitle.setText(info.getName());
|
||||
metadataArtist.setText(info.getUploaderName());
|
||||
|
|
@ -710,7 +734,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
|
||||
private void onMaybeMuteChanged() {
|
||||
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
|
||||
//1) Text change:
|
||||
|
|
|
|||
|
|
@ -30,20 +30,21 @@ import android.content.SharedPreferences;
|
|||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
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.VideoPlaybackResolver;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.views.ExpandableSurfaceView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
|
@ -117,8 +119,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
|
||||
private View rootView;
|
||||
|
||||
private AspectRatioFrameLayout aspectRatioFrameLayout;
|
||||
private SurfaceView surfaceView;
|
||||
private ExpandableSurfaceView surfaceView;
|
||||
private View surfaceForeground;
|
||||
|
||||
private View loadingPanel;
|
||||
|
|
@ -135,7 +136,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
private TextView playbackLiveSync;
|
||||
private TextView playbackSpeedTextView;
|
||||
|
||||
private View topControlsRoot;
|
||||
private LinearLayout topControlsRoot;
|
||||
private TextView qualityTextView;
|
||||
|
||||
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
|
||||
private static boolean containsCaseInsensitive(final List<String> list, final String toFind) {
|
||||
for (String i : list) {
|
||||
for (final String i : list) {
|
||||
if (i.equalsIgnoreCase(toFind)) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -182,7 +183,6 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
|
||||
public void initViews(final View view) {
|
||||
this.rootView = view;
|
||||
this.aspectRatioFrameLayout = view.findViewById(R.id.aspectRatioLayout);
|
||||
this.surfaceView = view.findViewById(R.id.surfaceView);
|
||||
this.surfaceForeground = view.findViewById(R.id.surfaceForeground);
|
||||
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);
|
||||
resizeView.setText(PlayerHelper
|
||||
.resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode()));
|
||||
.resizeTypeOf(context, getSurfaceView().getResizeMode()));
|
||||
|
||||
this.captionTextView = view.findViewById(R.id.captionTextView);
|
||||
|
||||
//this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
this.playbackSeekBar.getProgressDrawable().
|
||||
setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
|
||||
playbackSeekBar.getThumb()
|
||||
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
|
||||
this.playbackSeekBar.getProgressDrawable()
|
||||
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
|
||||
this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeedTextView);
|
||||
this.captionPopupMenu = new PopupMenu(context, captionTextView);
|
||||
|
||||
((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,
|
||||
|
|
@ -252,7 +250,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
simpleExoPlayer.addTextOutput(cues -> subtitleView.onCues(cues));
|
||||
|
||||
// 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()
|
||||
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
|
||||
}
|
||||
|
|
@ -282,7 +280,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
|
||||
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
||||
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
|
||||
.getNameById(videoStream.getFormatId()) + " " + videoStream.resolution);
|
||||
}
|
||||
|
|
@ -314,7 +312,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
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);
|
||||
/*
|
||||
* only search for autogenerated cc as fallback
|
||||
|
|
@ -326,7 +324,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
&& !userPreferredLanguage.contains("(");
|
||||
|
||||
// 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);
|
||||
captionOffItem.setOnMenuItemClickListener(menuItem -> {
|
||||
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
||||
|
|
@ -342,7 +340,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
// Add all available captions
|
||||
for (int i = 0; i < availableLanguages.size(); 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);
|
||||
captionItem.setOnMenuItemClickListener(menuItem -> {
|
||||
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
||||
|
|
@ -459,11 +457,8 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
animateView(controlsRoot, false, DEFAULT_CONTROLS_DURATION);
|
||||
|
||||
playbackSeekBar.setEnabled(false);
|
||||
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-,
|
||||
// so sets the color again
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
playbackSeekBar.getThumb()
|
||||
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
|
||||
|
||||
loadingPanel.setBackgroundColor(Color.BLACK);
|
||||
animateView(loadingPanel, true, 0);
|
||||
|
|
@ -479,11 +474,8 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
showAndAnimateControl(-1, true);
|
||||
|
||||
playbackSeekBar.setEnabled(true);
|
||||
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-,
|
||||
// so sets the color again
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
playbackSeekBar.getThumb()
|
||||
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
|
||||
|
||||
loadingPanel.setVisibility(View.GONE);
|
||||
|
||||
|
|
@ -520,7 +512,6 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
super.onCompleted();
|
||||
|
||||
showControls(500);
|
||||
animateView(endScreen, true, 800);
|
||||
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
|
||||
loadingPanel.setVisibility(View.GONE);
|
||||
|
||||
|
|
@ -555,7 +546,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
+ "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
|
||||
+ "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
|
||||
}
|
||||
aspectRatioFrameLayout.setAspectRatio(((float) width) / height);
|
||||
getSurfaceView().setAspectRatio(((float) width) / height);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -583,7 +574,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
.getTrackGroups(textRenderer);
|
||||
|
||||
// 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++) {
|
||||
final TrackGroup textTrack = textTracks.get(i);
|
||||
if (textTrack.length > 0 && textTrack.getFormat(0) != null) {
|
||||
|
|
@ -620,12 +611,6 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
|
||||
|
||||
super.onPrepared(playWhenReady);
|
||||
|
||||
if (simpleExoPlayer.getCurrentPosition() != 0 && !isControlsVisible()) {
|
||||
controlsVisibilityHandler.removeCallbacksAndMessages(null);
|
||||
controlsVisibilityHandler
|
||||
.postDelayed(this::showControlsThenHide, DEFAULT_CONTROLS_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -675,7 +660,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
}
|
||||
|
||||
protected void onFullScreenButtonClicked() {
|
||||
protected void toggleFullscreen() {
|
||||
changeState(STATE_BLOCKED);
|
||||
}
|
||||
|
||||
|
|
@ -739,8 +724,8 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
qualityTextView.setText(menuItem.getTitle());
|
||||
return true;
|
||||
} else if (playbackSpeedPopupMenuGroupId == menuItem.getGroupId()) {
|
||||
int speedIndex = menuItem.getItemId();
|
||||
float speed = PLAYBACK_SPEEDS[speedIndex];
|
||||
final int speedIndex = menuItem.getItemId();
|
||||
final float speed = PLAYBACK_SPEEDS[speedIndex];
|
||||
|
||||
setPlaybackSpeed(speed);
|
||||
playbackSpeedTextView.setText(formatSpeed(speed));
|
||||
|
|
@ -799,16 +784,16 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
showControls(DEFAULT_CONTROLS_DURATION);
|
||||
}
|
||||
|
||||
private void onResizeClicked() {
|
||||
if (getAspectRatioFrameLayout() != null) {
|
||||
final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode();
|
||||
void onResizeClicked() {
|
||||
if (getSurfaceView() != null) {
|
||||
final int currentResizeMode = getSurfaceView().getResizeMode();
|
||||
final int newResizeMode = nextResizeMode(currentResizeMode);
|
||||
setResizeMode(newResizeMode);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
|
||||
getAspectRatioFrameLayout().setResizeMode(resizeMode);
|
||||
getSurfaceView().setResizeMode(resizeMode);
|
||||
getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode));
|
||||
}
|
||||
|
||||
|
|
@ -916,9 +901,9 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
if (drawableId == -1) {
|
||||
if (controlAnimationView.getVisibility() == View.VISIBLE) {
|
||||
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
|
||||
PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1f),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1f)
|
||||
PropertyValuesHolder.ofFloat(View.ALPHA, 1.0f, 0.0f),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1.0f),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1.0f)
|
||||
).setDuration(DEFAULT_CONTROLS_DURATION);
|
||||
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
|
|
@ -931,10 +916,10 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
return;
|
||||
}
|
||||
|
||||
float scaleFrom = goneOnEnd ? 1f : 1f;
|
||||
float scaleTo = goneOnEnd ? 1.8f : 1.4f;
|
||||
float alphaFrom = goneOnEnd ? 1f : 0f;
|
||||
float alphaTo = goneOnEnd ? 0f : 1f;
|
||||
final float scaleFrom = goneOnEnd ? 1f : 1f;
|
||||
final float scaleTo = goneOnEnd ? 1.8f : 1.4f;
|
||||
final float alphaFrom = goneOnEnd ? 1f : 0f;
|
||||
final float alphaTo = goneOnEnd ? 0f : 1f;
|
||||
|
||||
|
||||
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
|
||||
|
|
@ -1020,6 +1005,9 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
animateView(controlsRoot, false, duration);
|
||||
};
|
||||
}
|
||||
|
||||
public abstract void hideSystemUIIfNeeded();
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Getters and Setters
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
@ -1033,11 +1021,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
this.resolver.setPlaybackQuality(quality);
|
||||
}
|
||||
|
||||
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
|
||||
return aspectRatioFrameLayout;
|
||||
}
|
||||
|
||||
public SurfaceView getSurfaceView() {
|
||||
public ExpandableSurfaceView getSurfaceView() {
|
||||
return surfaceView;
|
||||
}
|
||||
|
||||
|
|
@ -1096,7 +1080,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
return playbackEndTime;
|
||||
}
|
||||
|
||||
public View getTopControlsRoot() {
|
||||
public LinearLayout getTopControlsRoot() {
|
||||
return topControlsRoot;
|
||||
}
|
||||
|
||||
|
|
@ -1108,6 +1092,10 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
return qualityPopupMenu;
|
||||
}
|
||||
|
||||
public TextView getPlaybackSpeedTextView() {
|
||||
return playbackSpeedTextView;
|
||||
}
|
||||
|
||||
public PopupMenu getPlaybackSpeedPopupMenu() {
|
||||
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 org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
|
||||
public interface PlayerEventListener {
|
||||
void onQueueUpdate(PlayQueue queue);
|
||||
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled,
|
||||
PlaybackParameters parameters);
|
||||
|
||||
void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
|
||||
|
||||
void onMetadataUpdate(StreamInfo info);
|
||||
|
||||
void onMetadataUpdate(StreamInfo info, PlayQueue queue);
|
||||
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() {
|
||||
Log.d(TAG, "onAudioFocusGain() called");
|
||||
player.setVolume(DUCK_AUDIO_TO);
|
||||
animateAudio(DUCK_AUDIO_TO, 1f);
|
||||
animateAudio(DUCK_AUDIO_TO, 1.0f);
|
||||
|
||||
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
|
||||
player.setPlayWhenReady(true);
|
||||
|
|
@ -133,7 +133,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
|
|||
}
|
||||
|
||||
private void animateAudio(final float from, final float to) {
|
||||
ValueAnimator valueAnimator = new ValueAnimator();
|
||||
final ValueAnimator valueAnimator = new ValueAnimator();
|
||||
valueAnimator.setFloatValues(from, to);
|
||||
valueAnimator.setDuration(AudioReactor.DUCK_DURATION);
|
||||
valueAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
|
|
|
|||
|
|
@ -80,13 +80,13 @@ import java.io.File;
|
|||
}
|
||||
|
||||
try {
|
||||
for (File file : cacheDir.listFiles()) {
|
||||
for (final File file : cacheDir.listFiles()) {
|
||||
final String filePath = file.getAbsolutePath();
|
||||
final boolean deleteSuccessful = file.delete();
|
||||
|
||||
Log.d(TAG, "tryDeleteCacheFiles: " + filePath + " deleted = " + deleteSuccessful);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
} catch (final Exception ignored) {
|
||||
Log.e(TAG, "Failed to delete file.", ignored);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class LoadController implements LoadControl {
|
|||
final int optimalPlaybackBufferMs) {
|
||||
this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000;
|
||||
|
||||
DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
|
||||
final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
|
||||
builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs,
|
||||
initialPlaybackBufferMs, initialPlaybackBufferMs);
|
||||
internalLoadControl = builder.createDefaultLoadControl();
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ public class MediaSessionManager {
|
|||
.build()
|
||||
);
|
||||
|
||||
MediaStyle mediaStyle = new MediaStyle()
|
||||
final MediaStyle mediaStyle = new MediaStyle()
|
||||
.setMediaSession(mediaSession.getSessionToken());
|
||||
|
||||
builder.setStyle(mediaStyle);
|
||||
|
|
@ -76,7 +76,7 @@ public class MediaSessionManager {
|
|||
.build()
|
||||
);
|
||||
|
||||
MediaStyle mediaStyle = new MediaStyle()
|
||||
final MediaStyle mediaStyle = new MediaStyle()
|
||||
.setMediaSession(mediaSession.getSessionToken());
|
||||
|
||||
builder.setStyle(mediaStyle);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package org.schabi.newpipe.player.helper;
|
|||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
|
|
@ -92,8 +92,10 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
|
||||
public static PlaybackParameterDialog newInstance(final double playbackTempo,
|
||||
final double playbackPitch,
|
||||
final boolean playbackSkipSilence) {
|
||||
PlaybackParameterDialog dialog = new PlaybackParameterDialog();
|
||||
final boolean playbackSkipSilence,
|
||||
final Callback callback) {
|
||||
final PlaybackParameterDialog dialog = new PlaybackParameterDialog();
|
||||
dialog.callback = callback;
|
||||
dialog.initialTempo = playbackTempo;
|
||||
dialog.initialPitch = playbackPitch;
|
||||
|
||||
|
|
@ -111,9 +113,9 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
@Override
|
||||
public void onAttach(final Context context) {
|
||||
super.onAttach(context);
|
||||
if (context != null && context instanceof Callback) {
|
||||
if (context instanceof Callback) {
|
||||
callback = (Callback) context;
|
||||
} else {
|
||||
} else if (callback == null) {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
|
@ -185,8 +187,8 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
|
||||
private void setupTempoControl(@NonNull final View rootView) {
|
||||
tempoSlider = rootView.findViewById(R.id.tempoSeekbar);
|
||||
TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
|
||||
TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
|
||||
final TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
|
||||
final TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
|
||||
tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText);
|
||||
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
|
||||
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
|
||||
|
|
@ -210,8 +212,8 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
|
||||
private void setupPitchControl(@NonNull final View rootView) {
|
||||
pitchSlider = rootView.findViewById(R.id.pitchSeekbar);
|
||||
TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
|
||||
TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
|
||||
final TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
|
||||
final TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
|
||||
pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText);
|
||||
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
|
||||
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
|
||||
|
|
@ -237,12 +239,13 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
|
||||
if (unhookingCheckbox != null) {
|
||||
// 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));
|
||||
|
||||
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
||||
// save whether pitch and tempo are unhooked or not
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
.edit()
|
||||
.putBoolean(getString(R.string.playback_unhook_key), isChecked)
|
||||
.apply();
|
||||
|
|
@ -267,12 +270,12 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
private void setupStepSizeSelector(@NonNull final View rootView) {
|
||||
TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
|
||||
TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
|
||||
TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
|
||||
TextView stepSizeTwentyFivePercentText = rootView
|
||||
final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
|
||||
final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
|
||||
final TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
|
||||
final TextView stepSizeTwentyFivePercentText = rootView
|
||||
.findViewById(R.id.stepSizeTwentyFivePercent);
|
||||
TextView stepSizeOneHundredPercentText = rootView
|
||||
final TextView stepSizeOneHundredPercentText = rootView
|
||||
.findViewById(R.id.stepSizeOneHundredPercent);
|
||||
|
||||
if (stepSizeOnePercentText != null) {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package org.schabi.newpipe.player.helper;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.view.accessibility.CaptioningManager;
|
||||
|
||||
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_ZOOM;
|
||||
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_NONE;
|
||||
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 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() { }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -63,10 +75,10 @@ public final class PlayerHelper {
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static String getTimeString(final int milliSeconds) {
|
||||
int seconds = (milliSeconds % 60000) / 1000;
|
||||
int minutes = (milliSeconds % 3600000) / 60000;
|
||||
int hours = (milliSeconds % 86400000) / 3600000;
|
||||
int days = (milliSeconds % (86400000 * 7)) / 86400000;
|
||||
final int seconds = (milliSeconds % 60000) / 1000;
|
||||
final int minutes = (milliSeconds % 3600000) / 60000;
|
||||
final int hours = (milliSeconds % 86400000) / 3600000;
|
||||
final int days = (milliSeconds % (86400000 * 7)) / 86400000;
|
||||
|
||||
STRING_BUILDER.setLength(0);
|
||||
return days > 0
|
||||
|
|
@ -132,17 +144,17 @@ public final class PlayerHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Given a {@link StreamInfo} and the existing queue items, provide the
|
||||
* {@link SinglePlayQueue} consisting of the next video for auto queuing.
|
||||
* Given a {@link StreamInfo} and the existing queue items,
|
||||
* provide the {@link SinglePlayQueue} consisting of the next video for auto queueing.
|
||||
* <p>
|
||||
* This method detects and prevents cycle by naively checking if a
|
||||
* candidate next video's url already exists in the existing items.
|
||||
* This method detects and prevents cycles by naively checking
|
||||
* if a candidate next video's url already exists in the existing items.
|
||||
* </p>
|
||||
* <p>
|
||||
* To select the next video, {@link StreamInfo#getNextVideo()} is first
|
||||
* checked. If it is nonnull and is not part of the existing items, then
|
||||
* it will be used as the next video. Otherwise, an random item with
|
||||
* non-repeating url will be selected from the {@link StreamInfo#getRelatedStreams()}.
|
||||
* The first item in {@link StreamInfo#getRelatedStreams()} is checked first.
|
||||
* If it is non-null and is not part of the existing items, it will be used as the next stream.
|
||||
* Otherwise, a random item with non-repeating url will be selected
|
||||
* from the {@link StreamInfo#getRelatedStreams()}.
|
||||
* </p>
|
||||
*
|
||||
* @param info currently playing stream
|
||||
|
|
@ -152,27 +164,28 @@ public final class PlayerHelper {
|
|||
@Nullable
|
||||
public static PlayQueue autoQueueOf(@NonNull final StreamInfo info,
|
||||
@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) {
|
||||
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();
|
||||
if (relatedItems == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StreamInfoItem> autoQueueItems = new ArrayList<>();
|
||||
for (final InfoItem item : info.getRelatedStreams()) {
|
||||
if (relatedItems.get(0) != null && relatedItems.get(0) instanceof StreamInfoItem
|
||||
&& !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())) {
|
||||
autoQueueItems.add((StreamInfoItem) item);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.shuffle(autoQueueItems);
|
||||
return autoQueueItems.isEmpty()
|
||||
? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
|
||||
|
|
@ -202,6 +215,11 @@ public final class PlayerHelper {
|
|||
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
|
||||
public static int getMinimizeOnExitAction(@NonNull final Context context) {
|
||||
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
|
||||
public static SeekParameters getSeekParameters(@NonNull final Context context) {
|
||||
return isUsingInexactSeek(context) ? SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
|
||||
|
|
@ -272,10 +302,6 @@ public final class PlayerHelper {
|
|||
|
||||
@NonNull
|
||||
public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return CaptionStyleCompat.DEFAULT;
|
||||
}
|
||||
|
||||
final CaptioningManager captioningManager = (CaptioningManager)
|
||||
context.getSystemService(Context.CAPTIONING_SERVICE);
|
||||
if (captioningManager == null || !captioningManager.isEnabled()) {
|
||||
|
|
@ -300,14 +326,10 @@ public final class PlayerHelper {
|
|||
* @return caption scaling
|
||||
*/
|
||||
public static float getCaptionScale(@NonNull final Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return 1f;
|
||||
}
|
||||
|
||||
final CaptioningManager captioningManager
|
||||
= (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
|
||||
if (captioningManager == null || !captioningManager.isEnabled()) {
|
||||
return 1f;
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return captioningManager.getFontScale();
|
||||
|
|
@ -323,6 +345,13 @@ public final class PlayerHelper {
|
|||
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
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -367,7 +396,7 @@ public final class PlayerHelper {
|
|||
|
||||
private static void setScreenBrightness(@NonNull final Context context,
|
||||
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.putLong(context.getString(R.string.screen_brightness_timestamp_key), timestamp);
|
||||
editor.apply();
|
||||
|
|
@ -375,8 +404,8 @@ public final class PlayerHelper {
|
|||
|
||||
private static float getScreenBrightness(@NonNull final Context context,
|
||||
final float screenBrightness) {
|
||||
SharedPreferences sp = getPreferences(context);
|
||||
long timestamp = sp
|
||||
final SharedPreferences sp = getPreferences(context);
|
||||
final long timestamp = sp
|
||||
.getLong(context.getString(R.string.screen_brightness_timestamp_key), 0);
|
||||
// Hypothesis: 4h covers a viewing block, e.g. evening.
|
||||
// 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);
|
||||
}
|
||||
|
||||
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(
|
||||
final StreamInfoItem streamInfoItem) {
|
||||
SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);
|
||||
final SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);
|
||||
singlePlayQueue.getItem().setAutoQueued(true);
|
||||
return singlePlayQueue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,13 +87,13 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator
|
|||
}
|
||||
|
||||
// Yes this is almost a copypasta, got a problem with that? =\
|
||||
int windowCount = callback.getQueueSize();
|
||||
int currentWindowIndex = callback.getCurrentPlayingIndex();
|
||||
int queueSize = Math.min(maxQueueSize, windowCount);
|
||||
int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0,
|
||||
final int windowCount = callback.getQueueSize();
|
||||
final int currentWindowIndex = callback.getCurrentPlayingIndex();
|
||||
final int queueSize = Math.min(maxQueueSize, windowCount);
|
||||
final int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0,
|
||||
windowCount - queueSize);
|
||||
|
||||
List<MediaSessionCompat.QueueItem> queue = new ArrayList<>();
|
||||
final List<MediaSessionCompat.QueueItem> queue = new ArrayList<>();
|
||||
for (int i = startIndex; i < startIndex + queueSize; 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);
|
||||
MediaDescriptionCompat.Builder descriptionBuilder = new MediaDescriptionCompat.Builder()
|
||||
final MediaDescriptionCompat.Builder descriptionBuilder
|
||||
= new MediaDescriptionCompat.Builder()
|
||||
.setMediaId(String.valueOf(index))
|
||||
.setTitle(item.getTitle())
|
||||
.setSubtitle(item.getUploader());
|
||||
|
||||
// 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_ARTIST, item.getUploader());
|
||||
additionalMetadata
|
||||
|
|
|
|||
|
|
@ -60,14 +60,14 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
|||
TextTrackScore selectedTrackScore = null;
|
||||
|
||||
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
|
||||
TrackGroup trackGroup = groups.get(groupIndex);
|
||||
@Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
|
||||
final TrackGroup trackGroup = groups.get(groupIndex);
|
||||
@Capabilities final int[] trackFormatSupport = formatSupport[groupIndex];
|
||||
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
if (isSupported(trackFormatSupport[trackIndex],
|
||||
params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
Format format = trackGroup.getFormat(trackIndex);
|
||||
TextTrackScore trackScore = new TextTrackScore(format, params,
|
||||
final Format format = trackGroup.getFormat(trackIndex);
|
||||
final TextTrackScore trackScore = new TextTrackScore(format, params,
|
||||
trackFormatSupport[trackIndex], selectedAudioLanguage);
|
||||
|
||||
if (formatHasLanguage(format, preferredTextLanguage)) {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ package org.schabi.newpipe.player.playqueue;
|
|||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -12,7 +15,6 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> extends PlayQueue {
|
||||
|
|
@ -21,7 +23,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
|||
|
||||
final int serviceId;
|
||||
final String baseUrl;
|
||||
String nextUrl;
|
||||
Page nextPage;
|
||||
|
||||
private transient Disposable fetchReactor;
|
||||
|
||||
|
|
@ -29,16 +31,16 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
|||
this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0);
|
||||
}
|
||||
|
||||
AbstractInfoPlayQueue(final int serviceId, final String url, final String nextPageUrl,
|
||||
AbstractInfoPlayQueue(final int serviceId, final String url, final Page nextPage,
|
||||
final List<StreamInfoItem> streams, final int index) {
|
||||
super(index, extractListItems(streams));
|
||||
|
||||
this.baseUrl = url;
|
||||
this.nextUrl = nextPageUrl;
|
||||
this.nextPage = nextPage;
|
||||
this.serviceId = serviceId;
|
||||
|
||||
this.isInitial = streams.isEmpty();
|
||||
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
|
||||
this.isComplete = !isInitial && !Page.isValid(nextPage);
|
||||
}
|
||||
|
||||
protected abstract String getTag();
|
||||
|
|
@ -66,7 +68,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
|||
if (!result.hasNextPage()) {
|
||||
isComplete = true;
|
||||
}
|
||||
nextUrl = result.getNextPageUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
append(extractListItems(result.getRelatedItems()));
|
||||
|
||||
|
|
@ -100,7 +102,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
|||
if (!result.hasNextPage()) {
|
||||
isComplete = true;
|
||||
}
|
||||
nextUrl = result.getNextPageUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
append(extractListItems(result.getItems()));
|
||||
|
||||
|
|
@ -127,7 +129,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
|||
}
|
||||
|
||||
private static List<PlayQueueItem> extractListItems(final List<StreamInfoItem> infos) {
|
||||
List<PlayQueueItem> result = new ArrayList<>();
|
||||
final List<PlayQueueItem> result = new ArrayList<>();
|
||||
for (final InfoItem stream : infos) {
|
||||
if (stream instanceof StreamInfoItem) {
|
||||
result.add(new PlayQueueItem((StreamInfoItem) stream));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.player.playqueue;
|
||||
|
||||
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
|
@ -17,15 +18,15 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, C
|
|||
}
|
||||
|
||||
public ChannelPlayQueue(final ChannelInfo info) {
|
||||
this(info.getServiceId(), info.getUrl(), info.getNextPageUrl(), info.getRelatedItems(), 0);
|
||||
this(info.getServiceId(), info.getUrl(), info.getNextPage(), info.getRelatedItems(), 0);
|
||||
}
|
||||
|
||||
public ChannelPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final Page nextPage,
|
||||
final List<StreamInfoItem> streams,
|
||||
final int index) {
|
||||
super(serviceId, url, nextPageUrl, streams, index);
|
||||
super(serviceId, url, nextPage, streams, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -41,7 +42,7 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, C
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHeadListObserver());
|
||||
} else {
|
||||
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextPage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getNextPageObserver());
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import androidx.annotation.Nullable;
|
|||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.player.playqueue.events.AppendEvent;
|
||||
import org.schabi.newpipe.player.playqueue.events.ErrorEvent;
|
||||
import org.schabi.newpipe.player.playqueue.events.InitEvent;
|
||||
|
|
@ -44,23 +44,31 @@ import io.reactivex.subjects.BehaviorSubject;
|
|||
*/
|
||||
public abstract class PlayQueue implements Serializable {
|
||||
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
public static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
private ArrayList<PlayQueueItem> backup;
|
||||
private ArrayList<PlayQueueItem> streams;
|
||||
|
||||
@NonNull
|
||||
private final AtomicInteger queueIndex;
|
||||
private final ArrayList<PlayQueueItem> history;
|
||||
|
||||
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
|
||||
private transient Flowable<PlayQueueEvent> broadcastReceiver;
|
||||
private transient Subscription reportingReactor;
|
||||
|
||||
private transient boolean disposed;
|
||||
|
||||
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
|
||||
streams = new ArrayList<>();
|
||||
streams.addAll(startWith);
|
||||
history = new ArrayList<>();
|
||||
if (streams.size() > index) {
|
||||
history.add(streams.get(index));
|
||||
}
|
||||
|
||||
queueIndex = new AtomicInteger(index);
|
||||
disposed = false;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -99,6 +107,7 @@ public abstract class PlayQueue implements Serializable {
|
|||
eventBroadcast = null;
|
||||
broadcastReceiver = null;
|
||||
reportingReactor = null;
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -149,6 +158,9 @@ public abstract class PlayQueue implements Serializable {
|
|||
if (index >= streams.size()) {
|
||||
newIndex = isComplete() ? index % streams.size() : streams.size() - 1;
|
||||
}
|
||||
if (oldIndex != newIndex) {
|
||||
history.add(streams.get(newIndex));
|
||||
}
|
||||
|
||||
queueIndex.set(newIndex);
|
||||
broadcast(new SelectEvent(oldIndex, newIndex));
|
||||
|
|
@ -269,7 +281,7 @@ public abstract class PlayQueue implements Serializable {
|
|||
* @param items {@link PlayQueueItem}s to append
|
||||
*/
|
||||
public synchronized void append(@NonNull final List<PlayQueueItem> items) {
|
||||
List<PlayQueueItem> itemList = new ArrayList<>(items);
|
||||
final List<PlayQueueItem> itemList = new ArrayList<>(items);
|
||||
|
||||
if (isShuffled()) {
|
||||
backup.addAll(itemList);
|
||||
|
|
@ -314,6 +326,9 @@ public abstract class PlayQueue implements Serializable {
|
|||
public synchronized void error() {
|
||||
final int oldIndex = getIndex();
|
||||
queueIndex.incrementAndGet();
|
||||
if (streams.size() > queueIndex.get()) {
|
||||
history.add(streams.get(queueIndex.get()));
|
||||
}
|
||||
broadcast(new ErrorEvent(oldIndex, getIndex()));
|
||||
}
|
||||
|
||||
|
|
@ -334,7 +349,11 @@ public abstract class PlayQueue implements Serializable {
|
|||
if (backup != null) {
|
||||
backup.remove(getItem(removeIndex));
|
||||
}
|
||||
streams.remove(removeIndex);
|
||||
|
||||
history.remove(streams.remove(removeIndex));
|
||||
if (streams.size() > queueIndex.get()) {
|
||||
history.add(streams.get(queueIndex.get()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -367,7 +386,7 @@ public abstract class PlayQueue implements Serializable {
|
|||
queueIndex.incrementAndGet();
|
||||
}
|
||||
|
||||
PlayQueueItem playQueueItem = streams.remove(source);
|
||||
final PlayQueueItem playQueueItem = streams.remove(source);
|
||||
playQueueItem.setAutoQueued(false);
|
||||
streams.add(target, playQueueItem);
|
||||
broadcast(new MoveEvent(source, target));
|
||||
|
|
@ -427,6 +446,9 @@ public abstract class PlayQueue implements Serializable {
|
|||
streams.add(0, streams.remove(newIndex));
|
||||
}
|
||||
queueIndex.set(0);
|
||||
if (streams.size() > 0) {
|
||||
history.add(streams.get(0));
|
||||
}
|
||||
|
||||
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
|
||||
}
|
||||
|
|
@ -458,10 +480,60 @@ public abstract class PlayQueue implements Serializable {
|
|||
} else {
|
||||
queueIndex.set(0);
|
||||
}
|
||||
if (streams.size() > queueIndex.get()) {
|
||||
history.add(streams.get(queueIndex.get()));
|
||||
}
|
||||
|
||||
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects previous played item.
|
||||
*
|
||||
* This method removes currently playing item from history and
|
||||
* starts playing the last item from history if it exists
|
||||
*
|
||||
* @return true if history is not empty and the item can be played
|
||||
* */
|
||||
public synchronized boolean previous() {
|
||||
if (history.size() <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
history.remove(history.size() - 1);
|
||||
|
||||
final PlayQueueItem last = history.remove(history.size() - 1);
|
||||
setIndex(indexOf(last));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compares two PlayQueues. Useful when a user switches players but queue is the same so
|
||||
* we don't have to do anything with new queue.
|
||||
* This method also gives a chance to track history of items in a queue in
|
||||
* VideoDetailFragment without duplicating items from two identical queues
|
||||
* */
|
||||
@Override
|
||||
public boolean equals(@Nullable final Object obj) {
|
||||
if (!(obj instanceof PlayQueue)
|
||||
|| getStreams().size() != ((PlayQueue) obj).getStreams().size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PlayQueue other = (PlayQueue) obj;
|
||||
for (int i = 0; i < getStreams().size(); i++) {
|
||||
if (!getItem(i).getUrl().equals(other.getItem(i).getUrl())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDisposed() {
|
||||
return disposed;
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Rx Broadcast
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
|
@ -20,7 +21,6 @@ import org.schabi.newpipe.util.FallbackViewHolder;
|
|||
import java.util.List;
|
||||
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
/**
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue