1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-06-25 06:33:21 +00:00

Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Weblate 2018-02-22 22:41:15 +01:00
commit eb6dac2e9f
45 changed files with 373 additions and 269 deletions

View File

@ -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'

View File

@ -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)

View File

@ -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;
}
} }

View File

@ -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());
} }
} }

View File

@ -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);
} }

View File

@ -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);

View File

@ -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();

View File

@ -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;
} }

View File

@ -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() {

View File

@ -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) {}

View File

@ -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);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////

View File

@ -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) {

View File

@ -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,

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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);
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////

View File

@ -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()

View File

@ -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);
}
}

View File

@ -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);
}
} }
} }

View File

@ -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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"/>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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>