diff --git a/README.md b/README.md
index 0d615f43c..6c1aa3d4b 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,9 @@
Website • Blog • Press
-WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.
+WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.
+
+PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.
## Screenshots
diff --git a/app/build.gradle b/app/build.gradle
index 75f966a9b..da735302c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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'
diff --git a/app/src/main/java/android/support/design/widget/FlingBehavior.java b/app/src/main/java/android/support/design/widget/FlingBehavior.java
new file mode 100644
index 000000000..59eb08294
--- /dev/null
+++ b/app/src/main/java/android/support/design/widget/FlingBehavior.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java
index af9b88ac1..6a6d1b9c2 100644
--- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java
+++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java
@@ -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;
@@ -68,6 +70,8 @@ public class CheckForNewAppVersionTask extends AsyncTask {
@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 {
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
}
+
+ private boolean isConnected() {
+
+ ConnectivityManager cm =
+ (ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
+ return cm.getActiveNetworkInfo() != null
+ && cm.getActiveNetworkInfo().isConnected();
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/Downloader.java b/app/src/main/java/org/schabi/newpipe/Downloader.java
index 32e8bd414..ff274a91a 100644
--- a/app/src/main/java/org/schabi/newpipe/Downloader.java
+++ b/app/src/main/java/org/schabi/newpipe/Downloader.java
@@ -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 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 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> requestHeaders = request.getRequestHeaders();
+ // set custom headers in request
+ for (Map.Entry> 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> 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> 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());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
index b8941670f..f040dc8b4 100644
--- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
@@ -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);
}
@@ -112,7 +117,7 @@ public class RouterActivity extends AppCompatActivity {
@Override
protected void onStart() {
super.onStart();
-
+
handleUrl(currentUrl);
}
@@ -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 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 -> {
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ if(!internalRoute){
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
startActivity(intent);
finish();
diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
index 251e4c730..7ee686a66 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
@@ -47,7 +47,7 @@ public class DownloadActivity extends AppCompatActivity {
@Override
public void onGlobalLayout() {
updateFragments();
- getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
index acee1f111..4546483d2 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
@@ -230,21 +230,4 @@ public abstract class BaseStateFragment 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)));
- }
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java
new file mode 100644
index 000000000..0666667d6
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java
@@ -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);
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java
new file mode 100644
index 000000000..8314f9539
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java
@@ -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 mFragmentList = new ArrayList<>();
+ private final List 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();
+ }
+
+}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
index c346e1329..bbd1a315d 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -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 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,44 +432,17 @@ 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);
- videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
- } else {
- videoTitleTextView.setMaxLines(10);
- videoDescriptionRootLayout.setVisibility(View.VISIBLE);
- videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
- }
+ if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) {
+ videoTitleTextView.setMaxLines(1);
+ videoDescriptionRootLayout.setVisibility(View.GONE);
+ videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
+ } else {
+ videoTitleTextView.setMaxLines(10);
+ videoDescriptionRootLayout.setVisibility(View.VISIBLE);
+ 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() {
- @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
@@ -693,7 +628,7 @@ public class VideoDetailFragment
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- if(isLoading.get()) {
+ if (isLoading.get()) {
// if is still loading block menu
return true;
}
@@ -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;
}
@@ -717,7 +652,7 @@ public class VideoDetailFragment
NavigationHelper.playWithKore(activity, Uri.parse(
url.replace("https", "http")));
} catch (Exception e) {
- if(DEBUG) Log.i(TAG, "Failed to start kore", e);
+ if (DEBUG) Log.i(TAG, "Failed to start kore", e);
showInstallKoreDialog(activity);
}
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) appBarLayout.setExpanded(true, true);
+ handleResult(info);
+ showContent();
- if (scrollToTop) parallaxScrollRootView.smoothScrollTo(0, 0);
- animateView(contentRootLayoutHiding,
- false,
- greaterThanThreshold ? 250 : 0, 0, () -> {
- handleResult(info);
- showContentWithAnimation(120, 0, .01f);
- });
}
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,17 +1038,17 @@ 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);
+ videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
+ videoTitleToggleArrow.setVisibility(View.GONE);
+ videoTitleRoot.setClickable(false);
+
+ if(relatedStreamsLayout != null){
+ if(showRelatedStreams){
+ relatedStreamsLayout.setVisibility(View.INVISIBLE);
+ }else{
+ relatedStreamsLayout.setVisibility(View.GONE);
}
}
- videoTitleRoot.setClickable(false);
imageLoader.cancelDisplayTask(thumbnailImageView);
imageLoader.cancelDisplayTask(uploaderThumb);
@@ -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);
- }
+ videoTitleRoot.setClickable(true);
+ videoTitleToggleArrow.setVisibility(View.VISIBLE);
+ videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
+ videoDescriptionRootLayout.setVisibility(View.GONE);
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);
- }
- }
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
index 0816334ea..dbc3dd8a2 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
@@ -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 extends BaseStateFragment implem
}
});
+ infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture() {
+ @Override
+ public void selected(CommentsInfoItem selectedItem) {
+ onItemSelected(selectedItem);
+ }
+ });
+
itemsList.clearOnScrollListeners();
itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
@Override
@@ -247,6 +256,7 @@ public abstract class BaseListFragment extends BaseStateFragment 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 extends BaseStateFragment 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;
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
index b9489ffa7..71865b04d 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
@@ -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 {
}
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 {
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);
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
new file mode 100644
index 000000000..956e6c1c8
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
@@ -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 {
+
+ 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 loadMoreItemsLogic() {
+ return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
+ }
+
+ @Override
+ protected Single 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;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index 0019a3819..2a775fe8f 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -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 {
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 {
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 {
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() {
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
index e20d6bad3..a3b01f251 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
@@ -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);
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java
index 7b5f72c53..3f4e9af0b 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java
@@ -75,7 +75,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter 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 loadMoreItemsLogic() {
+ return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
+ }
+
+ @Override
+ protected Single 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;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
index f473e5d08..0e9fd3277 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
@@ -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 onStreamSelectedListener;
private OnClickGesture onChannelSelectedListener;
private OnClickGesture onPlaylistSelectedListener;
+ private OnClickGesture 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 getOnCommentsSelectedListener() {
+ return onCommentsSelectedListener;
+ }
+
+ public void setOnCommentsSelectedListener(OnClickGesture onCommentsSelectedListener) {
+ this.onCommentsSelectedListener = onCommentsSelectedListener;
+ }
+
}
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
index 15fdcad05..5e7095c7d 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
@@ -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 infoItemList;
@@ -98,6 +103,10 @@ public class InfoListAdapter extends RecyclerView.Adapter listener) {
+ infoItemBuilder.setOnCommentsSelectedListener(listener);
+ }
+
public void useMiniItemVariants(boolean useMiniVariant) {
this.useMiniVariant = useMiniVariant;
}
@@ -223,6 +232,8 @@ public class InfoListAdapter extends RecyclerView.Adapter
+ * 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 .
+ */
+
+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());
+ }
+
+}
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
new file mode 100644
index 000000000..ce8412b20
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
@@ -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);
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
index 32083fd42..5a62a3969 100644
--- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
@@ -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;
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
index f400061e1..dc101fade 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
@@ -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= 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);
diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
index d1b06c9c5..3db4be4a6 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
@@ -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);
diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
index f4fea5165..0bb9c7b2b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
@@ -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
- | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- } else {
- visibility = View.STATUS_BAR_VISIBLE;
- }
+ final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
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);
- }
+ shuffleButton.setImageAlpha(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();
}
@@ -851,8 +871,8 @@ public final class MainVideoPlayer extends AppCompatActivity
if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
getControlsVisibilityHandler().removeCallbacksAndMessages(null);
getControlsVisibilityHandler().postDelayed(() ->
- animateView(getControlsRoot(), false, duration, 0,
- MainVideoPlayer.this::hideSystemUi),
+ animateView(getControlsRoot(), false, duration, 0,
+ MainVideoPlayer.this::hideSystemUi),
/*delayMillis=*/delay
);
}
@@ -1056,9 +1076,9 @@ public final class MainVideoPlayer extends AppCompatActivity
final int resId =
currentProgressPercent <= 0 ? R.drawable.ic_volume_off_white_72dp
- : currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_72dp
- : currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_72dp
- : R.drawable.ic_volume_up_white_72dp;
+ : currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_72dp
+ : currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_72dp
+ : R.drawable.ic_volume_up_white_72dp;
playerImpl.getVolumeImageView().setImageDrawable(
AppCompatResources.getDrawable(getApplicationContext(), resId)
@@ -1082,8 +1102,8 @@ public final class MainVideoPlayer extends AppCompatActivity
final int resId =
currentProgressPercent < 0.25 ? R.drawable.ic_brightness_low_white_72dp
- : currentProgressPercent < 0.75 ? R.drawable.ic_brightness_medium_white_72dp
- : R.drawable.ic_brightness_high_white_72dp;
+ : currentProgressPercent < 0.75 ? R.drawable.ic_brightness_medium_white_72dp
+ : R.drawable.ic_brightness_high_white_72dp;
playerImpl.getBrightnessImageView().setImageDrawable(
AppCompatResources.getDrawable(getApplicationContext(), resId)
diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
index 8ea3d509c..7578c444c 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
@@ -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;
diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
index 2ec4275fc..3e04f1e3a 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
@@ -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);
- }
+ shuffleButton.setImageAlpha(shuffleAlpha);
}
private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
index d30d9b8be..4dbbc571d 100644
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
@@ -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 list, String toFind) {
+ for(String i : list){
+ if(i.equalsIgnoreCase(toFind))
+ return true;
+ }
+ return false;
+ }
+
/*//////////////////////////////////////////////////////////////////////////
// General Player
//////////////////////////////////////////////////////////////////////////*/
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
index f148aed27..24d1ee1ca 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
@@ -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) {}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java
index b8d8dc12f..091efc942 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java
@@ -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;
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
index 7670deb98..4239dd62f 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
@@ -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();
}
/*//////////////////////////////////////////////////////////////////////////
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java
index 133121269..5743891c2 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java
@@ -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) {
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
index 19b728b3a..7248857b5 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
@@ -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,
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
index 2f233c464..cc9cd36bc 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
@@ -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);
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java
index c39b0a03d..d36a3e305 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java
@@ -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
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java
index 5fe107657..fe29707fc 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java
@@ -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);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java
index bfd734393..377ca55a3 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java
@@ -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
diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java b/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java
index efe6f3a58..063d6b93e 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java
@@ -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;
@@ -17,7 +18,7 @@ import com.google.android.exoplayer2.util.Assertions;
* is mostly a copy-paste from {@link DefaultTrackSelector}.
*
* This is a hack and should be removed once ExoPlayer fixes language normalization to accept
- * a broader set of languages.
+ * a broader set of languages.
* */
public class CustomTrackSelector extends DefaultTrackSelector {
private static final int WITHIN_RENDERER_CAPABILITIES_BONUS = 1000;
@@ -52,8 +53,8 @@ public class CustomTrackSelector extends DefaultTrackSelector {
/** @see DefaultTrackSelector#selectTextTrack(TrackGroupArray, int[][], Parameters) */
@Override
- protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport,
- Parameters params) {
+ protected Pair selectTextTrack(TrackGroupArray groups, int[][] formatSupport,
+ Parameters params) {
TrackGroup selectedGroup = null;
int selectedTrackIndex = 0;
int selectedTrackScore = 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);
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java
index 3c5642d51..db8cc797e 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java
@@ -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() {
diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java
index 5993481e2..40d1a11e7 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java
@@ -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 items, final int index) {
super(index, playQueueItemsOf(items));
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
index c62dc1088..7abebc49e 100644
--- a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
@@ -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;
diff --git a/app/src/main/java/org/schabi/newpipe/report/UserAction.java b/app/src/main/java/org/schabi/newpipe/report/UserAction.java
index 00a25ed8d..2b2369ad3 100644
--- a/app/src/main/java/org/schabi/newpipe/report/UserAction.java
+++ b/app/src/main/java/org/schabi/newpipe/report/UserAction.java
@@ -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");
diff --git a/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java b/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java
index 3c5f16929..6a398a8a2 100644
--- a/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java
@@ -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();
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
new file mode 100644
index 000000000..e1ecc662d
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
@@ -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;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index 041f4933f..0f1c39473 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -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;
@@ -62,7 +63,7 @@ public final class ExtractorHelper {
}
private static void checkServiceId(int serviceId) {
- if(serviceId == Constants.NO_SERVICE_ID) {
+ if (serviceId == Constants.NO_SERVICE_ID) {
throw new IllegalArgumentException("serviceId is NO_SERVICE_ID");
}
}
@@ -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,29 +119,45 @@ 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)));
}
public static Single getMoreChannelItems(final int serviceId,
- final String url,
- final String nextStreamsUrl) {
+ final String url,
+ final String nextStreamsUrl) {
checkServiceId(serviceId);
return Single.fromCallable(() ->
ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl));
}
+ public static Single 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 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 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)));
}
public static Single getMorePlaylistItems(final int serviceId,
- final String url,
- final String nextStreamsUrl) {
+ final String url,
+ final String nextStreamsUrl) {
checkServiceId(serviceId);
return Single.fromCallable(() ->
PlaylistInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl));
@@ -149,7 +166,7 @@ public final class ExtractorHelper {
public static Single 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 Single checkCache(boolean forceLoad,
int serviceId,
String url,
+ InfoItem.InfoType infoType,
Single loadFromNetwork) {
checkServiceId(serviceId);
- loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info));
+ loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info, infoType));
Single 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,20 +212,20 @@ public final class ExtractorHelper {
/**
* Default implementation uses the {@link InfoCache} to get cached results
*/
- public static Maybe loadFromCache(final int serviceId, final String url) {
+ public static Maybe loadFromCache(final int serviceId, final String url, InfoItem.InfoType infoType) {
checkServiceId(serviceId);
return Maybe.defer(() -> {
- //noinspection unchecked
- I info = (I) cache.getFromKey(serviceId, url);
- if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info);
+ //noinspection unchecked
+ 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)
- if (info != null) {
- return Maybe.just(info);
- }
+ // Only return info if it's not null (it is cached)
+ if (info != null) {
+ return Maybe.just(info);
+ }
- return Maybe.empty();
- });
+ return Maybe.empty();
+ });
}
/**
diff --git a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java
index 318db37a1..23b134281 100644
--- a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java
+++ b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java
@@ -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() {
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index 5df2e8be4..98ae3a88a 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -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);
diff --git a/app/src/main/java/org/schabi/newpipe/util/RelatedStreamInfo.java b/app/src/main/java/org/schabi/newpipe/util/RelatedStreamInfo.java
new file mode 100644
index 000000000..6de663c13
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/RelatedStreamInfo.java
@@ -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 {
+
+ 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 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;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
new file mode 100644
index 000000000..c5c78a726
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
@@ -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)));
+ }
+}
diff --git a/app/src/main/res/drawable/default_dot.xml b/app/src/main/res/drawable/default_dot.xml
new file mode 100644
index 000000000..3380dca3b
--- /dev/null
+++ b/app/src/main/res/drawable/default_dot.xml
@@ -0,0 +1,12 @@
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/selected_dot.xml b/app/src/main/res/drawable/selected_dot.xml
new file mode 100644
index 000000000..017e99d43
--- /dev/null
+++ b/app/src/main/res/drawable/selected_dot.xml
@@ -0,0 +1,12 @@
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/tab_selector.xml b/app/src/main/res/drawable/tab_selector.xml
new file mode 100644
index 000000000..b7307674b
--- /dev/null
+++ b/app/src/main/res/drawable/tab_selector.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-large-land/activity_main_player.xml b/app/src/main/res/layout-large-land/activity_main_player.xml
index 5f484267b..8428d489a 100644
--- a/app/src/main/res/layout-large-land/activity_main_player.xml
+++ b/app/src/main/res/layout-large-land/activity_main_player.xml
@@ -249,7 +249,7 @@
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginLeft="2dp"
- android:padding="5dp"
+ android:padding="5dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
@@ -305,7 +305,7 @@
tools:text="English" />
+
+
+
+
diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml
index 73939d60a..8cdc2f307 100644
--- a/app/src/main/res/layout-large-land/fragment_video_detail.xml
+++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml
@@ -8,107 +8,121 @@
android:focusableInTouchMode="true"
android:orientation="horizontal">
-
+ android:layout_height="match_parent"
+ android:layout_weight="5"
+ android:fitsSystemWindows="true">
-
-
+ android:fitsSystemWindows="true"
+ app:elevation="0dp"
+ app:layout_behavior="android.support.design.widget.FlingBehavior">
-
-
+ app:layout_scrollFlags="scroll">
-
+
+ android:background="@android:color/black"
+ android:clickable="true"
+ android:focusable="true"
+ android:foreground="?attr/selectableItemBackground"
+ app:layout_collapseMode="parallax">
-
+
-
+
-
-
+
+
+
+
+
+
+ android:layout_height="wrap_content"
+ android:background="?android:windowBackground"
+ app:layout_scrollFlags="scroll">
@@ -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." />
+
+
@@ -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"
@@ -209,17 +233,17 @@
tools:text="Uploader" />
+ android:id="@+id/detail_uploader_subscribe"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|right"
+ android:layout_marginRight="12dp"
+ android:text="@string/rss_button_title"
+ android:textSize="12sp"
+ android:theme="@style/RedButton"
+ android:drawableLeft="@drawable/ic_rss_feed_white_24dp"
+ tools:ignore="RtlHardcoded"
+ 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">
+
-
-
+
-
-
-
+
+
+
+ android:layout_gravity="bottom|center"
+ android:background="@color/transparent_background_color"
+ app:tabBackground="@drawable/tab_selector"
+ app:tabGravity="center"
+ app:tabIndicatorHeight="0dp">
-
+
-
+
-
-
-
+
+
+
diff --git a/app/src/main/res/layout/activity_history.xml b/app/src/main/res/layout/activity_history.xml
deleted file mode 100644
index e53b9bff9..000000000
--- a/app/src/main/res/layout/activity_history.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml
index 8cf9ba32f..a55a5df05 100644
--- a/app/src/main/res/layout/activity_main_player.xml
+++ b/app/src/main/res/layout/activity_main_player.xml
@@ -247,7 +247,7 @@
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginLeft="2dp"
- android:padding="5dp"
+ android:padding="5dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
@@ -303,7 +303,7 @@
tools:text="English" />
+
+
+
+
diff --git a/app/src/main/res/layout/activity_play_video.xml b/app/src/main/res/layout/activity_play_video.xml
deleted file mode 100644
index bc37184a4..000000000
--- a/app/src/main/res/layout/activity_play_video.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_comments.xml b/app/src/main/res/layout/fragment_comments.xml
new file mode 100644
index 000000000..64d4d73bd
--- /dev/null
+++ b/app/src/main/res/layout/fragment_comments.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_empty.xml b/app/src/main/res/layout/fragment_empty.xml
new file mode 100644
index 000000000..6e5c06f6c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_empty.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml
deleted file mode 100644
index 5c325cac4..000000000
--- a/app/src/main/res/layout/fragment_history.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_related_streams.xml b/app/src/main/res/layout/fragment_related_streams.xml
new file mode 100644
index 000000000..5bb0b9497
--- /dev/null
+++ b/app/src/main/res/layout/fragment_related_streams.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml
index d49d23175..c6f733e3f 100644
--- a/app/src/main/res/layout/fragment_search.xml
+++ b/app/src/main/res/layout/fragment_search.xml
@@ -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"/>
diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml
index 7c6568b67..b0ee65dde 100644
--- a/app/src/main/res/layout/fragment_video_detail.xml
+++ b/app/src/main/res/layout/fragment_video_detail.xml
@@ -1,6 +1,5 @@
-
-
+ android:fitsSystemWindows="true">
-
-
+ android:fitsSystemWindows="true"
+ app:elevation="0dp"
+ app:layout_behavior="android.support.design.widget.FlingBehavior">
-
-
+ app:layout_scrollFlags="scroll">
-
+
+ android:background="@android:color/black"
+ android:clickable="true"
+ android:focusable="true"
+ android:foreground="?attr/selectableItemBackground"
+ app:layout_collapseMode="parallax">
-
+
-
+
-
-
+
+
+
+
+
+
+ android:layout_height="wrap_content"
+ android:background="?android:windowBackground"
+ app:layout_scrollFlags="scroll">
+ 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." />
+ tools:ignore="ContentDescription,RtlHardcoded" />
@@ -150,7 +159,7 @@
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
android:indeterminate="true"
android:visibility="gone"
- tools:visibility="visible"/>
+ tools:visibility="visible" />
+ tools:visibility="visible" />
+ android:padding="6dp">
+ tools:ignore="RtlHardcoded" />
+ tools:text="Uploader" />
+ tools:text="Published on Oct 2, 2009" />
+ tools:text="Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum." />
+ android:background="?attr/separator_color" />
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml
new file mode 100644
index 000000000..393d7d1b4
--- /dev/null
+++ b/app/src/main/res/layout/list_comments_item.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml
new file mode 100644
index 000000000..3f3c6c468
--- /dev/null
+++ b/app/src/main/res/layout/list_comments_mini_item.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/related_streams_header.xml b/app/src/main/res/layout/related_streams_header.xml
new file mode 100644
index 000000000..b98244b7e
--- /dev/null
+++ b/app/src/main/res/layout/related_streams_header.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_history.xml b/app/src/main/res/menu/menu_history.xml
index 526f500f6..9c030b5fe 100644
--- a/app/src/main/res/menu/menu_history.xml
+++ b/app/src/main/res/menu/menu_history.xml
@@ -3,8 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.schabi.newpipe.history.HistoryActivity">
-
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index f197bf975..b7a0ec7b0 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -281,7 +281,6 @@
عرض المعلومات
إضافة إلى
تحليل
- مٌباشِر
لم يتم العثور على أي بث مرئي
لم يتم العثور على أي بث صوتي
إسحب للقيام بالترتيب
@@ -398,7 +397,6 @@
سرعة الأداء
تردد الصوت
نزع الإرتباط (قد يسبب تشويه)
- تعديل الايقاع Nightcore
هل تريد أيضا استيراد الإعدادات؟
"سياسة الخصوصية في NewPipe "
يأخذ مشروع NewPipe خصوصيتك على محمل الجد. لذلك ، لا يجمع التطبيق أي بيانات دون موافقتك.
diff --git a/app/src/main/res/values-b+ast/strings.xml b/app/src/main/res/values-b+ast/strings.xml
index f9834119d..5f482809a 100644
--- a/app/src/main/res/values-b+ast/strings.xml
+++ b/app/src/main/res/values-b+ast/strings.xml
@@ -134,8 +134,6 @@
Más sero
Desactivóse
- Usar reproductor vieyu
-
M
Mill
@@ -168,8 +166,7 @@
Ventanu emerxente
Redimensionáu
- Reproductor vieyu integráu de Mediaframework
-Soscribise
+ Soscribise
Soscribiéstite
Desoscribiéstite de la canal
Nun pue camudase la resolución
@@ -325,8 +322,7 @@
Reproductor de videu
Reproductor en segundu planu
Reproductor en ventanu
- Entrugar siempres
-
+
Consiguiendo información…
Ta cargando\'l conteníu solicitáu
Baxa\'l ficheru del fluxu.
diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml
index dc9e6b571..fe6dd7fe6 100644
--- a/app/src/main/res/values-be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -54,8 +54,6 @@
Аўдыё
Фармат аўдыё па змаўчанні
Фармат відэа па змаўчанні
- WebM - свабодны
- M4A - вышэй якасць
Тэма
Светлая
Цёмная
@@ -203,8 +201,6 @@
Аўдыё
Паспрабаваць зноў
Няма доступу да носьбіта
- Стары плэер
- Стары убудаваны плэер на Mediaframework
тыс.
млн.
млрд.
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index ce175b28e..9a28dc625 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -156,8 +156,6 @@
Аудио
Опитай отново
Достъпа до хранилището е отказан
- Използвай стария плейър
- Използваю стария вграден Mediaframewoek плейър
- %s абонат
diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml
index d6f061888..eaf9eed9a 100644
--- a/app/src/main/res/values-bn-rBD/strings.xml
+++ b/app/src/main/res/values-bn-rBD/strings.xml
@@ -118,8 +118,6 @@
অডিও
পুনরায় চেষ্টা করো
স্টোরেজ অ্যাক্সেস করার অনুমতি অস্বীকার করা হয়েছে
- পুরানো প্লেয়ার ব্যবহার করো
- মিডিয়াফ্রেমওয়ার্ক প্লেয়ারের পুরানো বিল্ড।
K
M
B
diff --git a/app/src/main/res/values-bs-rLatn/strings.xml b/app/src/main/res/values-bs-rLatn/strings.xml
deleted file mode 100644
index 7f524c0ae..000000000
--- a/app/src/main/res/values-bs-rLatn/strings.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-Instaliraj
- Otkaži
- Otvori u slika u sliki modu
- Podijeli
- Pretraga
- Postavke
- Podijeli sa
- Rotacija
- Preplati se
- Preplaćen
- Pokaži informacije
- Novi Tab
- Izaberi Tab
- Šta je novo
- Pozadina
- Skinuti audio je spašen ovdje
- Zadana rezolucija
- Pokaži više rezolucije
- Samo neki uređaji podržavaju puštanje 2K/4K videa
- Kore aplikacija nije nađena. Instaliraj te\?
- Zvuk
- Zadani zvučni format
- Zadani video format
- Mračno
- Izgled
- Drugo
- Skinuto
- Auto-generirano
- Fajl ne može biti krejiran
- Ovaj server ne šalje podatke
-
\ No newline at end of file
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 5ac2f2134..4285a73dc 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -216,8 +216,6 @@
S\'ha creat el directori de baixades «%1$s»
Torna a intentar-ho
S\'ha denegat el permís d\'accés a l\'emmagatzematge
- Fes servir el reproductor antic
- Antic reproductor integrat de Mediaframework
Sense subscriptors
Sense reproduccions
diff --git a/app/src/main/res/values-cmn/strings.xml b/app/src/main/res/values-cmn/strings.xml
index 858c22b23..abaaa2ed2 100644
--- a/app/src/main/res/values-cmn/strings.xml
+++ b/app/src/main/res/values-cmn/strings.xml
@@ -59,7 +59,6 @@
酷黑
黑色
记住悬浮窗的尺寸与位置
- M4A — 更好的音质
记住上一次悬浮窗的位置以及大小
已清除图像缓存
最小化悬浮窗播放器
@@ -108,7 +107,6 @@
从主视频播放器切换到其他应用时的操作 - %s
没有
最小化后台播放
- WebM — 自由视频格式
使用快速粗略定位
粗略定位功能允许播放器以略低的精确度为代价换取更快的定位速度
下载缩略图
@@ -243,8 +241,6 @@
音频
重试
手机存储访问权限被拒绝
- 使用旧的播放器
- 旧的内置 Mediaframework 播放器
千
万
亿
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index ce2943fc4..306fb4ccd 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -120,7 +120,6 @@
mil.
Toto oprávnění je vyžadováno pro
otevření ve vyskakovacím okně
- Použít starý přehrávač
Odstraňuje zvuk v některých rozlišeních
Zobrazovat vyšší rozlišení
Pouze některá zařízení podporují přehrávání 2K/4K videí
@@ -166,7 +165,6 @@ otevření ve vyskakovacím okně
Notifikace pro NewPipe přehrávače v pozadí a v okně
Žádné výsledky
Je tu sranda jak v márnici
- Starý zabudovaný Mediaframework přehrávač
mld.
Žádní odběratelé
@@ -282,7 +280,6 @@ otevření ve vyskakovacím okně
Video přehrávač
Přehrávač na pozadí
Přehrávač v okně
- Vždy se ptát
Získávám informace…
Načítání požadovaného obsahu
Stáhnout soubor streamu
@@ -322,6 +319,7 @@ otevření ve vyskakovacím okně
Menší písmo
Normální písmo
Větší písmo
+
Sledovat únik paměti
Sledování úniku paměti vypnuto
Sledování úniku paměti povoleno, aplikace může při zátěži přestat reagovat
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 4578040d5..621045ed9 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -120,13 +120,11 @@
Alle
Kanal
Deaktiviert
- Alten Player benutzen
Im Pop-up-Modus öffnen
Bevorzugtes Videoformat
Spiele im Pop-up Modus ab
NewPipe-Pop-up-Modus
Diese Berechtigung ist für das Öffnen im Pop-up-Modus erforderlich
- Alter eingebauter Mediaframework-Player
Standardauflösung des Pop-ups
Höhere Auflösungen anzeigen
Nur manche Geräte unterstützen das Abspielen von 2K-/4K-Videos
@@ -270,7 +268,6 @@
Video-Player
Hintergrund-Player
Popup-Player
- Immer fragen
Informationen werden abgerufen…
Gewünschten Inhalt laden
Datenbank importieren
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 2422a493b..68e93ba58 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -29,8 +29,6 @@
Προβολή μιας επιλογής για αναπαραγωγή με το Kodi media center
Ήχος
Προεπιλεγμένη μορφή ήχου
- WebM — δωρεάν μορφή
- Μ4Α — καλύτερη ποιότητα
Θέμα
Σκοτεινό
Φωτεινό
@@ -216,8 +214,6 @@
Σύρετε για ταξινόμηση
Προσπάθεια εκ νέου
Δεν δώθηκε άδεια εγγραφής στην εσωτερική μνήμη
- Χρήση παλαιάς συσκευής αναπαραγωγής
- Παλαιά συσκευή αναπαραγωγής Mediaframework
χιλ
Εκ
Κανένας εγγεγραμένος χρήστης
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 47748195d..e75d905df 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -268,7 +268,6 @@ abrir en modo popup
Reproductor de vídeo
Reproductor de fondo
Reproductor de popup
- Preguntar siempre
Obteniendo información…
Cargando contenido solicitado
Importar base de datos
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index b57fdd306..a11805f3b 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -195,8 +195,6 @@
Audio
Proovi uuesti
Pääsuõigused salvestile puuduvad
- Kasuta vana pleierit
- Vana sisseehitatud mediaframework pleier
K
M
B
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index cd0c618ee..095bbfe5e 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -117,8 +117,6 @@
Audioa
Saiatu berriro
Biltegia atzitzeko baimena ukatu da
- Erabili erreproduzigailu zaharra
- Barne Media Framework erreproduzigailu zaharra
K
M
MM
@@ -270,7 +268,6 @@
Bideo erreproduzigailua
Bigarren planoko erreproduzigailua
Laster-leiho erreproduzigailua
- Galdetu beti
Informazioa eskuratzen…
Kargatzen eskatutako edukia
Deskargatu jario fitxategia
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index f97a85463..e79a37557 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -32,8 +32,6 @@
نمایش گزینهای برای پخش ویدیو با مرکز رسانهٔ کودی
صدا
قالب صدای پیشگزیده
- قالب آزاد — WebM
- کیفیت بهتر — M4A
زمینه
تیره
روشن
@@ -189,7 +187,6 @@
خطایی رخ داد: %1$s
جریانی برای بارگیری در دسترس نیست
بدون نتیجه
- استفاده از پخشکننده قدیمی
K
M
B
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index ead13fb95..2a86445e0 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -148,8 +148,6 @@
Ääni
Toista uudelleen
Oikeus tallennustilan hallintaan evätty
- Käytä vanhaa soitinta
- Käytä vanhaa sisäänrakennettua Mediaframework-soitinta
t.
milj.
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 6d8d03dec..b81195d41 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -119,8 +119,6 @@
Plus tard
Désactivé
Quoi :\\nRequête :\\nLangue du contenu :\\nService :\\nHeure GMT :\\nPaquet :\\nVersion :\\nVersion du système :
- Utiliser l\'ancien lecteur
- Ancienne version du lecteur Mediaframework
K
M
Cette autorisation est nécessaire pour
@@ -270,7 +268,6 @@
Lecteur vidéo
Lecteur en arrière-plan
Lecteur en fenêtré
- Toujours demander
Obtention des infos…
Chargement du contenu
Importer les données
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index f137a092e..68c831b70 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -59,8 +59,6 @@
Audio
Formato de audio predeterminado
Formato de vídeo predeterminado
- WebM — formato libre
- M4A — mellor calidade
Tema
Claro
Escuro
@@ -218,8 +216,6 @@
Audio
Tentar de novo
A permisión de acceso ao almacenamento foi denegada
- Usar o reprodutor antigo
- Versión interna anticuada do reprodutor Mediaframework
K
M
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml
index 155c6a223..0f48379d5 100644
--- a/app/src/main/res/values-he/strings.xml
+++ b/app/src/main/res/values-he/strings.xml
@@ -149,8 +149,6 @@
שמע
ניסיון חוזר
הגישה לאחסון נדחתה
- השתמש בנגן הישן
- השתמש בנגן המובנה Mediaframework
K
M
B
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index ed2815638..c872e41b6 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -80,8 +80,6 @@
कोडी मीडिया सेंटर के माध्यम से वीडियो चलाने के लिए एक विकल्प प्रदर्शित करें
डिफ़ॉल्ट ऑडियो का फॉर्मेट
डिफ़ॉल्ट विडियो का फॉर्मेट
- WebM - libre फॉर्मेट
- M4A - बेहतर क्वालिटी
एप्प का नया रूप
काला
विडियो पॉपअप की आकर और उसकी स्थति को याद रखे
@@ -174,8 +172,6 @@
ऑडियो
फिर से कोशिश करे
स्टोरेज में प्रवेश के लिए अनुमति नहीं मिली
- पुराना विडियो प्लेयर प्रयोग करे
- पुराना Mediaframework Player जो फ़ोन में बना हुआ है
हज़ार
करोड़
अरब
@@ -319,9 +315,6 @@
छोटे फ़ॉंट
सामांय फ़ॉंट
बड़ा फ़ॉंट
- मॉनिटर लीक
- मेमोरी लीक मॉनिटरिंग सक्षम है, हीप डंपिंग के समय ऐप अप्रतिसादी हो सकती है
- मेमोरी लीक मॉनिटरिंग अक्षम है
डीबग करें
ऑटो-जनरेटेड
LeakCanary सक्षम करें
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 6e8595010..e7aeb924a 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -131,8 +131,6 @@
Zvuk
Ponovno pokušaj
Dozvola za pisanje po pohrani je odbijena
- Koristi stari reproduktor
- Stari ugrađeni Mediaframework reproduktor
tis
mil
mlrd
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 24662feed..40593fdbf 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -24,8 +24,6 @@
Opció mutatása a videók Kodi médiaközponttal való lejátszására
Hang
Alapértelmezett hang formátum
- WebM — szabad formátum
- M4A — jobb minőség
Letöltés
Következő
Nem támogatott webcím
@@ -193,7 +191,6 @@
Mi:\\nKérés:\\nTartalom nyelve:\\nSzolgáltatás:\\nGMT Idő:\\nCsomag:\\nVerzió:\\nOperációs Rendszer verzió:
Nincs találat
- Régi lejátszó használata
Adatfolyam fájl letöltése
Hozzáadás
@@ -236,8 +233,6 @@
Itt nincs semmi
Húzza az átrendezéshez
- Régi beépített Mediaframework lejátszó
-
e
M
Mrd
diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml
index 66b901ac9..56f31eac6 100644
--- a/app/src/main/res/values-id/strings.xml
+++ b/app/src/main/res/values-id/strings.xml
@@ -29,8 +29,6 @@
Tampilkan opsi untuk memutar video via Kodi
Audio
Format audio
- WebM — format bebas
- M4A — kualitas lebih baik
Gelap
Terang
Unduh
@@ -124,8 +122,6 @@
\nmembuka di mode popup
Mode popup NewPipe
Memutar dalam mode popup
- Gunakan pemutar lama
- Pemutar Mediaframework bawaan versi lama
Dinonaktifkan
Format video
Resolusi popup
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index b4bcd2926..9bccc11df 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -122,7 +122,6 @@
Modalità Popup di NewPipe
Riproduzione in modalità popup
Disattivato
- Usa il vecchio lettore multimediale
Non riproduce l\'audio con ALCUNE risoluzioni
In sottofondo
Popup
@@ -142,8 +141,7 @@
Cancella
Ridimensionamento
Risoluzione migliore
- Precedente lettore multimediale Mediaframework integrato
- Questo permesso è necessario
+ Questo permesso è necessario
\nper riprodurre in modalità popup
Impostazioni
Informazioni
@@ -270,7 +268,6 @@
Lettore video
Riproduzione in sottofondo
Riproduzione in modalità popup
- Chiedi sempre
Raccogliendo informazioni…
Caricamento del contenuto richiesto
Importa database
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index e1e76475e..38acee61d 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -123,8 +123,6 @@
このアクセス許可が必要です
NewPipe ポップアップモード
ポップアップモードで再生中
- 古いプレーヤーを使用する
- 古い内蔵のMediaframeworkプレーヤー
無効
デフォルトの動画形式
デフォルトのポップアップ解像度
@@ -253,7 +251,6 @@
動画プレーヤー
バックグラウンドプレーヤー
ポップアッププレーヤー
- 常に尋ねる
情報を取得しています…
コンテンツを読み込んでいます
動画ファイルをダウンロード
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index bcaafbcfc..a3f25b033 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -173,9 +173,6 @@
결과 없음
구독할 항목을 추가하세요
- 구형 플레이어 사용
- 내장된 구형 Mediaframework 플레이어 사용
-
천
백만
10억
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index 4ffcd26d6..4b21a57cd 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -120,8 +120,6 @@
Vaizdas
Muzika
Bandyti iš naujo
- Naudoti seną grotuvą
- Senas įtaisytas media grotuvas
- %s prenumeratorius
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index 47413c6c3..fc941d0db 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -202,8 +202,6 @@
Звук
Пробај повторно
Нема привилегии за пристап
- Користи го стариот плеер
- Користи го стариот Mediaframework плеер
K
M
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index baaa48ca8..a806df31a 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -141,8 +141,6 @@
Senere
Avskrudd
- Bruk gammel avspiller
-
K
M
@@ -153,8 +151,7 @@
reCAPTCHA-oppgave forespurt
- Gammel innebygd Mediaframework-avspiller
-Fjerner lyd ved NOEN oppløsninger
+ Fjerner lyd ved NOEN oppløsninger
Abonner
Abonnert
Kanalabonnent oppsagt
@@ -315,8 +312,7 @@
Videoavspiller
Bakgrunnsavspiller
Oppsprettsavspiller
- Spør alltid
-
+
Henter informasjon…
Laster forespurt innhold
Importer database
diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml
index 5fb175b3e..51a30aa36 100644
--- a/app/src/main/res/values-nl-rBE/strings.xml
+++ b/app/src/main/res/values-nl-rBE/strings.xml
@@ -189,8 +189,6 @@
Geluid
Opnieuw proberen
Toegang tot opslag geweigerd
- Gebruik oude speler
- Verouderden ingebouwde Mediaframework-speler
K
M
B
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 3bf219912..04b47c4c8 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -123,8 +123,6 @@
\nopenen in pop-upmodus
NewPipe-pop-upmodus
Speelt af in pop-upmodus
- Oude speler gebruiken
- Verouderde ingebouwde Mediaframework-speler
Standaard videoformaat
Uitgeschakeld
Standaardresolutie van pop-up
@@ -270,7 +268,6 @@
Videospeler
Achtergrondspeler
Pop-upspeler
- Altijd vragen
Bezig met ophalen van informatie…
Bezig met laden van gevraagde inhoud
Databank importeren
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index cc88743ff..d4bf5d803 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -470,5 +470,6 @@
Не удалось соединиться с сервером
Не удалось получить данные с сервера
Пост-обработка не удалась
- Приостановить в мобильной сети
+ Останавливать скачивание при переходе на мобильную сеть
+ Закрыть
\ No newline at end of file
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index c33bfde1b..de26a1638 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -125,8 +125,6 @@
Preferovaný formát videa
Prehrávanie v mini okne
Vypnuté
- Použiť starší prehrávač
- Starý zabudovaný prehrávač Mediaframework
Predvolená veľkosť mini okna
Zobraziť vyššie rozlíšenie
Len niektoré zariadenia podporujú videá 2K/4K
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index ec2a0bea5..4f604ae5b 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -153,8 +153,6 @@ odpiranje v pojavnem načinu
Predvajanje v pojavnem načinu
Onemogočeno
- Uporabi star predvajalnik
- Zastarela različica predvajalnika Mediaframework
Prednostni zapis video datoteke
Privzeta ločljivost pojavnega okna
Pokaži večje ločljivosti
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 5182144e0..a34cc9e11 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -124,7 +124,6 @@
Режим прозорчета
Пуштање у режиму прозорчета
Искључено
- Користи стари плејер
Пожељни формат видеа
Резолуција искачућег прозора
Прикажи више резолуције
@@ -143,7 +142,6 @@
Приказује предлоге током претраге
Искачући прозор
Мењање величине
- Стара градња Медијафрејмворк плејера
Претплаћен
Претплати
Главно
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
index 28c84fc9e..7aa021182 100644
--- a/app/src/main/res/values-ta/strings.xml
+++ b/app/src/main/res/values-ta/strings.xml
@@ -51,7 +51,6 @@
ஒலி
முதல் ஒலி வடிவம்
முதல் காணிலி வடிவம்
- M4A - மேம்பட்ட தரம்
வார்ப்புரு
வெளிர்
அடர்
diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml
index 5915aee7a..ff9969abc 100644
--- a/app/src/main/res/values-te/strings.xml
+++ b/app/src/main/res/values-te/strings.xml
@@ -102,7 +102,6 @@
ఆడియో
మళ్ళీ ప్రయత్నించు
నిల్వ అనుమతి తిరస్కరించబడింది
- పాత ఆటగాడు ఉపయోగించండి
కె
ఎం
బి
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index b7c4b0156..5209fe250 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -120,8 +120,6 @@
Yorumunuz (İngilizce):
Ayrıntılar:
Video ön izleme küçük resmi
- Eski oynatıcıyı kullan
- Eski içe gömülü Mediaframework oynatıcısı
B
M
MR
@@ -270,7 +268,6 @@
Video oynatıcı
Arka plan oynatıcı
Açılır oynatıcı
- Her zaman sor
Bilgi alınıyor…
İstenen içerik yükleniyor
Veri tabanını içe aktar
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index ffc996f49..6eca64d34 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -90,7 +90,6 @@
Відео
Аудіо
Повторити спробу
- Використовувати старий програвач
тис.
млн.
млрд.
@@ -245,7 +244,7 @@
Вільне та легке потокове програвання на Android.
Які б не були Ваші ідеї: переклад, дизайн, легкий чи глобальний рефакторинг - будь-яка допомога завжди у нагоді. Що більше зроблено, то ліпшим стає NewPipe!
Налагодження
- Нічого немає... чути лише цвіркунів
+ Нічого немає… чути лише цвіркунів
Немає переглядів
- %s перегляд
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index af23f383c..b1899622f 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -113,8 +113,6 @@
Âm thanh
Thử lại
Quyền truy cập bộ nhớ đã bị từ chối
- Sử dụng trình phát cũ
- Máy nghe nhạc Mediaframework tích hợp sẵn
ngàn
triệu
tỉ
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 55cfad9ef..8d5701a4f 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -175,8 +175,6 @@
NewPipe 通知
NewPipe 后台播放和窗口播放器的通知
- 使用旧版播放器
- 旧版本的内置媒体播放器
K
M
B
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index d1b5fde31..3b38daca6 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -119,7 +119,6 @@
無法取得圖片
應用程式或介面出現問題
事件:\\n請求:\\n內容語言:\\n服務:\\nGMT 時間:\\nPackage:\\n版本:\\n作業系統版本:
- 使用舊播放器
K
M
reCAPTCHA
@@ -142,7 +141,6 @@
清除
最佳解像度
調整大小
- 使用舊的內置 Mediaframework 播放器
B
關於 NewPipe
設定
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 655cd0dd6..441061657 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -115,8 +115,6 @@
音訊
重試
無法存取儲存空間
- 使用舊式播放器
- 舊型內建 Mediaframework 播放器
千
百萬
十億
@@ -275,7 +273,6 @@
影片播放器
背景播放器
懸浮視窗播放器
- 總是詢問
正在取得資訊…
正在載入要求的內容
下載串流檔案
@@ -315,9 +312,6 @@
正常字體
加大字體
某些東西即將在此出現 ;D
- 監測流失
- 啟用記憶體流失監測技術,當 heap dump 時,應用程式可能會反應遲鈍
- 已停用記憶體流失監測
除錯
自動產生
啟用 LeakCanary
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 229c00533..94101cfb0 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -85,4 +85,8 @@
14sp
+
+
+ 12sp
+ 12sp
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index fcc09872e..3861f53d5 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -138,6 +138,8 @@
show_search_suggestions
show_play_with_kodi
show_next_video
+ show_comments
+ stream_info_selected_tab
show_hold_to_append
en
GB
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2d12f6ad6..90554a04f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -76,6 +76,8 @@
Use fast inexact seek
Inexact seek allows the player to seek to positions faster with reduced precision
Load thumbnails
+ Show comments
+ Disable to stop showing comments
Turn off to prevent loading thumbnails, saving data and memory usage. Changes clear both in-memory and on-disk image cache.
Image cache wiped
Wipe cached metadata
@@ -98,7 +100,8 @@
Resume on focus gain
Continue playing after interruptions (e.g. phone calls)
Download
- Next
+ Up next
+ Autoplay
Show \'Next\' and \'Similar\' videos
Show \"Hold to append\" tip
Show tip when background or popup button is pressed on video details page
@@ -135,6 +138,7 @@
Playlist
Playlists
Videos
+ Comments
Tracks
Users
Events
@@ -238,6 +242,7 @@
User report
No results
@string/no_videos
+ @string/no_comments
Nothing here but crickets
Drag to reorder
@@ -273,6 +278,12 @@
- Videos
+ No comments
+
+ - %s comment
+ - %s comments
+
+
Start
Pause
@@ -391,6 +402,7 @@
Warning: Could not import all files.
This will override your current setup.
Do you want to also import settings?
+ Could not load comments
Kiosk
@@ -407,11 +419,12 @@
Details
Audio Settings
Hold To enqueue
- Enqueue when backgrounded
- Enqueue on new popup
+ Play directly in background
+ Enqueue in the background
+ Enqueue in a new popup
Start playing here
- Start here when backgrounded
- Start here on new popup
+ Start playing in the background
+ Start playing in a new popup
Open Drawer
@@ -588,5 +601,6 @@
Maximum number of attempts before canceling the download
Pause on switching to mobile data
Downloads that can not be paused will be restarted
+ Close
\ No newline at end of file
diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml
index 0254514ba..1a1a39e21 100644
--- a/app/src/main/res/xml/content_settings.xml
+++ b/app/src/main/res/xml/content_settings.xml
@@ -41,6 +41,13 @@
android:title="@string/download_thumbnail_title"
android:summary="@string/download_thumbnail_summary"/>
+
+
Improvements
+
+- make links in comments clickable, increase text size
+- seek on clicking timestamp links in comments
+- show preferred tab based on recently selected state
+- add playlist to queue when long clicking on 'Background' in playlist window
+- search for shared text when it is not an URL
+- add "share at current time" button to the main video player
+- add close button to main player when video queue is finished
+- add "Play directly in Background" to longpress menu for video list items
+- improve English translations for Play/Enqueue commands
+- small performance improvements
+- remove unused files
+- update ExoPlayer to 2.9.6
+- add support for Invidious links
+
+Fixed
+
+- fixed scroll w/ comments and related streams disabled
+- fixed CheckForNewAppVersionTask being executed when it shouldn't
+- fixed youtube subscription import: ignore ones with invalid url and keep ones with empty title
+- fix invalid YouTube url: signature tag name is not always "signature" preventing streams from loading
+