mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2024-11-22 11:02:35 +01:00
Merge branch 'master' into dev
This commit is contained in:
commit
281cae7a18
@ -16,7 +16,9 @@
|
||||
<p align="center"><a href="https://newpipe.schabi.org">Website</a> • <a href="https://newpipe.schabi.org/blog/">Blog</a> • <a href="https://newpipe.schabi.org/press/">Press</a></p>
|
||||
<hr>
|
||||
|
||||
<b>WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.</b>
|
||||
<b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>
|
||||
|
||||
<b>PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.</b>
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
@ -8,8 +8,8 @@ android {
|
||||
applicationId "org.schabi.newpipe"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 71
|
||||
versionName "0.15.1"
|
||||
versionCode 740
|
||||
versionName "0.16.2"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@ -44,10 +44,10 @@ android {
|
||||
|
||||
ext {
|
||||
supportLibVersion = '28.0.0'
|
||||
exoPlayerLibVersion = '2.8.4' //2.9.0
|
||||
exoPlayerLibVersion = '2.9.6'
|
||||
roomDbLibVersion = '1.1.1'
|
||||
leakCanaryLibVersion = '1.5.4' //1.6.1
|
||||
okHttpLibVersion = '3.11.0'
|
||||
okHttpLibVersion = '3.12.1'
|
||||
icepickLibVersion = '3.2.0'
|
||||
stethoLibVersion = '1.5.0'
|
||||
}
|
||||
@ -57,7 +57,7 @@ dependencies {
|
||||
exclude module: 'support-annotations'
|
||||
})
|
||||
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:79b0a19d1af'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:2ac713e'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.mockito:mockito-core:2.23.0'
|
||||
|
@ -0,0 +1,116 @@
|
||||
package android.support.design.widget;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.animation.AnimationUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
// check this https://github.com/ToDou/appbarlayout-spring-behavior/blob/master/appbarspring/src/main/java/android/support/design/widget/AppBarFlingFixBehavior.java
|
||||
public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||
|
||||
private ValueAnimator mOffsetAnimator;
|
||||
private static final int MAX_OFFSET_ANIMATION_DURATION = 600; // ms
|
||||
|
||||
public FlingBehavior() {
|
||||
}
|
||||
|
||||
public FlingBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) {
|
||||
if (dy != 0) {
|
||||
int val = child.getBottom();
|
||||
if (val != 0) {
|
||||
int min, max;
|
||||
if (dy < 0) {
|
||||
// We're scrolling down
|
||||
} else {
|
||||
// We're scrolling up
|
||||
if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) {
|
||||
mOffsetAnimator.cancel();
|
||||
}
|
||||
min = -child.getUpNestedPreScrollRange();
|
||||
max = 0;
|
||||
consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull AppBarLayout child, @NonNull View target, float velocityX, float velocityY) {
|
||||
|
||||
if (velocityY != 0) {
|
||||
if (velocityY < 0) {
|
||||
// We're flinging down
|
||||
int val = child.getBottom();
|
||||
if (val != 0) {
|
||||
final int targetScroll =
|
||||
+child.getDownNestedPreScrollRange();
|
||||
animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
|
||||
}
|
||||
|
||||
} else {
|
||||
// We're flinging up
|
||||
int val = child.getBottom();
|
||||
if (val != 0) {
|
||||
final int targetScroll = -child.getUpNestedPreScrollRange();
|
||||
if (getTopBottomOffsetForScrollingSibling() > targetScroll) {
|
||||
animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
|
||||
}
|
||||
|
||||
private void animateOffsetTo(final CoordinatorLayout coordinatorLayout,
|
||||
final AppBarLayout child, final int offset, float velocity) {
|
||||
final int distance = Math.abs(getTopBottomOffsetForScrollingSibling() - offset);
|
||||
|
||||
final int duration;
|
||||
velocity = Math.abs(velocity);
|
||||
if (velocity > 0) {
|
||||
duration = 3 * Math.round(1000 * (distance / velocity));
|
||||
} else {
|
||||
final float distanceRatio = (float) distance / child.getHeight();
|
||||
duration = (int) ((distanceRatio + 1) * 150);
|
||||
}
|
||||
|
||||
animateOffsetWithDuration(coordinatorLayout, child, offset, duration);
|
||||
}
|
||||
|
||||
private void animateOffsetWithDuration(final CoordinatorLayout coordinatorLayout,
|
||||
final AppBarLayout child, final int offset, final int duration) {
|
||||
final int currentOffset = getTopBottomOffsetForScrollingSibling();
|
||||
if (currentOffset == offset) {
|
||||
if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) {
|
||||
mOffsetAnimator.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mOffsetAnimator == null) {
|
||||
mOffsetAnimator = new ValueAnimator();
|
||||
mOffsetAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
|
||||
mOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animator) {
|
||||
setHeaderTopBottomOffset(coordinatorLayout, child,
|
||||
(Integer) animator.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mOffsetAnimator.cancel();
|
||||
}
|
||||
|
||||
mOffsetAnimator.setDuration(Math.min(duration, MAX_OFFSET_ANIMATION_DURATION));
|
||||
mOffsetAnimator.setIntValues(currentOffset, offset);
|
||||
mOffsetAnimator.start();
|
||||
}
|
||||
}
|
@ -2,11 +2,13 @@ package org.schabi.newpipe;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.preference.PreferenceManager;
|
||||
@ -69,6 +71,8 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
|
||||
if(isCancelled() || !isConnected()) return null;
|
||||
|
||||
// Make a network request to get latest NewPipe data.
|
||||
if (client == null) {
|
||||
|
||||
@ -227,4 +231,12 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||
|
||||
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
|
||||
}
|
||||
|
||||
private boolean isConnected() {
|
||||
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
return cm.getActiveNetworkInfo() != null
|
||||
&& cm.getActiveNetworkInfo().isConnected();
|
||||
}
|
||||
}
|
||||
|
@ -3,18 +3,24 @@ package org.schabi.newpipe;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.schabi.newpipe.extractor.DownloadRequest;
|
||||
import org.schabi.newpipe.extractor.DownloadResponse;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.utils.Localization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
@ -139,13 +145,16 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
|
||||
private ResponseBody getBody(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException {
|
||||
final Request.Builder requestBuilder = new Request.Builder()
|
||||
.method("GET", null).url(siteUrl)
|
||||
.addHeader("User-Agent", USER_AGENT);
|
||||
.method("GET", null).url(siteUrl);
|
||||
|
||||
for (Map.Entry<String, String> header : customProperties.entrySet()) {
|
||||
requestBuilder.addHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
|
||||
if (!customProperties.containsKey("User-Agent")) {
|
||||
requestBuilder.header("User-Agent", USER_AGENT);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
}
|
||||
@ -177,4 +186,96 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
public String download(String siteUrl) throws IOException, ReCaptchaException {
|
||||
return download(siteUrl, Collections.emptyMap());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DownloadResponse get(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
|
||||
final Request.Builder requestBuilder = new Request.Builder()
|
||||
.method("GET", null).url(siteUrl);
|
||||
|
||||
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
|
||||
// set custom headers in request
|
||||
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
|
||||
for(String value : pair.getValue()){
|
||||
requestBuilder.addHeader(pair.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!requestHeaders.containsKey("User-Agent")) {
|
||||
requestBuilder.header("User-Agent", USER_AGENT);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
}
|
||||
|
||||
final Request okRequest = requestBuilder.build();
|
||||
final Response response = client.newCall(okRequest).execute();
|
||||
final ResponseBody body = response.body();
|
||||
|
||||
if (response.code() == 429) {
|
||||
throw new ReCaptchaException("reCaptcha Challenge requested");
|
||||
}
|
||||
|
||||
if (body == null) {
|
||||
response.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DownloadResponse(body.string(), response.headers().toMultimap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadResponse get(String siteUrl) throws IOException, ReCaptchaException {
|
||||
return get(siteUrl, DownloadRequest.emptyRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadResponse post(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
|
||||
|
||||
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
|
||||
if(null == requestHeaders.get("Content-Type") || requestHeaders.get("Content-Type").isEmpty()){
|
||||
// content type header is required. maybe throw an exception here
|
||||
return null;
|
||||
}
|
||||
|
||||
String contentType = requestHeaders.get("Content-Type").get(0);
|
||||
|
||||
RequestBody okRequestBody = null;
|
||||
if(null != request.getRequestBody()){
|
||||
okRequestBody = RequestBody.create(MediaType.parse(contentType), request.getRequestBody());
|
||||
}
|
||||
final Request.Builder requestBuilder = new Request.Builder()
|
||||
.method("POST", okRequestBody).url(siteUrl);
|
||||
|
||||
// set custom headers in request
|
||||
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
|
||||
for(String value : pair.getValue()){
|
||||
requestBuilder.addHeader(pair.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!requestHeaders.containsKey("User-Agent")) {
|
||||
requestBuilder.header("User-Agent", USER_AGENT);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
}
|
||||
|
||||
final Request okRequest = requestBuilder.build();
|
||||
final Response response = client.newCall(okRequest).execute();
|
||||
final ResponseBody body = response.body();
|
||||
|
||||
if (response.code() == 429) {
|
||||
throw new ReCaptchaException("reCaptcha Challenge requested");
|
||||
}
|
||||
|
||||
if (body == null) {
|
||||
response.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DownloadResponse(body.string(), response.headers().toMultimap());
|
||||
}
|
||||
}
|
@ -36,12 +36,12 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
@ -81,10 +81,13 @@ public class RouterActivity extends AppCompatActivity {
|
||||
protected int selectedPreviously = -1;
|
||||
|
||||
protected String currentUrl;
|
||||
protected boolean internalRoute = false;
|
||||
protected final CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private boolean selectionIsDownload = false;
|
||||
|
||||
public static final String internalRouteKey = "internalRoute";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -94,11 +97,13 @@ public class RouterActivity extends AppCompatActivity {
|
||||
currentUrl = getUrl(getIntent());
|
||||
|
||||
if (TextUtils.isEmpty(currentUrl)) {
|
||||
Toast.makeText(this, R.string.invalid_url_toast, Toast.LENGTH_LONG).show();
|
||||
handleText();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
internalRoute = getIntent().getBooleanExtra(internalRouteKey, false);
|
||||
|
||||
setTheme(ThemeHelper.isLightThemeSelected(this)
|
||||
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
|
||||
}
|
||||
@ -353,6 +358,15 @@ public class RouterActivity extends AppCompatActivity {
|
||||
positiveButton.setEnabled(state);
|
||||
}
|
||||
|
||||
private void handleText(){
|
||||
String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
NavigationHelper.openSearch(getThemeWrapperContext(),serviceId,searchString);
|
||||
}
|
||||
|
||||
private void handleChoice(final String selectedChoiceKey) {
|
||||
final List<String> validChoicesList = Arrays.asList(getResources().getStringArray(R.array.preferred_open_action_values_list));
|
||||
if (validChoicesList.contains(selectedChoiceKey)) {
|
||||
@ -383,8 +397,10 @@ public class RouterActivity extends AppCompatActivity {
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(intent -> {
|
||||
if(!internalRoute){
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
}
|
||||
startActivity(intent);
|
||||
|
||||
finish();
|
||||
|
@ -47,7 +47,7 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
updateFragments();
|
||||
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -230,21 +230,4 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
|
||||
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void openUrlInBrowser(String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
|
||||
}
|
||||
|
||||
protected void shareUrl(String subject, String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class EmptyFragment extends BaseFragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_empty, container, false);
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package org.schabi.newpipe.fragments.detail;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TabAdaptor extends FragmentPagerAdapter {
|
||||
|
||||
private final List<Fragment> mFragmentList = new ArrayList<>();
|
||||
private final List<String> mFragmentTitleList = new ArrayList<>();
|
||||
private final FragmentManager fragmentManager;
|
||||
|
||||
public TabAdaptor(FragmentManager fm) {
|
||||
super(fm);
|
||||
this.fragmentManager = fm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return mFragmentList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mFragmentList.size();
|
||||
}
|
||||
|
||||
public void addFragment(Fragment fragment, String title) {
|
||||
mFragmentList.add(fragment);
|
||||
mFragmentTitleList.add(title);
|
||||
}
|
||||
|
||||
public void clearAllItems() {
|
||||
mFragmentList.clear();
|
||||
mFragmentTitleList.clear();
|
||||
}
|
||||
|
||||
public void removeItem(int position){
|
||||
mFragmentList.remove(position == 0 ? 0 : position - 1);
|
||||
mFragmentTitleList.remove(position == 0 ? 0 : position - 1);
|
||||
}
|
||||
|
||||
public void updateItem(int position, Fragment fragment){
|
||||
mFragmentList.set(position, fragment);
|
||||
}
|
||||
|
||||
public void updateItem(String title, Fragment fragment){
|
||||
int index = mFragmentTitleList.indexOf(title);
|
||||
if(index != -1){
|
||||
updateItem(index, fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemPosition(Object object) {
|
||||
if (mFragmentList.contains(object)) return mFragmentList.indexOf(object);
|
||||
else return POSITION_NONE;
|
||||
}
|
||||
|
||||
public int getItemPositionByTitle(String title) {
|
||||
return mFragmentTitleList.indexOf(title);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getItemTitle(int position) {
|
||||
if (position < 0 || position >= mFragmentTitleList.size()) {
|
||||
return null;
|
||||
}
|
||||
return mFragmentTitleList.get(position);
|
||||
}
|
||||
|
||||
public void notifyDataSetUpdate(){
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
|
||||
}
|
||||
|
||||
}
|
@ -10,11 +10,13 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.FloatRange;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
@ -25,7 +27,6 @@ import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@ -33,19 +34,15 @@ import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nirhart.parallaxscroll.views.ParallaxScrollView;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||
@ -57,6 +54,7 @@ import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
@ -64,21 +62,22 @@ import org.schabi.newpipe.extractor.stream.Stream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.fragments.EmptyFragment;
|
||||
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
|
||||
import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.player.MainVideoPlayer;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
@ -86,11 +85,10 @@ import org.schabi.newpipe.util.InfoCache;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
@ -105,6 +103,7 @@ import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class VideoDetailFragment
|
||||
@ -115,27 +114,28 @@ public class VideoDetailFragment
|
||||
View.OnLongClickListener {
|
||||
public static final String AUTO_PLAY = "auto_play";
|
||||
|
||||
// Amount of videos to show on start
|
||||
private static final int INITIAL_RELATED_VIDEOS = 8;
|
||||
|
||||
private InfoItemBuilder infoItemBuilder = null;
|
||||
|
||||
private int updateFlags = 0;
|
||||
private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1;
|
||||
private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2;
|
||||
private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4;
|
||||
private static final int COMMENTS_UPDATE_FLAG = 0x4;
|
||||
|
||||
private boolean autoPlayEnabled;
|
||||
private boolean showRelatedStreams;
|
||||
private boolean wasRelatedStreamsExpanded = false;
|
||||
private boolean showComments;
|
||||
private String selectedTabTag;
|
||||
|
||||
@State protected int serviceId = Constants.NO_SERVICE_ID;
|
||||
@State protected String name;
|
||||
@State protected String url;
|
||||
@State
|
||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||
@State
|
||||
protected String name;
|
||||
@State
|
||||
protected String url;
|
||||
|
||||
private StreamInfo currentInfo;
|
||||
private Disposable currentWorker;
|
||||
@NonNull private CompositeDisposable disposables = new CompositeDisposable();
|
||||
@NonNull
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private List<VideoStream> sortedVideoStreams;
|
||||
private int selectedVideoStreamIndex = -1;
|
||||
@ -148,7 +148,6 @@ public class VideoDetailFragment
|
||||
|
||||
private Spinner spinnerToolbar;
|
||||
|
||||
private ParallaxScrollView parallaxScrollRootView;
|
||||
private LinearLayout contentRootLayoutHiding;
|
||||
|
||||
private View thumbnailBackgroundButton;
|
||||
@ -157,7 +156,6 @@ public class VideoDetailFragment
|
||||
|
||||
private View videoTitleRoot;
|
||||
private TextView videoTitleTextView;
|
||||
@Nullable
|
||||
private ImageView videoTitleToggleArrow;
|
||||
private TextView videoCountView;
|
||||
|
||||
@ -182,10 +180,15 @@ public class VideoDetailFragment
|
||||
private ImageView thumbsDownImageView;
|
||||
private TextView thumbsDisabledTextView;
|
||||
|
||||
private TextView nextStreamTitle;
|
||||
private LinearLayout relatedStreamRootLayout;
|
||||
private LinearLayout relatedStreamsView;
|
||||
private ImageButton relatedStreamExpandButton;
|
||||
private static final String COMMENTS_TAB_TAG = "COMMENTS";
|
||||
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
|
||||
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
|
||||
|
||||
private AppBarLayout appBarLayout;
|
||||
private ViewPager viewPager;
|
||||
private TabAdaptor pageAdapter;
|
||||
private TabLayout tabLayout;
|
||||
private FrameLayout relatedStreamsLayout;
|
||||
|
||||
|
||||
/*////////////////////////////////////////////////////////////////////////*/
|
||||
@ -201,12 +204,20 @@ public class VideoDetailFragment
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
public void
|
||||
onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
showRelatedStreams = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.getBoolean(getString(R.string.show_next_video_key), true);
|
||||
|
||||
showComments = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.getBoolean(getString(R.string.show_comments_key), true);
|
||||
|
||||
selectedTabTag = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.getString(getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG);
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
@ -220,6 +231,10 @@ public class VideoDetailFragment
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (currentWorker != null) currentWorker.dispose();
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||
.edit()
|
||||
.putString(getString(R.string.stream_info_selected_tab_key), pageAdapter.getItemTitle(viewPager.getCurrentItem()))
|
||||
.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -228,14 +243,16 @@ public class VideoDetailFragment
|
||||
|
||||
if (updateFlags != 0) {
|
||||
if (!isLoading.get() && currentInfo != null) {
|
||||
if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) initRelatedVideos(currentInfo);
|
||||
if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) startLoading(false);
|
||||
if ((updateFlags & RESOLUTIONS_MENU_UPDATE_FLAG) != 0) setupActionBar(currentInfo);
|
||||
if ((updateFlags & COMMENTS_UPDATE_FLAG) != 0) startLoading(false);
|
||||
}
|
||||
|
||||
if ((updateFlags & TOOLBAR_ITEMS_UPDATE_FLAG) != 0
|
||||
&& menu != null) {
|
||||
updateMenuItemVisibility();
|
||||
}
|
||||
|
||||
updateFlags = 0;
|
||||
}
|
||||
|
||||
@ -292,6 +309,9 @@ public class VideoDetailFragment
|
||||
updateFlags |= RESOLUTIONS_MENU_UPDATE_FLAG;
|
||||
} else if (key.equals(getString(R.string.show_play_with_kodi_key))) {
|
||||
updateFlags |= TOOLBAR_ITEMS_UPDATE_FLAG;
|
||||
} else if (key.equals(getString(R.string.show_comments_key))) {
|
||||
showComments = sharedPreferences.getBoolean(key, true);
|
||||
updateFlags |= COMMENTS_UPDATE_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,7 +321,6 @@ public class VideoDetailFragment
|
||||
|
||||
private static final String INFO_KEY = "info_key";
|
||||
private static final String STACK_KEY = "stack_key";
|
||||
private static final String WAS_RELATED_EXPANDED_KEY = "was_related_expanded_key";
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
@ -310,10 +329,6 @@ public class VideoDetailFragment
|
||||
// Check if the next video label and video is visible,
|
||||
// if it is, include the two elements in the next check
|
||||
int nextCount = currentInfo != null && currentInfo.getNextVideo() != null ? 2 : 0;
|
||||
if (relatedStreamsView != null
|
||||
&& relatedStreamsView.getChildCount() > INITIAL_RELATED_VIDEOS + nextCount) {
|
||||
outState.putSerializable(WAS_RELATED_EXPANDED_KEY, true);
|
||||
}
|
||||
|
||||
if (!isLoading.get() && currentInfo != null && isVisible()) {
|
||||
outState.putSerializable(INFO_KEY, currentInfo);
|
||||
@ -326,12 +341,11 @@ public class VideoDetailFragment
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedState) {
|
||||
super.onRestoreInstanceState(savedState);
|
||||
|
||||
wasRelatedStreamsExpanded = savedState.getBoolean(WAS_RELATED_EXPANDED_KEY, false);
|
||||
Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||
if (serializable instanceof StreamInfo) {
|
||||
//noinspection unchecked
|
||||
currentInfo = (StreamInfo) serializable;
|
||||
InfoCache.getInstance().putInfo(serviceId, url, currentInfo);
|
||||
InfoCache.getInstance().putInfo(serviceId, url, currentInfo, InfoItem.InfoType.STREAM);
|
||||
}
|
||||
|
||||
serializable = savedState.getSerializable(STACK_KEY);
|
||||
@ -339,6 +353,7 @@ public class VideoDetailFragment
|
||||
//noinspection unchecked
|
||||
stack.addAll((Collection<? extends StackItem>) serializable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@ -394,9 +409,6 @@ public class VideoDetailFragment
|
||||
case R.id.detail_title_root_layout:
|
||||
toggleTitleAndDescription();
|
||||
break;
|
||||
case R.id.detail_related_streams_expand:
|
||||
toggleExpandRelatedVideos(currentInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -420,7 +432,6 @@ public class VideoDetailFragment
|
||||
}
|
||||
|
||||
private void toggleTitleAndDescription() {
|
||||
if (videoTitleToggleArrow != null) { //it is null for tablets
|
||||
if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) {
|
||||
videoTitleTextView.setMaxLines(1);
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
@ -431,32 +442,6 @@ public class VideoDetailFragment
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleExpandRelatedVideos(StreamInfo info) {
|
||||
if (DEBUG) Log.d(TAG, "toggleExpandRelatedVideos() called with: info = [" + info + "]");
|
||||
if (!showRelatedStreams) return;
|
||||
|
||||
int nextCount = info.getNextVideo() != null ? 2 : 0;
|
||||
int initialCount = INITIAL_RELATED_VIDEOS + nextCount;
|
||||
|
||||
if (relatedStreamsView.getChildCount() > initialCount) {
|
||||
relatedStreamsView.removeViews(initialCount,
|
||||
relatedStreamsView.getChildCount() - (initialCount));
|
||||
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(
|
||||
activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand)));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = INITIAL_RELATED_VIDEOS; i < info.getRelatedStreams().size(); i++) {
|
||||
InfoItem item = info.getRelatedStreams().get(i);
|
||||
//Log.d(TAG, "i = " + i);
|
||||
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
|
||||
}
|
||||
relatedStreamExpandButton.setImageDrawable(
|
||||
ContextCompat.getDrawable(activity,
|
||||
ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.collapse)));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Init
|
||||
@ -467,8 +452,6 @@ public class VideoDetailFragment
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
spinnerToolbar = activity.findViewById(R.id.toolbar).findViewById(R.id.toolbar_spinner);
|
||||
|
||||
parallaxScrollRootView = rootView.findViewById(R.id.detail_main_content);
|
||||
|
||||
thumbnailBackgroundButton = rootView.findViewById(R.id.detail_thumbnail_root_layout);
|
||||
thumbnailImageView = rootView.findViewById(R.id.detail_thumbnail_image_view);
|
||||
thumbnailPlayButton = rootView.findViewById(R.id.detail_thumbnail_play_button);
|
||||
@ -504,32 +487,23 @@ public class VideoDetailFragment
|
||||
uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view);
|
||||
uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view);
|
||||
|
||||
relatedStreamRootLayout = rootView.findViewById(R.id.detail_related_streams_root_layout);
|
||||
nextStreamTitle = rootView.findViewById(R.id.detail_next_stream_title);
|
||||
relatedStreamsView = rootView.findViewById(R.id.detail_related_streams_view);
|
||||
appBarLayout = rootView.findViewById(R.id.appbarlayout);
|
||||
viewPager = rootView.findViewById(R.id.viewpager);
|
||||
pageAdapter = new TabAdaptor(getChildFragmentManager());
|
||||
viewPager.setAdapter(pageAdapter);
|
||||
tabLayout = rootView.findViewById(R.id.tablayout);
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
|
||||
relatedStreamExpandButton = rootView.findViewById(R.id.detail_related_streams_expand);
|
||||
relatedStreamsLayout = rootView.findViewById(R.id.relatedStreamsLayout);
|
||||
|
||||
infoItemBuilder = new InfoItemBuilder(activity);
|
||||
setHeightThumbnail();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initListeners() {
|
||||
super.initListeners();
|
||||
infoItemBuilder.setOnStreamSelectedListener(new OnClickGesture<StreamInfoItem>() {
|
||||
@Override
|
||||
public void selected(StreamInfoItem selectedItem) {
|
||||
selectAndLoadVideo(selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(StreamInfoItem selectedItem) {
|
||||
showStreamDialog(selectedItem);
|
||||
}
|
||||
});
|
||||
|
||||
videoTitleRoot.setOnClickListener(this);
|
||||
uploaderRootLayout.setOnClickListener(this);
|
||||
@ -539,7 +513,6 @@ public class VideoDetailFragment
|
||||
detailControlsAddToPlaylist.setOnClickListener(this);
|
||||
detailControlsDownload.setOnClickListener(this);
|
||||
detailControlsDownload.setOnLongClickListener(this);
|
||||
relatedStreamExpandButton.setOnClickListener(this);
|
||||
|
||||
detailControlsBackground.setLongClickable(true);
|
||||
detailControlsPopup.setLongClickable(true);
|
||||
@ -575,7 +548,7 @@ public class VideoDetailFragment
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
shareUrl(item.getName(), item.getUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -622,44 +595,6 @@ public class VideoDetailFragment
|
||||
}
|
||||
}
|
||||
|
||||
private void initRelatedVideos(StreamInfo info) {
|
||||
if (relatedStreamsView.getChildCount() > 0) relatedStreamsView.removeAllViews();
|
||||
|
||||
if (info.getNextVideo() != null && showRelatedStreams) {
|
||||
nextStreamTitle.setVisibility(View.VISIBLE);
|
||||
relatedStreamsView.addView(
|
||||
infoItemBuilder.buildView(relatedStreamsView, info.getNextVideo()));
|
||||
relatedStreamsView.addView(getSeparatorView());
|
||||
setRelatedStreamsVisibility(View.VISIBLE);
|
||||
} else {
|
||||
nextStreamTitle.setVisibility(View.GONE);
|
||||
setRelatedStreamsVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (info.getRelatedStreams() != null
|
||||
&& !info.getRelatedStreams().isEmpty() && showRelatedStreams) {
|
||||
//long first = System.nanoTime(), each;
|
||||
int to = info.getRelatedStreams().size() >= INITIAL_RELATED_VIDEOS
|
||||
? INITIAL_RELATED_VIDEOS
|
||||
: info.getRelatedStreams().size();
|
||||
for (int i = 0; i < to; i++) {
|
||||
InfoItem item = info.getRelatedStreams().get(i);
|
||||
//each = System.nanoTime();
|
||||
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
|
||||
//if (DEBUG) Log.d(TAG, "each took " + ((System.nanoTime() - each) / 1000000L) + "ms");
|
||||
}
|
||||
//if (DEBUG) Log.d(TAG, "Total time " + ((System.nanoTime() - first) / 1000000L) + "ms");
|
||||
|
||||
setRelatedStreamsVisibility(View.VISIBLE);
|
||||
relatedStreamExpandButton.setVisibility(View.VISIBLE);
|
||||
|
||||
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(
|
||||
activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand)));
|
||||
} else {
|
||||
if (info.getNextVideo() == null) setRelatedStreamsVisibility(View.GONE);
|
||||
relatedStreamExpandButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
@ -702,13 +637,13 @@ public class VideoDetailFragment
|
||||
switch (id) {
|
||||
case R.id.menu_item_share: {
|
||||
if (currentInfo != null) {
|
||||
shareUrl(currentInfo.getName(), currentInfo.getOriginalUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), currentInfo.getName(), currentInfo.getOriginalUrl());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_item_openInBrowser: {
|
||||
if (currentInfo != null) {
|
||||
openUrlInBrowser(currentInfo.getOriginalUrl());
|
||||
ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -731,7 +666,8 @@ public class VideoDetailFragment
|
||||
builder.setMessage(R.string.kore_not_found)
|
||||
.setPositiveButton(R.string.install, (DialogInterface dialog, int which) ->
|
||||
NavigationHelper.installKore(context))
|
||||
.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {});
|
||||
.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
@ -850,23 +786,16 @@ public class VideoDetailFragment
|
||||
setInitialData(info.getServiceId(), info.getUrl(), info.getName());
|
||||
pushToStack(serviceId, url, name);
|
||||
showLoading();
|
||||
initTabs();
|
||||
|
||||
Log.d(TAG, "prepareAndHandleInfo() called parallaxScrollRootView.getScrollY(): "
|
||||
+ parallaxScrollRootView.getScrollY());
|
||||
final boolean greaterThanThreshold = parallaxScrollRootView.getScrollY() > (int)
|
||||
(getResources().getDisplayMetrics().heightPixels * .1f);
|
||||
|
||||
if (scrollToTop) parallaxScrollRootView.smoothScrollTo(0, 0);
|
||||
animateView(contentRootLayoutHiding,
|
||||
false,
|
||||
greaterThanThreshold ? 250 : 0, 0, () -> {
|
||||
if (scrollToTop) appBarLayout.setExpanded(true, true);
|
||||
handleResult(info);
|
||||
showContentWithAnimation(120, 0, .01f);
|
||||
});
|
||||
showContent();
|
||||
|
||||
}
|
||||
|
||||
protected void prepareAndLoadInfo() {
|
||||
parallaxScrollRootView.smoothScrollTo(0, 0);
|
||||
appBarLayout.setExpanded(true, true);
|
||||
pushToStack(serviceId, url, name);
|
||||
startLoading(false);
|
||||
}
|
||||
@ -875,6 +804,7 @@ public class VideoDetailFragment
|
||||
public void startLoading(boolean forceLoad) {
|
||||
super.startLoading(forceLoad);
|
||||
|
||||
initTabs();
|
||||
currentInfo = null;
|
||||
if (currentWorker != null) currentWorker.dispose();
|
||||
|
||||
@ -884,12 +814,54 @@ public class VideoDetailFragment
|
||||
.subscribe((@NonNull StreamInfo result) -> {
|
||||
isLoading.set(false);
|
||||
currentInfo = result;
|
||||
showContentWithAnimation(120, 0, 0);
|
||||
handleResult(result);
|
||||
showContent();
|
||||
}, (@NonNull Throwable throwable) -> {
|
||||
isLoading.set(false);
|
||||
onError(throwable);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void initTabs() {
|
||||
if (pageAdapter.getCount() != 0) {
|
||||
selectedTabTag = pageAdapter.getItemTitle(viewPager.getCurrentItem());
|
||||
}
|
||||
pageAdapter.clearAllItems();
|
||||
|
||||
if(shouldShowComments()){
|
||||
pageAdapter.addFragment(CommentsFragment.getInstance(serviceId, url, name), COMMENTS_TAB_TAG);
|
||||
}
|
||||
|
||||
if(showRelatedStreams && null == relatedStreamsLayout){
|
||||
//temp empty fragment. will be updated in handleResult
|
||||
pageAdapter.addFragment(new Fragment(), RELATED_TAB_TAG);
|
||||
}
|
||||
|
||||
if(pageAdapter.getCount() == 0){
|
||||
pageAdapter.addFragment(new EmptyFragment(), EMPTY_TAB_TAG);
|
||||
}
|
||||
|
||||
pageAdapter.notifyDataSetUpdate();
|
||||
|
||||
if(pageAdapter.getCount() < 2){
|
||||
tabLayout.setVisibility(View.GONE);
|
||||
}else{
|
||||
int position = pageAdapter.getItemPositionByTitle(selectedTabTag);
|
||||
if(position != -1) viewPager.setCurrentItem(position);
|
||||
tabLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldShowComments() {
|
||||
try {
|
||||
return showComments && NewPipe.getService(serviceId)
|
||||
.getServiceInfo()
|
||||
.getMediaCapabilities()
|
||||
.contains(COMMENTS);
|
||||
} catch (ExtractionException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@ -1009,24 +981,6 @@ public class VideoDetailFragment
|
||||
}));
|
||||
}
|
||||
|
||||
private View getSeparatorView() {
|
||||
View separator = new View(activity);
|
||||
LinearLayout.LayoutParams params =
|
||||
new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1);
|
||||
int m8 = (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
|
||||
int m5 = (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics());
|
||||
params.setMargins(m8, m5, m8, m5);
|
||||
separator.setLayoutParams(params);
|
||||
|
||||
TypedValue typedValue = new TypedValue();
|
||||
activity.getTheme().resolveAttribute(R.attr.separator_color, typedValue, true);
|
||||
separator.setBackgroundColor(typedValue.data);
|
||||
|
||||
return separator;
|
||||
}
|
||||
|
||||
private void setHeightThumbnail() {
|
||||
final DisplayMetrics metrics = getResources().getDisplayMetrics();
|
||||
boolean isPortrait = metrics.heightPixels > metrics.widthPixels;
|
||||
@ -1038,50 +992,8 @@ public class VideoDetailFragment
|
||||
thumbnailImageView.setMinimumHeight(height);
|
||||
}
|
||||
|
||||
private void showContentWithAnimation(long duration,
|
||||
long delay,
|
||||
@FloatRange(from = 0.0f, to = 1.0f)
|
||||
float translationPercent) {
|
||||
int translationY = (int) (getResources().getDisplayMetrics().heightPixels *
|
||||
(translationPercent > 0.0f ? translationPercent : .06f));
|
||||
|
||||
contentRootLayoutHiding.animate().setListener(null).cancel();
|
||||
contentRootLayoutHiding.setAlpha(0f);
|
||||
contentRootLayoutHiding.setTranslationY(translationY);
|
||||
contentRootLayoutHiding.setVisibility(View.VISIBLE);
|
||||
contentRootLayoutHiding.animate()
|
||||
.alpha(1f)
|
||||
.translationY(0)
|
||||
.setStartDelay(delay)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.start();
|
||||
|
||||
uploaderRootLayout.animate().setListener(null).cancel();
|
||||
uploaderRootLayout.setAlpha(0f);
|
||||
uploaderRootLayout.setTranslationY(translationY);
|
||||
uploaderRootLayout.setVisibility(View.VISIBLE);
|
||||
uploaderRootLayout.animate()
|
||||
.alpha(1f)
|
||||
.translationY(0)
|
||||
.setStartDelay((long) (duration * .5f) + delay)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.start();
|
||||
|
||||
if (showRelatedStreams) {
|
||||
relatedStreamRootLayout.animate().setListener(null).cancel();
|
||||
relatedStreamRootLayout.setAlpha(0f);
|
||||
relatedStreamRootLayout.setTranslationY(translationY);
|
||||
relatedStreamRootLayout.setVisibility(View.VISIBLE);
|
||||
relatedStreamRootLayout.animate()
|
||||
.alpha(1f)
|
||||
.translationY(0)
|
||||
.setStartDelay((long) (duration * .8f) + delay)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.start();
|
||||
}
|
||||
private void showContent() {
|
||||
AnimationUtils.slideUp(contentRootLayoutHiding,120, 96, 0.06f);
|
||||
}
|
||||
|
||||
protected void setInitialData(int serviceId, String url, String name) {
|
||||
@ -1116,7 +1028,7 @@ public class VideoDetailFragment
|
||||
public void showLoading() {
|
||||
super.showLoading();
|
||||
|
||||
animateView(contentRootLayoutHiding, false, 200);
|
||||
contentRootLayoutHiding.setVisibility(View.INVISIBLE);
|
||||
animateView(spinnerToolbar, false, 200);
|
||||
animateView(thumbnailPlayButton, false, 50);
|
||||
animateView(detailDurationView, false, 100);
|
||||
@ -1126,18 +1038,18 @@ public class VideoDetailFragment
|
||||
animateView(videoTitleTextView, true, 0);
|
||||
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
if (videoTitleToggleArrow != null) { //phone
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
videoTitleToggleArrow.setVisibility(View.GONE);
|
||||
} else { //tablet
|
||||
final View related = (View) relatedStreamRootLayout.getParent();
|
||||
//don`t need to hide it if related streams are disabled
|
||||
if (related.getVisibility() == View.VISIBLE) {
|
||||
related.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
videoTitleRoot.setClickable(false);
|
||||
|
||||
if(relatedStreamsLayout != null){
|
||||
if(showRelatedStreams){
|
||||
relatedStreamsLayout.setVisibility(View.INVISIBLE);
|
||||
}else{
|
||||
relatedStreamsLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
imageLoader.cancelDisplayTask(thumbnailImageView);
|
||||
imageLoader.cancelDisplayTask(uploaderThumb);
|
||||
thumbnailImageView.setImageBitmap(null);
|
||||
@ -1149,6 +1061,19 @@ public class VideoDetailFragment
|
||||
super.handleResult(info);
|
||||
|
||||
setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName());
|
||||
|
||||
if(showRelatedStreams){
|
||||
if(null == relatedStreamsLayout){ //phone
|
||||
pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(currentInfo));
|
||||
pageAdapter.notifyDataSetUpdate();
|
||||
}else{ //tablet
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.replace(R.id.relatedStreamsLayout, RelatedVideosFragment.getInstance(currentInfo))
|
||||
.commitNow();
|
||||
relatedStreamsLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
//pushToStack(serviceId, url, name);
|
||||
|
||||
animateView(thumbnailPlayButton, true, 200);
|
||||
@ -1213,14 +1138,10 @@ public class VideoDetailFragment
|
||||
}
|
||||
|
||||
videoDescriptionView.setVisibility(View.GONE);
|
||||
if (videoTitleToggleArrow != null) {
|
||||
videoTitleRoot.setClickable(true);
|
||||
videoTitleToggleArrow.setVisibility(View.VISIBLE);
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (!TextUtils.isEmpty(info.getUploadDate())) {
|
||||
videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate()));
|
||||
}
|
||||
@ -1229,11 +1150,6 @@ public class VideoDetailFragment
|
||||
animateView(spinnerToolbar, true, 500);
|
||||
setupActionBar(info);
|
||||
initThumbnailViews(info);
|
||||
initRelatedVideos(info);
|
||||
if (wasRelatedStreamsExpanded) {
|
||||
toggleExpandRelatedVideos(currentInfo);
|
||||
wasRelatedStreamsExpanded = false;
|
||||
}
|
||||
|
||||
setTitleToUrl(info.getServiceId(), info.getUrl(), info.getName());
|
||||
setTitleToUrl(info.getServiceId(), info.getOriginalUrl(), info.getName());
|
||||
@ -1268,11 +1184,6 @@ public class VideoDetailFragment
|
||||
// Only auto play in the first open
|
||||
autoPlayEnabled = false;
|
||||
}
|
||||
|
||||
final ViewParent related = relatedStreamRootLayout.getParent();
|
||||
if (related instanceof ScrollView) {
|
||||
((ScrollView) related).scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1339,13 +1250,4 @@ public class VideoDetailFragment
|
||||
|
||||
showError(getString(R.string.blocked_by_gema), false, R.drawable.gruese_die_gema);
|
||||
}
|
||||
|
||||
private void setRelatedStreamsVisibility(int visibility) {
|
||||
final ViewParent parent = relatedStreamRootLayout.getParent();
|
||||
if (parent instanceof ScrollView) {
|
||||
((ScrollView) parent).setVisibility(visibility);
|
||||
} else {
|
||||
relatedStreamRootLayout.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import android.view.View;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
@ -33,6 +34,7 @@ import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
|
||||
import java.util.Collections;
|
||||
@ -220,6 +222,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
}
|
||||
});
|
||||
|
||||
infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() {
|
||||
@Override
|
||||
public void selected(CommentsInfoItem selectedItem) {
|
||||
onItemSelected(selectedItem);
|
||||
}
|
||||
});
|
||||
|
||||
itemsList.clearOnScrollListeners();
|
||||
itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
|
||||
@Override
|
||||
@ -247,6 +256,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.direct_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup),
|
||||
context.getResources().getString(R.string.append_playlist),
|
||||
@ -256,19 +266,22 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
NavigationHelper.playOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 2:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 3:
|
||||
if (getFragmentManager() != null) {
|
||||
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
|
||||
.show(getFragmentManager(), TAG);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
shareUrl(item.getName(), item.getUrl());
|
||||
case 4:
|
||||
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -46,6 +46,7 @@ import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -190,7 +191,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
shareUrl(item.getName(), item.getUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -233,10 +234,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
openRssFeed();
|
||||
break;
|
||||
case R.id.menu_item_openInBrowser:
|
||||
openUrlInBrowser(currentInfo.getOriginalUrl());
|
||||
ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl());
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
shareUrl(name, currentInfo.getOriginalUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), name, currentInfo.getOriginalUrl());
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
@ -0,0 +1,149 @@
|
||||
package org.schabi.newpipe.fragments.list.comments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
|
||||
|
||||
private boolean mIsVisibleToUser = false;
|
||||
|
||||
public static CommentsFragment getInstance(int serviceId, String url, String name) {
|
||||
CommentsFragment instance = new CommentsFragment();
|
||||
instance.setInitialData(serviceId, url, name);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_comments, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Load and handle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<CommentsInfo> loadResult(boolean forceLoad) {
|
||||
return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void showLoading() {
|
||||
super.showLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull CommentsInfo result) {
|
||||
super.handleResult(result);
|
||||
|
||||
AnimationUtils.slideUp(getView(),120, 96, 0.06f);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||
}
|
||||
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
||||
super.handleNextItems(result);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(),
|
||||
UserAction.REQUESTED_COMMENTS,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
"Get next page of: " + url,
|
||||
R.string.general_error);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// OnError
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected boolean onError(Throwable exception) {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
hideLoading();
|
||||
showSnackBarError(exception, UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(serviceId), url, R.string.error_unable_to_load_comments);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setTitle(String title) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGridLayout() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -168,7 +169,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
||||
break;
|
||||
case 5:
|
||||
shareUrl(item.getName(), item.getUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -230,10 +231,10 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_item_openInBrowser:
|
||||
openUrlInBrowser(url);
|
||||
ShareUtils.openUrlInBrowser(this.getContext(), url);
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
shareUrl(name, url);
|
||||
ShareUtils.shareUrl(this.getContext(), name, url);
|
||||
break;
|
||||
case R.id.menu_item_bookmark:
|
||||
onBookmarkClicked();
|
||||
@ -305,6 +306,16 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
|
||||
headerBackgroundButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
|
||||
|
||||
headerPopupButton.setOnLongClickListener(view -> {
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue());
|
||||
return true;
|
||||
});
|
||||
|
||||
headerBackgroundButton.setOnLongClickListener(view -> {
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue() {
|
||||
|
@ -12,6 +12,7 @@ import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.TooltipCompat;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
@ -45,10 +46,9 @@ import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.LayoutManagerSmoothScroller;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
|
||||
@ -73,8 +73,8 @@ import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
import static android.support.v7.widget.helper.ItemTouchHelper.Callback.makeMovementFlags;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class SearchFragment
|
||||
@ -298,7 +298,23 @@ public class SearchFragment
|
||||
suggestionsPanel = rootView.findViewById(R.id.suggestions_panel);
|
||||
suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list);
|
||||
suggestionsRecyclerView.setAdapter(suggestionListAdapter);
|
||||
suggestionsRecyclerView.setLayoutManager(new LayoutManagerSmoothScroller(activity));
|
||||
new ItemTouchHelper(new ItemTouchHelper.Callback() {
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
return getSuggestionMovementFlags(recyclerView, viewHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
onSuggestionItemSwiped(viewHolder, i);
|
||||
}
|
||||
}).attachToRecyclerView(suggestionsRecyclerView);
|
||||
|
||||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||
@ -901,4 +917,28 @@ public class SearchFragment
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Suggestion item touch helper
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public int getSuggestionMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final SuggestionItem item = suggestionListAdapter.getItem(position);
|
||||
return item.fromHistory ? makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
|
||||
}
|
||||
|
||||
public void onSuggestionItemSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final String query = suggestionListAdapter.getItem(position).query;
|
||||
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> suggestionPublisher
|
||||
.onNext(searchEditText.getText().toString()),
|
||||
throwable -> showSnackBarError(throwable,
|
||||
UserAction.DELETE_FROM_HISTORY, "none",
|
||||
"Deleting item failed", R.string.general_error));
|
||||
disposables.add(onDelete);
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
||||
});
|
||||
}
|
||||
|
||||
private SuggestionItem getItem(int position) {
|
||||
SuggestionItem getItem(int position) {
|
||||
return items.get(position);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,208 @@
|
||||
package org.schabi.newpipe.fragments.list.videos;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.Switch;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.RelatedStreamInfo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo> implements SharedPreferences.OnSharedPreferenceChangeListener{
|
||||
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
private RelatedStreamInfo relatedStreamInfo;
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
private View headerRootLayout;
|
||||
private Switch aSwitch;
|
||||
|
||||
private boolean mIsVisibleToUser = false;
|
||||
|
||||
public static RelatedVideosFragment getInstance(StreamInfo info) {
|
||||
RelatedVideosFragment instance = new RelatedVideosFragment();
|
||||
instance.setInitialData(info);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_related_streams, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
protected View getListHeader(){
|
||||
if(relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null){
|
||||
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.related_streams_header, itemsList, false);
|
||||
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
|
||||
|
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
aSwitch.setChecked(autoplay);
|
||||
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
|
||||
SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
|
||||
prefEdit.putBoolean(getString(R.string.auto_queue_key), b);
|
||||
prefEdit.apply();
|
||||
}
|
||||
});
|
||||
return headerRootLayout;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<RelatedStreamInfo> loadResult(boolean forceLoad) {
|
||||
return Single.fromCallable(() -> relatedStreamInfo);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void showLoading() {
|
||||
super.showLoading();
|
||||
if(null != headerRootLayout) headerRootLayout.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull RelatedStreamInfo result) {
|
||||
|
||||
super.handleResult(result);
|
||||
|
||||
if(null != headerRootLayout) headerRootLayout.setVisibility(View.VISIBLE);
|
||||
AnimationUtils.slideUp(getView(),120, 96, 0.06f);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||
}
|
||||
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
||||
super.handleNextItems(result);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(),
|
||||
UserAction.REQUESTED_STREAM,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
"Get next page of: " + url,
|
||||
R.string.general_error);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// OnError
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected boolean onError(Throwable exception) {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
hideLoading();
|
||||
showSnackBarError(exception, UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(serviceId), url, R.string.general_error);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setTitle(String title) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void setInitialData(StreamInfo info) {
|
||||
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
|
||||
if(this.relatedStreamInfo == null) this.relatedStreamInfo = RelatedStreamInfo.getInfo(info);
|
||||
}
|
||||
|
||||
|
||||
private static final String INFO_KEY = "related_info_key";
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putSerializable(INFO_KEY, relatedStreamInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedState) {
|
||||
super.onRestoreInstanceState(savedState);
|
||||
if (savedState != null) {
|
||||
Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||
if(serializable instanceof RelatedStreamInfo){
|
||||
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
|
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
if(null != aSwitch) aSwitch.setChecked(autoplay);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGridLayout() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -10,10 +10,13 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
|
||||
@ -50,6 +53,7 @@ public class InfoItemBuilder {
|
||||
private OnClickGesture<StreamInfoItem> onStreamSelectedListener;
|
||||
private OnClickGesture<ChannelInfoItem> onChannelSelectedListener;
|
||||
private OnClickGesture<PlaylistInfoItem> onPlaylistSelectedListener;
|
||||
private OnClickGesture<CommentsInfoItem> onCommentsSelectedListener;
|
||||
|
||||
public InfoItemBuilder(Context context) {
|
||||
this.context = context;
|
||||
@ -73,6 +77,8 @@ public class InfoItemBuilder {
|
||||
return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent) : new ChannelInfoItemHolder(this, parent);
|
||||
case PLAYLIST:
|
||||
return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent) : new PlaylistInfoItemHolder(this, parent);
|
||||
case COMMENT:
|
||||
return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) : new CommentsInfoItemHolder(this, parent);
|
||||
default:
|
||||
Log.e(TAG, "Trollolo");
|
||||
throw new RuntimeException("InfoType not expected = " + infoType.name());
|
||||
@ -111,4 +117,12 @@ public class InfoItemBuilder {
|
||||
this.onPlaylistSelectedListener = listener;
|
||||
}
|
||||
|
||||
public OnClickGesture<CommentsInfoItem> getOnCommentsSelectedListener() {
|
||||
return onCommentsSelectedListener;
|
||||
}
|
||||
|
||||
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> onCommentsSelectedListener) {
|
||||
this.onCommentsSelectedListener = onCommentsSelectedListener;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,10 +9,13 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder;
|
||||
@ -63,6 +66,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300;
|
||||
private static final int PLAYLIST_HOLDER_TYPE = 0x301;
|
||||
private static final int GRID_PLAYLIST_HOLDER_TYPE = 0x302;
|
||||
private static final int MINI_COMMENT_HOLDER_TYPE = 0x400;
|
||||
private static final int COMMENT_HOLDER_TYPE = 0x401;
|
||||
|
||||
private final InfoItemBuilder infoItemBuilder;
|
||||
private final ArrayList<InfoItem> infoItemList;
|
||||
@ -98,6 +103,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
infoItemBuilder.setOnPlaylistSelectedListener(listener);
|
||||
}
|
||||
|
||||
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> listener) {
|
||||
infoItemBuilder.setOnCommentsSelectedListener(listener);
|
||||
}
|
||||
|
||||
public void useMiniItemVariants(boolean useMiniVariant) {
|
||||
this.useMiniVariant = useMiniVariant;
|
||||
}
|
||||
@ -223,6 +232,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
|
||||
case PLAYLIST:
|
||||
return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
|
||||
case COMMENT:
|
||||
return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE;
|
||||
default:
|
||||
Log.e(TAG, "Trollolo");
|
||||
return -1;
|
||||
@ -231,7 +242,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
|
||||
if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
|
||||
switch (type) {
|
||||
case HEADER_TYPE:
|
||||
return new HFHolder(header);
|
||||
@ -255,6 +267,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
return new PlaylistInfoItemHolder(infoItemBuilder, parent);
|
||||
case GRID_PLAYLIST_HOLDER_TYPE:
|
||||
return new PlaylistGridInfoItemHolder(infoItemBuilder, parent);
|
||||
case MINI_COMMENT_HOLDER_TYPE:
|
||||
return new CommentsMiniInfoItemHolder(infoItemBuilder, parent);
|
||||
case COMMENT_HOLDER_TYPE:
|
||||
return new CommentsInfoItemHolder(infoItemBuilder, parent);
|
||||
default:
|
||||
Log.e(TAG, "Trollolo");
|
||||
return new FallbackViewHolder(new View(parent.getContext()));
|
||||
|
@ -0,0 +1,53 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItemHolder .java is part of NewPipe.
|
||||
*
|
||||
* NewPipe 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.
|
||||
*
|
||||
* NewPipe 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 NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
|
||||
|
||||
public final TextView itemTitleView;
|
||||
|
||||
public CommentsInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_comments_item, parent);
|
||||
|
||||
itemTitleView = itemView.findViewById(R.id.itemTitleView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem) {
|
||||
super.updateFromItem(infoItem);
|
||||
|
||||
if (!(infoItem instanceof CommentsInfoItem)) return;
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
itemTitleView.setText(item.getAuthorName());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.jsoup.helper.StringUtil;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.CommentTextOnTouchListener;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
|
||||
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
public final CircleImageView itemThumbnailView;
|
||||
private final TextView itemContentView;
|
||||
private final TextView itemLikesCountView;
|
||||
private final TextView itemDislikesCountView;
|
||||
private final TextView itemPublishedTime;
|
||||
|
||||
private static final int commentDefaultLines = 2;
|
||||
private static final int commentExpandedLines = 1000;
|
||||
|
||||
private String commentText;
|
||||
private String streamUrl;
|
||||
|
||||
private static final Pattern pattern = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)");
|
||||
|
||||
private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() {
|
||||
@Override
|
||||
public String transformUrl(Matcher match, String url) {
|
||||
int timestamp = 0;
|
||||
String hours = match.group(1);
|
||||
String minutes = match.group(2);
|
||||
String seconds = match.group(3);
|
||||
if(hours != null) timestamp += (Integer.parseInt(hours.replace(":", ""))*3600);
|
||||
if(minutes != null) timestamp += (Integer.parseInt(minutes.replace(":", ""))*60);
|
||||
if(seconds != null) timestamp += (Integer.parseInt(seconds));
|
||||
return streamUrl + url.replace(match.group(0), "#timestamp=" + String.valueOf(timestamp));
|
||||
}
|
||||
};
|
||||
|
||||
CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
|
||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||
itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
|
||||
itemDislikesCountView = itemView.findViewById(R.id.detail_thumbs_down_count_view);
|
||||
itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime);
|
||||
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
|
||||
}
|
||||
|
||||
public CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
this(infoItemBuilder, R.layout.list_comments_mini_item, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem) {
|
||||
if (!(infoItem instanceof CommentsInfoItem)) return;
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
itemBuilder.getImageLoader()
|
||||
.displayImage(item.getAuthorThumbnail(),
|
||||
itemThumbnailView,
|
||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
||||
|
||||
itemThumbnailView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if(StringUtil.isBlank(item.getAuthorEndpoint())) return;
|
||||
try {
|
||||
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
||||
NavigationHelper.openChannelFragment(
|
||||
activity.getSupportFragmentManager(),
|
||||
item.getServiceId(),
|
||||
item.getAuthorEndpoint(),
|
||||
item.getAuthorName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
streamUrl = item.getUrl();
|
||||
|
||||
itemContentView.setLines(commentDefaultLines);
|
||||
commentText = item.getCommentText();
|
||||
itemContentView.setText(commentText);
|
||||
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
|
||||
|
||||
if (itemContentView.getLineCount() == 0) {
|
||||
itemContentView.post(() -> ellipsize());
|
||||
} else {
|
||||
ellipsize();
|
||||
}
|
||||
|
||||
if (null != item.getLikeCount()) {
|
||||
itemLikesCountView.setText(String.valueOf(item.getLikeCount()));
|
||||
}
|
||||
itemPublishedTime.setText(item.getPublishedTime());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
toggleEllipsize();
|
||||
if (itemBuilder.getOnCommentsSelectedListener() != null) {
|
||||
itemBuilder.getOnCommentsSelectedListener().selected(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ellipsize() {
|
||||
if (itemContentView.getLineCount() > commentDefaultLines){
|
||||
int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1);
|
||||
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine -2);
|
||||
if(end == -1) end = Math.max(endOfLastLine -2, 0);
|
||||
String newVal = itemContentView.getText().subSequence(0, end) + " …";
|
||||
itemContentView.setText(newVal);
|
||||
}
|
||||
linkify();
|
||||
}
|
||||
|
||||
private void toggleEllipsize() {
|
||||
if (itemContentView.getText().toString().equals(commentText)) {
|
||||
if (itemContentView.getLineCount() > commentDefaultLines) ellipsize();
|
||||
} else {
|
||||
expand();
|
||||
}
|
||||
}
|
||||
|
||||
private void expand() {
|
||||
itemContentView.setMaxLines(commentExpandedLines);
|
||||
itemContentView.setText(commentText);
|
||||
linkify();
|
||||
}
|
||||
|
||||
private void linkify(){
|
||||
Linkify.addLinks(itemContentView, Linkify.WEB_URLS);
|
||||
Linkify.addLinks(itemContentView, pattern, null, null, timestampLink);
|
||||
itemContentView.setMovementMethod(null);
|
||||
}
|
||||
}
|
@ -8,7 +8,11 @@ import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
@ -21,13 +25,16 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -104,6 +111,12 @@ public class StatisticsPlaylistFragment
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.menu_history, menu);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Views
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@ -155,6 +168,53 @@ public class StatisticsPlaylistFragment
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_history_clear:
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.delete_view_history_alert)
|
||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
||||
final Disposable onDelete = recordManager.deleteWholeStreamHistory()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> Toast.makeText(getContext(),
|
||||
R.string.view_history_deleted,
|
||||
Toast.LENGTH_SHORT).show(),
|
||||
throwable -> ErrorActivity.reportError(getContext(),
|
||||
throwable,
|
||||
SettingsActivity.class, null,
|
||||
ErrorActivity.ErrorInfo.make(
|
||||
UserAction.DELETE_FROM_HISTORY,
|
||||
"none",
|
||||
"Delete view history",
|
||||
R.string.general_error)));
|
||||
|
||||
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> {},
|
||||
throwable -> ErrorActivity.reportError(getContext(),
|
||||
throwable,
|
||||
SettingsActivity.class, null,
|
||||
ErrorActivity.ErrorInfo.make(
|
||||
UserAction.DELETE_FROM_HISTORY,
|
||||
"none",
|
||||
"Delete search history",
|
||||
R.string.general_error)));
|
||||
disposables.add(onClearOrphans);
|
||||
disposables.add(onDelete);
|
||||
}))
|
||||
.create()
|
||||
.show();
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Loading
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@ -335,7 +395,7 @@ public class StatisticsPlaylistFragment
|
||||
deleteEntry(index);
|
||||
break;
|
||||
case 6:
|
||||
shareUrl(item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -34,6 +34,7 @@ import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -555,7 +556,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
deleteItem(item);
|
||||
break;
|
||||
case 7:
|
||||
shareUrl(item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -57,6 +57,7 @@ import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.CollapsibleView;
|
||||
|
||||
@ -425,7 +426,7 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
}
|
||||
|
||||
private void shareChannel (ChannelInfoItem selectedItem) {
|
||||
shareUrl(selectedItem.getName(), selectedItem.getUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), selectedItem.getName(), selectedItem.getUrl());
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
|
@ -147,11 +147,16 @@ public class SubscriptionService {
|
||||
}
|
||||
|
||||
private boolean isSubscriptionUpToDate(final ChannelInfo info, final SubscriptionEntity entity) {
|
||||
return info.getUrl().equals(entity.getUrl()) &&
|
||||
return equalsAndNotNull(info.getUrl(), entity.getUrl()) &&
|
||||
info.getServiceId() == entity.getServiceId() &&
|
||||
info.getName().equals(entity.getName()) &&
|
||||
info.getAvatarUrl().equals(entity.getAvatarUrl()) &&
|
||||
info.getDescription().equals(entity.getDescription()) &&
|
||||
equalsAndNotNull(info.getAvatarUrl(), entity.getAvatarUrl()) &&
|
||||
equalsAndNotNull(info.getDescription(), entity.getDescription()) &&
|
||||
info.getSubscriberCount() == entity.getSubscriberCount();
|
||||
}
|
||||
|
||||
private boolean equalsAndNotNull(final Object o1, final Object o2) {
|
||||
return (o1 != null && o2 != null)
|
||||
&& o1.equals(o2);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
package org.schabi.newpipe.player;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
|
||||
/**
|
||||
* Fixes a leak caused by AudioManager using an Activity context.
|
||||
* Tracked at https://android-review.googlesource.com/#/c/140481/1 and
|
||||
* https://github.com/square/leakcanary/issues/205
|
||||
* Source:
|
||||
* https://gist.github.com/jankovd/891d96f476f7a9ce24e2
|
||||
*/
|
||||
public class AudioServiceLeakFix extends ContextWrapper {
|
||||
|
||||
AudioServiceLeakFix(Context base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
public static ContextWrapper preventLeakOf(Context base) {
|
||||
return new AudioServiceLeakFix(base);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSystemService(String name) {
|
||||
if (Context.AUDIO_SERVICE.equals(name)) {
|
||||
return getApplicationContext().getSystemService(name);
|
||||
}
|
||||
return super.getSystemService(name);
|
||||
}
|
||||
}
|
@ -130,6 +130,11 @@ public final class BackgroundPlayer extends Service {
|
||||
onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
@ -270,6 +275,8 @@ public final class BackgroundPlayer extends Service {
|
||||
protected class BasePlayerImpl extends BasePlayer {
|
||||
|
||||
@NonNull final private AudioPlaybackResolver resolver;
|
||||
private int cachedDuration;
|
||||
private String cachedDurationString;
|
||||
|
||||
BasePlayerImpl(Context context) {
|
||||
super(context);
|
||||
@ -344,10 +351,14 @@ public final class BackgroundPlayer extends Service {
|
||||
|
||||
if (!shouldUpdateOnProgress) return;
|
||||
resetNotification();
|
||||
if(Build.VERSION.SDK_INT >= 26 /*Oreo*/) updateNotificationThumbnail();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Oreo*/) updateNotificationThumbnail();
|
||||
if (bigNotRemoteView != null) {
|
||||
if(cachedDuration != duration) {
|
||||
cachedDuration = duration;
|
||||
cachedDurationString = getTimeString(duration);
|
||||
}
|
||||
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
|
||||
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration));
|
||||
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + cachedDurationString);
|
||||
}
|
||||
if (notRemoteView != null) {
|
||||
notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
|
||||
|
@ -207,8 +207,7 @@ public abstract class BasePlayer implements
|
||||
final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||
this.dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter);
|
||||
|
||||
final TrackSelection.Factory trackSelectionFactory =
|
||||
PlayerHelper.getQualitySelector(context, bandwidthMeter);
|
||||
final TrackSelection.Factory trackSelectionFactory = PlayerHelper.getQualitySelector(context);
|
||||
this.trackSelector = new CustomTrackSelector(trackSelectionFactory);
|
||||
|
||||
this.loadControl = new LoadController(context);
|
||||
@ -225,7 +224,7 @@ public abstract class BasePlayer implements
|
||||
public void initPlayer(final boolean playOnReady) {
|
||||
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
||||
|
||||
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderFactory, trackSelector, loadControl);
|
||||
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderFactory, trackSelector, loadControl);
|
||||
simpleExoPlayer.addListener(this);
|
||||
simpleExoPlayer.setPlayWhenReady(playOnReady);
|
||||
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
|
||||
@ -270,6 +269,18 @@ public abstract class BasePlayer implements
|
||||
final boolean playbackSkipSilence = intent.getBooleanExtra(PLAYBACK_SKIP_SILENCE,
|
||||
getPlaybackSkipSilence());
|
||||
|
||||
// seek to timestamp if stream is already playing
|
||||
if (simpleExoPlayer != null
|
||||
&& queue.size() == 1
|
||||
&& playQueue != null
|
||||
&& playQueue.getItem() != null
|
||||
&& queue.getItem().getUrl().equals(playQueue.getItem().getUrl())
|
||||
&& queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET
|
||||
) {
|
||||
simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
|
||||
return;
|
||||
}
|
||||
|
||||
// Good to go...
|
||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
|
||||
/*playOnInit=*/true);
|
||||
|
@ -46,6 +46,7 @@ import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
@ -75,6 +76,7 @@ import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
@ -241,6 +243,11 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
isBackPressed = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context newBase) {
|
||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(newBase));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// State Saving
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@ -277,14 +284,9 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
if (DEBUG) Log.d(TAG, "showSystemUi() called");
|
||||
if (playerImpl != null && playerImpl.queueVisible) return;
|
||||
|
||||
final int visibility;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
|
||||
} else {
|
||||
visibility = View.STATUS_BAR_VISIBLE;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
@ColorInt final int systenUiColor =
|
||||
@ -353,11 +355,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
|
||||
protected void setShuffleButton(final ImageButton shuffleButton, final boolean shuffled) {
|
||||
final int shuffleAlpha = shuffled ? 255 : 77;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
shuffleButton.setImageAlpha(shuffleAlpha);
|
||||
} else {
|
||||
shuffleButton.setAlpha(shuffleAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInMultiWindow() {
|
||||
@ -397,6 +395,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
private ImageButton playPauseButton;
|
||||
private ImageButton playPreviousButton;
|
||||
private ImageButton playNextButton;
|
||||
private Button closeButton;
|
||||
|
||||
private RelativeLayout queueLayout;
|
||||
private ImageButton itemsListCloseButton;
|
||||
@ -406,6 +405,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
private boolean queueVisible;
|
||||
|
||||
private ImageButton moreOptionsButton;
|
||||
private ImageButton shareButton;
|
||||
private ImageButton toggleOrientationButton;
|
||||
private ImageButton switchPopupButton;
|
||||
private ImageButton switchBackgroundButton;
|
||||
@ -437,9 +437,11 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
this.playPauseButton = rootView.findViewById(R.id.playPauseButton);
|
||||
this.playPreviousButton = rootView.findViewById(R.id.playPreviousButton);
|
||||
this.playNextButton = rootView.findViewById(R.id.playNextButton);
|
||||
this.closeButton = rootView.findViewById(R.id.closeButton);
|
||||
|
||||
this.moreOptionsButton = rootView.findViewById(R.id.moreOptionsButton);
|
||||
this.secondaryControls = rootView.findViewById(R.id.secondaryControls);
|
||||
this.shareButton = rootView.findViewById(R.id.share);
|
||||
this.toggleOrientationButton = rootView.findViewById(R.id.toggleOrientation);
|
||||
this.switchBackgroundButton = rootView.findViewById(R.id.switchBackground);
|
||||
this.switchPopupButton = rootView.findViewById(R.id.switchPopup);
|
||||
@ -483,8 +485,10 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
playPauseButton.setOnClickListener(this);
|
||||
playPreviousButton.setOnClickListener(this);
|
||||
playNextButton.setOnClickListener(this);
|
||||
closeButton.setOnClickListener(this);
|
||||
|
||||
moreOptionsButton.setOnClickListener(this);
|
||||
shareButton.setOnClickListener(this);
|
||||
toggleOrientationButton.setOnClickListener(this);
|
||||
switchBackgroundButton.setOnClickListener(this);
|
||||
switchPopupButton.setOnClickListener(this);
|
||||
@ -635,6 +639,9 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
} else if (v.getId() == moreOptionsButton.getId()) {
|
||||
onMoreOptionsClicked();
|
||||
|
||||
} else if (v.getId() == shareButton.getId()) {
|
||||
onShareClicked();
|
||||
|
||||
} else if (v.getId() == toggleOrientationButton.getId()) {
|
||||
onScreenRotationClicked();
|
||||
|
||||
@ -644,6 +651,9 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
} else if (v.getId() == switchBackgroundButton.getId()) {
|
||||
onPlayBackgroundButtonClicked();
|
||||
|
||||
} else if (v.getId() == closeButton.getId()) {
|
||||
onPlaybackShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
if (getCurrentState() != STATE_COMPLETED) {
|
||||
@ -688,6 +698,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
showControls(DEFAULT_CONTROLS_DURATION);
|
||||
}
|
||||
|
||||
private void onShareClicked() {
|
||||
// share video at the current time (youtube.com/watch?v=ID&t=SECONDS)
|
||||
ShareUtils.shareUrl(MainVideoPlayer.this,
|
||||
playerImpl.getVideoTitle(),
|
||||
playerImpl.getVideoUrl() + "&t=" + String.valueOf(playerImpl.getPlaybackSeekBar().getProgress()/1000));
|
||||
}
|
||||
|
||||
private void onScreenRotationClicked() {
|
||||
if (DEBUG) Log.d(TAG, "onScreenRotationClicked() called");
|
||||
toggleOrientation();
|
||||
@ -770,6 +787,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
super.onBlocked();
|
||||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||
animatePlayButtons(false, 100);
|
||||
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
||||
getRootView().setKeepScreenOn(true);
|
||||
}
|
||||
|
||||
@ -785,6 +803,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
|
||||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||
animatePlayButtons(true, 200);
|
||||
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
||||
});
|
||||
|
||||
getRootView().setKeepScreenOn(true);
|
||||
@ -796,6 +815,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
|
||||
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
|
||||
animatePlayButtons(true, 200);
|
||||
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
||||
});
|
||||
|
||||
showSystemUi();
|
||||
@ -815,8 +835,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, () -> {
|
||||
playPauseButton.setImageResource(R.drawable.ic_replay_white);
|
||||
animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
|
||||
animateView(closeButton, true, DEFAULT_CONTROLS_DURATION);
|
||||
});
|
||||
|
||||
getRootView().setKeepScreenOn(false);
|
||||
super.onCompleted();
|
||||
}
|
||||
|
@ -181,6 +181,11 @@ public final class PopupVideoPlayer extends Service {
|
||||
closePopup();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
|
@ -3,7 +3,6 @@ package org.schabi.newpipe.player;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.provider.Settings;
|
||||
@ -653,11 +652,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
final int shuffleAlpha = shuffled ? 255 : 77;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
shuffleButton.setImageAlpha(shuffleAlpha);
|
||||
} else {
|
||||
shuffleButton.setAlpha(shuffleAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
|
||||
|
@ -212,7 +212,6 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
|
||||
@Override
|
||||
public void initListeners() {
|
||||
super.initListeners();
|
||||
playbackSeekBar.setOnSeekBarChangeListener(this);
|
||||
playbackSpeedTextView.setOnClickListener(this);
|
||||
qualityTextView.setOnClickListener(this);
|
||||
@ -298,7 +297,6 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
return true;
|
||||
});
|
||||
|
||||
// Add all available captions
|
||||
for (int i = 0; i < availableLanguages.size(); i++) {
|
||||
final String captionLanguage = availableLanguages.get(i);
|
||||
MenuItem captionItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
|
||||
@ -316,7 +314,6 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
captionPopupMenu.setOnDismissListener(this);
|
||||
}
|
||||
|
||||
|
||||
private void updateStreamRelatedViews() {
|
||||
if (getCurrentMetadata() == null) return;
|
||||
|
||||
@ -509,12 +506,12 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
}
|
||||
|
||||
// Normalize mismatching language strings
|
||||
final String preferredLanguage = trackSelector.getPreferredTextLanguage();
|
||||
|
||||
final String preferredLanguage = trackSelector.getParameters().preferredTextLanguage;
|
||||
// Build UI
|
||||
buildCaptionMenu(availableLanguages);
|
||||
if (trackSelector.getParameters().getRendererDisabled(textRenderer) ||
|
||||
preferredLanguage == null || !availableLanguages.contains(preferredLanguage)) {
|
||||
preferredLanguage == null || (!availableLanguages.contains(preferredLanguage)
|
||||
&& !containsCaseInsensitive(availableLanguages, preferredLanguage))) {
|
||||
captionTextView.setText(R.string.caption_none);
|
||||
} else {
|
||||
captionTextView.setText(preferredLanguage);
|
||||
@ -522,6 +519,15 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
captionTextView.setVisibility(availableLanguages.isEmpty() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
// workaround to match normalized captions like english to English or deutsch to Deutsch
|
||||
private static boolean containsCaseInsensitive(List<String> list, String toFind) {
|
||||
for(String i : list){
|
||||
if(i.equalsIgnoreCase(toFind))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// General Player
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -12,13 +12,11 @@ import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||||
|
||||
public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
AudioRendererEventListener {
|
||||
AnalyticsListener {
|
||||
|
||||
private static final String TAG = "AudioFocusReactor";
|
||||
|
||||
@ -42,7 +40,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
this.player = player;
|
||||
this.context = context;
|
||||
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
player.addAudioDebugListener(this);
|
||||
player.addAnalyticsListener(this);
|
||||
|
||||
if (SHOULD_BUILD_FOCUS_REQUEST) {
|
||||
request = new AudioFocusRequest.Builder(FOCUS_GAIN_TYPE)
|
||||
@ -57,7 +55,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
|
||||
public void dispose() {
|
||||
abandonAudioFocus();
|
||||
player.removeAudioDebugListener(this);
|
||||
player.removeAnalyticsListener(this);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@ -164,29 +162,12 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onAudioSessionId(int i) {
|
||||
public void onAudioSessionId(EventTime eventTime, int audioSessionId) {
|
||||
if (!PlayerHelper.isUsingDSP(context)) return;
|
||||
|
||||
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
|
||||
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, i);
|
||||
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId);
|
||||
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioEnabled(DecoderCounters decoderCounters) {}
|
||||
|
||||
@Override
|
||||
public void onAudioDecoderInitialized(String s, long l, long l1) {}
|
||||
|
||||
@Override
|
||||
public void onAudioInputFormatChanged(Format format) {}
|
||||
|
||||
@Override
|
||||
public void onAudioSinkUnderrun(int bufferSize,
|
||||
long bufferSizeMs,
|
||||
long elapsedSinceLastFeedMs) {}
|
||||
|
||||
@Override
|
||||
public void onAudioDisabled(DecoderCounters decoderCounters) {}
|
||||
}
|
||||
|
@ -33,14 +33,14 @@ import java.io.File;
|
||||
|
||||
public CacheFactory(@NonNull final Context context,
|
||||
@NonNull final String userAgent,
|
||||
@NonNull final TransferListener<? super DataSource> transferListener) {
|
||||
@NonNull final TransferListener transferListener) {
|
||||
this(context, userAgent, transferListener, PlayerHelper.getPreferredCacheSize(context),
|
||||
PlayerHelper.getPreferredFileSize(context));
|
||||
}
|
||||
|
||||
private CacheFactory(@NonNull final Context context,
|
||||
@NonNull final String userAgent,
|
||||
@NonNull final TransferListener<? super DataSource> transferListener,
|
||||
@NonNull final TransferListener transferListener,
|
||||
final long maxCacheSize,
|
||||
final long maxFileSize) {
|
||||
this.maxFileSize = maxFileSize;
|
||||
|
@ -2,17 +2,12 @@ package org.schabi.newpipe.player.helper;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.LoadControl;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
||||
|
||||
import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS;
|
||||
import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_TARGET_BUFFER_BYTES;
|
||||
|
||||
public class LoadController implements LoadControl {
|
||||
|
||||
@ -36,15 +31,10 @@ public class LoadController implements LoadControl {
|
||||
final int optimalPlaybackBufferMs) {
|
||||
this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000;
|
||||
|
||||
final DefaultAllocator allocator = new DefaultAllocator(true,
|
||||
C.DEFAULT_BUFFER_SEGMENT_SIZE);
|
||||
|
||||
internalLoadControl = new DefaultLoadControl(allocator,
|
||||
/*minBufferMs=*/minimumPlaybackbufferMs,
|
||||
/*maxBufferMs=*/optimalPlaybackBufferMs,
|
||||
/*bufferForPlaybackMs=*/initialPlaybackBufferMs,
|
||||
/*bufferForPlaybackAfterRebufferMs=*/initialPlaybackBufferMs,
|
||||
DEFAULT_TARGET_BUFFER_BYTES, DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS);
|
||||
DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
|
||||
builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs,
|
||||
initialPlaybackBufferMs, initialPlaybackBufferMs);
|
||||
internalLoadControl = builder.createDefaultLoadControl();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -12,6 +12,7 @@ import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
public class PlayerDataSource {
|
||||
@ -24,7 +25,7 @@ public class PlayerDataSource {
|
||||
|
||||
public PlayerDataSource(@NonNull final Context context,
|
||||
@NonNull final String userAgent,
|
||||
@NonNull final TransferListener<? super DataSource> transferListener) {
|
||||
@NonNull final TransferListener transferListener) {
|
||||
cacheDataSourceFactory = new CacheFactory(context, userAgent, transferListener);
|
||||
cachelessDataSourceFactory = new DefaultDataSourceFactory(context, userAgent, transferListener);
|
||||
}
|
||||
@ -32,21 +33,21 @@ public class PlayerDataSource {
|
||||
public SsMediaSource.Factory getLiveSsMediaSourceFactory() {
|
||||
return new SsMediaSource.Factory(new DefaultSsChunkSource.Factory(
|
||||
cachelessDataSourceFactory), cachelessDataSourceFactory)
|
||||
.setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY)
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
|
||||
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
|
||||
}
|
||||
|
||||
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
|
||||
return new HlsMediaSource.Factory(cachelessDataSourceFactory)
|
||||
.setAllowChunklessPreparation(true)
|
||||
.setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY);
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
|
||||
}
|
||||
|
||||
public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
|
||||
return new DashMediaSource.Factory(new DefaultDashChunkSource.Factory(
|
||||
cachelessDataSourceFactory), cachelessDataSourceFactory)
|
||||
.setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY)
|
||||
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
|
||||
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS, true);
|
||||
}
|
||||
|
||||
public SsMediaSource.Factory getSsMediaSourceFactory() {
|
||||
@ -65,7 +66,7 @@ public class PlayerDataSource {
|
||||
|
||||
public ExtractorMediaSource.Factory getExtractorMediaSourceFactory() {
|
||||
return new ExtractorMediaSource.Factory(cacheDataSourceFactory)
|
||||
.setMinLoadableRetryCount(EXTRACTOR_MINIMUM_RETRY);
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
|
||||
}
|
||||
|
||||
public ExtractorMediaSource.Factory getExtractorMediaSourceFactory(@NonNull final String key) {
|
||||
|
@ -14,7 +14,6 @@ import com.google.android.exoplayer2.text.CaptionStyleCompat;
|
||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
@ -45,7 +44,9 @@ import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MOD
|
||||
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT;
|
||||
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.*;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
|
||||
|
||||
public class PlayerHelper {
|
||||
private PlayerHelper() {}
|
||||
@ -68,10 +69,10 @@ public class PlayerHelper {
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static String getTimeString(int milliSeconds) {
|
||||
long seconds = (milliSeconds % 60000L) / 1000L;
|
||||
long minutes = (milliSeconds % 3600000L) / 60000L;
|
||||
long hours = (milliSeconds % 86400000L) / 3600000L;
|
||||
long days = (milliSeconds % (86400000L * 7L)) / 86400000L;
|
||||
int seconds = (milliSeconds % 60000) / 1000;
|
||||
int minutes = (milliSeconds % 3600000) / 60000;
|
||||
int hours = (milliSeconds % 86400000) / 3600000;
|
||||
int days = (milliSeconds % (86400000 * 7)) / 86400000;
|
||||
|
||||
stringBuilder.setLength(0);
|
||||
return days > 0 ? stringFormatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString()
|
||||
@ -238,9 +239,8 @@ public class PlayerHelper {
|
||||
return 60000;
|
||||
}
|
||||
|
||||
public static TrackSelection.Factory getQualitySelector(@NonNull final Context context,
|
||||
@NonNull final BandwidthMeter meter) {
|
||||
return new AdaptiveTrackSelection.Factory(meter,
|
||||
public static TrackSelection.Factory getQualitySelector(@NonNull final Context context) {
|
||||
return new AdaptiveTrackSelection.Factory(
|
||||
/*bufferDurationRequiredForQualityIncrease=*/1000,
|
||||
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
|
||||
|
@ -1,12 +1,13 @@
|
||||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
@ -79,7 +80,7 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -88,7 +89,7 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo
|
||||
|
||||
|
||||
@Override
|
||||
protected void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {
|
||||
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
Log.e(TAG, "Loading failed source: ", error);
|
||||
}
|
||||
|
||||
|
@ -2,12 +2,13 @@ package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
@ -36,9 +37,8 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareSource(ExoPlayer player, boolean isTopLevelSource,
|
||||
SourceInfoRefreshListener listener) {
|
||||
source.prepareSource(player, isTopLevelSource, listener);
|
||||
public void prepareSource(SourceInfoRefreshListener listener, @Nullable TransferListener mediaTransferListener) {
|
||||
source.prepareSource(listener, mediaTransferListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -47,8 +47,8 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
|
||||
return source.createPeriod(id, allocator);
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||
return source.createPeriod(id, allocator, startPositionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,5 @@
|
||||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
@ -86,21 +86,22 @@ public class ManagedMediaSourcePlaylist {
|
||||
/**
|
||||
* Invalidates the {@link ManagedMediaSource} at the given index by replacing it
|
||||
* with a {@link PlaceholderMediaSource}.
|
||||
* @see #update(int, ManagedMediaSource, Runnable)
|
||||
* @see #update(int, ManagedMediaSource, Handler, Runnable)
|
||||
* */
|
||||
public synchronized void invalidate(final int index,
|
||||
@Nullable final Handler handler,
|
||||
@Nullable final Runnable finalizingAction) {
|
||||
if (get(index) instanceof PlaceholderMediaSource) return;
|
||||
update(index, new PlaceholderMediaSource(), finalizingAction);
|
||||
update(index, new PlaceholderMediaSource(), handler, finalizingAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
|
||||
* at the given index with a given {@link ManagedMediaSource}.
|
||||
* @see #update(int, ManagedMediaSource, Runnable)
|
||||
* @see #update(int, ManagedMediaSource, Handler, Runnable)
|
||||
* */
|
||||
public synchronized void update(final int index, @NonNull final ManagedMediaSource source) {
|
||||
update(index, source, /*doNothing=*/null);
|
||||
update(index, source, null, /*doNothing=*/null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,9 +109,10 @@ public class ManagedMediaSourcePlaylist {
|
||||
* at the given index with a given {@link ManagedMediaSource}. If the index is out of bound,
|
||||
* then the replacement is ignored.
|
||||
* @see ConcatenatingMediaSource#addMediaSource
|
||||
* @see ConcatenatingMediaSource#removeMediaSource(int, Runnable)
|
||||
* @see ConcatenatingMediaSource#removeMediaSource(int, Handler, Runnable)
|
||||
* */
|
||||
public synchronized void update(final int index, @NonNull final ManagedMediaSource source,
|
||||
@Nullable final Handler handler,
|
||||
@Nullable final Runnable finalizingAction) {
|
||||
if (index < 0 || index >= internalSource.getSize()) return;
|
||||
|
||||
@ -126,6 +128,6 @@ public class ManagedMediaSourcePlaylist {
|
||||
|
||||
// Because of the above race condition, it is thus only safe to synchronize the player
|
||||
// in the finalizing action AFTER the removal is complete and the timeline has changed.
|
||||
internalSource.removeMediaSource(index, finalizingAction);
|
||||
internalSource.removeMediaSource(index, handler, finalizingAction);
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
public class PlaceholderMediaSource extends BaseMediaSource implements ManagedMediaSource {
|
||||
// Do nothing, so this will stall the playback
|
||||
@Override public void maybeThrowSourceInfoRefreshError() {}
|
||||
@Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { return null; }
|
||||
@Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { return null; }
|
||||
@Override public void releasePeriod(MediaPeriod mediaPeriod) {}
|
||||
@Override protected void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {}
|
||||
@Override protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {}
|
||||
@Override protected void releaseSourceInternal() {}
|
||||
|
||||
@Override
|
||||
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.player.playback;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
@ -52,7 +53,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
||||
|
||||
/** @see DefaultTrackSelector#selectTextTrack(TrackGroupArray, int[][], Parameters) */
|
||||
@Override
|
||||
protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport,
|
||||
protected Pair<TrackSelection, Integer> selectTextTrack(TrackGroupArray groups, int[][] formatSupport,
|
||||
Parameters params) {
|
||||
TrackGroup selectedGroup = null;
|
||||
int selectedTrackIndex = 0;
|
||||
@ -106,7 +107,9 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
||||
}
|
||||
}
|
||||
}
|
||||
return selectedGroup == null ? null
|
||||
: new FixedTrackSelection(selectedGroup, selectedTrackIndex);
|
||||
return selectedGroup == null
|
||||
? null
|
||||
: Pair.create(
|
||||
new FixedTrackSelection(selectedGroup, selectedTrackIndex), selectedTrackScore);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
package org.schabi.newpipe.player.playback;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.util.ArraySet;
|
||||
@ -103,6 +103,8 @@ public class MediaSourceManager {
|
||||
|
||||
@NonNull private ManagedMediaSourcePlaylist playlist;
|
||||
|
||||
private Handler removeMediaSourceHandler = new Handler();
|
||||
|
||||
public MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue) {
|
||||
this(listener, playQueue, /*loadDebounceMillis=*/400L,
|
||||
@ -395,7 +397,7 @@ public class MediaSourceManager {
|
||||
if (isCorrectionNeeded(item)) {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - Updating index=[" + itemIndex + "] with " +
|
||||
"title=[" + item.getTitle() + "] at url=[" + item.getUrl() + "]");
|
||||
playlist.update(itemIndex, mediaSource, this::maybeSynchronizePlayer);
|
||||
playlist.update(itemIndex, mediaSource, removeMediaSourceHandler, this::maybeSynchronizePlayer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,7 +443,7 @@ public class MediaSourceManager {
|
||||
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - Reloading currently playing, " +
|
||||
"index=[" + currentIndex + "], item=[" + currentItem.getTitle() + "]");
|
||||
playlist.invalidate(currentIndex, this::loadImmediate);
|
||||
playlist.invalidate(currentIndex, removeMediaSourceHandler, this::loadImmediate);
|
||||
}
|
||||
|
||||
private void maybeClearLoaders() {
|
||||
|
@ -16,6 +16,11 @@ public final class SinglePlayQueue extends PlayQueue {
|
||||
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
||||
}
|
||||
|
||||
public SinglePlayQueue(final StreamInfo info, final long startPosition) {
|
||||
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
||||
getItem().setRecoveryPosition(startPosition);
|
||||
}
|
||||
|
||||
public SinglePlayQueue(final List<StreamInfoItem> items, final int index) {
|
||||
super(index, playQueueItemsOf(items));
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
|
@ -15,6 +15,7 @@ public enum UserAction {
|
||||
REQUESTED_CHANNEL("requested channel"),
|
||||
REQUESTED_PLAYLIST("requested playlist"),
|
||||
REQUESTED_KIOSK("requested kiosk"),
|
||||
REQUESTED_COMMENTS("requested comments"),
|
||||
DELETE_FROM_HISTORY("delete from history"),
|
||||
PLAY_STREAM("Play stream");
|
||||
|
||||
|
@ -25,6 +25,7 @@ import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.FloatRange;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||
import android.util.Log;
|
||||
@ -363,4 +364,24 @@ public class AnimationUtils {
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
public static void slideUp(final View view,
|
||||
long duration,
|
||||
long delay,
|
||||
@FloatRange(from = 0.0f, to = 1.0f) float translationPercent) {
|
||||
int translationY = (int) (view.getResources().getDisplayMetrics().heightPixels *
|
||||
(translationPercent));
|
||||
|
||||
view.animate().setListener(null).cancel();
|
||||
view.setAlpha(0f);
|
||||
view.setTranslationY(translationY);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.animate()
|
||||
.alpha(1f)
|
||||
.translationY(0)
|
||||
.setStartDelay(delay)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,131 @@
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Layout;
|
||||
import android.text.Selection;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class CommentTextOnTouchListener implements View.OnTouchListener {
|
||||
|
||||
public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener();
|
||||
|
||||
private static final Pattern timestampPattern = Pattern.compile("(.*)#timestamp=(\\d+)");
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if(!(v instanceof TextView)){
|
||||
return false;
|
||||
}
|
||||
TextView widget = (TextView) v;
|
||||
Object text = widget.getText();
|
||||
if (text instanceof Spanned) {
|
||||
Spannable buffer = (Spannable) text;
|
||||
|
||||
int action = event.getAction();
|
||||
|
||||
if (action == MotionEvent.ACTION_UP
|
||||
|| action == MotionEvent.ACTION_DOWN) {
|
||||
int x = (int) event.getX();
|
||||
int y = (int) event.getY();
|
||||
|
||||
x -= widget.getTotalPaddingLeft();
|
||||
y -= widget.getTotalPaddingTop();
|
||||
|
||||
x += widget.getScrollX();
|
||||
y += widget.getScrollY();
|
||||
|
||||
Layout layout = widget.getLayout();
|
||||
int line = layout.getLineForVertical(y);
|
||||
int off = layout.getOffsetForHorizontal(line, x);
|
||||
|
||||
ClickableSpan[] link = buffer.getSpans(off, off,
|
||||
ClickableSpan.class);
|
||||
|
||||
if (link.length != 0) {
|
||||
if (action == MotionEvent.ACTION_UP) {
|
||||
boolean handled = false;
|
||||
if(link[0] instanceof URLSpan){
|
||||
handled = handleUrl(v.getContext(), (URLSpan) link[0]);
|
||||
}
|
||||
if(!handled) link[0].onClick(widget);
|
||||
} else if (action == MotionEvent.ACTION_DOWN) {
|
||||
Selection.setSelection(buffer,
|
||||
buffer.getSpanStart(link[0]),
|
||||
buffer.getSpanEnd(link[0]));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleUrl(Context context, URLSpan urlSpan) {
|
||||
String url = urlSpan.getURL();
|
||||
int seconds = -1;
|
||||
Matcher matcher = timestampPattern.matcher(url);
|
||||
if(matcher.matches()){
|
||||
url = matcher.group(1);
|
||||
seconds = Integer.parseInt(matcher.group(2));
|
||||
}
|
||||
StreamingService service;
|
||||
StreamingService.LinkType linkType;
|
||||
try {
|
||||
service = NewPipe.getServiceByUrl(url);
|
||||
linkType = service.getLinkTypeByUrl(url);
|
||||
} catch (ExtractionException e) {
|
||||
return false;
|
||||
}
|
||||
if(linkType == StreamingService.LinkType.NONE){
|
||||
return false;
|
||||
}
|
||||
if(linkType == StreamingService.LinkType.STREAM && seconds != -1){
|
||||
return playOnPopup(context, url, service, seconds);
|
||||
}else{
|
||||
NavigationHelper.openRouterActivity(context, url);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean playOnPopup(Context context, String url, StreamingService service, int seconds) {
|
||||
LinkHandlerFactory factory = service.getStreamLHFactory();
|
||||
String cleanUrl = null;
|
||||
try {
|
||||
cleanUrl = factory.getUrl(factory.getId(url));
|
||||
} catch (ParsingException e) {
|
||||
return false;
|
||||
}
|
||||
Single single = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false);
|
||||
single.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(info -> {
|
||||
PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info, seconds*1000);
|
||||
NavigationHelper.playOnPopupPlayer(context, playQueue);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
@ -29,11 +29,12 @@ import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.SuggestionExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
@ -110,7 +111,7 @@ public final class ExtractorHelper {
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.STREAM, Single.fromCallable(() ->
|
||||
StreamInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
@ -118,7 +119,7 @@ public final class ExtractorHelper {
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.CHANNEL, Single.fromCallable(() ->
|
||||
ChannelInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
@ -130,11 +131,27 @@ public final class ExtractorHelper {
|
||||
ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl));
|
||||
}
|
||||
|
||||
public static Single<CommentsInfo> getCommentsInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.COMMENT, Single.fromCallable(() ->
|
||||
CommentsInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
public static Single<InfoItemsPage> getMoreCommentItems(final int serviceId,
|
||||
final CommentsInfo info,
|
||||
final String nextPageUrl) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(() ->
|
||||
CommentsInfo.getMoreItems(NewPipe.getService(serviceId), info, nextPageUrl));
|
||||
}
|
||||
|
||||
public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() ->
|
||||
PlaylistInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
@ -149,7 +166,7 @@ public final class ExtractorHelper {
|
||||
public static Single<KioskInfo> getKioskInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() ->
|
||||
KioskInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
@ -173,16 +190,17 @@ public final class ExtractorHelper {
|
||||
private static <I extends Info> Single<I> checkCache(boolean forceLoad,
|
||||
int serviceId,
|
||||
String url,
|
||||
InfoItem.InfoType infoType,
|
||||
Single<I> loadFromNetwork) {
|
||||
checkServiceId(serviceId);
|
||||
loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info));
|
||||
loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info, infoType));
|
||||
|
||||
Single<I> load;
|
||||
if (forceLoad) {
|
||||
cache.removeInfo(serviceId, url);
|
||||
cache.removeInfo(serviceId, url, infoType);
|
||||
load = loadFromNetwork;
|
||||
} else {
|
||||
load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url),
|
||||
load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url, infoType),
|
||||
loadFromNetwork.toMaybe())
|
||||
.firstElement() //Take the first valid
|
||||
.toSingle();
|
||||
@ -194,11 +212,11 @@ public final class ExtractorHelper {
|
||||
/**
|
||||
* Default implementation uses the {@link InfoCache} to get cached results
|
||||
*/
|
||||
public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url) {
|
||||
public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url, InfoItem.InfoType infoType) {
|
||||
checkServiceId(serviceId);
|
||||
return Maybe.defer(() -> {
|
||||
//noinspection unchecked
|
||||
I info = (I) cache.getFromKey(serviceId, url);
|
||||
I info = (I) cache.getFromKey(serviceId, url, infoType);
|
||||
if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info);
|
||||
|
||||
// Only return info if it's not null (it is cached)
|
||||
|
@ -26,6 +26,7 @@ import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@ -52,27 +53,27 @@ public final class InfoCache {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Info getFromKey(int serviceId, @NonNull String url) {
|
||||
public Info getFromKey(int serviceId, @NonNull String url, @NonNull InfoItem.InfoType infoType) {
|
||||
if (DEBUG) Log.d(TAG, "getFromKey() called with: serviceId = [" + serviceId + "], url = [" + url + "]");
|
||||
synchronized (lruCache) {
|
||||
return getInfo(keyOf(serviceId, url));
|
||||
return getInfo(keyOf(serviceId, url, infoType));
|
||||
}
|
||||
}
|
||||
|
||||
public void putInfo(int serviceId, @NonNull String url, @NonNull Info info) {
|
||||
public void putInfo(int serviceId, @NonNull String url, @NonNull Info info, @NonNull InfoItem.InfoType infoType) {
|
||||
if (DEBUG) Log.d(TAG, "putInfo() called with: info = [" + info + "]");
|
||||
|
||||
final long expirationMillis = ServiceHelper.getCacheExpirationMillis(info.getServiceId());
|
||||
synchronized (lruCache) {
|
||||
final CacheData data = new CacheData(info, expirationMillis);
|
||||
lruCache.put(keyOf(serviceId, url), data);
|
||||
lruCache.put(keyOf(serviceId, url, infoType), data);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeInfo(int serviceId, @NonNull String url) {
|
||||
public void removeInfo(int serviceId, @NonNull String url, @NonNull InfoItem.InfoType infoType) {
|
||||
if (DEBUG) Log.d(TAG, "removeInfo() called with: serviceId = [" + serviceId + "], url = [" + url + "]");
|
||||
synchronized (lruCache) {
|
||||
lruCache.remove(keyOf(serviceId, url));
|
||||
lruCache.remove(keyOf(serviceId, url, infoType));
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,8 +99,8 @@ public final class InfoCache {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String keyOf(final int serviceId, @NonNull final String url) {
|
||||
return serviceId + url;
|
||||
private static String keyOf(final int serviceId, @NonNull final String url, @NonNull InfoItem.InfoType infoType) {
|
||||
return serviceId + url + infoType.toString();
|
||||
}
|
||||
|
||||
private static void removeStaleCache() {
|
||||
|
@ -21,6 +21,7 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.RouterActivity;
|
||||
import org.schabi.newpipe.about.AboutActivity;
|
||||
import org.schabi.newpipe.download.DownloadActivity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
@ -33,11 +34,12 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.fragments.MainFragment;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
|
||||
import org.schabi.newpipe.local.feed.FeedFragment;
|
||||
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
|
||||
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
||||
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
|
||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
||||
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
|
||||
import org.schabi.newpipe.local.feed.FeedFragment;
|
||||
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
||||
@ -309,6 +311,18 @@ public class NavigationHelper {
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openCommentsFragment(
|
||||
FragmentManager fragmentManager,
|
||||
int serviceId,
|
||||
String url,
|
||||
String name) {
|
||||
if (name == null) name = "";
|
||||
fragmentManager.beginTransaction().setCustomAnimations(R.anim.switch_service_in, R.anim.switch_service_out)
|
||||
.replace(R.id.fragment_holder, CommentsFragment.getInstance(serviceId, url, name))
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openPlaylistFragment(FragmentManager fragmentManager,
|
||||
int serviceId,
|
||||
String url,
|
||||
@ -409,6 +423,13 @@ public class NavigationHelper {
|
||||
context.startActivity(mIntent);
|
||||
}
|
||||
|
||||
public static void openRouterActivity(Context context, String url) {
|
||||
Intent mIntent = new Intent(context, RouterActivity.class);
|
||||
mIntent.setData(Uri.parse(url));
|
||||
mIntent.putExtra(RouterActivity.internalRouteKey, true);
|
||||
context.startActivity(mIntent);
|
||||
}
|
||||
|
||||
public static void openAbout(Context context) {
|
||||
Intent intent = new Intent(context, AboutActivity.class);
|
||||
context.startActivity(intent);
|
||||
|
@ -0,0 +1,41 @@
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class RelatedStreamInfo extends ListInfo<InfoItem> {
|
||||
|
||||
private StreamInfoItem nextStream;
|
||||
|
||||
public RelatedStreamInfo(int serviceId, ListLinkHandler listUrlIdHandler, String name) {
|
||||
super(serviceId, listUrlIdHandler, name);
|
||||
}
|
||||
|
||||
public static RelatedStreamInfo getInfo(StreamInfo info) {
|
||||
ListLinkHandler handler = new ListLinkHandler(info.getOriginalUrl(), info.getUrl(), info.getId(), Collections.emptyList(), null);
|
||||
RelatedStreamInfo relatedStreamInfo = new RelatedStreamInfo(info.getServiceId(), handler, info.getName());
|
||||
List<InfoItem> streams = new ArrayList<>();
|
||||
if(info.getNextVideo() != null){
|
||||
streams.add(info.getNextVideo());
|
||||
}
|
||||
streams.addAll(info.getRelatedStreams());
|
||||
relatedStreamInfo.setRelatedItems(streams);
|
||||
relatedStreamInfo.setNextStream(info.getNextVideo());
|
||||
return relatedStreamInfo;
|
||||
}
|
||||
|
||||
public StreamInfoItem getNextStream() {
|
||||
return nextStream;
|
||||
}
|
||||
|
||||
public void setNextStream(StreamInfoItem nextStream) {
|
||||
this.nextStream = nextStream;
|
||||
}
|
||||
}
|
22
app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
Normal file
22
app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
Normal file
@ -0,0 +1,22 @@
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class ShareUtils {
|
||||
public static void openUrlInBrowser(Context context, String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title)));
|
||||
}
|
||||
|
||||
public static void shareUrl(Context context, String subject, String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title)));
|
||||
}
|
||||
}
|
12
app/src/main/res/drawable/default_dot.xml
Normal file
12
app/src/main/res/drawable/default_dot.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape
|
||||
android:innerRadius="0dp"
|
||||
android:shape="ring"
|
||||
android:thickness="4dp"
|
||||
android:useLevel="false">
|
||||
<solid android:color="@android:color/darker_gray"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
12
app/src/main/res/drawable/selected_dot.xml
Normal file
12
app/src/main/res/drawable/selected_dot.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape
|
||||
android:innerRadius="0dp"
|
||||
android:shape="ring"
|
||||
android:thickness="6dp"
|
||||
android:useLevel="false">
|
||||
<solid android:color="@android:color/darker_gray"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
8
app/src/main/res/drawable/tab_selector.xml
Normal file
8
app/src/main/res/drawable/tab_selector.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:drawable="@drawable/selected_dot"
|
||||
android:state_selected="true"/>
|
||||
|
||||
<item android:drawable="@drawable/default_dot"/>
|
||||
</selector>
|
@ -305,7 +305,7 @@
|
||||
tools:text="English" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/toggleOrientation"
|
||||
android:id="@+id/share"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
@ -316,6 +316,23 @@
|
||||
android:focusable="true"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_share_white_24dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:contentDescription="@string/share"
|
||||
tools:ignore="RtlHardcoded"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/toggleOrientation"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:layout_toLeftOf="@id/share"
|
||||
android:layout_centerVertical="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_screen_rotation_white"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:contentDescription="@string/toggle_orientation"
|
||||
@ -460,6 +477,22 @@
|
||||
android:src="@drawable/exo_controls_next"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/closeButton"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/playPauseButton"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:text="@string/close"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/white"
|
||||
android:visibility="invisible" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
|
@ -8,18 +8,25 @@
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.nirhart.parallaxscroll.views.ParallaxScrollView
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/detail_main_content"
|
||||
app:parallax_factor="1.9"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="5">
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="5"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<!--WRAPPER-->
|
||||
<LinearLayout
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:id="@+id/appbarlayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:fitsSystemWindows="true"
|
||||
app:elevation="0dp"
|
||||
app:layout_behavior="android.support.design.widget.FlingBehavior">
|
||||
|
||||
<android.support.design.widget.CollapsingToolbarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_scrollFlags="scroll">
|
||||
|
||||
<!-- THUMBNAIL -->
|
||||
<FrameLayout
|
||||
@ -29,7 +36,8 @@
|
||||
android:background="@android:color/black"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground">
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:layout_collapseMode="parallax">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/detail_thumbnail_image_view"
|
||||
@ -97,18 +105,24 @@
|
||||
tools:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
</android.support.design.widget.CollapsingToolbarLayout>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<RelativeLayout
|
||||
android:id="@+id/detail_content_root_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:windowBackground">
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:windowBackground"
|
||||
app:layout_scrollFlags="scroll">
|
||||
|
||||
<!-- TITLE -->
|
||||
<FrameLayout
|
||||
android:id="@+id/detail_title_root_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp">
|
||||
|
||||
@ -126,6 +140,15 @@
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero." />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/detail_toggle_description_view"
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:src="@drawable/arrow_down"
|
||||
tools:ignore="ContentDescription,RtlHardcoded" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<!-- LOADING INDICATOR-->
|
||||
@ -156,6 +179,7 @@
|
||||
android:id="@+id/detail_content_root_hiding"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="10dp"
|
||||
android:layout_below="@+id/detail_title_root_layout"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
@ -401,7 +425,9 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_upload_date_view"
|
||||
@ -435,57 +461,40 @@
|
||||
android:background="?attr/separator_color" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</com.nirhart.parallaxscroll.views.ParallaxScrollView>
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<ScrollView
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/tablayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center"
|
||||
android:background="@color/transparent_background_color"
|
||||
app:tabBackground="@drawable/tab_selector"
|
||||
app:tabGravity="center"
|
||||
app:tabIndicatorHeight="0dp">
|
||||
|
||||
</android.support.design.widget.TabLayout>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/relatedStreamsLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_weight="3">
|
||||
|
||||
<!--NEXT AND RELATED VIDEOS-->
|
||||
<LinearLayout
|
||||
android:id="@+id/detail_related_streams_root_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingTop="14dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_next_stream_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:text="@string/next_video_title"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textSize="@dimen/video_item_detail_next_text_size"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/detail_related_streams_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="2dp"
|
||||
android:orientation="vertical"
|
||||
tools:minHeight="50dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/detail_related_streams_expand"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingTop="4dp"
|
||||
android:src="?attr/expand"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="true"
|
||||
tools:ignore="ContentDescription" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="org.schabi.newpipe.history.HistoryActivity">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:title="@string/app_name"/>
|
||||
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
app:srcCompat="?attr/clear_history"/>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
@ -303,7 +303,7 @@
|
||||
tools:text="English" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/toggleOrientation"
|
||||
android:id="@+id/share"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
@ -314,6 +314,23 @@
|
||||
android:focusable="true"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_share_white_24dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:contentDescription="@string/share"
|
||||
tools:ignore="RtlHardcoded"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/toggleOrientation"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:layout_toLeftOf="@id/share"
|
||||
android:layout_centerVertical="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_screen_rotation_white"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:contentDescription="@string/toggle_orientation"
|
||||
@ -458,6 +475,22 @@
|
||||
android:src="@drawable/exo_controls_next"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/closeButton"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/playPauseButton"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:text="@string/close"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/white"
|
||||
android:visibility="invisible" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
|
@ -1,27 +0,0 @@
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".player.old.PlayVideoActivity"
|
||||
android:gravity="center">
|
||||
|
||||
<VideoView android:id="@+id/video_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:focusable="false"/>
|
||||
|
||||
<Button android:id="@+id/content_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@null"
|
||||
android:focusable="false"/>
|
||||
|
||||
<ProgressBar android:id="@+id/play_video_progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:layout_gravity="center"
|
||||
android:focusable="false"/>
|
||||
|
||||
</merge>
|
71
app/src/main/res/layout/fragment_comments.xml
Normal file
71
app/src/main/res/layout/fragment_comments.xml
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/items_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
tools:listitem="@layout/list_comments_item"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading_progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/empty_state_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="85dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:fontFamily="monospace"
|
||||
android:text="(╯°-°)╯"
|
||||
android:textSize="35sp"
|
||||
tools:ignore="HardcodedText,UnusedAttribute"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/empty_view_no_comments"
|
||||
android:textSize="24sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!--ERROR PANEL-->
|
||||
<include
|
||||
android:id="@+id/error_panel"
|
||||
layout="@layout/error_retry"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginTop="50dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:background="?attr/toolbar_shadow_drawable"
|
||||
android:layout_alignParentTop="true"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</RelativeLayout>
|
29
app/src/main/res/layout/fragment_empty.xml
Normal file
29
app/src/main/res/layout/fragment_empty.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v4.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
android:id="@+id/empty_state_view"
|
||||
layout="@layout/list_empty_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="90dp"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
</android.support.v4.widget.NestedScrollView>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="?attr/toolbar_shadow_drawable"
|
||||
android:visibility="gone" />
|
||||
|
||||
</RelativeLayout>
|
@ -1,30 +0,0 @@
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/constraintLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="org.schabi.newpipe.history.HistoryFragment">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/history_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/item_search_history"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/history_empty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/history_empty"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/history_disabled_view"
|
||||
layout="@layout/history_disabled_view"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</FrameLayout>
|
71
app/src/main/res/layout/fragment_related_streams.xml
Normal file
71
app/src/main/res/layout/fragment_related_streams.xml
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/items_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
tools:listitem="@layout/list_stream_item"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading_progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/empty_state_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="85dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:fontFamily="monospace"
|
||||
android:text="(╯°-°)╯"
|
||||
android:textSize="35sp"
|
||||
tools:ignore="HardcodedText,UnusedAttribute"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/empty_view_no_videos"
|
||||
android:textSize="24sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!--ERROR PANEL-->
|
||||
<include
|
||||
android:id="@+id/error_panel"
|
||||
layout="@layout/error_retry"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginTop="50dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:background="?attr/toolbar_shadow_drawable"
|
||||
android:layout_alignParentTop="true"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</RelativeLayout>
|
@ -67,6 +67,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:listitem="@layout/item_search_suggestion"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/video_item_detail"
|
||||
@ -8,18 +7,24 @@
|
||||
android:layout_height="match_parent"
|
||||
android:focusableInTouchMode="true">
|
||||
|
||||
<com.nirhart.parallaxscroll.views.ParallaxScrollView
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/detail_main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="visible"
|
||||
app:parallax_factor="1.9">
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<!--WRAPPER-->
|
||||
<LinearLayout
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:id="@+id/appbarlayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:fitsSystemWindows="true"
|
||||
app:elevation="0dp"
|
||||
app:layout_behavior="android.support.design.widget.FlingBehavior">
|
||||
|
||||
<android.support.design.widget.CollapsingToolbarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_scrollFlags="scroll">
|
||||
|
||||
<!-- THUMBNAIL -->
|
||||
<FrameLayout
|
||||
@ -29,7 +34,8 @@
|
||||
android:background="@android:color/black"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground">
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:layout_collapseMode="parallax">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/detail_thumbnail_image_view"
|
||||
@ -57,16 +63,16 @@
|
||||
android:id="@+id/touch_append_detail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="#64000000"
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingLeft="30dp"
|
||||
android:paddingRight="30dp"
|
||||
android:paddingTop="10dp"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/hold_to_append"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/hold_to_append"
|
||||
android:visibility="gone"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible" />
|
||||
@ -97,12 +103,15 @@
|
||||
tools:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
</android.support.design.widget.CollapsingToolbarLayout>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<RelativeLayout
|
||||
android:id="@+id/detail_content_root_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:windowBackground">
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:windowBackground"
|
||||
app:layout_scrollFlags="scroll">
|
||||
|
||||
<!-- TITLE -->
|
||||
<FrameLayout
|
||||
@ -168,6 +177,7 @@
|
||||
android:id="@+id/detail_content_root_hiding"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="10dp"
|
||||
android:layout_below="@+id/detail_title_root_layout"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
@ -189,12 +199,12 @@
|
||||
android:id="@+id/detail_uploader_root_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toLeftOf="@id/details_panel"
|
||||
android:layout_toStartOf="@id/details_panel"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="6dp"
|
||||
android:layout_toLeftOf="@id/details_panel"
|
||||
android:layout_toStartOf="@id/details_panel">
|
||||
android:padding="6dp">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/detail_uploader_thumbnail_view"
|
||||
@ -209,14 +219,14 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_detail_uploader_text_size"
|
||||
android:textStyle="bold"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_detail_uploader_text_size"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Uploader" />
|
||||
|
||||
@ -239,10 +249,11 @@
|
||||
android:id="@+id/details_panel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:paddingLeft="6dp"
|
||||
android:paddingRight="6dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_view_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
@ -337,9 +348,9 @@
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:contentDescription="@string/append_playlist"
|
||||
android:drawableTop="?attr/ic_playlist_add"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="6dp"
|
||||
android:paddingTop="6dp"
|
||||
@ -354,9 +365,9 @@
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:contentDescription="@string/play_audio"
|
||||
android:drawableTop="?attr/audio"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="6dp"
|
||||
android:paddingTop="6dp"
|
||||
@ -371,9 +382,9 @@
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:contentDescription="@string/open_in_popup_mode"
|
||||
android:drawableTop="?attr/popup"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="6dp"
|
||||
android:paddingTop="6dp"
|
||||
@ -388,9 +399,9 @@
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:contentDescription="@string/controls_download_desc"
|
||||
android:drawableTop="?attr/download"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="6dp"
|
||||
android:paddingTop="6dp"
|
||||
@ -449,48 +460,30 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!--NEXT AND RELATED VIDEOS-->
|
||||
<LinearLayout
|
||||
android:id="@+id/detail_related_streams_root_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal|bottom"
|
||||
android:layout_marginTop="14dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_next_stream_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:text="@string/next_video_title"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textSize="@dimen/video_item_detail_next_text_size"
|
||||
tools:ignore="RtlHardcoded"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/detail_related_streams_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="2dp"
|
||||
android:orientation="vertical"
|
||||
tools:minHeight="50dp"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/detail_related_streams_expand"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingTop="4dp"
|
||||
android:src="?attr/expand"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="true"
|
||||
tools:ignore="ContentDescription"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
</com.nirhart.parallaxscroll.views.ParallaxScrollView>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/tablayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center"
|
||||
android:background="@color/transparent_background_color"
|
||||
app:tabBackground="@drawable/tab_selector"
|
||||
app:tabGravity="center"
|
||||
app:tabIndicatorHeight="0dp">
|
||||
|
||||
</android.support.design.widget.TabLayout>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</FrameLayout>
|
||||
|
113
app/src/main/res/layout/list_comments_item.xml
Normal file
113
app/src/main/res/layout/list_comments_item.xml
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/itemRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/itemThumbnailView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
|
||||
android:contentDescription="@string/list_thumbnail_view_description"
|
||||
android:src="@drawable/buddy"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemTitleView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginBottom="@dimen/video_item_search_image_right_margin"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/comment_item_title_text_size"
|
||||
tools:text="Author Name, Lorem ipsum" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemCommentContentView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/itemTitleView"
|
||||
android:layout_marginBottom="@dimen/channel_item_description_to_details_margin"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/comment_item_content_text_size"
|
||||
tools:text="Comment Content, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/detail_thumbs_up_img_view"
|
||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
android:layout_below="@id/itemCommentContentView"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:contentDescription="@string/detail_likes_img_view_description"
|
||||
android:src="?attr/thumbs_up" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_thumbs_up_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
android:layout_below="@id/itemCommentContentView"
|
||||
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
|
||||
android:layout_toRightOf="@id/detail_thumbs_up_img_view"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="12M" />
|
||||
|
||||
|
||||
<!--we can uncomment below code if we need dislike button and count in future-->
|
||||
|
||||
<!--<ImageView
|
||||
android:id="@+id/detail_thumbs_down_img_view"
|
||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
android:layout_below="@id/itemCommentContentView"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
|
||||
android:contentDescription="@string/detail_dislikes_img_view_description"
|
||||
android:src="?attr/thumbs_down"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_thumbs_down_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
android:layout_below="@id/itemCommentContentView"
|
||||
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
|
||||
android:layout_toRightOf="@id/detail_thumbs_down_img_view"
|
||||
android:gravity="center_vertical"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="10K" />-->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemPublishedTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/itemCommentContentView"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||
tools:text="1 year ago" />
|
||||
|
||||
</RelativeLayout>
|
95
app/src/main/res/layout/list_comments_mini_item.xml
Normal file
95
app/src/main/res/layout/list_comments_mini_item.xml
Normal file
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/itemRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/itemThumbnailView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="12dp"
|
||||
android:contentDescription="@string/list_thumbnail_view_description"
|
||||
android:src="@drawable/buddy_channel_item"
|
||||
tools:ignore="RtlHardcoded"/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemCommentContentView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/channel_item_description_to_details_margin"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/comment_item_content_text_size"
|
||||
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/detail_thumbs_up_img_view"
|
||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
android:layout_below="@id/itemCommentContentView"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:contentDescription="@string/detail_likes_img_view_description"
|
||||
android:src="?attr/thumbs_up" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_thumbs_up_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
android:layout_below="@id/itemCommentContentView"
|
||||
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
|
||||
android:layout_toRightOf="@id/detail_thumbs_up_img_view"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="12M" />
|
||||
|
||||
<!--we can uncomment below code if we need dislike button and count in future-->
|
||||
|
||||
<!--<ImageView
|
||||
android:id="@+id/detail_thumbs_down_img_view"
|
||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
android:layout_below="@id/itemCommentContentView"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
|
||||
android:contentDescription="@string/detail_dislikes_img_view_description"
|
||||
android:src="?attr/thumbs_down"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_thumbs_down_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
android:layout_below="@id/itemCommentContentView"
|
||||
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
|
||||
android:layout_toRightOf="@id/detail_thumbs_down_img_view"
|
||||
android:gravity="center_vertical"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="10K" />-->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemPublishedTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/itemCommentContentView"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||
tools:text="1 year ago" />
|
||||
|
||||
</RelativeLayout>
|
32
app/src/main/res/layout/related_streams_header.xml
Normal file
32
app/src/main/res/layout/related_streams_header.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/channel_header_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/next_stream_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_alignBaseline="@+id/autoplay_switch"
|
||||
android:text="@string/next_video_title"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textSize="12sp"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<Switch
|
||||
android:id="@+id/autoplay_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:paddingRight="5dp"
|
||||
android:switchPadding="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tab_indicator_text"
|
||||
android:text="@string/autoplay_title" />
|
||||
|
||||
</RelativeLayout>
|
@ -3,8 +3,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="org.schabi.newpipe.history.HistoryActivity">
|
||||
|
||||
<item android:id="@+id/action_settings"
|
||||
android:orderInCategory="990"
|
||||
android:title="@string/settings"
|
||||
<item android:id="@+id/action_history_clear"
|
||||
android:orderInCategory="940"
|
||||
android:title="@string/clear_views_history_title"
|
||||
app:showAsAction="never"/>
|
||||
</menu>
|
||||
|
@ -281,7 +281,6 @@
|
||||
<string name="show_info">عرض المعلومات</string>
|
||||
<string name="controls_add_to_playlist_title">إضافة إلى</string>
|
||||
<string name="settings_category_debug_title">تحليل</string>
|
||||
<string name="live">مٌباشِر</string>
|
||||
<string name="video_streams_empty">لم يتم العثور على أي بث مرئي</string>
|
||||
<string name="audio_streams_empty">لم يتم العثور على أي بث صوتي</string>
|
||||
<string name="detail_drag_description">إسحب للقيام بالترتيب</string>
|
||||
@ -398,7 +397,6 @@
|
||||
<string name="playback_tempo">سرعة الأداء</string>
|
||||
<string name="playback_pitch">تردد الصوت</string>
|
||||
<string name="unhook_checkbox">نزع الإرتباط (قد يسبب تشويه)</string>
|
||||
<string name="playback_nightcore">تعديل الايقاع Nightcore</string>
|
||||
<string name="import_settings">هل تريد أيضا استيراد الإعدادات؟</string>
|
||||
<string name="privacy_policy_title">"سياسة الخصوصية في NewPipe "</string>
|
||||
<string name="privacy_policy_encouragement">يأخذ مشروع NewPipe خصوصيتك على محمل الجد. لذلك ، لا يجمع التطبيق أي بيانات دون موافقتك.
|
||||
|
@ -134,8 +134,6 @@
|
||||
<string name="later">Más sero</string>
|
||||
<string name="disabled">Desactivóse</string>
|
||||
|
||||
<string name="use_old_player_title">Usar reproductor vieyu</string>
|
||||
|
||||
|
||||
<string name="short_thousand">M</string>
|
||||
<string name="short_million">Mill</string>
|
||||
@ -168,7 +166,6 @@
|
||||
<string name="settings_category_popup_title">Ventanu emerxente</string>
|
||||
<string name="popup_resizing_indicator_title">Redimensionáu</string>
|
||||
|
||||
<string name="use_old_player_summary">Reproductor vieyu integráu de Mediaframework</string>
|
||||
<string name="subscribe_button_title">Soscribise</string>
|
||||
<string name="subscribed_button_title">Soscribiéstite</string>
|
||||
<string name="channel_unsubscribed">Desoscribiéstite de la canal</string>
|
||||
@ -325,7 +322,6 @@
|
||||
<string name="video_player">Reproductor de videu</string>
|
||||
<string name="background_player">Reproductor en segundu planu</string>
|
||||
<string name="popup_player">Reproductor en ventanu</string>
|
||||
<string name="always_ask_player">Entrugar siempres</string>
|
||||
|
||||
<string name="preferred_player_fetcher_notification_title">Consiguiendo información…</string>
|
||||
<string name="preferred_player_fetcher_notification_message">Ta cargando\'l conteníu solicitáu</string>
|
||||
|
@ -54,8 +54,6 @@
|
||||
<string name="play_audio">Аўдыё</string>
|
||||
<string name="default_audio_format_title">Фармат аўдыё па змаўчанні</string>
|
||||
<string name="default_video_format_title">Фармат відэа па змаўчанні</string>
|
||||
<string name="webm_description">WebM - свабодны</string>
|
||||
<string name="m4a_description">M4A - вышэй якасць</string>
|
||||
<string name="theme_title">Тэма</string>
|
||||
<string name="light_theme_title">Светлая</string>
|
||||
<string name="dark_theme_title">Цёмная</string>
|
||||
@ -203,8 +201,6 @@
|
||||
<string name="audio">Аўдыё</string>
|
||||
<string name="retry">Паспрабаваць зноў</string>
|
||||
<string name="storage_permission_denied">Няма доступу да носьбіта</string>
|
||||
<string name="use_old_player_title">Стары плэер</string>
|
||||
<string name="use_old_player_summary">Стары убудаваны плэер на Mediaframework</string>
|
||||
<string name="short_thousand">тыс.</string>
|
||||
<string name="short_million">млн.</string>
|
||||
<string name="short_billion">млрд.</string>
|
||||
|
@ -156,8 +156,6 @@
|
||||
<string name="audio">Аудио</string>
|
||||
<string name="retry">Опитай отново</string>
|
||||
<string name="storage_permission_denied">Достъпа до хранилището е отказан</string>
|
||||
<string name="use_old_player_title">Използвай стария плейър</string>
|
||||
<string name="use_old_player_summary">Използваю стария вграден Mediaframewoek плейър</string>
|
||||
|
||||
<plurals name="subscribers">
|
||||
<item quantity="one">%s абонат</item>
|
||||
|
@ -118,8 +118,6 @@
|
||||
<string name="audio">অডিও</string>
|
||||
<string name="retry">পুনরায় চেষ্টা করো</string>
|
||||
<string name="storage_permission_denied">স্টোরেজ অ্যাক্সেস করার অনুমতি অস্বীকার করা হয়েছে</string>
|
||||
<string name="use_old_player_title">পুরানো প্লেয়ার ব্যবহার করো</string>
|
||||
<string name="use_old_player_summary">মিডিয়াফ্রেমওয়ার্ক প্লেয়ারের পুরানো বিল্ড।</string>
|
||||
<string name="short_thousand">K</string>
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">B</string>
|
||||
|
@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources><string name="install">Instaliraj</string>
|
||||
<string name="cancel">Otkaži</string>
|
||||
<string name="open_in_popup_mode">Otvori u slika u sliki modu</string>
|
||||
<string name="share">Podijeli</string>
|
||||
<string name="search">Pretraga</string>
|
||||
<string name="settings">Postavke</string>
|
||||
<string name="share_dialog_title">Podijeli sa</string>
|
||||
<string name="screen_rotation">Rotacija</string>
|
||||
<string name="subscribe_button_title">Preplati se</string>
|
||||
<string name="subscribed_button_title">Preplaćen</string>
|
||||
<string name="show_info">Pokaži informacije</string>
|
||||
<string name="tab_new">Novi Tab</string>
|
||||
<string name="tab_choose">Izaberi Tab</string>
|
||||
<string name="fragment_whats_new">Šta je novo</string>
|
||||
<string name="controls_background_title">Pozadina</string>
|
||||
<string name="download_path_audio_summary">Skinuti audio je spašen ovdje</string>
|
||||
<string name="default_resolution_title">Zadana rezolucija</string>
|
||||
<string name="show_higher_resolutions_title">Pokaži više rezolucije</string>
|
||||
<string name="show_higher_resolutions_summary">Samo neki uređaji podržavaju puštanje 2K/4K videa</string>
|
||||
<string name="kore_not_found">Kore aplikacija nije nađena. Instaliraj te\?</string>
|
||||
<string name="play_audio">Zvuk</string>
|
||||
<string name="default_audio_format_title">Zadani zvučni format</string>
|
||||
<string name="default_video_format_title">Zadani video format</string>
|
||||
<string name="dark_theme_title">Mračno</string>
|
||||
<string name="settings_category_appearance_title">Izgled</string>
|
||||
<string name="settings_category_other_title">Drugo</string>
|
||||
<string name="downloads">Skinuto</string>
|
||||
<string name="caption_auto_generated">Auto-generirano</string>
|
||||
<string name="error_path_creation">Fajl ne može biti krejiran</string>
|
||||
<string name="error_http_no_content">Ovaj server ne šalje podatke</string>
|
||||
</resources>
|
@ -216,8 +216,6 @@
|
||||
<string name="info_dir_created">S\'ha creat el directori de baixades «%1$s»</string>
|
||||
<string name="retry">Torna a intentar-ho</string>
|
||||
<string name="storage_permission_denied">S\'ha denegat el permís d\'accés a l\'emmagatzematge</string>
|
||||
<string name="use_old_player_title">Fes servir el reproductor antic</string>
|
||||
<string name="use_old_player_summary">Antic reproductor integrat de Mediaframework</string>
|
||||
<string name="no_subscribers">Sense subscriptors</string>
|
||||
<string name="no_views">Sense reproduccions</string>
|
||||
<plurals name="views">
|
||||
|
@ -59,7 +59,6 @@
|
||||
<string name="dark_theme_title">酷黑</string>
|
||||
<string name="black_theme_title">黑色</string>
|
||||
<string name="popup_remember_size_pos_title">记住悬浮窗的尺寸与位置</string>
|
||||
<string name="m4a_description">M4A — 更好的音质</string>
|
||||
<string name="popup_remember_size_pos_summary">记住上一次悬浮窗的位置以及大小</string>
|
||||
<string name="thumbnail_cache_wipe_complete_notice">已清除图像缓存</string>
|
||||
<string name="minimize_on_exit_popup_description">最小化悬浮窗播放器</string>
|
||||
@ -108,7 +107,6 @@
|
||||
<string name="minimize_on_exit_summary">从主视频播放器切换到其他应用时的操作 - %s</string>
|
||||
<string name="minimize_on_exit_none_description">没有</string>
|
||||
<string name="minimize_on_exit_background_description">最小化后台播放</string>
|
||||
<string name="webm_description">WebM — 自由视频格式</string>
|
||||
<string name="use_inexact_seek_title">使用快速粗略定位</string>
|
||||
<string name="use_inexact_seek_summary">粗略定位功能允许播放器以略低的精确度为代价换取更快的定位速度</string>
|
||||
<string name="download_thumbnail_title">下载缩略图</string>
|
||||
@ -243,8 +241,6 @@
|
||||
<string name="audio">音频</string>
|
||||
<string name="retry">重试</string>
|
||||
<string name="storage_permission_denied">手机存储访问权限被拒绝</string>
|
||||
<string name="use_old_player_title">使用旧的播放器</string>
|
||||
<string name="use_old_player_summary">旧的内置 Mediaframework 播放器</string>
|
||||
<string name="short_thousand">千</string>
|
||||
<string name="short_million">万</string>
|
||||
<string name="short_billion">亿</string>
|
||||
|
@ -120,7 +120,6 @@
|
||||
<string name="short_million">mil.</string>
|
||||
<string name="msg_popup_permission">Toto oprávnění je vyžadováno pro
|
||||
otevření ve vyskakovacím okně</string>
|
||||
<string name="use_old_player_title">Použít starý přehrávač</string>
|
||||
<string name="use_external_video_player_summary">Odstraňuje zvuk v některých rozlišeních</string>
|
||||
<string name="show_higher_resolutions_title">Zobrazovat vyšší rozlišení</string>
|
||||
<string name="show_higher_resolutions_summary">Pouze některá zařízení podporují přehrávání 2K/4K videí</string>
|
||||
@ -166,7 +165,6 @@ otevření ve vyskakovacím okně</string>
|
||||
<string name="notification_channel_description">Notifikace pro NewPipe přehrávače v pozadí a v okně</string>
|
||||
<string name="search_no_results">Žádné výsledky</string>
|
||||
<string name="empty_subscription_feed_subtitle">Je tu sranda jak v márnici</string>
|
||||
<string name="use_old_player_summary">Starý zabudovaný Mediaframework přehrávač</string>
|
||||
<string name="short_billion">mld.</string>
|
||||
<string name="no_subscribers">Žádní odběratelé</string>
|
||||
<plurals name="subscribers">
|
||||
@ -282,7 +280,6 @@ otevření ve vyskakovacím okně</string>
|
||||
<string name="video_player">Video přehrávač</string>
|
||||
<string name="background_player">Přehrávač na pozadí</string>
|
||||
<string name="popup_player">Přehrávač v okně</string>
|
||||
<string name="always_ask_player">Vždy se ptát</string>
|
||||
<string name="preferred_player_fetcher_notification_title">Získávám informace…</string>
|
||||
<string name="preferred_player_fetcher_notification_message">Načítání požadovaného obsahu</string>
|
||||
<string name="controls_download_desc">Stáhnout soubor streamu</string>
|
||||
@ -322,6 +319,7 @@ otevření ve vyskakovacím okně</string>
|
||||
<string name="smaller_caption_font_size">Menší písmo</string>
|
||||
<string name="normal_caption_font_size">Normální písmo</string>
|
||||
<string name="larger_caption_font_size">Větší písmo</string>
|
||||
|
||||
<string name="toggle_leak_canary">Sledovat únik paměti</string>
|
||||
<string name="disable_leak_canary_notice">Sledování úniku paměti vypnuto</string>
|
||||
<string name="enable_leak_canary_notice">Sledování úniku paměti povoleno, aplikace může při zátěži přestat reagovat</string>
|
||||
|
@ -120,13 +120,11 @@
|
||||
<string name="all">Alle</string>
|
||||
<string name="channel">Kanal</string>
|
||||
<string name="disabled">Deaktiviert</string>
|
||||
<string name="use_old_player_title">Alten Player benutzen</string>
|
||||
<string name="open_in_popup_mode">Im Pop-up-Modus öffnen</string>
|
||||
<string name="default_video_format_title">Bevorzugtes Videoformat</string>
|
||||
<string name="popup_playing_toast">Spiele im Pop-up Modus ab</string>
|
||||
<string name="popup_mode_share_menu_title">NewPipe-Pop-up-Modus</string>
|
||||
<string name="msg_popup_permission">Diese Berechtigung ist für das Öffnen im Pop-up-Modus erforderlich</string>
|
||||
<string name="use_old_player_summary">Alter eingebauter Mediaframework-Player</string>
|
||||
<string name="default_popup_resolution_title">Standardauflösung des Pop-ups</string>
|
||||
<string name="show_higher_resolutions_title">Höhere Auflösungen anzeigen</string>
|
||||
<string name="show_higher_resolutions_summary">Nur manche Geräte unterstützen das Abspielen von 2K-/4K-Videos</string>
|
||||
@ -270,7 +268,6 @@
|
||||
<string name="video_player">Video-Player</string>
|
||||
<string name="background_player">Hintergrund-Player</string>
|
||||
<string name="popup_player">Popup-Player</string>
|
||||
<string name="always_ask_player">Immer fragen</string>
|
||||
<string name="preferred_player_fetcher_notification_title">Informationen werden abgerufen…</string>
|
||||
<string name="preferred_player_fetcher_notification_message">Gewünschten Inhalt laden</string>
|
||||
<string name="import_data_title">Datenbank importieren</string>
|
||||
|
@ -29,8 +29,6 @@
|
||||
<string name="show_play_with_kodi_summary">Προβολή μιας επιλογής για αναπαραγωγή με το Kodi media center</string>
|
||||
<string name="play_audio">Ήχος</string>
|
||||
<string name="default_audio_format_title">Προεπιλεγμένη μορφή ήχου</string>
|
||||
<string name="webm_description">WebM — δωρεάν μορφή</string>
|
||||
<string name="m4a_description">Μ4Α — καλύτερη ποιότητα</string>
|
||||
<string name="theme_title">Θέμα</string>
|
||||
<string name="dark_theme_title">Σκοτεινό</string>
|
||||
<string name="light_theme_title">Φωτεινό</string>
|
||||
@ -216,8 +214,6 @@
|
||||
<string name="detail_drag_description">Σύρετε για ταξινόμηση</string>
|
||||
<string name="retry">Προσπάθεια εκ νέου</string>
|
||||
<string name="storage_permission_denied">Δεν δώθηκε άδεια εγγραφής στην εσωτερική μνήμη</string>
|
||||
<string name="use_old_player_title">Χρήση παλαιάς συσκευής αναπαραγωγής</string>
|
||||
<string name="use_old_player_summary">Παλαιά συσκευή αναπαραγωγής Mediaframework</string>
|
||||
<string name="short_thousand">χιλ</string>
|
||||
<string name="short_million">Εκ</string>
|
||||
<string name="no_subscribers">Κανένας εγγεγραμένος χρήστης</string>
|
||||
|
@ -268,7 +268,6 @@ abrir en modo popup</string>
|
||||
<string name="video_player">Reproductor de vídeo</string>
|
||||
<string name="background_player">Reproductor de fondo</string>
|
||||
<string name="popup_player">Reproductor de popup</string>
|
||||
<string name="always_ask_player">Preguntar siempre</string>
|
||||
<string name="preferred_player_fetcher_notification_title">Obteniendo información…</string>
|
||||
<string name="preferred_player_fetcher_notification_message">Cargando contenido solicitado</string>
|
||||
<string name="import_data_title">Importar base de datos</string>
|
||||
|
@ -195,8 +195,6 @@
|
||||
<string name="audio">Audio</string>
|
||||
<string name="retry">Proovi uuesti</string>
|
||||
<string name="storage_permission_denied">Pääsuõigused salvestile puuduvad</string>
|
||||
<string name="use_old_player_title">Kasuta vana pleierit</string>
|
||||
<string name="use_old_player_summary">Vana sisseehitatud mediaframework pleier</string>
|
||||
<string name="short_thousand">K</string>
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">B</string>
|
||||
|
@ -117,8 +117,6 @@
|
||||
<string name="audio">Audioa</string>
|
||||
<string name="retry">Saiatu berriro</string>
|
||||
<string name="storage_permission_denied">Biltegia atzitzeko baimena ukatu da</string>
|
||||
<string name="use_old_player_title">Erabili erreproduzigailu zaharra</string>
|
||||
<string name="use_old_player_summary">Barne Media Framework erreproduzigailu zaharra</string>
|
||||
<string name="short_thousand">K</string>
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">MM</string>
|
||||
@ -270,7 +268,6 @@
|
||||
<string name="video_player">Bideo erreproduzigailua</string>
|
||||
<string name="background_player">Bigarren planoko erreproduzigailua</string>
|
||||
<string name="popup_player">Laster-leiho erreproduzigailua</string>
|
||||
<string name="always_ask_player">Galdetu beti</string>
|
||||
<string name="preferred_player_fetcher_notification_title">Informazioa eskuratzen…</string>
|
||||
<string name="preferred_player_fetcher_notification_message">Kargatzen eskatutako edukia</string>
|
||||
<string name="controls_download_desc">Deskargatu jario fitxategia</string>
|
||||
|
@ -32,8 +32,6 @@
|
||||
<string name="show_play_with_kodi_summary">نمایش گزینهای برای پخش ویدیو با مرکز رسانهٔ کودی</string>
|
||||
<string name="play_audio">صدا</string>
|
||||
<string name="default_audio_format_title">قالب صدای پیشگزیده</string>
|
||||
<string name="webm_description">قالب آزاد — WebM</string>
|
||||
<string name="m4a_description">کیفیت بهتر — M4A</string>
|
||||
<string name="theme_title">زمینه</string>
|
||||
<string name="dark_theme_title">تیره</string>
|
||||
<string name="light_theme_title">روشن</string>
|
||||
@ -189,7 +187,6 @@
|
||||
<string name="error_occurred_detail">خطایی رخ داد: %1$s</string>
|
||||
<string name="no_streams_available_download">جریانی برای بارگیری در دسترس نیست</string>
|
||||
<string name="search_no_results">بدون نتیجه</string>
|
||||
<string name="use_old_player_title">استفاده از پخشکننده قدیمی</string>
|
||||
<string name="short_thousand">K</string>
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">B</string>
|
||||
|
@ -148,8 +148,6 @@
|
||||
<string name="audio">Ääni</string>
|
||||
<string name="retry">Toista uudelleen</string>
|
||||
<string name="storage_permission_denied">Oikeus tallennustilan hallintaan evätty</string>
|
||||
<string name="use_old_player_title">Käytä vanhaa soitinta</string>
|
||||
<string name="use_old_player_summary">Käytä vanhaa sisäänrakennettua Mediaframework-soitinta</string>
|
||||
|
||||
<string name="short_thousand">t.</string>
|
||||
<string name="short_million">milj.</string>
|
||||
|
@ -119,8 +119,6 @@
|
||||
<string name="later">Plus tard</string>
|
||||
<string name="disabled">Désactivé</string>
|
||||
<string name="info_labels">Quoi :\\nRequête :\\nLangue du contenu :\\nService :\\nHeure GMT :\\nPaquet :\\nVersion :\\nVersion du système :</string>
|
||||
<string name="use_old_player_title">Utiliser l\'ancien lecteur</string>
|
||||
<string name="use_old_player_summary">Ancienne version du lecteur Mediaframework</string>
|
||||
<string name="short_thousand">K</string>
|
||||
<string name="short_million">M</string>
|
||||
<string name="msg_popup_permission">Cette autorisation est nécessaire pour
|
||||
@ -270,7 +268,6 @@
|
||||
<string name="video_player">Lecteur vidéo</string>
|
||||
<string name="background_player">Lecteur en arrière-plan</string>
|
||||
<string name="popup_player">Lecteur en fenêtré</string>
|
||||
<string name="always_ask_player">Toujours demander</string>
|
||||
<string name="preferred_player_fetcher_notification_title">Obtention des infos…</string>
|
||||
<string name="preferred_player_fetcher_notification_message">Chargement du contenu</string>
|
||||
<string name="import_data_title">Importer les données</string>
|
||||
|
@ -59,8 +59,6 @@
|
||||
<string name="play_audio">Audio</string>
|
||||
<string name="default_audio_format_title">Formato de audio predeterminado</string>
|
||||
<string name="default_video_format_title">Formato de vídeo predeterminado</string>
|
||||
<string name="webm_description">WebM — formato libre</string>
|
||||
<string name="m4a_description">M4A — mellor calidade</string>
|
||||
<string name="theme_title">Tema</string>
|
||||
<string name="light_theme_title">Claro</string>
|
||||
<string name="dark_theme_title">Escuro</string>
|
||||
@ -218,8 +216,6 @@
|
||||
<string name="audio">Audio</string>
|
||||
<string name="retry">Tentar de novo</string>
|
||||
<string name="storage_permission_denied">A permisión de acceso ao almacenamento foi denegada</string>
|
||||
<string name="use_old_player_title">Usar o reprodutor antigo</string>
|
||||
<string name="use_old_player_summary">Versión interna anticuada do reprodutor Mediaframework</string>
|
||||
|
||||
<string name="short_thousand">K</string>
|
||||
<string name="short_million">M</string>
|
||||
|
@ -149,8 +149,6 @@
|
||||
<string name="audio">שמע</string>
|
||||
<string name="retry">ניסיון חוזר</string>
|
||||
<string name="storage_permission_denied">הגישה לאחסון נדחתה</string>
|
||||
<string name="use_old_player_title">השתמש בנגן הישן</string>
|
||||
<string name="use_old_player_summary">השתמש בנגן המובנה Mediaframework</string>
|
||||
<string name="short_thousand">K</string>
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">B</string>
|
||||
|
@ -80,8 +80,6 @@
|
||||
<string name="show_play_with_kodi_summary">कोडी मीडिया सेंटर के माध्यम से वीडियो चलाने के लिए एक विकल्प प्रदर्शित करें</string>
|
||||
<string name="default_audio_format_title">डिफ़ॉल्ट ऑडियो का फॉर्मेट</string>
|
||||
<string name="default_video_format_title">डिफ़ॉल्ट विडियो का फॉर्मेट</string>
|
||||
<string name="webm_description">WebM - libre फॉर्मेट</string>
|
||||
<string name="m4a_description">M4A - बेहतर क्वालिटी</string>
|
||||
<string name="theme_title">एप्प का नया रूप</string>
|
||||
<string name="dark_theme_title">काला</string>
|
||||
<string name="popup_remember_size_pos_title">विडियो पॉपअप की आकर और उसकी स्थति को याद रखे</string>
|
||||
@ -174,8 +172,6 @@
|
||||
<string name="audio">ऑडियो</string>
|
||||
<string name="retry">फिर से कोशिश करे</string>
|
||||
<string name="storage_permission_denied">स्टोरेज में प्रवेश के लिए अनुमति नहीं मिली</string>
|
||||
<string name="use_old_player_title">पुराना विडियो प्लेयर प्रयोग करे</string>
|
||||
<string name="use_old_player_summary">पुराना Mediaframework Player जो फ़ोन में बना हुआ है</string>
|
||||
<string name="short_thousand">हज़ार</string>
|
||||
<string name="short_million">करोड़</string>
|
||||
<string name="short_billion">अरब</string>
|
||||
@ -319,9 +315,6 @@
|
||||
<string name="smaller_caption_font_size">छोटे फ़ॉंट</string>
|
||||
<string name="normal_caption_font_size">सामांय फ़ॉंट</string>
|
||||
<string name="larger_caption_font_size">बड़ा फ़ॉंट</string>
|
||||
<string name="toggle_leak_canary">मॉनिटर लीक</string>
|
||||
<string name="enable_leak_canary_notice">मेमोरी लीक मॉनिटरिंग सक्षम है, हीप डंपिंग के समय ऐप अप्रतिसादी हो सकती है</string>
|
||||
<string name="disable_leak_canary_notice">मेमोरी लीक मॉनिटरिंग अक्षम है</string>
|
||||
<string name="settings_category_debug_title">डीबग करें</string>
|
||||
<string name="caption_auto_generated">ऑटो-जनरेटेड</string>
|
||||
<string name="enable_leak_canary_title">LeakCanary सक्षम करें</string>
|
||||
|
@ -131,8 +131,6 @@
|
||||
<string name="audio">Zvuk</string>
|
||||
<string name="retry">Ponovno pokušaj</string>
|
||||
<string name="storage_permission_denied">Dozvola za pisanje po pohrani je odbijena</string>
|
||||
<string name="use_old_player_title">Koristi stari reproduktor</string>
|
||||
<string name="use_old_player_summary">Stari ugrađeni Mediaframework reproduktor</string>
|
||||
<string name="short_thousand">tis</string>
|
||||
<string name="short_million">mil</string>
|
||||
<string name="short_billion">mlrd</string>
|
||||
|
@ -24,8 +24,6 @@
|
||||
<string name="show_play_with_kodi_summary">Opció mutatása a videók Kodi médiaközponttal való lejátszására</string>
|
||||
<string name="play_audio">Hang</string>
|
||||
<string name="default_audio_format_title">Alapértelmezett hang formátum</string>
|
||||
<string name="webm_description">WebM — szabad formátum</string>
|
||||
<string name="m4a_description">M4A — jobb minőség</string>
|
||||
<string name="download_dialog_title">Letöltés</string>
|
||||
<string name="next_video_title">Következő</string>
|
||||
<string name="url_not_supported_toast">Nem támogatott webcím</string>
|
||||
@ -193,7 +191,6 @@
|
||||
|
||||
<string name="info_labels">Mi:\\nKérés:\\nTartalom nyelve:\\nSzolgáltatás:\\nGMT Idő:\\nCsomag:\\nVerzió:\\nOperációs Rendszer verzió:</string>
|
||||
<string name="search_no_results">Nincs találat</string>
|
||||
<string name="use_old_player_title">Régi lejátszó használata</string>
|
||||
<string name="controls_download_desc">Adatfolyam fájl letöltése</string>
|
||||
<string name="controls_add_to_playlist_title">Hozzáadás</string>
|
||||
|
||||
@ -236,8 +233,6 @@
|
||||
<string name="empty_subscription_feed_subtitle">Itt nincs semmi</string>
|
||||
<string name="detail_drag_description">Húzza az átrendezéshez</string>
|
||||
|
||||
<string name="use_old_player_summary">Régi beépített Mediaframework lejátszó</string>
|
||||
|
||||
<string name="short_thousand">e</string>
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">Mrd</string>
|
||||
|
@ -29,8 +29,6 @@
|
||||
<string name="show_play_with_kodi_summary">Tampilkan opsi untuk memutar video via Kodi</string>
|
||||
<string name="play_audio">Audio</string>
|
||||
<string name="default_audio_format_title">Format audio</string>
|
||||
<string name="webm_description">WebM — format bebas</string>
|
||||
<string name="m4a_description">M4A — kualitas lebih baik</string>
|
||||
<string name="dark_theme_title">Gelap</string>
|
||||
<string name="light_theme_title">Terang</string>
|
||||
<string name="download_dialog_title">Unduh</string>
|
||||
@ -124,8 +122,6 @@
|
||||
\nmembuka di mode popup</string>
|
||||
<string name="popup_mode_share_menu_title">Mode popup NewPipe</string>
|
||||
<string name="popup_playing_toast">Memutar dalam mode popup</string>
|
||||
<string name="use_old_player_title">Gunakan pemutar lama</string>
|
||||
<string name="use_old_player_summary">Pemutar Mediaframework bawaan versi lama</string>
|
||||
<string name="disabled">Dinonaktifkan</string>
|
||||
<string name="default_video_format_title">Format video</string>
|
||||
<string name="default_popup_resolution_title">Resolusi popup</string>
|
||||
|
@ -122,7 +122,6 @@
|
||||
<string name="popup_mode_share_menu_title">Modalità Popup di NewPipe</string>
|
||||
<string name="popup_playing_toast">Riproduzione in modalità popup</string>
|
||||
<string name="disabled">Disattivato</string>
|
||||
<string name="use_old_player_title">Usa il vecchio lettore multimediale</string>
|
||||
<string name="use_external_video_player_summary">Non riproduce l\'audio con ALCUNE risoluzioni</string>
|
||||
<string name="controls_background_title">In sottofondo</string>
|
||||
<string name="controls_popup_title">Popup</string>
|
||||
@ -142,7 +141,6 @@
|
||||
<string name="clear">Cancella</string>
|
||||
<string name="popup_resizing_indicator_title">Ridimensionamento</string>
|
||||
<string name="best_resolution">Risoluzione migliore</string>
|
||||
<string name="use_old_player_summary">Precedente lettore multimediale Mediaframework integrato</string>
|
||||
<string name="msg_popup_permission">Questo permesso è necessario
|
||||
\nper riprodurre in modalità popup</string>
|
||||
<string name="action_settings">Impostazioni</string>
|
||||
@ -270,7 +268,6 @@
|
||||
<string name="video_player">Lettore video</string>
|
||||
<string name="background_player">Riproduzione in sottofondo</string>
|
||||
<string name="popup_player">Riproduzione in modalità popup</string>
|
||||
<string name="always_ask_player">Chiedi sempre</string>
|
||||
<string name="preferred_player_fetcher_notification_title">Raccogliendo informazioni…</string>
|
||||
<string name="preferred_player_fetcher_notification_message">Caricamento del contenuto richiesto</string>
|
||||
<string name="import_data_title">Importa database</string>
|
||||
|
@ -123,8 +123,6 @@
|
||||
このアクセス許可が必要です</string>
|
||||
<string name="popup_mode_share_menu_title">NewPipe ポップアップモード</string>
|
||||
<string name="popup_playing_toast">ポップアップモードで再生中</string>
|
||||
<string name="use_old_player_title">古いプレーヤーを使用する</string>
|
||||
<string name="use_old_player_summary">古い内蔵のMediaframeworkプレーヤー</string>
|
||||
<string name="disabled">無効</string>
|
||||
<string name="default_video_format_title">デフォルトの動画形式</string>
|
||||
<string name="default_popup_resolution_title">デフォルトのポップアップ解像度</string>
|
||||
@ -253,7 +251,6 @@
|
||||
<string name="video_player">動画プレーヤー</string>
|
||||
<string name="background_player">バックグラウンドプレーヤー</string>
|
||||
<string name="popup_player">ポップアッププレーヤー</string>
|
||||
<string name="always_ask_player">常に尋ねる</string>
|
||||
<string name="preferred_player_fetcher_notification_title">情報を取得しています…</string>
|
||||
<string name="preferred_player_fetcher_notification_message">コンテンツを読み込んでいます</string>
|
||||
<string name="controls_download_desc">動画ファイルをダウンロード</string>
|
||||
|
@ -173,9 +173,6 @@
|
||||
<string name="search_no_results">결과 없음</string>
|
||||
<string name="empty_subscription_feed_subtitle">구독할 항목을 추가하세요</string>
|
||||
|
||||
<string name="use_old_player_title">구형 플레이어 사용</string>
|
||||
<string name="use_old_player_summary">내장된 구형 Mediaframework 플레이어 사용</string>
|
||||
|
||||
<string name="short_thousand">천</string>
|
||||
<string name="short_million">백만</string>
|
||||
<string name="short_billion">10억</string>
|
||||
|
@ -120,8 +120,6 @@
|
||||
<string name="video">Vaizdas</string>
|
||||
<string name="audio">Muzika</string>
|
||||
<string name="retry">Bandyti iš naujo</string>
|
||||
<string name="use_old_player_title">Naudoti seną grotuvą</string>
|
||||
<string name="use_old_player_summary">Senas įtaisytas media grotuvas</string>
|
||||
|
||||
<plurals name="subscribers">
|
||||
<item quantity="one">%s prenumeratorius</item>
|
||||
|
@ -202,8 +202,6 @@
|
||||
<string name="audio">Звук</string>
|
||||
<string name="retry">Пробај повторно</string>
|
||||
<string name="storage_permission_denied">Нема привилегии за пристап</string>
|
||||
<string name="use_old_player_title">Користи го стариот плеер</string>
|
||||
<string name="use_old_player_summary">Користи го стариот Mediaframework плеер</string>
|
||||
|
||||
<string name="short_thousand">K</string>
|
||||
<string name="short_million">M</string>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user