Update extractor and refactored NewPipe
This commit is contained in:
parent
bddd9b3409
commit
146d4a8365
254 changed files with 7836 additions and 7302 deletions
|
|
@ -2,17 +2,24 @@ package org.schabi.newpipe.util;
|
|||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.player.BasePlayer;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
|
||||
public class AnimationUtils {
|
||||
private static final String TAG = "AnimationUtils";
|
||||
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
public enum Type {
|
||||
ALPHA, SCALE_AND_ALPHA
|
||||
ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA
|
||||
}
|
||||
|
||||
public static void animateView(View view, boolean enterOrExit, long duration) {
|
||||
|
|
@ -47,7 +54,16 @@ public class AnimationUtils {
|
|||
*/
|
||||
public static void animateView(final View view, Type animationType, boolean enterOrExit, long duration, long delay, Runnable execOnEnd) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "animateView() called with: view = [" + view + "], animationType = [" + animationType + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], delay = [" + delay + "], execOnEnd = [" + execOnEnd + "]");
|
||||
String id;
|
||||
try {
|
||||
id = view.getResources().getResourceEntryName(view.getId());
|
||||
} catch (Exception e) {
|
||||
id = view.getId() + "";
|
||||
}
|
||||
|
||||
String msg = String.format("%8s → [%s:%s] [%s %s:%s] execOnEnd=%s",
|
||||
enterOrExit, view.getClass().getSimpleName(), id, animationType, duration, delay, execOnEnd);
|
||||
Log.d(TAG, "animateView()" + msg);
|
||||
}
|
||||
|
||||
if (view.getVisibility() == View.VISIBLE && enterOrExit) {
|
||||
|
|
@ -76,15 +92,132 @@ public class AnimationUtils {
|
|||
case SCALE_AND_ALPHA:
|
||||
animateScaleAndAlpha(view, enterOrExit, duration, delay, execOnEnd);
|
||||
break;
|
||||
case LIGHT_SCALE_AND_ALPHA:
|
||||
animateLightScaleAndAlpha(view, enterOrExit, duration, delay, execOnEnd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the background color of a view
|
||||
*/
|
||||
public static void animateBackgroundColor(final View view, long duration, @ColorInt final int colorStart, @ColorInt final int colorEnd) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "animateBackgroundColor() called with: view = [" + view + "], duration = [" + duration + "], colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]");
|
||||
}
|
||||
|
||||
final int[][] EMPTY = new int[][]{new int[0]};
|
||||
ValueAnimator viewPropertyAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), colorStart, colorEnd);
|
||||
viewPropertyAnimator.setInterpolator(new FastOutSlowInInterpolator());
|
||||
viewPropertyAnimator.setDuration(duration);
|
||||
viewPropertyAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
ViewCompat.setBackgroundTintList(view, new ColorStateList(EMPTY, new int[]{(int) animation.getAnimatedValue()}));
|
||||
}
|
||||
});
|
||||
viewPropertyAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
ViewCompat.setBackgroundTintList(view, new ColorStateList(EMPTY, new int[]{colorEnd}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
onAnimationEnd(animation);
|
||||
}
|
||||
});
|
||||
viewPropertyAnimator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the text color of any view that extends {@link TextView} (Buttons, EditText...)
|
||||
*/
|
||||
public static void animateTextColor(final TextView view, long duration, @ColorInt final int colorStart, @ColorInt final int colorEnd) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "animateTextColor() called with: view = [" + view + "], duration = [" + duration + "], colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]");
|
||||
}
|
||||
|
||||
ValueAnimator viewPropertyAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), colorStart, colorEnd);
|
||||
viewPropertyAnimator.setInterpolator(new FastOutSlowInInterpolator());
|
||||
viewPropertyAnimator.setDuration(duration);
|
||||
viewPropertyAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
view.setTextColor((int) animation.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
viewPropertyAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
view.setTextColor(colorEnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
view.setTextColor(colorEnd);
|
||||
}
|
||||
});
|
||||
viewPropertyAnimator.start();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Internals
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private static void animateAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void animateScaleAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.setAlpha(0f);
|
||||
view.setScaleX(.8f);
|
||||
view.setScaleY(.8f);
|
||||
view.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).scaleX(1f).scaleY(1f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
view.setScaleX(1f);
|
||||
view.setScaleY(1f);
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).scaleX(.8f).scaleY(.8f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void animateLightScaleAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.setAlpha(.5f);
|
||||
view.setScaleX(.95f);
|
||||
view.setScaleY(.95f);
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).scaleX(1f).scaleY(1f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
|
|
@ -94,27 +227,8 @@ public class AnimationUtils {
|
|||
view.setAlpha(1f);
|
||||
view.setScaleX(1f);
|
||||
view.setScaleY(1f);
|
||||
view.animate().alpha(0f).scaleX(.8f).scaleY(.8f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void animateAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.animate().alpha(1f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
view.animate().alpha(0f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).scaleX(.95f).scaleY(.95f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
|
|
|
|||
236
app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
Normal file
236
app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
|
||||
* Extractors.java is part of NewPipe
|
||||
*
|
||||
* License: GPL-3.0+
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
import org.schabi.newpipe.extractor.ListExtractor.NextItemsResult;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.search.SearchEngine;
|
||||
import org.schabi.newpipe.extractor.search.SearchResult;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
|
||||
import java.io.InterruptedIOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.MaybeSource;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.functions.Function;
|
||||
|
||||
public final class ExtractorHelper {
|
||||
private static final String TAG = ExtractorHelper.class.getSimpleName();
|
||||
private static final InfoCache cache = InfoCache.getInstance();
|
||||
|
||||
private ExtractorHelper() {
|
||||
//no instance
|
||||
}
|
||||
|
||||
public static Single<SearchResult> searchFor(final int serviceId, final String query, final int pageNumber, final String searchLanguage, final SearchEngine.Filter filter) {
|
||||
return Single.fromCallable(new Callable<SearchResult>() {
|
||||
@Override
|
||||
public SearchResult call() throws Exception {
|
||||
return SearchResult.getSearchResult(NewPipe.getService(serviceId).getSearchEngine(),
|
||||
query, pageNumber, searchLanguage, filter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Single<NextItemsResult> getMoreSearchItems(final int serviceId, final String query, final int nextPageNumber, final String searchLanguage, final SearchEngine.Filter filter) {
|
||||
return searchFor(serviceId, query, nextPageNumber, searchLanguage, filter)
|
||||
.map(new Function<SearchResult, NextItemsResult>() {
|
||||
@Override
|
||||
public NextItemsResult apply(@NonNull SearchResult searchResult) throws Exception {
|
||||
return new NextItemsResult(searchResult.resultList, nextPageNumber + "", searchResult.errors);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Single<List<String>> suggestionsFor(final int serviceId, final String query, final String searchLanguage) {
|
||||
return Single.fromCallable(new Callable<List<String>>() {
|
||||
@Override
|
||||
public List<String> call() throws Exception {
|
||||
return NewPipe.getService(serviceId).getSuggestionExtractor().suggestionList(query, searchLanguage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Single<StreamInfo> getStreamInfo(final int serviceId, final String url, boolean forceLoad) {
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<StreamInfo>() {
|
||||
@Override
|
||||
public StreamInfo call() throws Exception {
|
||||
return StreamInfo.getInfo(NewPipe.getService(serviceId), url);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static Single<ChannelInfo> getChannelInfo(final int serviceId, final String url, boolean forceLoad) {
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<ChannelInfo>() {
|
||||
@Override
|
||||
public ChannelInfo call() throws Exception {
|
||||
return ChannelInfo.getInfo(NewPipe.getService(serviceId), url);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static Single<NextItemsResult> getMoreChannelItems(final int serviceId, final String nextStreamsUrl) {
|
||||
return Single.fromCallable(new Callable<NextItemsResult>() {
|
||||
@Override
|
||||
public NextItemsResult call() throws Exception {
|
||||
return ChannelInfo.getMoreItems(NewPipe.getService(serviceId), nextStreamsUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId, final String url, boolean forceLoad) {
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<PlaylistInfo>() {
|
||||
@Override
|
||||
public PlaylistInfo call() throws Exception {
|
||||
return PlaylistInfo.getInfo(NewPipe.getService(serviceId), url);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static Single<NextItemsResult> getMorePlaylistItems(final int serviceId, final String nextStreamsUrl) {
|
||||
return Single.fromCallable(new Callable<NextItemsResult>() {
|
||||
@Override
|
||||
public NextItemsResult call() throws Exception {
|
||||
return PlaylistInfo.getMoreItems(NewPipe.getService(serviceId), nextStreamsUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Check if we can load it from the cache (forceLoad parameter), if we can't, load from the network (Single loadFromNetwork)
|
||||
* and put the results in the cache.
|
||||
*/
|
||||
private static <I extends Info> Single<I> checkCache(boolean forceLoad, int serviceId, String url, Single<I> loadFromNetwork) {
|
||||
loadFromNetwork = loadFromNetwork.doOnSuccess(new Consumer<I>() {
|
||||
@Override
|
||||
public void accept(@NonNull I i) throws Exception {
|
||||
cache.putInfo(i);
|
||||
}
|
||||
});
|
||||
|
||||
Single<I> load;
|
||||
if (forceLoad) {
|
||||
cache.removeInfo(serviceId, url);
|
||||
load = loadFromNetwork;
|
||||
} else {
|
||||
load = Maybe.concat(ExtractorHelper.<I>loadFromCache(serviceId, url), loadFromNetwork.toMaybe())
|
||||
.firstElement() //Take the first valid
|
||||
.toSingle();
|
||||
}
|
||||
|
||||
return load;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation uses the {@link InfoCache} to get cached results
|
||||
*/
|
||||
public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url) {
|
||||
return Maybe.defer(new Callable<MaybeSource<? extends I>>() {
|
||||
@Override
|
||||
public MaybeSource<? extends I> call() throws Exception {
|
||||
//noinspection unchecked
|
||||
I info = (I) cache.getFromKey(serviceId, url);
|
||||
if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info);
|
||||
|
||||
// Only return info if it's not null (it is cached)
|
||||
if (info != null) {
|
||||
return Maybe.just(info);
|
||||
}
|
||||
|
||||
return Maybe.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if throwable have the cause that can be assignable from the causes to check.
|
||||
*
|
||||
* @see Class#isAssignableFrom(Class)
|
||||
*/
|
||||
public static boolean hasAssignableCauseThrowable(Throwable throwable, Class<?>... causesToCheck) {
|
||||
// Check if getCause is not the same as cause (the getCause is already the root),
|
||||
// as it will cause a infinite loop if it is
|
||||
Throwable cause, getCause = throwable;
|
||||
|
||||
for (Class<?> causesEl : causesToCheck) {
|
||||
if (throwable.getClass().isAssignableFrom(causesEl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
while ((cause = throwable.getCause()) != null && getCause != cause) {
|
||||
getCause = cause;
|
||||
for (Class<?> causesEl : causesToCheck) {
|
||||
if (cause.getClass().isAssignableFrom(causesEl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if throwable have the exact cause from one of the causes to check.
|
||||
*/
|
||||
public static boolean hasExactCauseThrowable(Throwable throwable, Class<?>... causesToCheck) {
|
||||
// Check if getCause is not the same as cause (the getCause is already the root),
|
||||
// as it will cause a infinite loop if it is
|
||||
Throwable cause, getCause = throwable;
|
||||
|
||||
for (Class<?> causesEl : causesToCheck) {
|
||||
if (throwable.getClass().equals(causesEl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
while ((cause = throwable.getCause()) != null && getCause != cause) {
|
||||
getCause = cause;
|
||||
for (Class<?> causesEl : causesToCheck) {
|
||||
if (cause.getClass().equals(causesEl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if throwable have Interrupted* exception as one of its causes.
|
||||
*/
|
||||
public static boolean isInterruptedCaused(Throwable throwable) {
|
||||
return ExtractorHelper.hasExactCauseThrowable(throwable, InterruptedIOException.class, InterruptedException.class);
|
||||
}
|
||||
}
|
||||
100
app/src/main/java/org/schabi/newpipe/util/InfoCache.java
Normal file
100
app/src/main/java/org/schabi/newpipe/util/InfoCache.java
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
|
||||
* InfoCache.java is part of NewPipe
|
||||
*
|
||||
* License: GPL-3.0+
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.util.LruCache;
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
|
||||
|
||||
public final class InfoCache {
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
private final String TAG = getClass().getSimpleName();
|
||||
|
||||
private static final InfoCache instance = new InfoCache();
|
||||
private static final int MAX_ITEMS_ON_CACHE = 60;
|
||||
/**
|
||||
* Trim the cache to this size
|
||||
*/
|
||||
private static final int TRIM_CACHE_TO = 30;
|
||||
|
||||
// TODO: Replace to one with timeout (like the one from guava)
|
||||
private static final LruCache<String, Info> lruCache = new LruCache<>(MAX_ITEMS_ON_CACHE);
|
||||
|
||||
private InfoCache() {
|
||||
//no instance
|
||||
}
|
||||
|
||||
public static InfoCache getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Info getFromKey(int serviceId, @NonNull String url) {
|
||||
if (DEBUG) Log.d(TAG, "getFromKey() called with: serviceId = [" + serviceId + "], url = [" + url + "]");
|
||||
synchronized (lruCache) {
|
||||
return lruCache.get(serviceId + url);
|
||||
}
|
||||
}
|
||||
|
||||
public void putInfo(@NonNull Info info) {
|
||||
if (DEBUG) Log.d(TAG, "putInfo() called with: info = [" + info + "]");
|
||||
synchronized (lruCache) {
|
||||
lruCache.put(info.service_id + info.url, info);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeInfo(@NonNull Info info) {
|
||||
if (DEBUG) Log.d(TAG, "removeInfo() called with: info = [" + info + "]");
|
||||
synchronized (lruCache) {
|
||||
lruCache.remove(info.service_id + info.url);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeInfo(int serviceId, @NonNull String url) {
|
||||
if (DEBUG) Log.d(TAG, "removeInfo() called with: serviceId = [" + serviceId + "], url = [" + url + "]");
|
||||
synchronized (lruCache) {
|
||||
lruCache.remove(serviceId + url);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
if (DEBUG) Log.d(TAG, "clearCache() called");
|
||||
synchronized (lruCache) {
|
||||
lruCache.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void trimCache() {
|
||||
if (DEBUG) Log.d(TAG, "trimCache() called");
|
||||
synchronized (lruCache) {
|
||||
lruCache.trimToSize(TRIM_CACHE_TO);
|
||||
}
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
synchronized (lruCache) {
|
||||
return lruCache.size();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
269
app/src/main/java/org/schabi/newpipe/util/ListHelper.java
Normal file
269
app/src/main/java/org/schabi/newpipe/util/ListHelper.java
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public final class ListHelper {
|
||||
|
||||
private static final List<String> HIGH_RESOLUTION_LIST = Arrays.asList("1440p", "2160p", "1440p60", "2160p60");
|
||||
|
||||
/**
|
||||
* Return the index of the default stream in the list, based on the parameters
|
||||
* defaultResolution and defaultFormat
|
||||
*
|
||||
* @return index of the default resolution&format
|
||||
*/
|
||||
public static int getDefaultResolutionIndex(String defaultResolution, String bestResolutionKey, MediaFormat defaultFormat, List<VideoStream> videoStreams) {
|
||||
if (videoStreams == null || videoStreams.isEmpty()) return -1;
|
||||
|
||||
sortStreamList(videoStreams, false);
|
||||
if (defaultResolution.equals(bestResolutionKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int defaultStreamIndex = getDefaultStreamIndex(defaultResolution, defaultFormat, videoStreams);
|
||||
if (defaultStreamIndex == -1 && defaultResolution.contains("p60")) {
|
||||
defaultStreamIndex = getDefaultStreamIndex(defaultResolution.replace("p60", "p"), defaultFormat, videoStreams);
|
||||
}
|
||||
|
||||
// this is actually an error,
|
||||
// but maybe there is really no stream fitting to the default value.
|
||||
if (defaultStreamIndex == -1) return 0;
|
||||
|
||||
return defaultStreamIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
*/
|
||||
public static int getDefaultResolutionIndex(Context context, List<VideoStream> videoStreams) {
|
||||
SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (defaultPreferences == null) return 0;
|
||||
|
||||
String defaultResolution = defaultPreferences.getString(context.getString(R.string.default_resolution_key), context.getString(R.string.default_resolution_value));
|
||||
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
*/
|
||||
public static int getPopupDefaultResolutionIndex(Context context, List<VideoStream> videoStreams) {
|
||||
SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (defaultPreferences == null) return 0;
|
||||
|
||||
String defaultResolution = defaultPreferences.getString(context.getString(R.string.default_popup_resolution_key), context.getString(R.string.default_popup_resolution_value));
|
||||
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
||||
}
|
||||
|
||||
public static int getDefaultAudioFormat(Context context, List<AudioStream> audioStreams) {
|
||||
MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_audio_format_key, R.string.default_audio_format_value);
|
||||
return getHighestQualityAudioIndex(defaultFormat, audioStreams);
|
||||
}
|
||||
|
||||
public static int getHighestQualityAudioIndex(List<AudioStream> audioStreams) {
|
||||
if (audioStreams == null || audioStreams.isEmpty()) return -1;
|
||||
|
||||
int highestQualityIndex = 0;
|
||||
if (audioStreams.size() > 1) for (int i = 1; i < audioStreams.size(); i++) {
|
||||
AudioStream audioStream = audioStreams.get(i);
|
||||
if (audioStream.average_bitrate >= audioStreams.get(highestQualityIndex).average_bitrate) highestQualityIndex = i;
|
||||
}
|
||||
return highestQualityIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio from the list with the highest bitrate
|
||||
*
|
||||
* @param audioStreams list the audio streams
|
||||
* @return audio with highest average bitrate
|
||||
*/
|
||||
public static AudioStream getHighestQualityAudio(List<AudioStream> audioStreams) {
|
||||
if (audioStreams == null || audioStreams.isEmpty()) return null;
|
||||
|
||||
return audioStreams.get(getHighestQualityAudioIndex(audioStreams));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio from the list with the highest bitrate
|
||||
*
|
||||
* @param audioStreams list the audio streams
|
||||
* @return index of the audio with the highest average bitrate of the default format
|
||||
*/
|
||||
public static int getHighestQualityAudioIndex(MediaFormat defaultFormat, List<AudioStream> audioStreams) {
|
||||
if (audioStreams == null || audioStreams.isEmpty() || defaultFormat == null) return -1;
|
||||
|
||||
int highestQualityIndex = -1;
|
||||
for (int i = 0; i < audioStreams.size(); i++) {
|
||||
AudioStream audioStream = audioStreams.get(i);
|
||||
if (highestQualityIndex == -1 && audioStream.format == defaultFormat.id) highestQualityIndex = i;
|
||||
|
||||
if (highestQualityIndex != -1 && audioStream.format == defaultFormat.id
|
||||
&& audioStream.average_bitrate > audioStreams.get(highestQualityIndex).average_bitrate) {
|
||||
highestQualityIndex = i;
|
||||
}
|
||||
}
|
||||
if (highestQualityIndex == -1) highestQualityIndex = getHighestQualityAudioIndex(audioStreams);
|
||||
return highestQualityIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the two lists of video streams (video_only and normal videos), and sort them according with default format
|
||||
* chosen by the user
|
||||
*
|
||||
* @param context context to search for the format to give preference
|
||||
* @param videoStreams normal videos list
|
||||
* @param videoOnlyStreams video only stream list
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||
* @return the sorted list
|
||||
*/
|
||||
public static List<VideoStream> getSortedStreamVideosList(Context context, List<VideoStream> videoStreams, List<VideoStream> videoOnlyStreams, boolean ascendingOrder) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
boolean showHigherResolutions = preferences.getBoolean(context.getString(R.string.show_higher_resolutions_key), false);
|
||||
MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key, R.string.default_video_format_value);
|
||||
|
||||
return getSortedStreamVideosList(defaultFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the two lists of video streams (video_only and normal videos), and sort them according with default format
|
||||
* chosen by the user
|
||||
*
|
||||
* @param defaultFormat format to give preference
|
||||
* @param showHigherResolutions show >1080p resolutions
|
||||
* @param videoStreams normal videos list
|
||||
* @param videoOnlyStreams video only stream list
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest @return the sorted list
|
||||
* @return the sorted list
|
||||
*/
|
||||
public static List<VideoStream> getSortedStreamVideosList(MediaFormat defaultFormat, boolean showHigherResolutions, List<VideoStream> videoStreams, List<VideoStream> videoOnlyStreams, boolean ascendingOrder) {
|
||||
ArrayList<VideoStream> retList = new ArrayList<>();
|
||||
HashMap<String, VideoStream> hashMap = new HashMap<>();
|
||||
|
||||
if (videoOnlyStreams != null) {
|
||||
for (VideoStream stream : videoOnlyStreams) {
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue;
|
||||
retList.add(stream);
|
||||
}
|
||||
}
|
||||
if (videoStreams != null) {
|
||||
for (VideoStream stream : videoStreams) {
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue;
|
||||
retList.add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all to the hashmap
|
||||
for (VideoStream videoStream : retList) hashMap.put(videoStream.resolution, videoStream);
|
||||
|
||||
// Override the values when the key == resolution, with the defaultFormat
|
||||
for (VideoStream videoStream : retList) {
|
||||
if (videoStream.format == defaultFormat.id) hashMap.put(videoStream.resolution, videoStream);
|
||||
}
|
||||
|
||||
retList.clear();
|
||||
retList.addAll(hashMap.values());
|
||||
sortStreamList(retList, ascendingOrder);
|
||||
return retList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the streams list depending on the parameter ascendingOrder;
|
||||
* <p>
|
||||
* It works like that:<br>
|
||||
* - Take a string resolution, remove the letters, replace "0p60" (for 60fps videos) with "1"
|
||||
* and sort by the greatest:<br>
|
||||
* <blockquote><pre>
|
||||
* 720p -> 720
|
||||
* 720p60 -> 721
|
||||
* 360p -> 360
|
||||
* 1080p -> 1080
|
||||
* 1080p60 -> 1081
|
||||
* <br>
|
||||
* ascendingOrder ? 360 < 720 < 721 < 1080 < 1081
|
||||
* !ascendingOrder ? 1081 < 1080 < 721 < 720 < 360</pre></blockquote>
|
||||
*
|
||||
* @param videoStreams list that the sorting will be applied
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||
*/
|
||||
public static void sortStreamList(List<VideoStream> videoStreams, final boolean ascendingOrder) {
|
||||
Collections.sort(videoStreams, new Comparator<VideoStream>() {
|
||||
@Override
|
||||
public int compare(VideoStream o1, VideoStream o2) {
|
||||
int res1 = Integer.parseInt(o1.resolution.replace("0p60", "1").replaceAll("[^\\d.]", ""));
|
||||
int res2 = Integer.parseInt(o2.resolution.replace("0p60", "1").replaceAll("[^\\d.]", ""));
|
||||
|
||||
return ascendingOrder ? res1 - res2 : res2 - res1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private static int getDefaultStreamIndex(String defaultResolution, MediaFormat defaultFormat, List<VideoStream> videoStreams) {
|
||||
int defaultStreamIndex = -1;
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
VideoStream stream = videoStreams.get(i);
|
||||
if (defaultStreamIndex == -1 && stream.resolution.equals(defaultResolution)) defaultStreamIndex = i;
|
||||
|
||||
if (stream.format == defaultFormat.id && stream.resolution.equals(defaultResolution)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultStreamIndex;
|
||||
}
|
||||
|
||||
private static int getDefaultResolutionWithDefaultFormat(Context context, String defaultResolution, List<VideoStream> videoStreams) {
|
||||
MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key, R.string.default_video_format_value);
|
||||
return getDefaultResolutionIndex(defaultResolution, context.getString(R.string.best_resolution_key), defaultFormat, videoStreams);
|
||||
}
|
||||
|
||||
private static MediaFormat getDefaultFormat(Context context, @StringRes int defaultFormatKey, @StringRes int defaultFormatValueKey) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
String defaultFormat = context.getString(defaultFormatValueKey);
|
||||
String defaultFormatString = preferences.getString(context.getString(defaultFormatKey), defaultFormat);
|
||||
|
||||
MediaFormat defaultMediaFormat = getMediaFormatFromKey(context, defaultFormatString);
|
||||
if (defaultMediaFormat == null) {
|
||||
preferences.edit().putString(context.getString(defaultFormatKey), defaultFormat).apply();
|
||||
defaultMediaFormat = getMediaFormatFromKey(context, defaultFormat);
|
||||
}
|
||||
|
||||
return defaultMediaFormat;
|
||||
}
|
||||
|
||||
private static MediaFormat getMediaFormatFromKey(Context context, String formatKey) {
|
||||
MediaFormat format = null;
|
||||
if (formatKey.equals(context.getString(R.string.video_webm_key))) {
|
||||
format = MediaFormat.WEBM;
|
||||
} else if (formatKey.equals(context.getString(R.string.video_mp4_key))) {
|
||||
format = MediaFormat.MPEG_4;
|
||||
} else if (formatKey.equals(context.getString(R.string.video_3gp_key))) {
|
||||
format = MediaFormat.v3GPP;
|
||||
} else if (formatKey.equals(context.getString(R.string.audio_webm_key))) {
|
||||
format = MediaFormat.WEBMA;
|
||||
} else if (formatKey.equals(context.getString(R.string.audio_m4a_key))) {
|
||||
format = MediaFormat.M4A;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.PluralsRes;
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
|
|
@ -14,7 +16,7 @@ import java.text.SimpleDateFormat;
|
|||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
/*
|
||||
* Created by chschtsch on 12/29/15.
|
||||
*
|
||||
* Copyright (C) Gregory Arkhipov 2015
|
||||
|
|
@ -42,38 +44,28 @@ public class Localization {
|
|||
public static Locale getPreferredLocale(Context context) {
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
String languageCode = sp.getString(String.valueOf(R.string.search_language_key),
|
||||
context.getString(R.string.default_language_value));
|
||||
String languageCode = sp.getString(context.getString(R.string.search_language_key), context.getString(R.string.default_language_value));
|
||||
|
||||
if(languageCode.length() == 2) {
|
||||
return new Locale(languageCode);
|
||||
}
|
||||
else if(languageCode.contains("_")) {
|
||||
String country = languageCode
|
||||
.substring(languageCode.indexOf("_"), languageCode.length());
|
||||
return new Locale(languageCode.substring(0, 2), country);
|
||||
try {
|
||||
if (languageCode.length() == 2) {
|
||||
return new Locale(languageCode);
|
||||
} else if (languageCode.contains("_")) {
|
||||
String country = languageCode.substring(languageCode.indexOf("_"), languageCode.length());
|
||||
return new Locale(languageCode.substring(0, 2), country);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return Locale.getDefault();
|
||||
}
|
||||
|
||||
public static String localizeViewCount(long viewCount, Context context) {
|
||||
Locale locale = getPreferredLocale(context);
|
||||
|
||||
Resources res = context.getResources();
|
||||
String viewsString = res.getString(R.string.view_count_text);
|
||||
|
||||
NumberFormat nf = NumberFormat.getInstance(locale);
|
||||
String formattedViewCount = nf.format(viewCount);
|
||||
return String.format(viewsString, formattedViewCount);
|
||||
}
|
||||
|
||||
public static String localizeNumber(long number, Context context) {
|
||||
public static String localizeNumber(Context context, long number) {
|
||||
Locale locale = getPreferredLocale(context);
|
||||
NumberFormat nf = NumberFormat.getInstance(locale);
|
||||
return nf.format(number);
|
||||
}
|
||||
|
||||
private static String formatDate(String date, Context context) {
|
||||
private static String formatDate(Context context, String date) {
|
||||
Locale locale = getPreferredLocale(context);
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Date datum = null;
|
||||
|
|
@ -88,11 +80,75 @@ public class Localization {
|
|||
return df.format(datum);
|
||||
}
|
||||
|
||||
public static String localizeDate(String date, Context context) {
|
||||
public static String localizeDate(Context context, String date) {
|
||||
Resources res = context.getResources();
|
||||
String dateString = res.getString(R.string.upload_date_text);
|
||||
|
||||
String formattedDate = formatDate(date, context);
|
||||
String formattedDate = formatDate(context, date);
|
||||
return String.format(dateString, formattedDate);
|
||||
}
|
||||
|
||||
public static String localizeViewCount(Context context, long viewCount) {
|
||||
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, localizeNumber(context, viewCount));
|
||||
}
|
||||
|
||||
public static String localizeSubscribersCount(Context context, long subscriberCount) {
|
||||
return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount, localizeNumber(context, subscriberCount));
|
||||
}
|
||||
|
||||
public static String localizeStreamCount(Context context, long streamCount) {
|
||||
return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, localizeNumber(context, streamCount));
|
||||
}
|
||||
|
||||
public static String shortCount(Context context, long count) {
|
||||
if (count >= 1000000000) {
|
||||
return Long.toString(count / 1000000000) + context.getString(R.string.short_billion);
|
||||
} else if (count >= 1000000) {
|
||||
return Long.toString(count / 1000000) + context.getString(R.string.short_million);
|
||||
} else if (count >= 1000) {
|
||||
return Long.toString(count / 1000) + context.getString(R.string.short_thousand);
|
||||
} else {
|
||||
return Long.toString(count);
|
||||
}
|
||||
}
|
||||
|
||||
public static String shortViewCount(Context context, long viewCount) {
|
||||
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, shortCount(context, viewCount));
|
||||
}
|
||||
|
||||
public static String shortSubscriberCount(Context context, long subscriberCount) {
|
||||
return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount, shortCount(context, subscriberCount));
|
||||
}
|
||||
|
||||
private static String getQuantity(Context context, @PluralsRes int pluralId, @StringRes int zeroCaseStringId, long count, String formattedCount) {
|
||||
if (count == 0) return context.getString(zeroCaseStringId);
|
||||
|
||||
// As we use the already formatted count, is not the responsibility of this method handle long numbers
|
||||
// (it probably will fall in the "other" category, or some language have some specific rule... then we have to change it)
|
||||
int safeCount = count > Integer.MAX_VALUE ? Integer.MAX_VALUE : count < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) count;
|
||||
return context.getResources().getQuantityString(pluralId, safeCount, formattedCount);
|
||||
}
|
||||
|
||||
public static String getDurationString(long duration) {
|
||||
if (duration < 0) {
|
||||
duration = 0;
|
||||
}
|
||||
String output;
|
||||
long days = duration / (24 * 60 * 60L); /* greater than a day */
|
||||
duration %= (24 * 60 * 60L);
|
||||
long hours = duration / (60 * 60L); /* greater than an hour */
|
||||
duration %= (60 * 60L);
|
||||
long minutes = duration / 60L;
|
||||
long seconds = duration % 60L;
|
||||
|
||||
//handle days
|
||||
if (days > 0) {
|
||||
output = String.format(Locale.US, "%d:%02d:%02d:%02d", days, hours, minutes, seconds);
|
||||
} else if (hours > 0) {
|
||||
output = String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds);
|
||||
} else {
|
||||
output = String.format(Locale.US, "%d:%02d", minutes, seconds);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,30 +4,33 @@ import android.app.Activity;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.CheckResult;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.about.AboutActivity;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.about.AboutActivity;
|
||||
import org.schabi.newpipe.download.DownloadActivity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.stream_info.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.FeedFragment;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.MainFragment;
|
||||
import org.schabi.newpipe.fragments.channel.ChannelFragment;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
import org.schabi.newpipe.fragments.search.SearchFragment;
|
||||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||
import org.schabi.newpipe.fragments.list.feed.FeedFragment;
|
||||
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
|
||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
||||
import org.schabi.newpipe.history.HistoryActivity;
|
||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||
import org.schabi.newpipe.player.BasePlayer;
|
||||
import org.schabi.newpipe.player.VideoPlayer;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public class NavigationHelper {
|
||||
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
|
||||
|
|
@ -38,14 +41,14 @@ public class NavigationHelper {
|
|||
|
||||
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) {
|
||||
Intent mIntent = new Intent(context, targetClazz)
|
||||
.putExtra(BasePlayer.VIDEO_TITLE, info.title)
|
||||
.putExtra(BasePlayer.VIDEO_URL, info.webpage_url)
|
||||
.putExtra(BasePlayer.VIDEO_TITLE, info.name)
|
||||
.putExtra(BasePlayer.VIDEO_URL, info.url)
|
||||
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url)
|
||||
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
|
||||
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader_name)
|
||||
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamIndex)
|
||||
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false))
|
||||
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, Utils.getHighestQualityAudio(info.audio_streams));
|
||||
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000);
|
||||
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, new ArrayList<>(ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false)))
|
||||
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, ListHelper.getHighestQualityAudio(info.audio_streams));
|
||||
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000L);
|
||||
return mIntent;
|
||||
}
|
||||
|
||||
|
|
@ -54,27 +57,26 @@ public class NavigationHelper {
|
|||
.putExtra(BasePlayer.VIDEO_TITLE, instance.getVideoTitle())
|
||||
.putExtra(BasePlayer.VIDEO_URL, instance.getVideoUrl())
|
||||
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, instance.getVideoThumbnailUrl())
|
||||
.putExtra(BasePlayer.CHANNEL_NAME, instance.getChannelName())
|
||||
.putExtra(BasePlayer.CHANNEL_NAME, instance.getUploaderName())
|
||||
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
|
||||
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList())
|
||||
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, instance.getAudioStream())
|
||||
.putExtra(BasePlayer.START_POSITION, ((int) instance.getPlayer().getCurrentPosition()))
|
||||
.putExtra(BasePlayer.START_POSITION, instance.getPlayer().getCurrentPosition())
|
||||
.putExtra(BasePlayer.PLAYBACK_SPEED, instance.getPlaybackSpeed());
|
||||
}
|
||||
|
||||
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info) {
|
||||
return getOpenBackgroundPlayerIntent(context, info, info.audio_streams.get(Utils.getPreferredAudioFormat(context, info.audio_streams)));
|
||||
return getOpenBackgroundPlayerIntent(context, info, info.audio_streams.get(ListHelper.getDefaultAudioFormat(context, info.audio_streams)));
|
||||
}
|
||||
|
||||
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info, AudioStream audioStream) {
|
||||
Intent mIntent = new Intent(context, BackgroundPlayer.class)
|
||||
.putExtra(BasePlayer.VIDEO_TITLE, info.title)
|
||||
.putExtra(BasePlayer.VIDEO_URL, info.webpage_url)
|
||||
.putExtra(BasePlayer.VIDEO_TITLE, info.name)
|
||||
.putExtra(BasePlayer.VIDEO_URL, info.url)
|
||||
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url)
|
||||
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
|
||||
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
|
||||
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader_name)
|
||||
.putExtra(BackgroundPlayer.AUDIO_STREAM, audioStream);
|
||||
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000);
|
||||
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000L);
|
||||
return mIntent;
|
||||
}
|
||||
|
||||
|
|
@ -90,9 +92,11 @@ public class NavigationHelper {
|
|||
}
|
||||
|
||||
private static void openMainFragment(FragmentManager fragmentManager) {
|
||||
InfoCache.getInstance().trimCache();
|
||||
|
||||
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
fragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
|
||||
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out)
|
||||
.replace(R.id.fragment_holder, new MainFragment())
|
||||
.addToBackStack(MAIN_FRAGMENT_TAG)
|
||||
.commit();
|
||||
|
|
@ -100,7 +104,7 @@ public class NavigationHelper {
|
|||
|
||||
public static void openSearchFragment(FragmentManager fragmentManager, int serviceId, String query) {
|
||||
fragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
|
||||
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out)
|
||||
.replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, query))
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
|
|
@ -125,7 +129,7 @@ public class NavigationHelper {
|
|||
instance.setAutoplay(autoPlay);
|
||||
|
||||
fragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
|
||||
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out)
|
||||
.replace(R.id.fragment_holder, instance)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
|
|
@ -134,15 +138,24 @@ public class NavigationHelper {
|
|||
public static void openChannelFragment(FragmentManager fragmentManager, int serviceId, String url, String name) {
|
||||
if (name == null) name = "";
|
||||
fragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
|
||||
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out)
|
||||
.replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url, name))
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openPlaylistFragment(FragmentManager fragmentManager, int serviceId, String url, String name) {
|
||||
if (name == null) name = "";
|
||||
fragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out)
|
||||
.replace(R.id.fragment_holder, PlaylistFragment.getInstance(serviceId, url, name))
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openWhatsNewFragment(FragmentManager fragmentManager) {
|
||||
fragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
|
||||
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out)
|
||||
.replace(R.id.fragment_holder, new FeedFragment())
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
|
|
@ -182,48 +195,21 @@ public class NavigationHelper {
|
|||
|
||||
public static void openMainActivity(Context context) {
|
||||
Intent mIntent = new Intent(context, MainActivity.class);
|
||||
mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
context.startActivity(mIntent);
|
||||
}
|
||||
|
||||
public static void openByLink(Context context, String url) throws Exception {
|
||||
Intent intentByLink = getIntentByLink(context, url);
|
||||
if (intentByLink == null) throw new NullPointerException("getIntentByLink(context = [" + context + "], url = [" + url + "]) returned null");
|
||||
intentByLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intentByLink.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
context.startActivity(intentByLink);
|
||||
}
|
||||
|
||||
private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) {
|
||||
Intent mIntent = new Intent(context, MainActivity.class);
|
||||
mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
|
||||
mIntent.putExtra(Constants.KEY_URL, url);
|
||||
mIntent.putExtra(Constants.KEY_LINK_TYPE, type);
|
||||
return mIntent;
|
||||
}
|
||||
|
||||
private static Intent getIntentByLink(Context context, String url) throws Exception {
|
||||
StreamingService service = NewPipe.getServiceByUrl(url);
|
||||
if (service == null) throw new Exception("NewPipe.getServiceByUrl returned null for url > \"" + url + "\"");
|
||||
int serviceId = service.getServiceId();
|
||||
switch (service.getLinkTypeByUrl(url)) {
|
||||
case STREAM:
|
||||
Intent sIntent = getOpenIntent(context, url, serviceId, StreamingService.LinkType.STREAM);
|
||||
sIntent.putExtra(VideoDetailFragment.AUTO_PLAY, PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(context.getString(R.string.autoplay_through_intent_key), false));
|
||||
return sIntent;
|
||||
case CHANNEL:
|
||||
return getOpenIntent(context, url, serviceId, StreamingService.LinkType.CHANNEL);
|
||||
case NONE:
|
||||
throw new Exception("Url not known to service. service=" + serviceId + " url=" + url);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void openAbout(Context context) {
|
||||
Intent intent = new Intent(context, AboutActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static void openHistory(Context context) {
|
||||
Intent intent = new Intent(context, HistoryActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static void openSettings(Context context) {
|
||||
Intent intent = new Intent(context, SettingsActivity.class);
|
||||
context.startActivity(intent);
|
||||
|
|
@ -237,4 +223,62 @@ public class NavigationHelper {
|
|||
activity.startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Link handling
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static void openByLink(Context context, String url) throws Exception {
|
||||
Intent intentByLink = getIntentByLink(context, url);
|
||||
if (intentByLink == null)
|
||||
throw new NullPointerException("getIntentByLink(context = [" + context + "], url = [" + url + "]) returned null");
|
||||
intentByLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intentByLink.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
context.startActivity(intentByLink);
|
||||
}
|
||||
|
||||
private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) {
|
||||
Intent mIntent = new Intent(context, MainActivity.class);
|
||||
mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
|
||||
mIntent.putExtra(Constants.KEY_URL, url);
|
||||
mIntent.putExtra(Constants.KEY_LINK_TYPE, type);
|
||||
return mIntent;
|
||||
}
|
||||
|
||||
private static Intent getIntentByLink(Context context, String url) throws Exception {
|
||||
StreamingService service = NewPipe.getServiceByUrl(url);
|
||||
|
||||
int serviceId = service.getServiceId();
|
||||
StreamingService.LinkType linkType = service.getLinkTypeByUrl(url);
|
||||
|
||||
if (linkType == StreamingService.LinkType.NONE) {
|
||||
throw new Exception("Url not known to service. service=" + serviceId + " url=" + url);
|
||||
}
|
||||
|
||||
url = getCleanUrl(service, url, linkType);
|
||||
Intent rIntent = getOpenIntent(context, url, serviceId, linkType);
|
||||
|
||||
switch (linkType) {
|
||||
case STREAM:
|
||||
rIntent.putExtra(VideoDetailFragment.AUTO_PLAY, PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(context.getString(R.string.autoplay_through_intent_key), false));
|
||||
break;
|
||||
}
|
||||
|
||||
return rIntent;
|
||||
}
|
||||
|
||||
private static String getCleanUrl(StreamingService service, String dirtyUrl, StreamingService.LinkType linkType) throws Exception {
|
||||
switch (linkType) {
|
||||
case STREAM:
|
||||
return service.getStreamUrlIdHandler().cleanUrl(dirtyUrl);
|
||||
case CHANNEL:
|
||||
return service.getChannelUrlIdHandler().cleanUrl(dirtyUrl);
|
||||
case PLAYLIST:
|
||||
return service.getPlaylistUrlIdHandler().cleanUrl(dirtyUrl);
|
||||
case NONE:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
319
app/src/main/java/org/schabi/newpipe/util/StateSaver.java
Normal file
319
app/src/main/java/org/schabi/newpipe/util/StateSaver.java
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
/*
|
||||
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
|
||||
* StateSaver.java is part of NewPipe
|
||||
*
|
||||
* License: GPL-3.0+
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* A way to save state to disk or in a in-memory map if it's just changing configurations (i.e. rotating the phone).
|
||||
*/
|
||||
public class StateSaver {
|
||||
private static final ConcurrentHashMap<String, Queue<Object>> stateObjectsHolder = new ConcurrentHashMap<>();
|
||||
private static final String TAG = "StateSaver";
|
||||
private static final String CACHE_DIR_NAME = "state_cache";
|
||||
|
||||
public static final String KEY_SAVED_STATE = "key_saved_state";
|
||||
private static String cacheDirPath;
|
||||
|
||||
private StateSaver() {
|
||||
//no instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the StateSaver, usually you want to call this in the Application class
|
||||
*
|
||||
* @param context used to get the available cache dir
|
||||
*/
|
||||
public static void init(Context context) {
|
||||
File externalCacheDir = context.getExternalCacheDir();
|
||||
if (externalCacheDir != null) cacheDirPath = externalCacheDir.getAbsolutePath();
|
||||
if (TextUtils.isEmpty(cacheDirPath)) cacheDirPath = context.getCacheDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for describe how to save/read the objects.
|
||||
* <p>
|
||||
* Queue was chosen by its FIFO property.
|
||||
*/
|
||||
public interface WriteRead {
|
||||
/**
|
||||
* Generate a changing suffix that will name the cache file,
|
||||
* and be used to identify if it changed (thus reducing useless reading/saving).
|
||||
*
|
||||
* @return a unique value
|
||||
*/
|
||||
String generateSuffix();
|
||||
|
||||
/**
|
||||
* Add to this queue objects that you want to save.
|
||||
*/
|
||||
void writeTo(Queue<Object> objectsToSave);
|
||||
|
||||
/**
|
||||
* Poll saved objects from the queue in the order they were written.
|
||||
*
|
||||
* @param savedObjects queue of objects returned by {@link #writeTo(Queue)}
|
||||
*/
|
||||
void readFrom(@NonNull Queue<Object> savedObjects) throws Exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #tryToRestore(SavedState, WriteRead)
|
||||
*/
|
||||
public static SavedState tryToRestore(Bundle outState, WriteRead writeRead) {
|
||||
if (outState == null || writeRead == null) return null;
|
||||
|
||||
SavedState savedState = outState.getParcelable(KEY_SAVED_STATE);
|
||||
if (savedState == null) return null;
|
||||
|
||||
return tryToRestore(savedState, writeRead);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to restore the state from memory and disk, using the {@link StateSaver.WriteRead#readFrom(Queue)} from the writeRead.
|
||||
*/
|
||||
private static SavedState tryToRestore(@NonNull SavedState savedState, @NonNull WriteRead writeRead) {
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "tryToRestore() called with: savedState = [" + savedState + "], writeRead = [" + writeRead + "]");
|
||||
}
|
||||
|
||||
FileInputStream fileInputStream = null;
|
||||
try {
|
||||
Queue<Object> savedObjects = stateObjectsHolder.remove(savedState.prefixFileSaved);
|
||||
if (savedObjects != null) {
|
||||
writeRead.readFrom(savedObjects);
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "tryToSave: reading objects from holder > " + savedObjects + ", stateObjectsHolder > " + stateObjectsHolder);
|
||||
}
|
||||
return savedState;
|
||||
}
|
||||
|
||||
File file = new File(savedState.pathFileSaved);
|
||||
if (!file.exists()) return null;
|
||||
|
||||
fileInputStream = new FileInputStream(file);
|
||||
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
|
||||
//noinspection unchecked
|
||||
savedObjects = (Queue<Object>) inputStream.readObject();
|
||||
if (savedObjects != null) {
|
||||
writeRead.readFrom(savedObjects);
|
||||
}
|
||||
|
||||
return savedState;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (fileInputStream != null) {
|
||||
try {
|
||||
fileInputStream.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #tryToSave(boolean, String, String, WriteRead)
|
||||
*/
|
||||
public static SavedState tryToSave(boolean isChangingConfig, @Nullable SavedState savedState, Bundle outState, WriteRead writeRead) {
|
||||
String currentSavedPrefix = savedState == null || TextUtils.isEmpty(savedState.prefixFileSaved)
|
||||
? System.nanoTime() - writeRead.hashCode() + ""
|
||||
: savedState.prefixFileSaved;
|
||||
|
||||
savedState = tryToSave(isChangingConfig, currentSavedPrefix, writeRead.generateSuffix(), writeRead);
|
||||
if (savedState != null) {
|
||||
outState.putParcelable(StateSaver.KEY_SAVED_STATE, savedState);
|
||||
return savedState;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If it's not changing configuration (i.e. rotating screen), try to write the state from {@link StateSaver.WriteRead#writeTo(Queue)}
|
||||
* to the file with the name of prefixFileName + suffixFileName, in a cache folder got from the {@link #init(Context)}.
|
||||
* <p>
|
||||
* It checks if the file already exists and if it does, just return the path, so a good way to save is:
|
||||
* <li> A fixed prefix for the file
|
||||
* <li> A changing suffix
|
||||
*/
|
||||
private static SavedState tryToSave(boolean isChangingConfig, final String prefixFileName, String suffixFileName, WriteRead writeRead) {
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "tryToSave() called with: isChangingConfig = [" + isChangingConfig + "], prefixFileName = [" + prefixFileName + "], suffixFileName = [" + suffixFileName + "], writeRead = [" + writeRead + "]");
|
||||
}
|
||||
|
||||
Queue<Object> savedObjects = new LinkedList<>();
|
||||
writeRead.writeTo(savedObjects);
|
||||
|
||||
if (isChangingConfig) {
|
||||
if (savedObjects.size() > 0) {
|
||||
stateObjectsHolder.put(prefixFileName, savedObjects);
|
||||
return new SavedState(prefixFileName, "");
|
||||
} else return null;
|
||||
}
|
||||
|
||||
FileOutputStream fileOutputStream = null;
|
||||
try {
|
||||
File cacheDir = new File(cacheDirPath);
|
||||
if (!cacheDir.exists()) throw new RuntimeException("Cache dir does not exist > " + cacheDirPath);
|
||||
cacheDir = new File(cacheDir, CACHE_DIR_NAME);
|
||||
if (!cacheDir.exists()) {
|
||||
boolean mkdirResult = cacheDir.mkdir();
|
||||
if (!mkdirResult) return null;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(suffixFileName)) suffixFileName = ".cache";
|
||||
File file = new File(cacheDir, prefixFileName + suffixFileName);
|
||||
if (file.exists() && file.length() > 0) {
|
||||
// If the file already exists, just return it
|
||||
return new SavedState(prefixFileName, file.getAbsolutePath());
|
||||
} else {
|
||||
// Delete any file that contains the prefix
|
||||
File[] files = cacheDir.listFiles(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.contains(prefixFileName);
|
||||
}
|
||||
});
|
||||
for (File file1 : files) file1.delete();
|
||||
}
|
||||
|
||||
fileOutputStream = new FileOutputStream(file);
|
||||
ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
|
||||
outputStream.writeObject(savedObjects);
|
||||
|
||||
return new SavedState(prefixFileName, file.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (fileOutputStream != null) {
|
||||
try {
|
||||
fileOutputStream.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the cache file contained in the savedState and remove any possible-existing value in the memory-cache.
|
||||
*/
|
||||
public static void onDestroy(SavedState savedState) {
|
||||
if (MainActivity.DEBUG) Log.d(TAG, "onDestroy() called with: savedState = [" + savedState + "]");
|
||||
|
||||
if (savedState != null && !TextUtils.isEmpty(savedState.pathFileSaved)) {
|
||||
stateObjectsHolder.remove(savedState.prefixFileSaved);
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
new File(savedState.pathFileSaved).delete();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the files in cache (in memory and disk).
|
||||
*/
|
||||
public static void clearStateFiles() {
|
||||
if (MainActivity.DEBUG) Log.d(TAG, "clearStateFiles() called");
|
||||
|
||||
stateObjectsHolder.clear();
|
||||
File cacheDir = new File(cacheDirPath);
|
||||
if (!cacheDir.exists()) return;
|
||||
|
||||
cacheDir = new File(cacheDir, CACHE_DIR_NAME);
|
||||
if (cacheDir.exists()) {
|
||||
for (File file : cacheDir.listFiles()) file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Inner
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static class SavedState implements Parcelable {
|
||||
public String prefixFileSaved;
|
||||
public String pathFileSaved;
|
||||
|
||||
public SavedState(String prefixFileSaved, String pathFileSaved) {
|
||||
this.prefixFileSaved = prefixFileSaved;
|
||||
this.pathFileSaved = pathFileSaved;
|
||||
}
|
||||
|
||||
protected SavedState(Parcel in) {
|
||||
prefixFileSaved = in.readString();
|
||||
pathFileSaved = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return prefixFileSaved + " > " + pathFileSaved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(prefixFileSaved);
|
||||
dest.writeString(pathFileSaved);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
|
||||
@Override
|
||||
public SavedState createFromParcel(Parcel in) {
|
||||
return new SavedState(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavedState[] newArray(int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -13,16 +13,17 @@ public class ThemeHelper {
|
|||
* @param context context that the theme will be applied
|
||||
*/
|
||||
public static void setTheme(Context context) {
|
||||
String lightTheme = context.getResources().getString(R.string.light_theme_key);
|
||||
String darkTheme = context.getResources().getString(R.string.dark_theme_key);
|
||||
String blackTheme = context.getResources().getString(R.string.black_theme_key);
|
||||
|
||||
String themeKey = context.getString(R.string.theme_key);
|
||||
String darkTheme = context.getResources().getString(R.string.dark_theme_title);
|
||||
String blackTheme = context.getResources().getString(R.string.black_theme_title);
|
||||
String selectedTheme = getSelectedTheme(context);
|
||||
|
||||
String sp = PreferenceManager.getDefaultSharedPreferences(context).getString(themeKey, context.getResources().getString(R.string.light_theme_title));
|
||||
|
||||
if (sp.equals(darkTheme)) context.setTheme(R.style.DarkTheme);
|
||||
else if (sp.equals(blackTheme)) context.setTheme(R.style.BlackTheme);
|
||||
else context.setTheme(R.style.AppTheme);
|
||||
if (selectedTheme.equals(lightTheme)) context.setTheme(R.style.LightTheme);
|
||||
else if (selectedTheme.equals(blackTheme)) context.setTheme(R.style.BlackTheme);
|
||||
else if (selectedTheme.equals(darkTheme)) context.setTheme(R.style.DarkTheme);
|
||||
// Fallback
|
||||
else context.setTheme(R.style.DarkTheme);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -31,12 +32,12 @@ public class ThemeHelper {
|
|||
* @param context context to get the preference
|
||||
*/
|
||||
public static boolean isLightThemeSelected(Context context) {
|
||||
return getSelectedTheme(context).equals(context.getResources().getString(R.string.light_theme_key));
|
||||
}
|
||||
|
||||
public static String getSelectedTheme(Context context) {
|
||||
String themeKey = context.getString(R.string.theme_key);
|
||||
String darkTheme = context.getResources().getString(R.string.dark_theme_title);
|
||||
String blackTheme = context.getResources().getString(R.string.black_theme_title);
|
||||
|
||||
String sp = PreferenceManager.getDefaultSharedPreferences(context).getString(themeKey, context.getResources().getString(R.string.light_theme_title));
|
||||
|
||||
return !(sp.equals(darkTheme) || sp.equals(blackTheme));
|
||||
String defaultTheme = context.getResources().getString(R.string.default_theme_value);
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getString(themeKey, defaultTheme);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,273 +0,0 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream_info.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class Utils {
|
||||
|
||||
private static final List<String> HIGH_RESOLUTION_LIST = Arrays.asList("1440p", "2160p", "1440p60", "2160p60");
|
||||
|
||||
/**
|
||||
* Return the index of the default stream in the list, based on the parameters
|
||||
* defaultResolution and preferredFormat
|
||||
*
|
||||
* @param videoStreams the list that will be extracted the index
|
||||
*
|
||||
* @return index of the preferred resolution&format
|
||||
*/
|
||||
public static int getDefaultResolution(String defaultResolution, String preferredFormat, List<VideoStream> videoStreams) {
|
||||
|
||||
if (defaultResolution.equals("Best resolution")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// first try to find the one with the right resolution
|
||||
int selectedFormat = 0;
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
VideoStream item = videoStreams.get(i);
|
||||
if (defaultResolution.equals(item.resolution)) {
|
||||
selectedFormat = i;
|
||||
}
|
||||
}
|
||||
|
||||
// than try to find the one with the right resolution and format
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
VideoStream item = videoStreams.get(i);
|
||||
if (defaultResolution.equals(item.resolution)
|
||||
&& preferredFormat.equals(MediaFormat.getNameById(item.format))) {
|
||||
selectedFormat = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedFormat == 0 && !videoStreams.get(selectedFormat).resolution.contains(defaultResolution.replace("p60", "p"))) {
|
||||
// Maybe there's no 60 fps variant available, so fallback to the normal version
|
||||
String replace = defaultResolution.replace("p60", "p");
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
VideoStream item = videoStreams.get(i);
|
||||
if (replace.equals(item.resolution)) selectedFormat = i;
|
||||
}
|
||||
|
||||
// than try to find the one with the right resolution and format
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
VideoStream item = videoStreams.get(i);
|
||||
if (replace.equals(item.resolution)
|
||||
&& preferredFormat.equals(MediaFormat.getNameById(item.format))) {
|
||||
selectedFormat = i;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// this is actually an error,
|
||||
// but maybe there is really no stream fitting to the default value.
|
||||
return selectedFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolution(String, String, List)
|
||||
*/
|
||||
public static int getDefaultResolution(Context context, List<VideoStream> videoStreams) {
|
||||
SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (defaultPreferences == null) return 0;
|
||||
|
||||
String defaultResolution = defaultPreferences
|
||||
.getString(context.getString(R.string.default_resolution_key), context.getString(R.string.default_resolution_value));
|
||||
|
||||
String preferredFormat = defaultPreferences
|
||||
.getString(context.getString(R.string.preferred_video_format_key), context.getString(R.string.preferred_video_format_default));
|
||||
|
||||
return getDefaultResolution(defaultResolution, preferredFormat, videoStreams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolution(String, String, List)
|
||||
*/
|
||||
public static int getPopupDefaultResolution(Context context, List<VideoStream> videoStreams) {
|
||||
SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (defaultPreferences == null) return 0;
|
||||
|
||||
String defaultResolution = defaultPreferences
|
||||
.getString(context.getString(R.string.default_popup_resolution_key), context.getString(R.string.default_popup_resolution_value));
|
||||
|
||||
String preferredFormat = defaultPreferences
|
||||
.getString(context.getString(R.string.preferred_video_format_key), context.getString(R.string.preferred_video_format_default));
|
||||
|
||||
return getDefaultResolution(defaultResolution, preferredFormat, videoStreams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index of the default stream in the list, based on the
|
||||
* preferred audio format chosen in the settings
|
||||
*
|
||||
* @param context context to get the preferred audio format
|
||||
* @param audioStreams the list that will be extracted the index
|
||||
*
|
||||
* @return index of the preferred format
|
||||
*/
|
||||
public static int getPreferredAudioFormat(Context context, List<AudioStream> audioStreams) {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (sharedPreferences == null) return 0;
|
||||
|
||||
String preferredFormatString = sharedPreferences.getString(context.getString(R.string.default_audio_format_key), "webm");
|
||||
|
||||
int preferredFormat = MediaFormat.WEBMA.id;
|
||||
switch (preferredFormatString) {
|
||||
case "webm":
|
||||
preferredFormat = MediaFormat.WEBMA.id;
|
||||
break;
|
||||
case "m4a":
|
||||
preferredFormat = MediaFormat.M4A.id;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
int highestQualityIndex = 0;
|
||||
|
||||
// Try to find a audio stream with the preferred format
|
||||
for (int i = 0; i < audioStreams.size(); i++) if (audioStreams.get(i).format == preferredFormat) highestQualityIndex = i;
|
||||
|
||||
// Try to find a audio stream with the highest bitrate and preferred format
|
||||
for (int i = 0; i < audioStreams.size(); i++) {
|
||||
AudioStream audioStream = audioStreams.get(i);
|
||||
if (audioStream.avgBitrate > audioStreams.get(highestQualityIndex).avgBitrate
|
||||
&& audioStream.format == preferredFormat) highestQualityIndex = i;
|
||||
}
|
||||
|
||||
return highestQualityIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio from the list with the highest bitrate
|
||||
*
|
||||
* @param audioStreams list the audio streams
|
||||
* @return audio with highest average bitrate
|
||||
*/
|
||||
public static AudioStream getHighestQualityAudio(List<AudioStream> audioStreams) {
|
||||
int highestQualityIndex = 0;
|
||||
|
||||
for (int i = 1; i < audioStreams.size(); i++) {
|
||||
AudioStream audioStream = audioStreams.get(i);
|
||||
if (audioStream.avgBitrate > audioStreams.get(highestQualityIndex).avgBitrate) highestQualityIndex = i;
|
||||
}
|
||||
|
||||
return audioStreams.get(highestQualityIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the two lists of video streams (video_only and normal videos), and sort them according with preferred format
|
||||
* chosen by the user
|
||||
*
|
||||
* @param context context to search for the format to give preference
|
||||
* @param videoStreams normal videos list
|
||||
* @param videoOnlyStreams video only stream list
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||
* @return the sorted list
|
||||
*/
|
||||
public static ArrayList<VideoStream> getSortedStreamVideosList(Context context, List<VideoStream> videoStreams, List<VideoStream> videoOnlyStreams, boolean ascendingOrder) {
|
||||
boolean showHigherResolutions = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(context.getString(R.string.show_higher_resolutions_key), false);
|
||||
String preferredFormatString = PreferenceManager.getDefaultSharedPreferences(context).getString(context.getString(R.string.preferred_video_format_key), context.getString(R.string.preferred_video_format_default));
|
||||
MediaFormat preferredFormat = MediaFormat.WEBM;
|
||||
switch (preferredFormatString) {
|
||||
case "WebM":
|
||||
preferredFormat = MediaFormat.WEBM;
|
||||
break;
|
||||
case "MPEG-4":
|
||||
preferredFormat = MediaFormat.MPEG_4;
|
||||
break;
|
||||
case "3GPP":
|
||||
preferredFormat = MediaFormat.v3GPP;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return getSortedStreamVideosList(preferredFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the two lists of video streams (video_only and normal videos), and sort them according with preferred format
|
||||
* chosen by the user
|
||||
*
|
||||
* @param preferredFormat format to give preference
|
||||
* @param showHigherResolutions show >1080p resolutions
|
||||
* @param videoStreams normal videos list
|
||||
* @param videoOnlyStreams video only stream list
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest @return the sorted list
|
||||
* @return the sorted list
|
||||
*/
|
||||
public static ArrayList<VideoStream> getSortedStreamVideosList(MediaFormat preferredFormat, boolean showHigherResolutions, List<VideoStream> videoStreams, List<VideoStream> videoOnlyStreams, boolean ascendingOrder) {
|
||||
ArrayList<VideoStream> retList = new ArrayList<>();
|
||||
HashMap<String, VideoStream> hashMap = new HashMap<>();
|
||||
|
||||
if (videoOnlyStreams != null) {
|
||||
for (VideoStream stream : videoOnlyStreams) {
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue;
|
||||
retList.add(stream);
|
||||
}
|
||||
}
|
||||
if (videoStreams != null) {
|
||||
for (VideoStream stream : videoStreams) {
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue;
|
||||
retList.add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all to the hashmap
|
||||
for (VideoStream videoStream : retList) hashMap.put(videoStream.resolution, videoStream);
|
||||
|
||||
// Override the values when the key == resolution, with the preferredFormat
|
||||
for (VideoStream videoStream : retList) {
|
||||
if (videoStream.format == preferredFormat.id) hashMap.put(videoStream.resolution, videoStream);
|
||||
}
|
||||
|
||||
retList.clear();
|
||||
retList.addAll(hashMap.values());
|
||||
sortStreamList(retList, ascendingOrder);
|
||||
return retList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the streams list depending on the parameter ascendingOrder;
|
||||
* <p>
|
||||
* It works like that:<br>
|
||||
* - Take a string resolution, remove the letters, replace "0p60" (for 60fps videos) with "1"
|
||||
* and sort by the greatest:<br>
|
||||
* <blockquote><pre>
|
||||
* 720p -> 720
|
||||
* 720p60 -> 721
|
||||
* 360p -> 360
|
||||
* 1080p -> 1080
|
||||
* 1080p60 -> 1081
|
||||
* <p>
|
||||
* ascendingOrder ? 360 < 720 < 721 < 1080 < 1081
|
||||
* !ascendingOrder ? 1081 < 1080 < 721 < 720 < 360/pre></blockquote>
|
||||
* <p>
|
||||
* @param videoStreams list that the sorting will be applied
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||
*/
|
||||
public static void sortStreamList(List<VideoStream> videoStreams, final boolean ascendingOrder) {
|
||||
Collections.sort(videoStreams, new Comparator<VideoStream>() {
|
||||
@Override
|
||||
public int compare(VideoStream o1, VideoStream o2) {
|
||||
int res1 = Integer.parseInt(o1.resolution.replace("0p60", "1").replaceAll("[^\\d.]", ""));
|
||||
int res2 = Integer.parseInt(o2.resolution.replace("0p60", "1").replaceAll("[^\\d.]", ""));
|
||||
|
||||
return ascendingOrder ? res1 - res2 : res2 - res1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue