Merge remote-tracking branch 'origin/dev' into dev
| @@ -73,7 +73,7 @@ dependencies { | |||||||
|     implementation 'de.hdodenhof:circleimageview:2.2.0' |     implementation 'de.hdodenhof:circleimageview:2.2.0' | ||||||
|     implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1' |     implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1' | ||||||
|     implementation 'com.nononsenseapps:filepicker:3.0.1' |     implementation 'com.nononsenseapps:filepicker:3.0.1' | ||||||
|     implementation 'com.google.android.exoplayer:exoplayer:r2.5.4' |     implementation 'com.google.android.exoplayer:exoplayer:2.6.0' | ||||||
|  |  | ||||||
|     debugImplementation 'com.facebook.stetho:stetho:1.5.0' |     debugImplementation 'com.facebook.stetho:stetho:1.5.0' | ||||||
|     debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.5.0' |     debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.5.0' | ||||||
|   | |||||||
| @@ -15,6 +15,8 @@ import com.squareup.leakcanary.LeakCanary; | |||||||
| import com.squareup.leakcanary.LeakDirectoryProvider; | import com.squareup.leakcanary.LeakDirectoryProvider; | ||||||
| import com.squareup.leakcanary.RefWatcher; | import com.squareup.leakcanary.RefWatcher; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.extractor.Downloader; | ||||||
|  |  | ||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| @@ -33,7 +35,12 @@ public class DebugApp extends App { | |||||||
|     public void onCreate() { |     public void onCreate() { | ||||||
|         super.onCreate(); |         super.onCreate(); | ||||||
|         initStetho(); |         initStetho(); | ||||||
|         Downloader.client = new OkHttpClient.Builder().addNetworkInterceptor(new StethoInterceptor()).readTimeout(30, TimeUnit.SECONDS).build(); |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected Downloader getDownloader() { | ||||||
|  |         return org.schabi.newpipe.Downloader.init(new OkHttpClient.Builder() | ||||||
|  |                 .addNetworkInterceptor(new StethoInterceptor())); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void initStetho() { |     private void initStetho() { | ||||||
| @@ -58,6 +65,12 @@ public class DebugApp extends App { | |||||||
|         Stetho.initialize(initializer); |         Stetho.initialize(initializer); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected boolean isDisposedRxExceptionsReported() { | ||||||
|  |         return PreferenceManager.getDefaultSharedPreferences(this) | ||||||
|  |                 .getBoolean(getString(R.string.allow_disposed_exceptions_key), false); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     protected RefWatcher installLeakCanary() { |     protected RefWatcher installLeakCanary() { | ||||||
|         return LeakCanary.refWatcher(this) |         return LeakCanary.refWatcher(this) | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ import org.acra.config.ACRAConfiguration; | |||||||
| import org.acra.config.ACRAConfigurationException; | import org.acra.config.ACRAConfigurationException; | ||||||
| import org.acra.config.ConfigurationBuilder; | import org.acra.config.ConfigurationBuilder; | ||||||
| import org.acra.sender.ReportSenderFactory; | import org.acra.sender.ReportSenderFactory; | ||||||
|  | import org.schabi.newpipe.extractor.Downloader; | ||||||
| import org.schabi.newpipe.extractor.NewPipe; | import org.schabi.newpipe.extractor.NewPipe; | ||||||
| import org.schabi.newpipe.report.AcraReportSenderFactory; | import org.schabi.newpipe.report.AcraReportSenderFactory; | ||||||
| import org.schabi.newpipe.report.ErrorActivity; | import org.schabi.newpipe.report.ErrorActivity; | ||||||
| @@ -30,9 +31,13 @@ import org.schabi.newpipe.util.StateSaver; | |||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.io.InterruptedIOException; | import java.io.InterruptedIOException; | ||||||
| import java.net.SocketException; | import java.net.SocketException; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
| import io.reactivex.annotations.NonNull; | import io.reactivex.annotations.NonNull; | ||||||
| import io.reactivex.exceptions.CompositeException; | import io.reactivex.exceptions.CompositeException; | ||||||
|  | import io.reactivex.exceptions.MissingBackpressureException; | ||||||
|  | import io.reactivex.exceptions.OnErrorNotImplementedException; | ||||||
| import io.reactivex.exceptions.UndeliverableException; | import io.reactivex.exceptions.UndeliverableException; | ||||||
| import io.reactivex.functions.Consumer; | import io.reactivex.functions.Consumer; | ||||||
| import io.reactivex.plugins.RxJavaPlugins; | import io.reactivex.plugins.RxJavaPlugins; | ||||||
| @@ -83,7 +88,7 @@ public class App extends Application { | |||||||
|         // Initialize settings first because others inits can use its values |         // Initialize settings first because others inits can use its values | ||||||
|         SettingsActivity.initSettings(this); |         SettingsActivity.initSettings(this); | ||||||
|  |  | ||||||
|         NewPipe.init(Downloader.getInstance()); |         NewPipe.init(getDownloader()); | ||||||
|         NewPipeDatabase.init(this); |         NewPipeDatabase.init(this); | ||||||
|         StateSaver.init(this); |         StateSaver.init(this); | ||||||
|         initNotificationChannel(); |         initNotificationChannel(); | ||||||
| @@ -94,36 +99,67 @@ public class App extends Application { | |||||||
|         configureRxJavaErrorHandler(); |         configureRxJavaErrorHandler(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     protected Downloader getDownloader() { | ||||||
|  |         return org.schabi.newpipe.Downloader.init(null); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void configureRxJavaErrorHandler() { |     private void configureRxJavaErrorHandler() { | ||||||
|         // https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling |         // https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | ||||||
|         RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() { |         RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() { | ||||||
|             @Override |             @Override | ||||||
|             public void accept(@NonNull Throwable throwable) throws Exception { |             public void accept(@NonNull Throwable throwable) throws Exception { | ||||||
|                 Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : throwable = [" + throwable.getClass().getName() + "]"); |                 Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " + | ||||||
|  |                         "throwable = [" + throwable.getClass().getName() + "]"); | ||||||
|  |  | ||||||
|                 if (throwable instanceof UndeliverableException) { |                 if (throwable instanceof UndeliverableException) { | ||||||
|                     // As UndeliverableException is a wrapper, get the cause of it to get the "real" exception |                     // As UndeliverableException is a wrapper, get the cause of it to get the "real" exception | ||||||
|                     throwable = throwable.getCause(); |                     throwable = throwable.getCause(); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 final List<Throwable> errors; | ||||||
|                 if (throwable instanceof CompositeException) { |                 if (throwable instanceof CompositeException) { | ||||||
|                     for (Throwable element : ((CompositeException) throwable).getExceptions()) { |                     errors = ((CompositeException) throwable).getExceptions(); | ||||||
|                         if (checkThrowable(element)) return; |                 } else { | ||||||
|  |                     errors = Collections.singletonList(throwable); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 for (final Throwable error : errors) { | ||||||
|  |                     if (isThrowableIgnored(error)) return; | ||||||
|  |                     if (isThrowableCritical(error)) { | ||||||
|  |                         reportException(error); | ||||||
|  |                         return; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 if (checkThrowable(throwable)) return; |                 // Out-of-lifecycle exceptions should only be reported if a debug user wishes so, | ||||||
|  |                 // When exception is not reported, log it | ||||||
|  |                 if (isDisposedRxExceptionsReported()) { | ||||||
|  |                     reportException(throwable); | ||||||
|  |                 } else { | ||||||
|  |                     Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", throwable); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             private boolean isThrowableIgnored(@NonNull final Throwable throwable) { | ||||||
|  |                 // Don't crash the application over a simple network problem | ||||||
|  |                 return ExtractorHelper.hasAssignableCauseThrowable(throwable, | ||||||
|  |                         IOException.class, SocketException.class, // network api cancellation | ||||||
|  |                         InterruptedException.class, InterruptedIOException.class); // blocking code disposed | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             private boolean isThrowableCritical(@NonNull final Throwable throwable) { | ||||||
|  |                 // Though these exceptions cannot be ignored | ||||||
|  |                 return ExtractorHelper.hasAssignableCauseThrowable(throwable, | ||||||
|  |                         NullPointerException.class, IllegalArgumentException.class, // bug in app | ||||||
|  |                         OnErrorNotImplementedException.class, MissingBackpressureException.class, | ||||||
|  |                         IllegalStateException.class); // bug in operator | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             private void reportException(@NonNull final Throwable throwable) { | ||||||
|                 // Throw uncaught exception that will trigger the report system |                 // Throw uncaught exception that will trigger the report system | ||||||
|                 Thread.currentThread().getUncaughtExceptionHandler() |                 Thread.currentThread().getUncaughtExceptionHandler() | ||||||
|                         .uncaughtException(Thread.currentThread(), throwable); |                         .uncaughtException(Thread.currentThread(), throwable); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             private boolean checkThrowable(@NonNull Throwable throwable) { |  | ||||||
|                 // Don't crash the application over a simple network problem |  | ||||||
|                 return ExtractorHelper.hasAssignableCauseThrowable(throwable, |  | ||||||
|                         IOException.class, SocketException.class, InterruptedException.class, InterruptedIOException.class); |  | ||||||
|             } |  | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -177,4 +213,8 @@ public class App extends Application { | |||||||
|     protected RefWatcher installLeakCanary() { |     protected RefWatcher installLeakCanary() { | ||||||
|         return RefWatcher.DISABLED; |         return RefWatcher.DISABLED; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     protected boolean isDisposedRxExceptionsReported() { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,8 +1,12 @@ | |||||||
| package org.schabi.newpipe; | package org.schabi.newpipe; | ||||||
|  |  | ||||||
|  | import android.support.annotation.Nullable; | ||||||
|  | import android.text.TextUtils; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; | import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; | ||||||
|  |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
|  | import java.util.Collections; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
| @@ -10,6 +14,7 @@ import java.util.concurrent.TimeUnit; | |||||||
| import okhttp3.OkHttpClient; | import okhttp3.OkHttpClient; | ||||||
| import okhttp3.Request; | import okhttp3.Request; | ||||||
| import okhttp3.Response; | import okhttp3.Response; | ||||||
|  | import okhttp3.ResponseBody; | ||||||
|  |  | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -33,34 +38,38 @@ import okhttp3.Response; | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| public class Downloader implements org.schabi.newpipe.extractor.Downloader { | public class Downloader implements org.schabi.newpipe.extractor.Downloader { | ||||||
|  |  | ||||||
|     public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0"; |     public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0"; | ||||||
|     private static String mCookies = ""; |  | ||||||
|  |  | ||||||
|     private static Downloader instance = null; |     private static Downloader instance; | ||||||
|  |     private String mCookies; | ||||||
|  |     private OkHttpClient client; | ||||||
|  |  | ||||||
|     protected static OkHttpClient client = new OkHttpClient.Builder().readTimeout(30, TimeUnit.SECONDS).build(); |     private Downloader(OkHttpClient.Builder builder) { | ||||||
|  |         this.client = builder | ||||||
|  |                 .readTimeout(30, TimeUnit.SECONDS) | ||||||
|  |                 //.cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"), 16 * 1024 * 1024)) | ||||||
|  |                 .build(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private Downloader() { |     /** | ||||||
|  |      * It's recommended to call exactly once in the entire lifetime of the application. | ||||||
|  |      * | ||||||
|  |      * @param builder if null, default builder will be used | ||||||
|  |      */ | ||||||
|  |     public static Downloader init(@Nullable OkHttpClient.Builder builder) { | ||||||
|  |         return instance = new Downloader(builder != null ? builder : new OkHttpClient.Builder()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static Downloader getInstance() { |     public static Downloader getInstance() { | ||||||
|         if (instance == null) { |  | ||||||
|             synchronized (Downloader.class) { |  | ||||||
|                 if (instance == null) { |  | ||||||
|                     instance = new Downloader(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return instance; |         return instance; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static synchronized void setCookies(String cookies) { |     public String getCookies() { | ||||||
|         Downloader.mCookies = cookies; |         return mCookies; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static synchronized String getCookies() { |     public void setCookies(String cookies) { | ||||||
|         return Downloader.mCookies; |         mCookies = cookies; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -89,22 +98,32 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { | |||||||
|      */ |      */ | ||||||
|     @Override |     @Override | ||||||
|     public String download(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException { |     public String download(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException { | ||||||
|         Request.Builder requestBuilder = new Request.Builder().url(siteUrl).addHeader("User-Agent", USER_AGENT).method("GET", null); |         final Request.Builder requestBuilder = new Request.Builder() | ||||||
|         for (Map.Entry<String, String> header : customProperties.entrySet()) { |                 .method("GET", null).url(siteUrl) | ||||||
|             requestBuilder = requestBuilder.addHeader(header.getKey(), header.getValue()); |                 .addHeader("User-Agent", USER_AGENT); | ||||||
|         } |  | ||||||
|         if (getCookies().length() > 0) { |  | ||||||
|             requestBuilder = requestBuilder.addHeader("Cookie", getCookies()); |  | ||||||
|         } |  | ||||||
|         Request request = requestBuilder.build(); |  | ||||||
|  |  | ||||||
|         Response response = client.newCall(request).execute(); |         for (Map.Entry<String, String> header : customProperties.entrySet()) { | ||||||
|  |             requestBuilder.addHeader(header.getKey(), header.getValue()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!TextUtils.isEmpty(mCookies)) { | ||||||
|  |             requestBuilder.addHeader("Cookie", mCookies); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         final Request request = requestBuilder.build(); | ||||||
|  |         final Response response = client.newCall(request).execute(); | ||||||
|  |         final ResponseBody body = response.body(); | ||||||
|  |  | ||||||
|         if (response.code() == 429) { |         if (response.code() == 429) { | ||||||
|             throw new ReCaptchaException("reCaptcha Challenge requested"); |             throw new ReCaptchaException("reCaptcha Challenge requested"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return response.body().string(); |         if (body == null) { | ||||||
|  |             response.close(); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return body.string(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -116,6 +135,6 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { | |||||||
|      */ |      */ | ||||||
|     @Override |     @Override | ||||||
|     public String download(String siteUrl) throws IOException, ReCaptchaException { |     public String download(String siteUrl) throws IOException, ReCaptchaException { | ||||||
|         return download(siteUrl, new HashMap<>()); |         return download(siteUrl, Collections.emptyMap()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -20,7 +20,6 @@ | |||||||
|  |  | ||||||
| package org.schabi.newpipe; | package org.schabi.newpipe; | ||||||
|  |  | ||||||
| import android.annotation.SuppressLint; |  | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| @@ -28,7 +27,6 @@ import android.os.Bundle; | |||||||
| import android.os.Handler; | import android.os.Handler; | ||||||
| import android.os.Looper; | import android.os.Looper; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.NonNull; |  | ||||||
| import android.support.design.widget.NavigationView; | import android.support.design.widget.NavigationView; | ||||||
| import android.support.v4.app.Fragment; | import android.support.v4.app.Fragment; | ||||||
| import android.support.v4.view.GravityCompat; | import android.support.v4.view.GravityCompat; | ||||||
| @@ -264,22 +262,6 @@ public class MainActivity extends AppCompatActivity { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressLint("ShowToast") |  | ||||||
|     private void onHeapDumpToggled(@NonNull MenuItem item) { |  | ||||||
|         final boolean isHeapDumpEnabled = !item.isChecked(); |  | ||||||
|  |  | ||||||
|         PreferenceManager.getDefaultSharedPreferences(this).edit() |  | ||||||
|                 .putBoolean(getString(R.string.allow_heap_dumping_key), isHeapDumpEnabled).apply(); |  | ||||||
|         item.setChecked(isHeapDumpEnabled); |  | ||||||
|  |  | ||||||
|         final String heapDumpNotice; |  | ||||||
|         if (isHeapDumpEnabled) { |  | ||||||
|             heapDumpNotice = getString(R.string.enable_leak_canary_notice); |  | ||||||
|         } else { |  | ||||||
|             heapDumpNotice = getString(R.string.disable_leak_canary_notice); |  | ||||||
|         } |  | ||||||
|         Toast.makeText(getApplicationContext(), heapDumpNotice, Toast.LENGTH_SHORT).show(); |  | ||||||
|     } |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Menu |     // Menu | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
| @@ -301,10 +283,6 @@ public class MainActivity extends AppCompatActivity { | |||||||
|             inflater.inflate(R.menu.main_menu, menu); |             inflater.inflate(R.menu.main_menu, menu); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (DEBUG) { |  | ||||||
|             getMenuInflater().inflate(R.menu.debug_menu, menu); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         ActionBar actionBar = getSupportActionBar(); |         ActionBar actionBar = getSupportActionBar(); | ||||||
|         if (actionBar != null) { |         if (actionBar != null) { | ||||||
|             actionBar.setDisplayHomeAsUpEnabled(false); |             actionBar.setDisplayHomeAsUpEnabled(false); | ||||||
| @@ -315,17 +293,6 @@ public class MainActivity extends AppCompatActivity { | |||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean onPrepareOptionsMenu(Menu menu) { |  | ||||||
|         MenuItem heapDumpToggle = menu.findItem(R.id.action_toggle_heap_dump); |  | ||||||
|         if (heapDumpToggle != null) { |  | ||||||
|             final boolean isToggled = PreferenceManager.getDefaultSharedPreferences(this) |  | ||||||
|                     .getBoolean(getString(R.string.allow_heap_dumping_key), false); |  | ||||||
|             heapDumpToggle.setChecked(isToggled); |  | ||||||
|         } |  | ||||||
|         return super.onPrepareOptionsMenu(menu); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public boolean onOptionsItemSelected(MenuItem item) { |     public boolean onOptionsItemSelected(MenuItem item) { | ||||||
|         if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]"); |         if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]"); | ||||||
| @@ -346,9 +313,6 @@ public class MainActivity extends AppCompatActivity { | |||||||
|             case R.id.action_history: |             case R.id.action_history: | ||||||
|                 NavigationHelper.openHistory(this); |                 NavigationHelper.openHistory(this); | ||||||
|                 return true; |                 return true; | ||||||
|             case R.id.action_toggle_heap_dump: |  | ||||||
|                 onHeapDumpToggled(item); |  | ||||||
|                 return true; |  | ||||||
|             default: |             default: | ||||||
|                 return super.onOptionsItemSelected(item); |                 return super.onOptionsItemSelected(item); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -107,7 +107,7 @@ public class ReCaptchaActivity extends AppCompatActivity { | |||||||
|             // find cookies : s_gl & goojf and Add cookies to Downloader |             // find cookies : s_gl & goojf and Add cookies to Downloader | ||||||
|             if (find_access_cookies(cookies)) { |             if (find_access_cookies(cookies)) { | ||||||
|                 // Give cookies to Downloader class |                 // Give cookies to Downloader class | ||||||
|                 Downloader.setCookies(mCookies); |                 Downloader.getInstance().setCookies(mCookies); | ||||||
|  |  | ||||||
|                 // Closing activity and return to parent |                 // Closing activity and return to parent | ||||||
|                 setResult(RESULT_OK); |                 setResult(RESULT_OK); | ||||||
|   | |||||||
| @@ -81,6 +81,10 @@ import io.reactivex.android.schedulers.AndroidSchedulers; | |||||||
| import io.reactivex.disposables.CompositeDisposable; | import io.reactivex.disposables.CompositeDisposable; | ||||||
| import io.reactivex.disposables.Disposable; | import io.reactivex.disposables.Disposable; | ||||||
|  |  | ||||||
|  | import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL; | ||||||
|  | import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION; | ||||||
|  | import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK; | ||||||
|  | import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT; | ||||||
| import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; | import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -279,6 +283,11 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen | |||||||
|         if (playbackManager != null) playbackManager.dispose(); |         if (playbackManager != null) playbackManager.dispose(); | ||||||
|         if (audioReactor != null) audioReactor.abandonAudioFocus(); |         if (audioReactor != null) audioReactor.abandonAudioFocus(); | ||||||
|         if (databaseUpdateReactor != null) databaseUpdateReactor.dispose(); |         if (databaseUpdateReactor != null) databaseUpdateReactor.dispose(); | ||||||
|  |  | ||||||
|  |         if (playQueueAdapter != null) { | ||||||
|  |             playQueueAdapter.unsetSelectedListener(); | ||||||
|  |             playQueueAdapter.dispose(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void destroy() { |     public void destroy() { | ||||||
| @@ -460,11 +469,11 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen | |||||||
|         final PlayQueueItem currentSourceItem = playQueue.getItem(); |         final PlayQueueItem currentSourceItem = playQueue.getItem(); | ||||||
|  |  | ||||||
|         // Check if already playing correct window |         // Check if already playing correct window | ||||||
|         final boolean isCurrentWindowCorrect = |         final boolean isCurrentPeriodCorrect = | ||||||
|                 simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex; |                 simpleExoPlayer.getCurrentPeriodIndex() == currentSourceIndex; | ||||||
|  |  | ||||||
|         // Check if recovering |         // Check if recovering | ||||||
|         if (isCurrentWindowCorrect && currentSourceItem != null) { |         if (isCurrentPeriodCorrect && currentSourceItem != null) { | ||||||
|             /* Recovering with sub-second position may cause a long buffer delay in ExoPlayer, |             /* Recovering with sub-second position may cause a long buffer delay in ExoPlayer, | ||||||
|              * rounding this position to the nearest second will help alleviate this.*/ |              * rounding this position to the nearest second will help alleviate this.*/ | ||||||
|             final long position = currentSourceItem.getRecoveryPosition(); |             final long position = currentSourceItem.getRecoveryPosition(); | ||||||
| @@ -605,17 +614,25 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onPositionDiscontinuity() { |     public void onPositionDiscontinuity(int reason) { | ||||||
|  |         if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with reason = [" + reason + "]"); | ||||||
|         // Refresh the playback if there is a transition to the next video |         // Refresh the playback if there is a transition to the next video | ||||||
|         final int newWindowIndex = simpleExoPlayer.getCurrentWindowIndex(); |         final int newPeriodIndex = simpleExoPlayer.getCurrentPeriodIndex(); | ||||||
|         if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with window index = [" + newWindowIndex + "]"); |  | ||||||
|  |  | ||||||
|         // If the user selects a new track, then the discontinuity occurs after the index is changed. |         /* Discontinuity reasons!! Thank you ExoPlayer lords */ | ||||||
|         // Therefore, the only source that causes a discrepancy would be gapless transition, |         switch (reason) { | ||||||
|         // which can only offset the current track by +1. |             case DISCONTINUITY_REASON_PERIOD_TRANSITION: | ||||||
|         if (newWindowIndex == playQueue.getIndex() + 1 || |                 if (newPeriodIndex == playQueue.getIndex()) { | ||||||
|                 (newWindowIndex == 0 && playQueue.getIndex() == playQueue.size() - 1)) { |                     registerView(); | ||||||
|             playQueue.offsetIndex(+1); |                 } else { | ||||||
|  |                     playQueue.offsetIndex(+1); | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |             case DISCONTINUITY_REASON_SEEK: | ||||||
|  |             case DISCONTINUITY_REASON_SEEK_ADJUSTMENT: | ||||||
|  |             case DISCONTINUITY_REASON_INTERNAL: | ||||||
|  |             default: | ||||||
|  |                 break; | ||||||
|         } |         } | ||||||
|         playbackManager.load(); |         playbackManager.load(); | ||||||
|     } |     } | ||||||
| @@ -625,6 +642,16 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen | |||||||
|         if (DEBUG) Log.d(TAG, "onRepeatModeChanged() called with: mode = [" + i + "]"); |         if (DEBUG) Log.d(TAG, "onRepeatModeChanged() called with: mode = [" + i + "]"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { | ||||||
|  |         if (DEBUG) Log.d(TAG, "onShuffleModeEnabledChanged() called with: " + | ||||||
|  |                 "mode = [" + shuffleModeEnabled + "]"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onSeekProcessed() { | ||||||
|  |         if (DEBUG) Log.d(TAG, "onSeekProcessed() called"); | ||||||
|  |     } | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Playback Listener |     // Playback Listener | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
| @@ -668,19 +695,14 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen | |||||||
|         if (currentSourceIndex != playQueue.getIndex()) { |         if (currentSourceIndex != playQueue.getIndex()) { | ||||||
|             Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex + |             Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex + | ||||||
|                     "], queue index=[" + playQueue.getIndex() + "]"); |                     "], queue index=[" + playQueue.getIndex() + "]"); | ||||||
|         } else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) { |         } else if (simpleExoPlayer.getCurrentPeriodIndex() != currentSourceIndex || !isPlaying()) { | ||||||
|             final long startPos = info != null ? info.start_position : 0; |             final long startPos = info != null ? info.start_position : 0; | ||||||
|             if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + |             if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + | ||||||
|                     " at: " + getTimeString((int)startPos)); |                     " at: " + getTimeString((int)startPos)); | ||||||
|             simpleExoPlayer.seekTo(currentSourceIndex, startPos); |             simpleExoPlayer.seekTo(currentSourceIndex, startPos); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // TODO: update exoplayer to 2.6.x in order to register view count on repeated streams |         registerView(); | ||||||
|         databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete() |  | ||||||
|                 .subscribe( |  | ||||||
|                         ignored -> {/* successful */}, |  | ||||||
|                         error -> Log.e(TAG, "Player onViewed() failure: ", error) |  | ||||||
|                 )); |  | ||||||
|         initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url); |         initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -814,6 +836,15 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen | |||||||
|     // Utils |     // Utils | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|  |     private void registerView() { | ||||||
|  |         if (databaseUpdateReactor == null || recordManager == null || currentInfo == null) return; | ||||||
|  |         databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete() | ||||||
|  |                 .subscribe( | ||||||
|  |                         ignored -> {/* successful */}, | ||||||
|  |                         error -> Log.e(TAG, "Player onViewed() failure: ", error) | ||||||
|  |                 )); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     protected void reload() { |     protected void reload() { | ||||||
|         if (playbackManager != null) { |         if (playbackManager != null) { | ||||||
|             playbackManager.reset(); |             playbackManager.reset(); | ||||||
|   | |||||||
| @@ -61,6 +61,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity | |||||||
|  |  | ||||||
|     private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; |     private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; | ||||||
|  |  | ||||||
|  |     private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 10; | ||||||
|  |     private static final int MAXIMUM_INITIAL_DRAG_VELOCITY = 25; | ||||||
|  |  | ||||||
|     private View rootView; |     private View rootView; | ||||||
|  |  | ||||||
|     private RecyclerView itemsList; |     private RecyclerView itemsList; | ||||||
| @@ -211,6 +214,15 @@ public abstract class ServicePlayerActivity extends AppCompatActivity | |||||||
|             unbindService(serviceConnection); |             unbindService(serviceConnection); | ||||||
|             serviceBound = false; |             serviceBound = false; | ||||||
|             stopPlayerListener(); |             stopPlayerListener(); | ||||||
|  |  | ||||||
|  |             if (player != null && player.getPlayQueueAdapter() != null) { | ||||||
|  |                 player.getPlayQueueAdapter().unsetSelectedListener(); | ||||||
|  |             } | ||||||
|  |             if (itemsList != null) itemsList.setAdapter(null); | ||||||
|  |             if (itemTouchHelper != null) itemTouchHelper.attachToRecyclerView(null); | ||||||
|  |  | ||||||
|  |             itemsList = null; | ||||||
|  |             itemTouchHelper = null; | ||||||
|             player = null; |             player = null; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -385,7 +397,19 @@ public abstract class ServicePlayerActivity extends AppCompatActivity | |||||||
|     private ItemTouchHelper.SimpleCallback getItemTouchCallback() { |     private ItemTouchHelper.SimpleCallback getItemTouchCallback() { | ||||||
|         return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) { |         return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) { | ||||||
|             @Override |             @Override | ||||||
|             public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) { |             public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, | ||||||
|  |                                                     int viewSizeOutOfBounds, int totalSize, | ||||||
|  |                                                     long msSinceStartScroll) { | ||||||
|  |                 final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, | ||||||
|  |                         viewSizeOutOfBounds, totalSize, msSinceStartScroll); | ||||||
|  |                 final int clampedAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY, | ||||||
|  |                         Math.min(Math.abs(standardSpeed), MAXIMUM_INITIAL_DRAG_VELOCITY)); | ||||||
|  |                 return clampedAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             @Override | ||||||
|  |             public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, | ||||||
|  |                                   RecyclerView.ViewHolder target) { | ||||||
|                 if (source.getItemViewType() != target.getItemViewType()) { |                 if (source.getItemViewType() != target.getItemViewType()) { | ||||||
|                     return false; |                     return false; | ||||||
|                 } |                 } | ||||||
|   | |||||||
| @@ -263,7 +263,9 @@ public abstract class VideoPlayer extends BasePlayer | |||||||
|             VideoStream videoStream = availableStreams.get(i); |             VideoStream videoStream = availableStreams.get(i); | ||||||
|             qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution); |             qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution); | ||||||
|         } |         } | ||||||
|         qualityTextView.setText(getSelectedVideoStream().resolution); |         if (getSelectedVideoStream() != null) { | ||||||
|  |             qualityTextView.setText(getSelectedVideoStream().resolution); | ||||||
|  |         } | ||||||
|         qualityPopupMenu.setOnMenuItemClickListener(this); |         qualityPopupMenu.setOnMenuItemClickListener(this); | ||||||
|         qualityPopupMenu.setOnDismissListener(this); |         qualityPopupMenu.setOnDismissListener(this); | ||||||
|     } |     } | ||||||
| @@ -326,7 +328,7 @@ public abstract class VideoPlayer extends BasePlayer | |||||||
|         qualityTextView.setVisibility(View.GONE); |         qualityTextView.setVisibility(View.GONE); | ||||||
|         playbackSpeedTextView.setVisibility(View.GONE); |         playbackSpeedTextView.setVisibility(View.GONE); | ||||||
|  |  | ||||||
|         if (info != null) { |         if (info != null && info.video_streams.size() + info.video_only_streams.size() > 0) { | ||||||
|             final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, |             final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, | ||||||
|                     info.video_streams, info.video_only_streams, false); |                     info.video_streams, info.video_only_streams, false); | ||||||
|             availableStreams = new ArrayList<>(videos); |             availableStreams = new ArrayList<>(videos); | ||||||
| @@ -337,48 +339,62 @@ public abstract class VideoPlayer extends BasePlayer | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             buildQualityMenu(); |             buildQualityMenu(); | ||||||
|             buildPlaybackSpeedMenu(); |  | ||||||
|             qualityTextView.setVisibility(View.VISIBLE); |             qualityTextView.setVisibility(View.VISIBLE); | ||||||
|             playbackSpeedTextView.setVisibility(View.VISIBLE); |             surfaceView.setVisibility(View.VISIBLE); | ||||||
|  |         } else { | ||||||
|  |             surfaceView.setVisibility(View.GONE); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         buildPlaybackSpeedMenu(); | ||||||
|  |         playbackSpeedTextView.setVisibility(View.VISIBLE); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     @Nullable |     @Nullable | ||||||
|     public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { |     public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { | ||||||
|         final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false); |         List<MediaSource> mediaSources = new ArrayList<>(); | ||||||
|  |  | ||||||
|  |         // Create video stream source | ||||||
|  |         final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, | ||||||
|  |                 info.video_streams, info.video_only_streams, false); | ||||||
|         final int index; |         final int index; | ||||||
|         if (playbackQuality == null) { |         if (videos.isEmpty()) { | ||||||
|  |             index = -1; | ||||||
|  |         } else if (playbackQuality == null) { | ||||||
|             index = getDefaultResolutionIndex(videos); |             index = getDefaultResolutionIndex(videos); | ||||||
|         } else { |         } else { | ||||||
|             index = getOverrideResolutionIndex(videos, getPlaybackQuality()); |             index = getOverrideResolutionIndex(videos, getPlaybackQuality()); | ||||||
|         } |         } | ||||||
|         if (index < 0 || index >= videos.size()) return null; |         final VideoStream video = index >= 0 && index < videos.size() ? videos.get(index) : null; | ||||||
|         final VideoStream video = videos.get(index); |         if (video != null) { | ||||||
|  |             final MediaSource streamSource = buildMediaSource(video.getUrl(), | ||||||
|         List<MediaSource> mediaSources = new ArrayList<>(); |                     MediaFormat.getSuffixById(video.getFormatId())); | ||||||
|         // Create video stream source |             mediaSources.add(streamSource); | ||||||
|         final MediaSource streamSource = buildMediaSource(video.getUrl(), |         } | ||||||
|                 MediaFormat.getSuffixById(video.getFormatId())); |  | ||||||
|         mediaSources.add(streamSource); |  | ||||||
|  |  | ||||||
|         // Create optional audio stream source |         // Create optional audio stream source | ||||||
|         final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams); |         final List<AudioStream> audioStreams = info.getAudioStreams(); | ||||||
|         if (video.isVideoOnly && audio != null) { |         final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get( | ||||||
|             // Merge with audio stream in case if video does not contain audio |                 ListHelper.getDefaultAudioFormat(context, audioStreams)); | ||||||
|  |         // Use the audio stream if there is no video stream, or | ||||||
|  |         // Merge with audio stream in case if video does not contain audio | ||||||
|  |         if (audio != null && ((video != null && video.isVideoOnly) || video == null)) { | ||||||
|             final MediaSource audioSource = buildMediaSource(audio.getUrl(), |             final MediaSource audioSource = buildMediaSource(audio.getUrl(), | ||||||
|                     MediaFormat.getSuffixById(audio.getFormatId())); |                     MediaFormat.getSuffixById(audio.getFormatId())); | ||||||
|             mediaSources.add(audioSource); |             mediaSources.add(audioSource); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // If there is no audio or video sources, then this media source cannot be played back | ||||||
|  |         if (mediaSources.isEmpty()) return null; | ||||||
|  |         // Below are auxiliary media sources | ||||||
|  |  | ||||||
|         // Create subtitle sources |         // Create subtitle sources | ||||||
|         for (final Subtitles subtitle : info.getSubtitles()) { |         for (final Subtitles subtitle : info.getSubtitles()) { | ||||||
|             final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType()); |             final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType()); | ||||||
|             if (mimeType == null) continue; |             if (mimeType == null || context == null) continue; | ||||||
|  |  | ||||||
|             final Format textFormat = Format.createTextSampleFormat(null, mimeType, |             final Format textFormat = Format.createTextSampleFormat(null, mimeType, | ||||||
|                     SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(subtitle)); |                     SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle)); | ||||||
|             final MediaSource textSource = new SingleSampleMediaSource( |             final MediaSource textSource = new SingleSampleMediaSource( | ||||||
|                     Uri.parse(subtitle.getURL()), cacheDataSourceFactory, textFormat, TIME_UNSET); |                     Uri.parse(subtitle.getURL()), cacheDataSourceFactory, textFormat, TIME_UNSET); | ||||||
|             mediaSources.add(textSource); |             mediaSources.add(textSource); | ||||||
| @@ -658,7 +674,9 @@ public abstract class VideoPlayer extends BasePlayer | |||||||
|     public void onDismiss(PopupMenu menu) { |     public void onDismiss(PopupMenu menu) { | ||||||
|         if (DEBUG) Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); |         if (DEBUG) Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); | ||||||
|         isSomePopupMenuVisible = false; |         isSomePopupMenuVisible = false; | ||||||
|         qualityTextView.setText(getSelectedVideoStream().resolution); |         if (getSelectedVideoStream() != null) { | ||||||
|  |             qualityTextView.setText(getSelectedVideoStream().resolution); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void onQualitySelectorClicked() { |     public void onQualitySelectorClicked() { | ||||||
| @@ -668,8 +686,12 @@ public abstract class VideoPlayer extends BasePlayer | |||||||
|         showControls(300); |         showControls(300); | ||||||
|  |  | ||||||
|         final VideoStream videoStream = getSelectedVideoStream(); |         final VideoStream videoStream = getSelectedVideoStream(); | ||||||
|         final String qualityText = MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution; |         if (videoStream != null) { | ||||||
|         qualityTextView.setText(qualityText); |             final String qualityText = MediaFormat.getNameById(videoStream.getFormatId()) + " " | ||||||
|  |                     + videoStream.resolution; | ||||||
|  |             qualityTextView.setText(qualityText); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         wasPlaying = simpleExoPlayer.getPlayWhenReady(); |         wasPlaying = simpleExoPlayer.getPlayWhenReady(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -864,8 +886,11 @@ public abstract class VideoPlayer extends BasePlayer | |||||||
|         return wasPlaying; |         return wasPlaying; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Nullable | ||||||
|     public VideoStream getSelectedVideoStream() { |     public VideoStream getSelectedVideoStream() { | ||||||
|         return availableStreams.get(selectedStreamIndex); |         return (selectedStreamIndex >= 0 && availableStreams != null && | ||||||
|  |                 availableStreams.size() > selectedStreamIndex) ? | ||||||
|  |                 availableStreams.get(selectedStreamIndex) : null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Handler getControlsVisibilityHandler() { |     public Handler getControlsVisibilityHandler() { | ||||||
|   | |||||||
| @@ -181,7 +181,9 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au | |||||||
|     public void onAudioInputFormatChanged(Format format) {} |     public void onAudioInputFormatChanged(Format format) {} | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onAudioTrackUnderrun(int i, long l, long l1) {} |     public void onAudioSinkUnderrun(int bufferSize, | ||||||
|  |                                     long bufferSizeMs, | ||||||
|  |                                     long elapsedSinceLastFeedMs) {} | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onAudioDisabled(DecoderCounters decoderCounters) {} |     public void onAudioDisabled(DecoderCounters decoderCounters) {} | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package org.schabi.newpipe.player.helper; | |||||||
|  |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
|  |  | ||||||
|  | import com.google.android.exoplayer2.C; | ||||||
| import com.google.android.exoplayer2.DefaultLoadControl; | import com.google.android.exoplayer2.DefaultLoadControl; | ||||||
| import com.google.android.exoplayer2.LoadControl; | import com.google.android.exoplayer2.LoadControl; | ||||||
| import com.google.android.exoplayer2.Renderer; | import com.google.android.exoplayer2.Renderer; | ||||||
| @@ -10,6 +11,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; | |||||||
| import com.google.android.exoplayer2.upstream.Allocator; | import com.google.android.exoplayer2.upstream.Allocator; | ||||||
| import com.google.android.exoplayer2.upstream.DefaultAllocator; | import com.google.android.exoplayer2.upstream.DefaultAllocator; | ||||||
|  |  | ||||||
|  | import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS; | ||||||
|  |  | ||||||
| public class LoadController implements LoadControl { | public class LoadController implements LoadControl { | ||||||
|  |  | ||||||
|     public static final String TAG = "LoadController"; |     public static final String TAG = "LoadController"; | ||||||
| @@ -23,16 +26,17 @@ public class LoadController implements LoadControl { | |||||||
|     public LoadController(final Context context) { |     public LoadController(final Context context) { | ||||||
|         this(PlayerHelper.getMinBufferMs(context), |         this(PlayerHelper.getMinBufferMs(context), | ||||||
|                 PlayerHelper.getMaxBufferMs(context), |                 PlayerHelper.getMaxBufferMs(context), | ||||||
|                 PlayerHelper.getBufferForPlaybackMs(context), |                 PlayerHelper.getBufferForPlaybackMs(context)); | ||||||
|                 PlayerHelper.getBufferForPlaybackAfterRebufferMs(context)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public LoadController(final int minBufferMs, |     public LoadController(final int minBufferMs, | ||||||
|                           final int maxBufferMs, |                           final int maxBufferMs, | ||||||
|                           final long bufferForPlaybackMs, |                           final int bufferForPlaybackMs) { | ||||||
|                           final long bufferForPlaybackAfterRebufferMs) { |         final DefaultAllocator allocator = new DefaultAllocator(true, | ||||||
|         final DefaultAllocator allocator = new DefaultAllocator(true, 65536); |                 C.DEFAULT_BUFFER_SEGMENT_SIZE); | ||||||
|         internalLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs); |  | ||||||
|  |         internalLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, | ||||||
|  |                 bufferForPlaybackMs, DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|   | |||||||
| @@ -66,9 +66,11 @@ public class PlayerHelper { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @NonNull |     @NonNull | ||||||
|     public static String captionLanguageOf(@NonNull final Subtitles subtitles) { |     public static String captionLanguageOf(@NonNull final Context context, | ||||||
|  |                                            @NonNull final Subtitles subtitles) { | ||||||
|         final String displayName = subtitles.getLocale().getDisplayName(subtitles.getLocale()); |         final String displayName = subtitles.getLocale().getDisplayName(subtitles.getLocale()); | ||||||
|         return displayName + (subtitles.isAutoGenerated() ? " (auto-generated)" : ""); |         return displayName + (subtitles.isAutoGenerated() ? | ||||||
|  |                 " (" + context.getString(R.string.caption_auto_generated)+ ")" : ""); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static String resizeTypeOf(@NonNull final Context context, |     public static String resizeTypeOf(@NonNull final Context context, | ||||||
| @@ -113,12 +115,8 @@ public class PlayerHelper { | |||||||
|         return 30000; |         return 30000; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static long getBufferForPlaybackMs(@NonNull final Context context) { |     public static int getBufferForPlaybackMs(@NonNull final Context context) { | ||||||
|         return 2500L; |         return 2500; | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long getBufferForPlaybackAfterRebufferMs(@NonNull final Context context) { |  | ||||||
|         return 5000L; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static boolean isUsingDSP(@NonNull final Context context) { |     public static boolean isUsingDSP(@NonNull final Context context) { | ||||||
|   | |||||||
| @@ -114,32 +114,10 @@ public final class DeferredMediaSource implements MediaSource { | |||||||
|  |  | ||||||
|         Log.d(TAG, "Loading: [" + stream.getTitle() + "] with url: " + stream.getUrl()); |         Log.d(TAG, "Loading: [" + stream.getTitle() + "] with url: " + stream.getUrl()); | ||||||
|  |  | ||||||
|         final Function<StreamInfo, MediaSource> onReceive = new Function<StreamInfo, MediaSource>() { |  | ||||||
|             @Override |  | ||||||
|             public MediaSource apply(StreamInfo streamInfo) throws Exception { |  | ||||||
|                 return onStreamInfoReceived(stream, streamInfo); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         final Consumer<MediaSource> onSuccess = new Consumer<MediaSource>() { |  | ||||||
|             @Override |  | ||||||
|             public void accept(MediaSource mediaSource) throws Exception { |  | ||||||
|                 onMediaSourceReceived(mediaSource); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         final Consumer<Throwable> onError = new Consumer<Throwable>() { |  | ||||||
|             @Override |  | ||||||
|             public void accept(Throwable throwable) throws Exception { |  | ||||||
|                 onStreamInfoError(throwable); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         loader = stream.getStream() |         loader = stream.getStream() | ||||||
|                 .observeOn(Schedulers.io()) |                 .map(streamInfo -> onStreamInfoReceived(stream, streamInfo)) | ||||||
|                 .map(onReceive) |  | ||||||
|                 .observeOn(AndroidSchedulers.mainThread()) |                 .observeOn(AndroidSchedulers.mainThread()) | ||||||
|                 .subscribe(onSuccess, onError); |                 .subscribe(this::onMediaSourceReceived, this::onStreamInfoError); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private MediaSource onStreamInfoReceived(@NonNull final PlayQueueItem item, |     private MediaSource onStreamInfoReceived(@NonNull final PlayQueueItem item, | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ import android.support.annotation.Nullable; | |||||||
| import android.util.Log; | import android.util.Log; | ||||||
|  |  | ||||||
| import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource; | import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource; | ||||||
| import com.google.android.exoplayer2.source.MediaSource; |  | ||||||
|  |  | ||||||
| import org.reactivestreams.Subscriber; | import org.reactivestreams.Subscriber; | ||||||
| import org.reactivestreams.Subscription; | import org.reactivestreams.Subscription; | ||||||
| @@ -21,6 +20,7 @@ import java.util.concurrent.TimeUnit; | |||||||
|  |  | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | import io.reactivex.android.schedulers.AndroidSchedulers; | ||||||
| import io.reactivex.annotations.NonNull; | import io.reactivex.annotations.NonNull; | ||||||
|  | import io.reactivex.disposables.CompositeDisposable; | ||||||
| import io.reactivex.disposables.Disposable; | import io.reactivex.disposables.Disposable; | ||||||
| import io.reactivex.disposables.SerialDisposable; | import io.reactivex.disposables.SerialDisposable; | ||||||
| import io.reactivex.functions.Consumer; | import io.reactivex.functions.Consumer; | ||||||
| @@ -48,6 +48,8 @@ public class MediaSourceManager { | |||||||
|     private Subscription playQueueReactor; |     private Subscription playQueueReactor; | ||||||
|     private SerialDisposable syncReactor; |     private SerialDisposable syncReactor; | ||||||
|  |  | ||||||
|  |     private PlayQueueItem syncedItem; | ||||||
|  |  | ||||||
|     private boolean isBlocked; |     private boolean isBlocked; | ||||||
|  |  | ||||||
|     public MediaSourceManager(@NonNull final PlaybackListener listener, |     public MediaSourceManager(@NonNull final PlaybackListener listener, | ||||||
| @@ -86,12 +88,7 @@ public class MediaSourceManager { | |||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     private DeferredMediaSource.Callback getSourceBuilder() { |     private DeferredMediaSource.Callback getSourceBuilder() { | ||||||
|         return new DeferredMediaSource.Callback() { |         return playbackListener::sourceOf; | ||||||
|             @Override |  | ||||||
|             public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) { |  | ||||||
|                 return playbackListener.sourceOf(item, info); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
| @@ -109,6 +106,7 @@ public class MediaSourceManager { | |||||||
|  |  | ||||||
|         playQueueReactor = null; |         playQueueReactor = null; | ||||||
|         syncReactor = null; |         syncReactor = null; | ||||||
|  |         syncedItem = null; | ||||||
|         sources = null; |         sources = null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -128,6 +126,8 @@ public class MediaSourceManager { | |||||||
|      * */ |      * */ | ||||||
|     public void reset() { |     public void reset() { | ||||||
|         tryBlock(); |         tryBlock(); | ||||||
|  |  | ||||||
|  |         syncedItem = null; | ||||||
|         populateSources(); |         populateSources(); | ||||||
|     } |     } | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
| @@ -241,22 +241,28 @@ public class MediaSourceManager { | |||||||
|         final PlayQueueItem currentItem = playQueue.getItem(); |         final PlayQueueItem currentItem = playQueue.getItem(); | ||||||
|         if (currentItem == null) return; |         if (currentItem == null) return; | ||||||
|  |  | ||||||
|         final Consumer<StreamInfo> syncPlayback = new Consumer<StreamInfo>() { |         final Consumer<StreamInfo> onSuccess = info -> syncInternal(currentItem, info); | ||||||
|             @Override |         final Consumer<Throwable> onError = throwable -> { | ||||||
|             public void accept(StreamInfo streamInfo) throws Exception { |             Log.e(TAG, "Sync error:", throwable); | ||||||
|                 playbackListener.sync(currentItem, streamInfo); |             syncInternal(currentItem, null); | ||||||
|             } |  | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         final Consumer<Throwable> onError = new Consumer<Throwable>() { |         if (syncedItem != currentItem) { | ||||||
|             @Override |             syncedItem = currentItem; | ||||||
|             public void accept(Throwable throwable) throws Exception { |             final Disposable sync = currentItem.getStream() | ||||||
|                 Log.e(TAG, "Sync error:", throwable); |                     .observeOn(AndroidSchedulers.mainThread()) | ||||||
|                 playbackListener.sync(currentItem,null); |                     .subscribe(onSuccess, onError); | ||||||
|             } |             syncReactor.set(sync); | ||||||
|         }; |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|         syncReactor.set(currentItem.getStream().subscribe(syncPlayback, onError)); |     private void syncInternal(@android.support.annotation.NonNull final PlayQueueItem item, | ||||||
|  |                               @Nullable final StreamInfo info) { | ||||||
|  |         if (playQueue == null || playbackListener == null) return; | ||||||
|  |         // Ensure the current item is up to date with the play queue | ||||||
|  |         if (playQueue.getItem() == item && playQueue.getItem() == syncedItem) { | ||||||
|  |             playbackListener.sync(syncedItem,info); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void loadDebounced() { |     private void loadDebounced() { | ||||||
| @@ -313,12 +319,7 @@ public class MediaSourceManager { | |||||||
|         return debouncedLoadSignal |         return debouncedLoadSignal | ||||||
|                 .debounce(loadDebounceMillis, TimeUnit.MILLISECONDS) |                 .debounce(loadDebounceMillis, TimeUnit.MILLISECONDS) | ||||||
|                 .observeOn(AndroidSchedulers.mainThread()) |                 .observeOn(AndroidSchedulers.mainThread()) | ||||||
|                 .subscribe(new Consumer<Long>() { |                 .subscribe(timestamp -> loadImmediate()); | ||||||
|             @Override |  | ||||||
|             public void accept(Long timestamp) throws Exception { |  | ||||||
|                 loadImmediate(); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Media Source List Manipulation |     // Media Source List Manipulation | ||||||
|   | |||||||
| @@ -33,6 +33,8 @@ public interface PlaybackListener { | |||||||
|      * Signals to the listener to synchronize the player's window to the manager's |      * Signals to the listener to synchronize the player's window to the manager's | ||||||
|      * window. |      * window. | ||||||
|      * |      * | ||||||
|  |      * Occurs once only per play queue item change. | ||||||
|  |      * | ||||||
|      * May be called only after unblock is called. |      * May be called only after unblock is called. | ||||||
|      * */ |      * */ | ||||||
|     void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info); |     void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info); | ||||||
|   | |||||||
| @@ -73,6 +73,10 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold | |||||||
|         playQueueItemBuilder.setOnSelectedListener(listener); |         playQueueItemBuilder.setOnSelectedListener(listener); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public void unsetSelectedListener() { | ||||||
|  |         playQueueItemBuilder.setOnSelectedListener(null); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void startReactor() { |     private void startReactor() { | ||||||
|         final Observer<PlayQueueEvent> observer = new Observer<PlayQueueEvent>() { |         final Observer<PlayQueueEvent> observer = new Observer<PlayQueueEvent>() { | ||||||
|             @Override |             @Override | ||||||
|   | |||||||
| @@ -104,17 +104,9 @@ public class PlayQueueItem implements Serializable { | |||||||
|  |  | ||||||
|     @NonNull |     @NonNull | ||||||
|     private Single<StreamInfo> getInfo() { |     private Single<StreamInfo> getInfo() { | ||||||
|         final Consumer<Throwable> onError = new Consumer<Throwable>() { |  | ||||||
|             @Override |  | ||||||
|             public void accept(Throwable throwable) throws Exception { |  | ||||||
|                 error = throwable; |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         return ExtractorHelper.getStreamInfo(this.serviceId, this.url, false) |         return ExtractorHelper.getStreamInfo(this.serviceId, this.url, false) | ||||||
|                 .subscribeOn(Schedulers.io()) |                 .subscribeOn(Schedulers.io()) | ||||||
|                 .observeOn(AndroidSchedulers.mainThread()) |                 .doOnError(throwable -> error = throwable); | ||||||
|                 .doOnError(onError); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     //////////////////////////////////////////////////////////////////////////// |     //////////////////////////////////////////////////////////////////////////// | ||||||
|   | |||||||
| @@ -53,24 +53,18 @@ public class PlayQueueItemBuilder { | |||||||
|  |  | ||||||
|         ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView, imageOptions); |         ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView, imageOptions); | ||||||
|  |  | ||||||
|         holder.itemRoot.setOnClickListener(new View.OnClickListener() { |         holder.itemRoot.setOnClickListener(view -> { | ||||||
|             @Override |             if (onItemClickListener != null) { | ||||||
|             public void onClick(View view) { |                 onItemClickListener.selected(item, view); | ||||||
|                 if (onItemClickListener != null) { |  | ||||||
|                     onItemClickListener.selected(item, view); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         holder.itemRoot.setOnLongClickListener(new View.OnLongClickListener() { |         holder.itemRoot.setOnLongClickListener(view -> { | ||||||
|             @Override |             if (onItemClickListener != null) { | ||||||
|             public boolean onLongClick(View view) { |                 onItemClickListener.held(item, view); | ||||||
|                 if (onItemClickListener != null) { |                 return true; | ||||||
|                     onItemClickListener.held(item, view); |  | ||||||
|                     return true; |  | ||||||
|                 } |  | ||||||
|                 return false; |  | ||||||
|             } |             } | ||||||
|  |             return false; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         holder.itemThumbnailView.setOnTouchListener(getOnTouchListener(holder)); |         holder.itemThumbnailView.setOnTouchListener(getOnTouchListener(holder)); | ||||||
| @@ -78,26 +72,21 @@ public class PlayQueueItemBuilder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private View.OnTouchListener getOnTouchListener(final PlayQueueItemHolder holder) { |     private View.OnTouchListener getOnTouchListener(final PlayQueueItemHolder holder) { | ||||||
|         return new View.OnTouchListener() { |         return (view, motionEvent) -> { | ||||||
|             @Override |             view.performClick(); | ||||||
|             public boolean onTouch(View view, MotionEvent motionEvent) { |             if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN | ||||||
|                 view.performClick(); |                     && onItemClickListener != null) { | ||||||
|                 if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { |                 onItemClickListener.onStartDrag(holder); | ||||||
|                     onItemClickListener.onStartDrag(holder); |  | ||||||
|                 } |  | ||||||
|                 return false; |  | ||||||
|             } |             } | ||||||
|  |             return false; | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private DisplayImageOptions buildImageOptions(final int widthPx, final int heightPx) { |     private DisplayImageOptions buildImageOptions(final int widthPx, final int heightPx) { | ||||||
|         final BitmapProcessor bitmapProcessor = new BitmapProcessor() { |         final BitmapProcessor bitmapProcessor = bitmap -> { | ||||||
|             @Override |             final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, widthPx, heightPx, false); | ||||||
|             public Bitmap process(Bitmap bitmap) { |             bitmap.recycle(); | ||||||
|                 final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, widthPx, heightPx, false); |             return resizedBitmap; | ||||||
|                 bitmap.recycle(); |  | ||||||
|                 return resizedBitmap; |  | ||||||
|             } |  | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         return new DisplayImageOptions.Builder() |         return new DisplayImageOptions.Builder() | ||||||
|   | |||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | package org.schabi.newpipe.settings; | ||||||
|  |  | ||||||
|  | import android.os.Bundle; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.R; | ||||||
|  |  | ||||||
|  | public class DebugSettingsFragment extends BasePreferenceFragment { | ||||||
|  |     @Override | ||||||
|  |     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { | ||||||
|  |         addPreferencesFromResource(R.xml.debug_settings); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -3,11 +3,19 @@ package org.schabi.newpipe.settings; | |||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.v7.preference.Preference; | import android.support.v7.preference.Preference; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.BuildConfig; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
|  |  | ||||||
| public class MainSettingsFragment extends BasePreferenceFragment { | public class MainSettingsFragment extends BasePreferenceFragment { | ||||||
|  |     public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { |     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { | ||||||
|         addPreferencesFromResource(R.xml.main_settings); |         addPreferencesFromResource(R.xml.main_settings); | ||||||
|  |  | ||||||
|  |         if (!DEBUG) { | ||||||
|  |             final Preference debug = findPreference(getString(R.string.debug_pref_screen_key)); | ||||||
|  |             getPreferenceScreen().removePreference(debug); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -64,6 +64,7 @@ public class NewPipeSettings { | |||||||
|         PreferenceManager.setDefaultValues(context, R.xml.history_settings, true); |         PreferenceManager.setDefaultValues(context, R.xml.history_settings, true); | ||||||
|         PreferenceManager.setDefaultValues(context, R.xml.main_settings, true); |         PreferenceManager.setDefaultValues(context, R.xml.main_settings, true); | ||||||
|         PreferenceManager.setDefaultValues(context, R.xml.video_audio_settings, true); |         PreferenceManager.setDefaultValues(context, R.xml.video_audio_settings, true); | ||||||
|  |         PreferenceManager.setDefaultValues(context, R.xml.debug_settings, true); | ||||||
|  |  | ||||||
|         getVideoDownloadFolder(context); |         getVideoDownloadFolder(context); | ||||||
|         getAudioDownloadFolder(context); |         getAudioDownloadFolder(context); | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/ic_bug_report_black_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 258 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/ic_bug_report_white_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 267 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-mdpi/ic_bug_report_black_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 200 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-mdpi/ic_bug_report_white_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 207 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xhdpi/ic_bug_report_black_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 289 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xhdpi/ic_bug_report_white_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 303 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/ic_bug_report_black_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 427 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/ic_bug_report_white_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 445 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxxhdpi/ic_bug_report_black_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 527 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxxhdpi/ic_bug_report_white_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 566 B | 
| @@ -1,12 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> |  | ||||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" |  | ||||||
|     xmlns:app="http://schemas.android.com/apk/res-auto"> |  | ||||||
|  |  | ||||||
|     <item android:id="@+id/action_toggle_heap_dump" |  | ||||||
|         android:orderInCategory="9999" |  | ||||||
|         android:checkable="true" |  | ||||||
|         android:title="@string/toggle_leak_canary" |  | ||||||
|         android:visible="true" |  | ||||||
|         app:showAsAction="never"/> |  | ||||||
|  |  | ||||||
| </menu> |  | ||||||
| @@ -373,5 +373,4 @@ | |||||||
|     <string name="caption_none">Keine Untertitel</string> |     <string name="caption_none">Keine Untertitel</string> | ||||||
|  |  | ||||||
|     <string name="caption_font_size_settings_title">Schriftgröße der Untertitel</string> |     <string name="caption_font_size_settings_title">Schriftgröße der Untertitel</string> | ||||||
|     <string name="toggle_leak_canary">"Speicherlecks nachverfolgen "</string> |  | ||||||
|     </resources> |     </resources> | ||||||
|   | |||||||
| @@ -395,10 +395,6 @@ | |||||||
|     <string name="normal_caption_font_size">Carattere normale</string> |     <string name="normal_caption_font_size">Carattere normale</string> | ||||||
|     <string name="larger_caption_font_size">Carattere più grande</string> |     <string name="larger_caption_font_size">Carattere più grande</string> | ||||||
|  |  | ||||||
|     <string name="toggle_leak_canary">Controllo delle perdite</string> | 	<string name="drawer_header_action_paceholder_text">A breve qualcosa si troverà qui ;D</string> | ||||||
|     <string name="enable_leak_canary_notice">Controllo delle perdite di memoria abilitato, l\'applicazione può non rispondere mentre effettua il dumping dell\'heap</string> |  | ||||||
|     <string name="disable_leak_canary_notice">Controllo delle perdite di memoria disabilitato</string> |  | ||||||
| <string name="drawer_header_action_paceholder_text">A breve qualcosa si troverà qui ;D</string> |  | ||||||
|  |  | ||||||
|  | </resources> | ||||||
|     </resources> |  | ||||||
|   | |||||||
| @@ -382,8 +382,4 @@ | |||||||
|     <string name="smaller_caption_font_size">Mindre skrift</string> |     <string name="smaller_caption_font_size">Mindre skrift</string> | ||||||
|     <string name="normal_caption_font_size">Normal skrift</string> |     <string name="normal_caption_font_size">Normal skrift</string> | ||||||
|     <string name="larger_caption_font_size">Større skrift</string> |     <string name="larger_caption_font_size">Større skrift</string> | ||||||
|  |  | ||||||
|     <string name="toggle_leak_canary">Hold oppsyn med lekkasjer</string> |  | ||||||
|     <string name="enable_leak_canary_notice">Oppsyn med minnelekasjer påslått, programmet kan slutte å svare under haug-dumping</string> |  | ||||||
|     <string name="disable_leak_canary_notice">Oppsyn med minnelekasjer slått av</string> |  | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -391,10 +391,5 @@ te openen in pop-upmodus</string> | |||||||
|     <string name="normal_caption_font_size">Normaal lettertype</string> |     <string name="normal_caption_font_size">Normaal lettertype</string> | ||||||
|     <string name="larger_caption_font_size">Groter lettertype</string> |     <string name="larger_caption_font_size">Groter lettertype</string> | ||||||
|  |  | ||||||
|     <string name="toggle_leak_canary">Controleren op lekken</string> | 	<string name="drawer_header_action_paceholder_text">Hier zal binnenkort iets verschijnen ;D</string> | ||||||
|     <string name="enable_leak_canary_notice">Controleren op geheugenlekken ingeschakeld, tijdens heapdumping kan de app tijdelijk niet reageren</string> | </resources> | ||||||
|     <string name="disable_leak_canary_notice">Controleren op geheugenlekken uitgeschakeld</string> |  | ||||||
| <string name="drawer_header_action_paceholder_text">Hier zal binnenkort iets verschijnen ;D</string> |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     </resources> |  | ||||||
|   | |||||||
| @@ -368,8 +368,4 @@ abrir em modo popup</string> | |||||||
|     <string name="smaller_caption_font_size">Fonte menor</string> |     <string name="smaller_caption_font_size">Fonte menor</string> | ||||||
|     <string name="normal_caption_font_size">Fonte normal</string> |     <string name="normal_caption_font_size">Fonte normal</string> | ||||||
|     <string name="larger_caption_font_size">Maior fonte</string> |     <string name="larger_caption_font_size">Maior fonte</string> | ||||||
|  |  | ||||||
|     <string name="toggle_leak_canary">Monitorar vazamentos de memória</string> |  | ||||||
|     <string name="enable_leak_canary_notice">Monitoramento de vazamentos de memória habilitado, o aplicativo pode ficar sem responder quando estiver descarregando pilha de memória</string> |  | ||||||
|     <string name="disable_leak_canary_notice">Monitoramento de vazamentos de memória desabilitado</string> |  | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -391,8 +391,4 @@ otvorenie okna na popredí</string> | |||||||
|     <string name="smaller_caption_font_size">Menšie Písmo</string> |     <string name="smaller_caption_font_size">Menšie Písmo</string> | ||||||
|     <string name="normal_caption_font_size">Normálne Písmo</string> |     <string name="normal_caption_font_size">Normálne Písmo</string> | ||||||
|     <string name="larger_caption_font_size">Väčšie Písmo</string> |     <string name="larger_caption_font_size">Väčšie Písmo</string> | ||||||
|  |  | ||||||
|     <string name="toggle_leak_canary">Monitorovanie pretečenia</string> |  | ||||||
|     <string name="enable_leak_canary_notice">Monitorovanie pretečenia pamäte je povolené, pri hromadnom zbere môže aplikácia prestať reagovať</string> |  | ||||||
|     <string name="disable_leak_canary_notice">Monitorovanie pretečenia pamäte je vypnuté</string> |  | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -384,8 +384,4 @@ | |||||||
|     <string name="smaller_caption_font_size">Küçük Yazı Tipi</string> |     <string name="smaller_caption_font_size">Küçük Yazı Tipi</string> | ||||||
|     <string name="normal_caption_font_size">Olağan Yazı Tipi</string> |     <string name="normal_caption_font_size">Olağan Yazı Tipi</string> | ||||||
|     <string name="larger_caption_font_size">Büyük Yazı Tipi</string> |     <string name="larger_caption_font_size">Büyük Yazı Tipi</string> | ||||||
|  |  | ||||||
|     <string name="toggle_leak_canary">Sızıntıları Gözlemle</string> |  | ||||||
|     <string name="enable_leak_canary_notice">Bellek sızıntısı gözlemleme etkinleştirildi, uygulama yığın atımı sırasında yanıtsız kalabilir</string> |  | ||||||
|     <string name="disable_leak_canary_notice">Bellek sızıntısı gözlemleme devre dışı</string> |  | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ | |||||||
|     <attr name="search_add" format="reference"/> |     <attr name="search_add" format="reference"/> | ||||||
|     <attr name="options" format="reference"/> |     <attr name="options" format="reference"/> | ||||||
|     <attr name="play" format="reference"/> |     <attr name="play" format="reference"/> | ||||||
|  |     <attr name="bug" format="reference"/> | ||||||
|     <attr name="settings" format="reference"/> |     <attr name="settings" format="reference"/> | ||||||
|     <attr name="ic_hot" format="reference"/> |     <attr name="ic_hot" format="reference"/> | ||||||
|     <attr name="ic_channel" format="reference"/> |     <attr name="ic_channel" format="reference"/> | ||||||
|   | |||||||
| @@ -84,8 +84,11 @@ | |||||||
|     <string name="last_orientation_landscape_key" translatable="false">last_orientation_landscape_key</string> |     <string name="last_orientation_landscape_key" translatable="false">last_orientation_landscape_key</string> | ||||||
|  |  | ||||||
|     <!-- DEBUG ONLY --> |     <!-- DEBUG ONLY --> | ||||||
|  |     <string name="debug_pref_screen_key" translatable="false">debug_pref_screen_key</string> | ||||||
|     <string name="allow_heap_dumping_key" translatable="false">allow_heap_dumping_key</string> |     <string name="allow_heap_dumping_key" translatable="false">allow_heap_dumping_key</string> | ||||||
|  |  | ||||||
|  |     <string name="allow_disposed_exceptions_key" translatable="false">allow_disposed_exceptions_key</string> | ||||||
|  |  | ||||||
|     <!-- THEMES --> |     <!-- THEMES --> | ||||||
|     <string name="theme_key" translatable="false">theme</string> |     <string name="theme_key" translatable="false">theme</string> | ||||||
|     <string name="light_theme_key" translatable="false">light_theme</string> |     <string name="light_theme_key" translatable="false">light_theme</string> | ||||||
|   | |||||||
| @@ -98,6 +98,7 @@ | |||||||
|     <string name="settings_category_popup_title">Popup</string> |     <string name="settings_category_popup_title">Popup</string> | ||||||
|     <string name="settings_category_appearance_title">Appearance</string> |     <string name="settings_category_appearance_title">Appearance</string> | ||||||
|     <string name="settings_category_other_title">Other</string> |     <string name="settings_category_other_title">Other</string> | ||||||
|  |     <string name="settings_category_debug_title">Debug</string> | ||||||
|     <string name="background_player_playing_toast">Playing in background</string> |     <string name="background_player_playing_toast">Playing in background</string> | ||||||
|     <string name="popup_playing_toast">Playing in popup mode</string> |     <string name="popup_playing_toast">Playing in popup mode</string> | ||||||
|     <string name="background_player_append">Queued on background player</string> |     <string name="background_player_append">Queued on background player</string> | ||||||
| @@ -406,13 +407,17 @@ | |||||||
|     <string name="resize_fill">FILL</string> |     <string name="resize_fill">FILL</string> | ||||||
|     <string name="resize_zoom">ZOOM</string> |     <string name="resize_zoom">ZOOM</string> | ||||||
|  |  | ||||||
|  |     <string name="caption_auto_generated">Auto-generated</string> | ||||||
|     <string name="caption_font_size_settings_title">Caption Font Size</string> |     <string name="caption_font_size_settings_title">Caption Font Size</string> | ||||||
|     <string name="smaller_caption_font_size">Smaller Font</string> |     <string name="smaller_caption_font_size">Smaller Font</string> | ||||||
|     <string name="normal_caption_font_size">Normal Font</string> |     <string name="normal_caption_font_size">Normal Font</string> | ||||||
|     <string name="larger_caption_font_size">Larger Font</string> |     <string name="larger_caption_font_size">Larger Font</string> | ||||||
|  |  | ||||||
|     <!-- Debug Only --> |     <!-- Debug Settings --> | ||||||
|     <string name="toggle_leak_canary">Monitor Leaks</string> |     <string name="enable_leak_canary_title">Enable LeakCanary</string> | ||||||
|     <string name="enable_leak_canary_notice">Memory leak monitoring enabled, app may become unresponsive when heap dumping</string> |     <string name="enable_leak_canary_summary">Memory leak monitoring may cause app to become unresponsive when heap dumping</string> | ||||||
|     <string name="disable_leak_canary_notice">Memory leak monitoring disabled</string> |  | ||||||
|  |     <string name="enable_disposed_exceptions_title">Report Out-of-Lifecycle Errors</string> | ||||||
|  |     <string name="enable_disposed_exceptions_summary">Force reporting of undeliverable Rx exceptions occurring outside of fragment or activity lifecycle after dispose</string> | ||||||
|  |  | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ | |||||||
|         <item name="thumbs_up">@drawable/ic_thumb_up_black_24dp</item> |         <item name="thumbs_up">@drawable/ic_thumb_up_black_24dp</item> | ||||||
|         <item name="thumbs_down">@drawable/ic_thumb_down_black_24dp</item> |         <item name="thumbs_down">@drawable/ic_thumb_down_black_24dp</item> | ||||||
|         <item name="info">@drawable/ic_info_outline_black_24dp</item> |         <item name="info">@drawable/ic_info_outline_black_24dp</item> | ||||||
|  |         <item name="bug">@drawable/ic_bug_report_black_24dp</item> | ||||||
|         <item name="audio">@drawable/ic_headset_black_24dp</item> |         <item name="audio">@drawable/ic_headset_black_24dp</item> | ||||||
|         <item name="clear_history">@drawable/ic_delete_sweep_white_24dp</item> |         <item name="clear_history">@drawable/ic_delete_sweep_white_24dp</item> | ||||||
|         <item name="download">@drawable/ic_file_download_black_24dp</item> |         <item name="download">@drawable/ic_file_download_black_24dp</item> | ||||||
| @@ -74,6 +75,7 @@ | |||||||
|         <item name="thumbs_down">@drawable/ic_thumb_down_white_24dp</item> |         <item name="thumbs_down">@drawable/ic_thumb_down_white_24dp</item> | ||||||
|         <item name="audio">@drawable/ic_headset_white_24dp</item> |         <item name="audio">@drawable/ic_headset_white_24dp</item> | ||||||
|         <item name="info">@drawable/ic_info_outline_white_24dp</item> |         <item name="info">@drawable/ic_info_outline_white_24dp</item> | ||||||
|  |         <item name="bug">@drawable/ic_bug_report_white_24dp</item> | ||||||
|         <item name="clear_history">@drawable/ic_delete_sweep_black_24dp</item> |         <item name="clear_history">@drawable/ic_delete_sweep_black_24dp</item> | ||||||
|         <item name="download">@drawable/ic_file_download_white_24dp</item> |         <item name="download">@drawable/ic_file_download_white_24dp</item> | ||||||
|         <item name="share">@drawable/ic_share_white_24dp</item> |         <item name="share">@drawable/ic_share_white_24dp</item> | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								app/src/main/res/xml/debug_settings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <PreferenceScreen | ||||||
|  |     xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:key="general_preferences" | ||||||
|  |     android:title="@string/settings_category_debug_title"> | ||||||
|  |  | ||||||
|  |     <SwitchPreference | ||||||
|  |         android:defaultValue="false" | ||||||
|  |         android:key="@string/allow_heap_dumping_key" | ||||||
|  |         android:title="@string/enable_leak_canary_title" | ||||||
|  |         android:summary="@string/enable_leak_canary_summary"/> | ||||||
|  |  | ||||||
|  |     <SwitchPreference | ||||||
|  |         android:defaultValue="false" | ||||||
|  |         android:key="@string/allow_disposed_exceptions_key" | ||||||
|  |         android:title="@string/enable_disposed_exceptions_title" | ||||||
|  |         android:summary="@string/enable_disposed_exceptions_summary"/> | ||||||
|  | </PreferenceScreen> | ||||||
| @@ -28,4 +28,10 @@ | |||||||
|         android:fragment="org.schabi.newpipe.settings.ContentSettingsFragment" |         android:fragment="org.schabi.newpipe.settings.ContentSettingsFragment" | ||||||
|         android:icon="?attr/language" |         android:icon="?attr/language" | ||||||
|         android:title="@string/content"/> |         android:title="@string/content"/> | ||||||
|  |  | ||||||
|  |     <PreferenceScreen | ||||||
|  |         android:fragment="org.schabi.newpipe.settings.DebugSettingsFragment" | ||||||
|  |         android:icon="?attr/bug" | ||||||
|  |         android:title="@string/settings_category_debug_title" | ||||||
|  |         android:key="@string/debug_pref_screen_key"/> | ||||||
| </PreferenceScreen> | </PreferenceScreen> | ||||||
|   | |||||||
 Weblate
					Weblate