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

Merge branch 'master' into dev

This commit is contained in:
Tobias Groza 2019-05-31 23:46:28 +02:00
commit 281cae7a18
121 changed files with 2606 additions and 1014 deletions

View File

@ -16,7 +16,9 @@
<p align="center"><a href="https://newpipe.schabi.org">Website</a> &bull; <a href="https://newpipe.schabi.org/blog/">Blog</a> &bull; <a href="https://newpipe.schabi.org/press/">Press</a></p>
<hr>
<b>WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.</b>
<b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>
<b>PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.</b>
## Screenshots

View File

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

View File

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

View File

@ -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<Void, Void, String> {
@Override
protected String doInBackground(Void... voids) {
if(isCancelled() || !isConnected()) return null;
// Make a network request to get latest NewPipe data.
if (client == null) {
@ -227,4 +231,12 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
}
private boolean isConnected() {
ConnectivityManager cm =
(ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isConnected();
}
}

View File

@ -3,18 +3,24 @@ package org.schabi.newpipe;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.schabi.newpipe.extractor.DownloadRequest;
import org.schabi.newpipe.extractor.DownloadResponse;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.utils.Localization;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
@ -139,13 +145,16 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
private ResponseBody getBody(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException {
final Request.Builder requestBuilder = new Request.Builder()
.method("GET", null).url(siteUrl)
.addHeader("User-Agent", USER_AGENT);
.method("GET", null).url(siteUrl);
for (Map.Entry<String, String> header : customProperties.entrySet()) {
requestBuilder.addHeader(header.getKey(), header.getValue());
}
if (!customProperties.containsKey("User-Agent")) {
requestBuilder.header("User-Agent", USER_AGENT);
}
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
}
@ -177,4 +186,96 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
public String download(String siteUrl) throws IOException, ReCaptchaException {
return download(siteUrl, Collections.emptyMap());
}
}
@Override
public DownloadResponse get(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
final Request.Builder requestBuilder = new Request.Builder()
.method("GET", null).url(siteUrl);
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
// set custom headers in request
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
for(String value : pair.getValue()){
requestBuilder.addHeader(pair.getKey(), value);
}
}
if (!requestHeaders.containsKey("User-Agent")) {
requestBuilder.header("User-Agent", USER_AGENT);
}
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
}
final Request okRequest = requestBuilder.build();
final Response response = client.newCall(okRequest).execute();
final ResponseBody body = response.body();
if (response.code() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested");
}
if (body == null) {
response.close();
return null;
}
return new DownloadResponse(body.string(), response.headers().toMultimap());
}
@Override
public DownloadResponse get(String siteUrl) throws IOException, ReCaptchaException {
return get(siteUrl, DownloadRequest.emptyRequest);
}
@Override
public DownloadResponse post(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
if(null == requestHeaders.get("Content-Type") || requestHeaders.get("Content-Type").isEmpty()){
// content type header is required. maybe throw an exception here
return null;
}
String contentType = requestHeaders.get("Content-Type").get(0);
RequestBody okRequestBody = null;
if(null != request.getRequestBody()){
okRequestBody = RequestBody.create(MediaType.parse(contentType), request.getRequestBody());
}
final Request.Builder requestBuilder = new Request.Builder()
.method("POST", okRequestBody).url(siteUrl);
// set custom headers in request
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
for(String value : pair.getValue()){
requestBuilder.addHeader(pair.getKey(), value);
}
}
if (!requestHeaders.containsKey("User-Agent")) {
requestBuilder.header("User-Agent", USER_AGENT);
}
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
}
final Request okRequest = requestBuilder.build();
final Response response = client.newCall(okRequest).execute();
final ResponseBody body = response.body();
if (response.code() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested");
}
if (body == null) {
response.close();
return null;
}
return new DownloadResponse(body.string(), response.headers().toMultimap());
}
}

View File

@ -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<String> validChoicesList = Arrays.asList(getResources().getStringArray(R.array.preferred_open_action_values_list));
if (validChoicesList.contains(selectedChoiceKey)) {
@ -383,8 +397,10 @@ public class RouterActivity extends AppCompatActivity {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
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();

View File

@ -47,7 +47,7 @@ public class DownloadActivity extends AppCompatActivity {
@Override
public void onGlobalLayout() {
updateFragments();
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}

View File

@ -230,21 +230,4 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected void openUrlInBrowser(String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
}
protected void shareUrl(String subject, String url) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, url);
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
}
}

View File

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

View File

@ -0,0 +1,86 @@
package org.schabi.newpipe.fragments.detail;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
public class TabAdaptor extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
private final FragmentManager fragmentManager;
public TabAdaptor(FragmentManager fm) {
super(fm);
this.fragmentManager = fm;
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
public void clearAllItems() {
mFragmentList.clear();
mFragmentTitleList.clear();
}
public void removeItem(int position){
mFragmentList.remove(position == 0 ? 0 : position - 1);
mFragmentTitleList.remove(position == 0 ? 0 : position - 1);
}
public void updateItem(int position, Fragment fragment){
mFragmentList.set(position, fragment);
}
public void updateItem(String title, Fragment fragment){
int index = mFragmentTitleList.indexOf(title);
if(index != -1){
updateItem(index, fragment);
}
}
@Override
public int getItemPosition(Object object) {
if (mFragmentList.contains(object)) return mFragmentList.indexOf(object);
else return POSITION_NONE;
}
public int getItemPositionByTitle(String title) {
return mFragmentTitleList.indexOf(title);
}
@Nullable
public String getItemTitle(int position) {
if (position < 0 || position >= mFragmentTitleList.size()) {
return null;
}
return mFragmentTitleList.get(position);
}
public void notifyDataSetUpdate(){
notifyDataSetChanged();
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
}
}

View File

@ -10,11 +10,13 @@ import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
@ -25,7 +27,6 @@ import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -33,19 +34,15 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.nirhart.parallaxscroll.views.ParallaxScrollView;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
@ -57,6 +54,7 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.stream.AudioStream;
@ -64,21 +62,22 @@ import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.fragments.EmptyFragment;
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
@ -86,11 +85,10 @@ import org.schabi.newpipe.util.InfoCache;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
import java.util.Collection;
@ -105,6 +103,7 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class VideoDetailFragment
@ -115,27 +114,28 @@ public class VideoDetailFragment
View.OnLongClickListener {
public static final String AUTO_PLAY = "auto_play";
// Amount of videos to show on start
private static final int INITIAL_RELATED_VIDEOS = 8;
private InfoItemBuilder infoItemBuilder = null;
private int updateFlags = 0;
private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1;
private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2;
private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4;
private static final int COMMENTS_UPDATE_FLAG = 0x4;
private boolean autoPlayEnabled;
private boolean showRelatedStreams;
private boolean wasRelatedStreamsExpanded = false;
private boolean showComments;
private String selectedTabTag;
@State protected int serviceId = Constants.NO_SERVICE_ID;
@State protected String name;
@State protected String url;
@State
protected int serviceId = Constants.NO_SERVICE_ID;
@State
protected String name;
@State
protected String url;
private StreamInfo currentInfo;
private Disposable currentWorker;
@NonNull private CompositeDisposable disposables = new CompositeDisposable();
@NonNull
private CompositeDisposable disposables = new CompositeDisposable();
private List<VideoStream> sortedVideoStreams;
private int selectedVideoStreamIndex = -1;
@ -148,7 +148,6 @@ public class VideoDetailFragment
private Spinner spinnerToolbar;
private ParallaxScrollView parallaxScrollRootView;
private LinearLayout contentRootLayoutHiding;
private View thumbnailBackgroundButton;
@ -157,7 +156,6 @@ public class VideoDetailFragment
private View videoTitleRoot;
private TextView videoTitleTextView;
@Nullable
private ImageView videoTitleToggleArrow;
private TextView videoCountView;
@ -182,10 +180,15 @@ public class VideoDetailFragment
private ImageView thumbsDownImageView;
private TextView thumbsDisabledTextView;
private TextView nextStreamTitle;
private LinearLayout relatedStreamRootLayout;
private LinearLayout relatedStreamsView;
private ImageButton relatedStreamExpandButton;
private static final String COMMENTS_TAB_TAG = "COMMENTS";
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
private AppBarLayout appBarLayout;
private ViewPager viewPager;
private TabAdaptor pageAdapter;
private TabLayout tabLayout;
private FrameLayout relatedStreamsLayout;
/*////////////////////////////////////////////////////////////////////////*/
@ -201,12 +204,20 @@ public class VideoDetailFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(Bundle savedInstanceState) {
public void
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
showRelatedStreams = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(getString(R.string.show_next_video_key), true);
showComments = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(getString(R.string.show_comments_key), true);
selectedTabTag = PreferenceManager.getDefaultSharedPreferences(activity)
.getString(getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG);
PreferenceManager.getDefaultSharedPreferences(activity)
.registerOnSharedPreferenceChangeListener(this);
}
@ -220,6 +231,10 @@ public class VideoDetailFragment
public void onPause() {
super.onPause();
if (currentWorker != null) currentWorker.dispose();
PreferenceManager.getDefaultSharedPreferences(getContext())
.edit()
.putString(getString(R.string.stream_info_selected_tab_key), pageAdapter.getItemTitle(viewPager.getCurrentItem()))
.apply();
}
@Override
@ -228,14 +243,16 @@ public class VideoDetailFragment
if (updateFlags != 0) {
if (!isLoading.get() && currentInfo != null) {
if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) initRelatedVideos(currentInfo);
if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) startLoading(false);
if ((updateFlags & RESOLUTIONS_MENU_UPDATE_FLAG) != 0) setupActionBar(currentInfo);
if ((updateFlags & COMMENTS_UPDATE_FLAG) != 0) startLoading(false);
}
if ((updateFlags & TOOLBAR_ITEMS_UPDATE_FLAG) != 0
&& menu != null) {
updateMenuItemVisibility();
}
updateFlags = 0;
}
@ -292,6 +309,9 @@ public class VideoDetailFragment
updateFlags |= RESOLUTIONS_MENU_UPDATE_FLAG;
} else if (key.equals(getString(R.string.show_play_with_kodi_key))) {
updateFlags |= TOOLBAR_ITEMS_UPDATE_FLAG;
} else if (key.equals(getString(R.string.show_comments_key))) {
showComments = sharedPreferences.getBoolean(key, true);
updateFlags |= COMMENTS_UPDATE_FLAG;
}
}
@ -301,7 +321,6 @@ public class VideoDetailFragment
private static final String INFO_KEY = "info_key";
private static final String STACK_KEY = "stack_key";
private static final String WAS_RELATED_EXPANDED_KEY = "was_related_expanded_key";
@Override
public void onSaveInstanceState(Bundle outState) {
@ -310,10 +329,6 @@ public class VideoDetailFragment
// Check if the next video label and video is visible,
// if it is, include the two elements in the next check
int nextCount = currentInfo != null && currentInfo.getNextVideo() != null ? 2 : 0;
if (relatedStreamsView != null
&& relatedStreamsView.getChildCount() > INITIAL_RELATED_VIDEOS + nextCount) {
outState.putSerializable(WAS_RELATED_EXPANDED_KEY, true);
}
if (!isLoading.get() && currentInfo != null && isVisible()) {
outState.putSerializable(INFO_KEY, currentInfo);
@ -326,12 +341,11 @@ public class VideoDetailFragment
protected void onRestoreInstanceState(@NonNull Bundle savedState) {
super.onRestoreInstanceState(savedState);
wasRelatedStreamsExpanded = savedState.getBoolean(WAS_RELATED_EXPANDED_KEY, false);
Serializable serializable = savedState.getSerializable(INFO_KEY);
if (serializable instanceof StreamInfo) {
//noinspection unchecked
currentInfo = (StreamInfo) serializable;
InfoCache.getInstance().putInfo(serviceId, url, currentInfo);
InfoCache.getInstance().putInfo(serviceId, url, currentInfo, InfoItem.InfoType.STREAM);
}
serializable = savedState.getSerializable(STACK_KEY);
@ -339,6 +353,7 @@ public class VideoDetailFragment
//noinspection unchecked
stack.addAll((Collection<? extends StackItem>) serializable);
}
}
/*//////////////////////////////////////////////////////////////////////////
@ -394,9 +409,6 @@ public class VideoDetailFragment
case R.id.detail_title_root_layout:
toggleTitleAndDescription();
break;
case R.id.detail_related_streams_expand:
toggleExpandRelatedVideos(currentInfo);
break;
}
}
@ -420,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<StreamInfoItem>() {
@Override
public void selected(StreamInfoItem selectedItem) {
selectAndLoadVideo(selectedItem.getServiceId(),
selectedItem.getUrl(),
selectedItem.getName());
}
@Override
public void held(StreamInfoItem selectedItem) {
showStreamDialog(selectedItem);
}
});
videoTitleRoot.setOnClickListener(this);
uploaderRootLayout.setOnClickListener(this);
@ -539,7 +513,6 @@ public class VideoDetailFragment
detailControlsAddToPlaylist.setOnClickListener(this);
detailControlsDownload.setOnClickListener(this);
detailControlsDownload.setOnLongClickListener(this);
relatedStreamExpandButton.setOnClickListener(this);
detailControlsBackground.setLongClickable(true);
detailControlsPopup.setLongClickable(true);
@ -575,7 +548,7 @@ public class VideoDetailFragment
}
break;
case 3:
shareUrl(item.getName(), item.getUrl());
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
break;
default:
break;
@ -622,44 +595,6 @@ public class VideoDetailFragment
}
}
private void initRelatedVideos(StreamInfo info) {
if (relatedStreamsView.getChildCount() > 0) relatedStreamsView.removeAllViews();
if (info.getNextVideo() != null && showRelatedStreams) {
nextStreamTitle.setVisibility(View.VISIBLE);
relatedStreamsView.addView(
infoItemBuilder.buildView(relatedStreamsView, info.getNextVideo()));
relatedStreamsView.addView(getSeparatorView());
setRelatedStreamsVisibility(View.VISIBLE);
} else {
nextStreamTitle.setVisibility(View.GONE);
setRelatedStreamsVisibility(View.GONE);
}
if (info.getRelatedStreams() != null
&& !info.getRelatedStreams().isEmpty() && showRelatedStreams) {
//long first = System.nanoTime(), each;
int to = info.getRelatedStreams().size() >= INITIAL_RELATED_VIDEOS
? INITIAL_RELATED_VIDEOS
: info.getRelatedStreams().size();
for (int i = 0; i < to; i++) {
InfoItem item = info.getRelatedStreams().get(i);
//each = System.nanoTime();
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
//if (DEBUG) Log.d(TAG, "each took " + ((System.nanoTime() - each) / 1000000L) + "ms");
}
//if (DEBUG) Log.d(TAG, "Total time " + ((System.nanoTime() - first) / 1000000L) + "ms");
setRelatedStreamsVisibility(View.VISIBLE);
relatedStreamExpandButton.setVisibility(View.VISIBLE);
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(
activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand)));
} else {
if (info.getNextVideo() == null) setRelatedStreamsVisibility(View.GONE);
relatedStreamExpandButton.setVisibility(View.GONE);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
@ -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);
}
}
}

View File

@ -22,6 +22,7 @@ import android.view.View;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.BaseStateFragment;
@ -33,6 +34,7 @@ import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StateSaver;
import java.util.Collections;
@ -220,6 +222,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
});
infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() {
@Override
public void selected(CommentsInfoItem selectedItem) {
onItemSelected(selectedItem);
}
});
itemsList.clearOnScrollListeners();
itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
@Override
@ -247,6 +256,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
if (context == null || context.getResources() == null || getActivity() == null) return;
final String[] commands = new String[]{
context.getResources().getString(R.string.direct_on_background),
context.getResources().getString(R.string.enqueue_on_background),
context.getResources().getString(R.string.enqueue_on_popup),
context.getResources().getString(R.string.append_playlist),
@ -256,19 +266,22 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
NavigationHelper.playOnBackgroundPlayer(context, new SinglePlayQueue(item));
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
break;
case 2:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
break;
case 3:
if (getFragmentManager() != null) {
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
.show(getFragmentManager(), TAG);
}
break;
case 3:
shareUrl(item.getName(), item.getUrl());
case 4:
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
break;
default:
break;

View File

@ -46,6 +46,7 @@ import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
import java.util.ArrayList;
import java.util.Collections;
@ -190,7 +191,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
}
break;
case 6:
shareUrl(item.getName(), item.getUrl());
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
break;
default:
break;
@ -233,10 +234,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
openRssFeed();
break;
case R.id.menu_item_openInBrowser:
openUrlInBrowser(currentInfo.getOriginalUrl());
ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl());
break;
case R.id.menu_item_share:
shareUrl(name, currentInfo.getOriginalUrl());
ShareUtils.shareUrl(this.getContext(), name, currentInfo.getOriginalUrl());
break;
default:
return super.onOptionsItemSelected(item);

View File

@ -0,0 +1,149 @@
package org.schabi.newpipe.fragments.list.comments;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.comments.CommentsInfo;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
private CompositeDisposable disposables = new CompositeDisposable();
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private boolean mIsVisibleToUser = false;
public static CommentsFragment getInstance(int serviceId, String url, String name) {
CommentsFragment instance = new CommentsFragment();
instance.setInitialData(serviceId, url, name);
return instance;
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_comments, container, false);
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.clear();
}
/*//////////////////////////////////////////////////////////////////////////
// Load and handle
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
}
@Override
protected Single<CommentsInfo> loadResult(boolean forceLoad) {
return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad);
}
/*//////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@Override
public void showLoading() {
super.showLoading();
}
@Override
public void handleResult(@NonNull CommentsInfo result) {
super.handleResult(result);
AnimationUtils.slideUp(getView(),120, 96, 0.06f);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
}
if (disposables != null) disposables.clear();
}
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(),
UserAction.REQUESTED_COMMENTS,
NewPipe.getNameOfService(serviceId),
"Get next page of: " + url,
R.string.general_error);
}
}
/*//////////////////////////////////////////////////////////////////////////
// OnError
//////////////////////////////////////////////////////////////////////////*/
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
hideLoading();
showSnackBarError(exception, UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(serviceId), url, R.string.error_unable_to_load_comments);
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setTitle(String title) {
return;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
return;
}
@Override
protected boolean isGridLayout() {
return false;
}
}

View File

@ -40,6 +40,7 @@ import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
@ -168,7 +169,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
break;
case 5:
shareUrl(item.getName(), item.getUrl());
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
break;
default:
break;
@ -230,10 +231,10 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_openInBrowser:
openUrlInBrowser(url);
ShareUtils.openUrlInBrowser(this.getContext(), url);
break;
case R.id.menu_item_share:
shareUrl(name, url);
ShareUtils.shareUrl(this.getContext(), name, url);
break;
case R.id.menu_item_bookmark:
onBookmarkClicked();
@ -305,6 +306,16 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
headerBackgroundButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
headerPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue());
return true;
});
headerBackgroundButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue());
return true;
});
}
private PlayQueue getPlayQueue() {

View File

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

View File

@ -75,7 +75,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
});
}
private SuggestionItem getItem(int position) {
SuggestionItem getItem(int position) {
return items.get(position);
}

View File

@ -0,0 +1,208 @@
package org.schabi.newpipe.fragments.list.videos;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Switch;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.RelatedStreamInfo;
import java.io.Serializable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo> implements SharedPreferences.OnSharedPreferenceChangeListener{
private CompositeDisposable disposables = new CompositeDisposable();
private RelatedStreamInfo relatedStreamInfo;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private View headerRootLayout;
private Switch aSwitch;
private boolean mIsVisibleToUser = false;
public static RelatedVideosFragment getInstance(StreamInfo info) {
RelatedVideosFragment instance = new RelatedVideosFragment();
instance.setInitialData(info);
return instance;
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_related_streams, container, false);
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.clear();
}
protected View getListHeader(){
if(relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null){
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.related_streams_header, itemsList, false);
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
aSwitch.setChecked(autoplay);
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
prefEdit.putBoolean(getString(R.string.auto_queue_key), b);
prefEdit.apply();
}
});
return headerRootLayout;
}else{
return null;
}
}
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
}
@Override
protected Single<RelatedStreamInfo> loadResult(boolean forceLoad) {
return Single.fromCallable(() -> relatedStreamInfo);
}
/*//////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@Override
public void showLoading() {
super.showLoading();
if(null != headerRootLayout) headerRootLayout.setVisibility(View.INVISIBLE);
}
@Override
public void handleResult(@NonNull RelatedStreamInfo result) {
super.handleResult(result);
if(null != headerRootLayout) headerRootLayout.setVisibility(View.VISIBLE);
AnimationUtils.slideUp(getView(),120, 96, 0.06f);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
}
if (disposables != null) disposables.clear();
}
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(),
UserAction.REQUESTED_STREAM,
NewPipe.getNameOfService(serviceId),
"Get next page of: " + url,
R.string.general_error);
}
}
/*//////////////////////////////////////////////////////////////////////////
// OnError
//////////////////////////////////////////////////////////////////////////*/
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
hideLoading();
showSnackBarError(exception, UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(serviceId), url, R.string.general_error);
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setTitle(String title) {
return;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
return;
}
private void setInitialData(StreamInfo info) {
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
if(this.relatedStreamInfo == null) this.relatedStreamInfo = RelatedStreamInfo.getInfo(info);
}
private static final String INFO_KEY = "related_info_key";
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, relatedStreamInfo);
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedState) {
super.onRestoreInstanceState(savedState);
if (savedState != null) {
Serializable serializable = savedState.getSerializable(INFO_KEY);
if(serializable instanceof RelatedStreamInfo){
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
}
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
if(null != aSwitch) aSwitch.setChecked(autoplay);
}
@Override
protected boolean isGridLayout() {
return false;
}
}

View File

@ -10,10 +10,13 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
@ -50,6 +53,7 @@ public class InfoItemBuilder {
private OnClickGesture<StreamInfoItem> onStreamSelectedListener;
private OnClickGesture<ChannelInfoItem> onChannelSelectedListener;
private OnClickGesture<PlaylistInfoItem> onPlaylistSelectedListener;
private OnClickGesture<CommentsInfoItem> onCommentsSelectedListener;
public InfoItemBuilder(Context context) {
this.context = context;
@ -73,6 +77,8 @@ public class InfoItemBuilder {
return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent) : new ChannelInfoItemHolder(this, parent);
case PLAYLIST:
return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent) : new PlaylistInfoItemHolder(this, parent);
case COMMENT:
return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) : new CommentsInfoItemHolder(this, parent);
default:
Log.e(TAG, "Trollolo");
throw new RuntimeException("InfoType not expected = " + infoType.name());
@ -111,4 +117,12 @@ public class InfoItemBuilder {
this.onPlaylistSelectedListener = listener;
}
public OnClickGesture<CommentsInfoItem> getOnCommentsSelectedListener() {
return onCommentsSelectedListener;
}
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> onCommentsSelectedListener) {
this.onCommentsSelectedListener = onCommentsSelectedListener;
}
}

View File

@ -9,10 +9,13 @@ import android.view.ViewGroup;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder;
@ -63,6 +66,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300;
private static final int PLAYLIST_HOLDER_TYPE = 0x301;
private static final int GRID_PLAYLIST_HOLDER_TYPE = 0x302;
private static final int MINI_COMMENT_HOLDER_TYPE = 0x400;
private static final int COMMENT_HOLDER_TYPE = 0x401;
private final InfoItemBuilder infoItemBuilder;
private final ArrayList<InfoItem> infoItemList;
@ -98,6 +103,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
infoItemBuilder.setOnPlaylistSelectedListener(listener);
}
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> listener) {
infoItemBuilder.setOnCommentsSelectedListener(listener);
}
public void useMiniItemVariants(boolean useMiniVariant) {
this.useMiniVariant = useMiniVariant;
}
@ -223,6 +232,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
case PLAYLIST:
return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
case COMMENT:
return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE;
default:
Log.e(TAG, "Trollolo");
return -1;
@ -231,7 +242,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
if (DEBUG)
Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
switch (type) {
case HEADER_TYPE:
return new HFHolder(header);
@ -255,6 +267,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
return new PlaylistInfoItemHolder(infoItemBuilder, parent);
case GRID_PLAYLIST_HOLDER_TYPE:
return new PlaylistGridInfoItemHolder(infoItemBuilder, parent);
case MINI_COMMENT_HOLDER_TYPE:
return new CommentsMiniInfoItemHolder(infoItemBuilder, parent);
case COMMENT_HOLDER_TYPE:
return new CommentsInfoItemHolder(infoItemBuilder, parent);
default:
Log.e(TAG, "Trollolo");
return new FallbackViewHolder(new View(parent.getContext()));

View File

@ -0,0 +1,53 @@
package org.schabi.newpipe.info_list.holder;
import android.view.ViewGroup;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.util.Localization;
/*
* Created by Christian Schabesberger on 12.02.17.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* ChannelInfoItemHolder .java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
public final TextView itemTitleView;
public CommentsInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_comments_item, parent);
itemTitleView = itemView.findViewById(R.id.itemTitleView);
}
@Override
public void updateFromItem(final InfoItem infoItem) {
super.updateFromItem(infoItem);
if (!(infoItem instanceof CommentsInfoItem)) return;
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
itemTitleView.setText(item.getAuthorName());
}
}

View File

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

View File

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

View File

@ -34,6 +34,7 @@ import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.ShareUtils;
import java.util.ArrayList;
import java.util.Collections;
@ -555,7 +556,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
deleteItem(item);
break;
case 7:
shareUrl(item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
ShareUtils.shareUrl(this.getContext(), item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
break;
default:
break;

View File

@ -57,6 +57,7 @@ import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.CollapsibleView;
@ -425,7 +426,7 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
}
private void shareChannel (ChannelInfoItem selectedItem) {
shareUrl(selectedItem.getName(), selectedItem.getUrl());
ShareUtils.shareUrl(this.getContext(), selectedItem.getName(), selectedItem.getUrl());
}
@SuppressLint("CheckResult")

View File

@ -147,11 +147,16 @@ public class SubscriptionService {
}
private boolean isSubscriptionUpToDate(final ChannelInfo info, final SubscriptionEntity entity) {
return info.getUrl().equals(entity.getUrl()) &&
return equalsAndNotNull(info.getUrl(), entity.getUrl()) &&
info.getServiceId() == entity.getServiceId() &&
info.getName().equals(entity.getName()) &&
info.getAvatarUrl().equals(entity.getAvatarUrl()) &&
info.getDescription().equals(entity.getDescription()) &&
equalsAndNotNull(info.getAvatarUrl(), entity.getAvatarUrl()) &&
equalsAndNotNull(info.getDescription(), entity.getDescription()) &&
info.getSubscriberCount() == entity.getSubscriberCount();
}
private boolean equalsAndNotNull(final Object o1, final Object o2) {
return (o1 != null && o2 != null)
&& o1.equals(o2);
}
}

View File

@ -0,0 +1,30 @@
package org.schabi.newpipe.player;
import android.content.Context;
import android.content.ContextWrapper;
/**
* Fixes a leak caused by AudioManager using an Activity context.
* Tracked at https://android-review.googlesource.com/#/c/140481/1 and
* https://github.com/square/leakcanary/issues/205
* Source:
* https://gist.github.com/jankovd/891d96f476f7a9ce24e2
*/
public class AudioServiceLeakFix extends ContextWrapper {
AudioServiceLeakFix(Context base) {
super(base);
}
public static ContextWrapper preventLeakOf(Context base) {
return new AudioServiceLeakFix(base);
}
@Override
public Object getSystemService(String name) {
if (Context.AUDIO_SERVICE.equals(name)) {
return getApplicationContext().getSystemService(name);
}
return super.getSystemService(name);
}
}

View File

@ -130,6 +130,11 @@ public final class BackgroundPlayer extends Service {
onClose();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
@ -270,6 +275,8 @@ public final class BackgroundPlayer extends Service {
protected class BasePlayerImpl extends BasePlayer {
@NonNull final private AudioPlaybackResolver resolver;
private int cachedDuration;
private String cachedDurationString;
BasePlayerImpl(Context context) {
super(context);
@ -344,10 +351,14 @@ public final class BackgroundPlayer extends Service {
if (!shouldUpdateOnProgress) return;
resetNotification();
if(Build.VERSION.SDK_INT >= 26 /*Oreo*/) updateNotificationThumbnail();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Oreo*/) updateNotificationThumbnail();
if (bigNotRemoteView != null) {
if(cachedDuration != duration) {
cachedDuration = duration;
cachedDurationString = getTimeString(duration);
}
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration));
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + cachedDurationString);
}
if (notRemoteView != null) {
notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);

View File

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

View File

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

View File

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

View File

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

View File

@ -212,7 +212,6 @@ public abstract class VideoPlayer extends BasePlayer
@Override
public void initListeners() {
super.initListeners();
playbackSeekBar.setOnSeekBarChangeListener(this);
playbackSpeedTextView.setOnClickListener(this);
qualityTextView.setOnClickListener(this);
@ -298,7 +297,6 @@ public abstract class VideoPlayer extends BasePlayer
return true;
});
// Add all available captions
for (int i = 0; i < availableLanguages.size(); i++) {
final String captionLanguage = availableLanguages.get(i);
MenuItem captionItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
@ -316,7 +314,6 @@ public abstract class VideoPlayer extends BasePlayer
captionPopupMenu.setOnDismissListener(this);
}
private void updateStreamRelatedViews() {
if (getCurrentMetadata() == null) return;
@ -509,12 +506,12 @@ public abstract class VideoPlayer extends BasePlayer
}
// Normalize mismatching language strings
final String preferredLanguage = trackSelector.getPreferredTextLanguage();
final String preferredLanguage = trackSelector.getParameters().preferredTextLanguage;
// Build UI
buildCaptionMenu(availableLanguages);
if (trackSelector.getParameters().getRendererDisabled(textRenderer) ||
preferredLanguage == null || !availableLanguages.contains(preferredLanguage)) {
preferredLanguage == null || (!availableLanguages.contains(preferredLanguage)
&& !containsCaseInsensitive(availableLanguages, preferredLanguage))) {
captionTextView.setText(R.string.caption_none);
} else {
captionTextView.setText(preferredLanguage);
@ -522,6 +519,15 @@ public abstract class VideoPlayer extends BasePlayer
captionTextView.setVisibility(availableLanguages.isEmpty() ? View.GONE : View.VISIBLE);
}
// workaround to match normalized captions like english to English or deutsch to Deutsch
private static boolean containsCaseInsensitive(List<String> list, String toFind) {
for(String i : list){
if(i.equalsIgnoreCase(toFind))
return true;
}
return false;
}
/*//////////////////////////////////////////////////////////////////////////
// General Player
//////////////////////////////////////////////////////////////////////////*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,6 +16,11 @@ public final class SinglePlayQueue extends PlayQueue {
super(0, Collections.singletonList(new PlayQueueItem(info)));
}
public SinglePlayQueue(final StreamInfo info, final long startPosition) {
super(0, Collections.singletonList(new PlayQueueItem(info)));
getItem().setRecoveryPosition(startPosition);
}
public SinglePlayQueue(final List<StreamInfoItem> items, final int index) {
super(index, playQueueItemsOf(items));
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<InfoItemsPage> 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<CommentsInfo> getCommentsInfo(final int serviceId,
final String url,
boolean forceLoad) {
checkServiceId(serviceId);
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.COMMENT, Single.fromCallable(() ->
CommentsInfo.getInfo(NewPipe.getService(serviceId), url)));
}
public static Single<InfoItemsPage> getMoreCommentItems(final int serviceId,
final CommentsInfo info,
final String nextPageUrl) {
checkServiceId(serviceId);
return Single.fromCallable(() ->
CommentsInfo.getMoreItems(NewPipe.getService(serviceId), info, nextPageUrl));
}
public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId,
final String url,
boolean forceLoad) {
checkServiceId(serviceId);
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() ->
PlaylistInfo.getInfo(NewPipe.getService(serviceId), url)));
}
public static Single<InfoItemsPage> 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<KioskInfo> getKioskInfo(final int serviceId,
final String url,
boolean forceLoad) {
return checkCache(forceLoad, serviceId, url, Single.fromCallable(() ->
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() ->
KioskInfo.getInfo(NewPipe.getService(serviceId), url)));
}
@ -173,16 +190,17 @@ public final class ExtractorHelper {
private static <I extends Info> Single<I> checkCache(boolean forceLoad,
int serviceId,
String url,
InfoItem.InfoType infoType,
Single<I> loadFromNetwork) {
checkServiceId(serviceId);
loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info));
loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info, infoType));
Single<I> load;
if (forceLoad) {
cache.removeInfo(serviceId, url);
cache.removeInfo(serviceId, url, infoType);
load = loadFromNetwork;
} else {
load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url),
load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url, infoType),
loadFromNetwork.toMaybe())
.firstElement() //Take the first valid
.toSingle();
@ -194,20 +212,20 @@ public final class ExtractorHelper {
/**
* Default implementation uses the {@link InfoCache} to get cached results
*/
public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url) {
public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url, InfoItem.InfoType infoType) {
checkServiceId(serviceId);
return Maybe.defer(() -> {
//noinspection unchecked
I info = (I) cache.getFromKey(serviceId, url);
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();
});
}
/**

View File

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

View File

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

View File

@ -0,0 +1,41 @@
package org.schabi.newpipe.util;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class RelatedStreamInfo extends ListInfo<InfoItem> {
private StreamInfoItem nextStream;
public RelatedStreamInfo(int serviceId, ListLinkHandler listUrlIdHandler, String name) {
super(serviceId, listUrlIdHandler, name);
}
public static RelatedStreamInfo getInfo(StreamInfo info) {
ListLinkHandler handler = new ListLinkHandler(info.getOriginalUrl(), info.getUrl(), info.getId(), Collections.emptyList(), null);
RelatedStreamInfo relatedStreamInfo = new RelatedStreamInfo(info.getServiceId(), handler, info.getName());
List<InfoItem> streams = new ArrayList<>();
if(info.getNextVideo() != null){
streams.add(info.getNextVideo());
}
streams.addAll(info.getRelatedStreams());
relatedStreamInfo.setRelatedItems(streams);
relatedStreamInfo.setNextStream(info.getNextVideo());
return relatedStreamInfo;
}
public StreamInfoItem getNextStream() {
return nextStream;
}
public void setNextStream(StreamInfoItem nextStream) {
this.nextStream = nextStream;
}
}

View File

@ -0,0 +1,22 @@
package org.schabi.newpipe.util;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import org.schabi.newpipe.R;
public class ShareUtils {
public static void openUrlInBrowser(Context context, String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title)));
}
public static void shareUrl(Context context, String subject, String url) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, url);
context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title)));
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:innerRadius="0dp"
android:shape="ring"
android:thickness="4dp"
android:useLevel="false">
<solid android:color="@android:color/darker_gray"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:innerRadius="0dp"
android:shape="ring"
android:thickness="6dp"
android:useLevel="false">
<solid android:color="@android:color/darker_gray"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/selected_dot"
android:state_selected="true"/>
<item android:drawable="@drawable/default_dot"/>
</selector>

View File

@ -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" />
<ImageButton
android:id="@+id/toggleOrientation"
android:id="@+id/share"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
@ -316,6 +316,23 @@
android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY"
android:src="@drawable/ic_share_white_24dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/share"
tools:ignore="RtlHardcoded"/>
<ImageButton
android:id="@+id/toggleOrientation"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@id/share"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY"
android:src="@drawable/ic_screen_rotation_white"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/toggle_orientation"
@ -460,6 +477,22 @@
android:src="@drawable/exo_controls_next"
tools:ignore="ContentDescription"/>
<Button
android:id="@+id/closeButton"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/playPauseButton"
android:layout_centerInParent="true"
android:layout_marginTop="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:text="@string/close"
android:textAllCaps="true"
android:textColor="@color/white"
android:visibility="invisible" />
</RelativeLayout>

View File

@ -8,107 +8,121 @@
android:focusableInTouchMode="true"
android:orientation="horizontal">
<com.nirhart.parallaxscroll.views.ParallaxScrollView
<android.support.design.widget.CoordinatorLayout
android:id="@+id/detail_main_content"
app:parallax_factor="1.9"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="5">
android:layout_height="match_parent"
android:layout_weight="5"
android:fitsSystemWindows="true">
<!--WRAPPER-->
<LinearLayout
<android.support.design.widget.AppBarLayout
android:id="@+id/appbarlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:fitsSystemWindows="true"
app:elevation="0dp"
app:layout_behavior="android.support.design.widget.FlingBehavior">
<!-- THUMBNAIL -->
<FrameLayout
android:id="@+id/detail_thumbnail_root_layout"
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground">
app:layout_scrollFlags="scroll">
<ImageView
android:id="@+id/detail_thumbnail_image_view"
<!-- THUMBNAIL -->
<FrameLayout
android:id="@+id/detail_thumbnail_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:contentDescription="@string/detail_thumbnail_view_description"
android:scaleType="fitCenter"
tools:ignore="RtlHardcoded"
tools:layout_height="200dp"
tools:src="@drawable/dummy_thumbnail" />
android:background="@android:color/black"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:layout_collapseMode="parallax">
<ImageView
android:id="@+id/detail_thumbnail_play_button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:src="@drawable/new_play_arrow"
android:visibility="invisible"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<ImageView
android:id="@+id/detail_thumbnail_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:contentDescription="@string/detail_thumbnail_view_description"
android:scaleType="fitCenter"
tools:ignore="RtlHardcoded"
tools:layout_height="200dp"
tools:src="@drawable/dummy_thumbnail" />
<TextView
android:id="@+id/touch_append_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#64000000"
android:paddingBottom="10dp"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:paddingTop="10dp"
android:text="@string/hold_to_append"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="visible" />
<ImageView
android:id="@+id/detail_thumbnail_play_button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:src="@drawable/new_play_arrow"
android:visibility="invisible"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<TextView
android:id="@+id/detail_duration_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginBottom="8dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="8dp"
android:alpha=".6"
android:background="#23000000"
android:gravity="center"
android:paddingBottom="2dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="2dp"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:text="12:38"
tools:visibility="visible" />
</FrameLayout>
<TextView
android:id="@+id/touch_append_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#64000000"
android:paddingBottom="10dp"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:paddingTop="10dp"
android:text="@string/hold_to_append"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="visible" />
<TextView
android:id="@+id/detail_duration_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginBottom="8dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="8dp"
android:alpha=".6"
android:background="#23000000"
android:gravity="center"
android:paddingBottom="2dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="2dp"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:text="12:38"
tools:visibility="visible" />
</FrameLayout>
</android.support.design.widget.CollapsingToolbarLayout>
<!-- CONTENT -->
<RelativeLayout
android:id="@+id/detail_content_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:windowBackground">
android:layout_height="wrap_content"
android:background="?android:windowBackground"
app:layout_scrollFlags="scroll">
<!-- TITLE -->
<FrameLayout
android:id="@+id/detail_title_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingLeft="12dp"
android:paddingRight="12dp">
@ -126,6 +140,15 @@
tools:ignore="RtlHardcoded"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero." />
<ImageView
android:id="@+id/detail_toggle_description_view"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_gravity="center_vertical|right"
android:layout_marginLeft="5dp"
android:src="@drawable/arrow_down"
tools:ignore="ContentDescription,RtlHardcoded" />
</FrameLayout>
<!-- LOADING INDICATOR-->
@ -156,6 +179,7 @@
android:id="@+id/detail_content_root_hiding"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="10dp"
android:layout_below="@+id/detail_title_root_layout"
android:orientation="vertical"
android:visibility="gone"
@ -209,17 +233,17 @@
tools:text="Uploader" />
<!--<Button
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"/>-->
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"/>-->
</LinearLayout>
<!-- VIEW & THUMBS -->
@ -401,7 +425,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/detail_upload_date_view"
@ -435,57 +461,40 @@
android:background="?attr/separator_color" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</com.nirhart.parallaxscroll.views.ParallaxScrollView>
</android.support.design.widget.AppBarLayout>
<ScrollView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3">
<!--NEXT AND RELATED VIDEOS-->
<LinearLayout
android:id="@+id/detail_related_streams_root_layout"
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingTop="14dp"
android:paddingBottom="8dp"
android:orientation="vertical">
android:layout_gravity="bottom|center"
android:background="@color/transparent_background_color"
app:tabBackground="@drawable/tab_selector"
app:tabGravity="center"
app:tabIndicatorHeight="0dp">
<TextView
android:id="@+id/detail_next_stream_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:text="@string/next_video_title"
android:textAllCaps="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_next_text_size"
tools:ignore="RtlHardcoded" />
</android.support.design.widget.TabLayout>
<LinearLayout
android:id="@+id/detail_related_streams_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="2dp"
android:orientation="vertical"
tools:minHeight="50dp" />
</android.support.design.widget.CoordinatorLayout>
<ImageButton
android:id="@+id/detail_related_streams_expand"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:paddingBottom="10dp"
android:paddingTop="4dp"
android:src="?attr/expand"
android:textAlignment="center"
android:textAllCaps="true"
tools:ignore="ContentDescription" />
</LinearLayout>
</ScrollView>
<FrameLayout
android:id="@+id/relatedStreamsLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:layout_weight="3">
</FrameLayout>
</LinearLayout>

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="org.schabi.newpipe.history.HistoryActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_weight="1"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:title="@string/app_name"/>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="?attr/clear_history"/>
</android.support.design.widget.CoordinatorLayout>

View File

@ -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" />
<ImageButton
android:id="@+id/toggleOrientation"
android:id="@+id/share"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
@ -314,6 +314,23 @@
android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY"
android:src="@drawable/ic_share_white_24dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/share"
tools:ignore="RtlHardcoded"/>
<ImageButton
android:id="@+id/toggleOrientation"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@id/share"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY"
android:src="@drawable/ic_screen_rotation_white"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/toggle_orientation"
@ -458,6 +475,22 @@
android:src="@drawable/exo_controls_next"
tools:ignore="ContentDescription"/>
<Button
android:id="@+id/closeButton"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/playPauseButton"
android:layout_centerInParent="true"
android:layout_marginTop="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:text="@string/close"
android:textAllCaps="true"
android:textColor="@color/white"
android:visibility="invisible" />
</RelativeLayout>

View File

@ -1,27 +0,0 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".player.old.PlayVideoActivity"
android:gravity="center">
<VideoView android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:focusable="false"/>
<Button android:id="@+id/content_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
android:focusable="false"/>
<ProgressBar android:id="@+id/play_video_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:layout_gravity="center"
android:focusable="false"/>
</merge>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/items_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
tools:listitem="@layout/list_comments_item"/>
<ProgressBar
android:id="@+id/loading_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible"/>
<LinearLayout
android:id="@+id/empty_state_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:orientation="vertical"
android:paddingTop="85dp"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="10dp"
android:fontFamily="monospace"
android:text="(╯°-°)╯"
android:textSize="35sp"
tools:ignore="HardcodedText,UnusedAttribute"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/empty_view_no_comments"
android:textSize="24sp"/>
</LinearLayout>
<!--ERROR PANEL-->
<include
android:id="@+id/error_panel"
layout="@layout/error_retry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="50dp"
android:visibility="gone"
tools:visibility="visible"/>
<View
android:layout_width="match_parent"
android:layout_height="4dp"
android:background="?attr/toolbar_shadow_drawable"
android:layout_alignParentTop="true"
android:visibility="gone"/>
</RelativeLayout>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/empty_state_view"
layout="@layout/list_empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="90dp"
tools:visibility="visible"/>
</android.support.v4.widget.NestedScrollView>
<View
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_alignParentTop="true"
android:background="?attr/toolbar_shadow_drawable"
android:visibility="gone" />
</RelativeLayout>

View File

@ -1,30 +0,0 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.schabi.newpipe.history.HistoryFragment">
<android.support.v7.widget.RecyclerView
android:id="@+id/history_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_search_history"/>
<TextView
android:id="@+id/history_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/history_empty"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="gone"
tools:visibility="visible"/>
<include
android:id="@+id/history_disabled_view"
layout="@layout/history_disabled_view"
android:visibility="gone"/>
</FrameLayout>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/items_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
tools:listitem="@layout/list_stream_item"/>
<ProgressBar
android:id="@+id/loading_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible"/>
<LinearLayout
android:id="@+id/empty_state_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:orientation="vertical"
android:paddingTop="85dp"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="10dp"
android:fontFamily="monospace"
android:text="(╯°-°)╯"
android:textSize="35sp"
tools:ignore="HardcodedText,UnusedAttribute"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/empty_view_no_videos"
android:textSize="24sp"/>
</LinearLayout>
<!--ERROR PANEL-->
<include
android:id="@+id/error_panel"
layout="@layout/error_retry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="50dp"
android:visibility="gone"
tools:visibility="visible"/>
<View
android:layout_width="match_parent"
android:layout_height="4dp"
android:background="?attr/toolbar_shadow_drawable"
android:layout_alignParentTop="true"
android:visibility="gone"/>
</RelativeLayout>

View File

@ -67,6 +67,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/item_search_suggestion"/>
</LinearLayout>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/video_item_detail"
@ -8,101 +7,111 @@
android:layout_height="match_parent"
android:focusableInTouchMode="true">
<com.nirhart.parallaxscroll.views.ParallaxScrollView
<android.support.design.widget.CoordinatorLayout
android:id="@+id/detail_main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
app:parallax_factor="1.9">
android:fitsSystemWindows="true">
<!--WRAPPER-->
<LinearLayout
<android.support.design.widget.AppBarLayout
android:id="@+id/appbarlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:fitsSystemWindows="true"
app:elevation="0dp"
app:layout_behavior="android.support.design.widget.FlingBehavior">
<!-- THUMBNAIL -->
<FrameLayout
android:id="@+id/detail_thumbnail_root_layout"
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground">
app:layout_scrollFlags="scroll">
<ImageView
android:id="@+id/detail_thumbnail_image_view"
<!-- THUMBNAIL -->
<FrameLayout
android:id="@+id/detail_thumbnail_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:contentDescription="@string/detail_thumbnail_view_description"
android:scaleType="fitCenter"
tools:ignore="RtlHardcoded"
tools:layout_height="200dp"
tools:src="@drawable/dummy_thumbnail"/>
android:background="@android:color/black"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:layout_collapseMode="parallax">
<ImageView
android:id="@+id/detail_thumbnail_play_button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:src="@drawable/new_play_arrow"
android:visibility="invisible"
tools:ignore="ContentDescription"
tools:visibility="visible"/>
<ImageView
android:id="@+id/detail_thumbnail_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:contentDescription="@string/detail_thumbnail_view_description"
android:scaleType="fitCenter"
tools:ignore="RtlHardcoded"
tools:layout_height="200dp"
tools:src="@drawable/dummy_thumbnail" />
<TextView
android:id="@+id/touch_append_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#64000000"
android:paddingBottom="10dp"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:paddingTop="10dp"
android:layout_gravity="center"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:text="@string/hold_to_append"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="visible"/>
<ImageView
android:id="@+id/detail_thumbnail_play_button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:src="@drawable/new_play_arrow"
android:visibility="invisible"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<TextView
android:id="@+id/detail_duration_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginBottom="8dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="8dp"
android:alpha=".6"
android:background="#23000000"
android:gravity="center"
android:paddingBottom="2dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="2dp"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:text="12:38"
tools:visibility="visible"/>
</FrameLayout>
<TextView
android:id="@+id/touch_append_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#64000000"
android:paddingBottom="10dp"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:paddingTop="10dp"
android:text="@string/hold_to_append"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="visible" />
<TextView
android:id="@+id/detail_duration_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginBottom="8dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="8dp"
android:alpha=".6"
android:background="#23000000"
android:gravity="center"
android:paddingBottom="2dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="2dp"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:text="12:38"
tools:visibility="visible" />
</FrameLayout>
</android.support.design.widget.CollapsingToolbarLayout>
<!-- CONTENT -->
<RelativeLayout
android:id="@+id/detail_content_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:windowBackground">
android:layout_height="wrap_content"
android:background="?android:windowBackground"
app:layout_scrollFlags="scroll">
<!-- TITLE -->
<FrameLayout
@ -127,7 +136,7 @@
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_title_text_size"
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."/>
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero." />
<ImageView
android:id="@+id/detail_toggle_description_view"
@ -136,7 +145,7 @@
android:layout_gravity="center_vertical|right"
android:layout_marginLeft="5dp"
android:src="@drawable/arrow_down"
tools:ignore="ContentDescription,RtlHardcoded"/>
tools:ignore="ContentDescription,RtlHardcoded" />
</FrameLayout>
@ -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" />
<!--ERROR PANEL-->
<include
@ -161,13 +170,14 @@
android:layout_below="@id/detail_title_root_layout"
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
android:visibility="gone"
tools:visibility="visible"/>
tools:visibility="visible" />
<!--HIDING ROOT-->
<LinearLayout
android:id="@+id/detail_content_root_hiding"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="10dp"
android:layout_below="@+id/detail_title_root_layout"
android:orientation="vertical"
android:visibility="gone"
@ -189,12 +199,12 @@
android:id="@+id/detail_uploader_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/details_panel"
android:layout_toStartOf="@id/details_panel"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="6dp"
android:layout_toLeftOf="@id/details_panel"
android:layout_toStartOf="@id/details_panel">
android:padding="6dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/detail_uploader_thumbnail_view"
@ -202,23 +212,23 @@
android:layout_height="@dimen/video_item_detail_uploader_image_size"
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
android:src="@drawable/buddy"
tools:ignore="RtlHardcoded"/>
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/detail_uploader_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_uploader_text_size"
android:textStyle="bold"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_uploader_text_size"
android:textStyle="bold"
tools:ignore="RtlHardcoded"
tools:text="Uploader"/>
tools:text="Uploader" />
<!--<Button
android:id="@+id/detail_uploader_subscribe"
@ -239,10 +249,11 @@
android:id="@+id/details_panel"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:paddingLeft="6dp"
android:paddingRight="6dp">
<TextView
android:id="@+id/detail_view_count_view"
android:layout_width="wrap_content"
@ -254,7 +265,7 @@
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_views_text_size"
tools:ignore="RtlHardcoded"
tools:text="2,816,821,505 views"/>
tools:text="2,816,821,505 views" />
<ImageView
android:id="@+id/detail_thumbs_up_img_view"
@ -262,7 +273,7 @@
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:contentDescription="@string/detail_likes_img_view_description"
android:src="?attr/thumbs_up"/>
android:src="?attr/thumbs_up" />
<TextView
android:id="@+id/detail_thumbs_up_count_view"
@ -276,7 +287,7 @@
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="12M"/>
tools:text="12M" />
<ImageView
android:id="@+id/detail_thumbs_down_img_view"
@ -287,7 +298,7 @@
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
android:contentDescription="@string/detail_dislikes_img_view_description"
android:src="?attr/thumbs_down"
tools:ignore="RtlHardcoded"/>
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/detail_thumbs_down_count_view"
@ -301,7 +312,7 @@
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="10K"/>
tools:text="10K" />
<TextView
android:id="@+id/detail_thumbs_disabled_view"
@ -317,7 +328,7 @@
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="visible"/>
tools:visibility="visible" />
</RelativeLayout>
</RelativeLayout>
@ -337,14 +348,14 @@
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:contentDescription="@string/append_playlist"
android:drawableTop="?attr/ic_playlist_add"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/controls_add_to_playlist_title"
android:textSize="12sp"/>
android:textSize="12sp" />
<TextView
android:id="@+id/detail_controls_background"
@ -354,14 +365,14 @@
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:contentDescription="@string/play_audio"
android:drawableTop="?attr/audio"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/controls_background_title"
android:textSize="12sp"/>
android:textSize="12sp" />
<TextView
android:id="@+id/detail_controls_popup"
@ -371,14 +382,14 @@
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:contentDescription="@string/open_in_popup_mode"
android:drawableTop="?attr/popup"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/controls_popup_title"
android:textSize="12sp"/>
android:textSize="12sp" />
<TextView
android:id="@+id/detail_controls_download"
@ -388,14 +399,14 @@
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:contentDescription="@string/controls_download_desc"
android:drawableTop="?attr/download"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/download"
android:textSize="12sp"/>
android:textSize="12sp" />
</LinearLayout>
@ -404,7 +415,7 @@
android:layout_height="1px"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/separator_color"/>
android:background="?attr/separator_color" />
<!--DESCRIPTIONS-->
<LinearLayout
@ -425,7 +436,7 @@
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_upload_date_text_size"
android:textStyle="bold"
tools:text="Published on Oct 2, 2009"/>
tools:text="Published on Oct 2, 2009" />
<TextView
android:id="@+id/detail_description_view"
@ -438,59 +449,41 @@
android:textAppearance="?android:attr/textAppearanceMedium"
android:textIsSelectable="true"
android:textSize="@dimen/video_item_detail_description_text_size"
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."/>
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." />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/separator_color"/>
android:background="?attr/separator_color" />
</LinearLayout>
<!--NEXT AND RELATED VIDEOS-->
<LinearLayout
android:id="@+id/detail_related_streams_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginTop="14dp"
android:orientation="vertical">
<TextView
android:id="@+id/detail_next_stream_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:text="@string/next_video_title"
android:textAllCaps="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_next_text_size"
tools:ignore="RtlHardcoded"/>
<LinearLayout
android:id="@+id/detail_related_streams_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="2dp"
android:orientation="vertical"
tools:minHeight="50dp"/>
<ImageButton
android:id="@+id/detail_related_streams_expand"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:paddingBottom="10dp"
android:paddingTop="4dp"
android:src="?attr/expand"
android:textAlignment="center"
android:textAllCaps="true"
tools:ignore="ContentDescription"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</com.nirhart.parallaxscroll.views.ParallaxScrollView>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:background="@color/transparent_background_color"
app:tabBackground="@drawable/tab_selector"
app:tabGravity="center"
app:tabIndicatorHeight="0dp">
</android.support.design.widget.TabLayout>
</android.support.design.widget.CoordinatorLayout>
</FrameLayout>

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="@dimen/video_item_search_padding">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/itemThumbnailView"
android:layout_width="48dp"
android:layout_height="42dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description"
android:src="@drawable/buddy"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/itemTitleView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginBottom="@dimen/video_item_search_image_right_margin"
android:layout_toEndOf="@+id/itemThumbnailView"
android:layout_toRightOf="@+id/itemThumbnailView"
android:ellipsize="end"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/comment_item_title_text_size"
tools:text="Author Name, Lorem ipsum" />
<TextView
android:id="@+id/itemCommentContentView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/itemTitleView"
android:layout_marginBottom="@dimen/channel_item_description_to_details_margin"
android:layout_toEndOf="@+id/itemThumbnailView"
android:layout_toRightOf="@+id/itemThumbnailView"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/comment_item_content_text_size"
tools:text="Comment Content, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
<ImageView
android:id="@+id/detail_thumbs_up_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/itemCommentContentView"
android:layout_toRightOf="@+id/itemThumbnailView"
android:contentDescription="@string/detail_likes_img_view_description"
android:src="?attr/thumbs_up" />
<TextView
android:id="@+id/detail_thumbs_up_count_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/itemCommentContentView"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:layout_toRightOf="@id/detail_thumbs_up_img_view"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="12M" />
<!--we can uncomment below code if we need dislike button and count in future-->
<!--<ImageView
android:id="@+id/detail_thumbs_down_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/itemCommentContentView"
android:layout_marginLeft="12dp"
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
android:contentDescription="@string/detail_dislikes_img_view_description"
android:src="?attr/thumbs_down"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/detail_thumbs_down_count_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/itemCommentContentView"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:layout_toRightOf="@id/detail_thumbs_down_img_view"
android:gravity="center_vertical"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="10K" />-->
<TextView
android:id="@+id/itemPublishedTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/itemCommentContentView"
android:layout_marginLeft="12dp"
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
tools:text="1 year ago" />
</RelativeLayout>

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="@dimen/video_item_search_padding">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/itemThumbnailView"
android:layout_width="48dp"
android:layout_height="42dp"
android:layout_centerVertical="true"
android:layout_marginRight="12dp"
android:contentDescription="@string/list_thumbnail_view_description"
android:src="@drawable/buddy_channel_item"
tools:ignore="RtlHardcoded"/>
<TextView
android:id="@+id/itemCommentContentView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/channel_item_description_to_details_margin"
android:layout_toRightOf="@+id/itemThumbnailView"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/comment_item_content_text_size"
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
<ImageView
android:id="@+id/detail_thumbs_up_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/itemCommentContentView"
android:layout_toRightOf="@+id/itemThumbnailView"
android:contentDescription="@string/detail_likes_img_view_description"
android:src="?attr/thumbs_up" />
<TextView
android:id="@+id/detail_thumbs_up_count_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/itemCommentContentView"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:layout_toRightOf="@id/detail_thumbs_up_img_view"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="12M" />
<!--we can uncomment below code if we need dislike button and count in future-->
<!--<ImageView
android:id="@+id/detail_thumbs_down_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/itemCommentContentView"
android:layout_marginLeft="12dp"
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
android:contentDescription="@string/detail_dislikes_img_view_description"
android:src="?attr/thumbs_down"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/detail_thumbs_down_count_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/itemCommentContentView"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:layout_toRightOf="@id/detail_thumbs_down_img_view"
android:gravity="center_vertical"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="10K" />-->
<TextView
android:id="@+id/itemPublishedTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/itemCommentContentView"
android:layout_marginLeft="12dp"
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
tools:text="1 year ago" />
</RelativeLayout>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/channel_header_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground">
<TextView
android:id="@+id/next_stream_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_alignBaseline="@+id/autoplay_switch"
android:text="@string/next_video_title"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="12sp"
tools:ignore="RtlHardcoded" />
<Switch
android:id="@+id/autoplay_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:paddingRight="5dp"
android:switchPadding="5dp"
android:textSize="12sp"
android:textColor="@android:color/tab_indicator_text"
android:text="@string/autoplay_title" />
</RelativeLayout>

View File

@ -3,8 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.schabi.newpipe.history.HistoryActivity">
<item android:id="@+id/action_settings"
android:orderInCategory="990"
android:title="@string/settings"
<item android:id="@+id/action_history_clear"
android:orderInCategory="940"
android:title="@string/clear_views_history_title"
app:showAsAction="never"/>
</menu>

View File

@ -281,7 +281,6 @@
<string name="show_info">عرض المعلومات</string>
<string name="controls_add_to_playlist_title">إضافة إلى</string>
<string name="settings_category_debug_title">تحليل</string>
<string name="live">مٌباشِر</string>
<string name="video_streams_empty">لم يتم العثور على أي بث مرئي</string>
<string name="audio_streams_empty">لم يتم العثور على أي بث صوتي</string>
<string name="detail_drag_description">إسحب للقيام بالترتيب</string>
@ -398,7 +397,6 @@
<string name="playback_tempo">سرعة الأداء</string>
<string name="playback_pitch">تردد الصوت</string>
<string name="unhook_checkbox">نزع الإرتباط (قد يسبب تشويه)</string>
<string name="playback_nightcore">تعديل الايقاع Nightcore</string>
<string name="import_settings">هل تريد أيضا استيراد الإعدادات؟</string>
<string name="privacy_policy_title">"سياسة الخصوصية في NewPipe "</string>
<string name="privacy_policy_encouragement">يأخذ مشروع NewPipe خصوصيتك على محمل الجد. لذلك ، لا يجمع التطبيق أي بيانات دون موافقتك.

View File

@ -134,8 +134,6 @@
<string name="later">Más sero</string>
<string name="disabled">Desactivóse</string>
<string name="use_old_player_title">Usar reproductor vieyu</string>
<string name="short_thousand">M</string>
<string name="short_million">Mill</string>
@ -168,8 +166,7 @@
<string name="settings_category_popup_title">Ventanu emerxente</string>
<string name="popup_resizing_indicator_title">Redimensionáu</string>
<string name="use_old_player_summary">Reproductor vieyu integráu de Mediaframework</string>
<string name="subscribe_button_title">Soscribise</string>
<string name="subscribe_button_title">Soscribise</string>
<string name="subscribed_button_title">Soscribiéstite</string>
<string name="channel_unsubscribed">Desoscribiéstite de la canal</string>
<string name="subscription_change_failed">Nun pue camudase la resolución</string>
@ -325,8 +322,7 @@
<string name="video_player">Reproductor de videu</string>
<string name="background_player">Reproductor en segundu planu</string>
<string name="popup_player">Reproductor en ventanu</string>
<string name="always_ask_player">Entrugar siempres</string>
<string name="preferred_player_fetcher_notification_title">Consiguiendo información…</string>
<string name="preferred_player_fetcher_notification_message">Ta cargando\'l conteníu solicitáu</string>
<string name="controls_download_desc">Baxa\'l ficheru del fluxu.</string>

View File

@ -54,8 +54,6 @@
<string name="play_audio">Аўдыё</string>
<string name="default_audio_format_title">Фармат аўдыё па змаўчанні</string>
<string name="default_video_format_title">Фармат відэа па змаўчанні</string>
<string name="webm_description">WebM - свабодны</string>
<string name="m4a_description">M4A - вышэй якасць</string>
<string name="theme_title">Тэма</string>
<string name="light_theme_title">Светлая</string>
<string name="dark_theme_title">Цёмная</string>
@ -203,8 +201,6 @@
<string name="audio">Аўдыё</string>
<string name="retry">Паспрабаваць зноў</string>
<string name="storage_permission_denied">Няма доступу да носьбіта</string>
<string name="use_old_player_title">Стары плэер</string>
<string name="use_old_player_summary">Стары убудаваны плэер на Mediaframework</string>
<string name="short_thousand">тыс.</string>
<string name="short_million">млн.</string>
<string name="short_billion">млрд.</string>

View File

@ -156,8 +156,6 @@
<string name="audio">Аудио</string>
<string name="retry">Опитай отново</string>
<string name="storage_permission_denied">Достъпа до хранилището е отказан</string>
<string name="use_old_player_title">Използвай стария плейър</string>
<string name="use_old_player_summary">Използваю стария вграден Mediaframewoek плейър</string>
<plurals name="subscribers">
<item quantity="one">%s абонат</item>

View File

@ -118,8 +118,6 @@
<string name="audio">অডিও</string>
<string name="retry">পুনরায় চেষ্টা করো</string>
<string name="storage_permission_denied">স্টোরেজ অ্যাক্সেস করার অনুমতি অস্বীকার করা হয়েছে</string>
<string name="use_old_player_title">পুরানো প্লেয়ার ব্যবহার করো</string>
<string name="use_old_player_summary">মিডিয়াফ্রেমওয়ার্ক প্লেয়ারের পুরানো বিল্ড।</string>
<string name="short_thousand">K</string>
<string name="short_million">M</string>
<string name="short_billion">B</string>

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources><string name="install">Instaliraj</string>
<string name="cancel">Otkaži</string>
<string name="open_in_popup_mode">Otvori u slika u sliki modu</string>
<string name="share">Podijeli</string>
<string name="search">Pretraga</string>
<string name="settings">Postavke</string>
<string name="share_dialog_title">Podijeli sa</string>
<string name="screen_rotation">Rotacija</string>
<string name="subscribe_button_title">Preplati se</string>
<string name="subscribed_button_title">Preplaćen</string>
<string name="show_info">Pokaži informacije</string>
<string name="tab_new">Novi Tab</string>
<string name="tab_choose">Izaberi Tab</string>
<string name="fragment_whats_new">Šta je novo</string>
<string name="controls_background_title">Pozadina</string>
<string name="download_path_audio_summary">Skinuti audio je spašen ovdje</string>
<string name="default_resolution_title">Zadana rezolucija</string>
<string name="show_higher_resolutions_title">Pokaži više rezolucije</string>
<string name="show_higher_resolutions_summary">Samo neki uređaji podržavaju puštanje 2K/4K videa</string>
<string name="kore_not_found">Kore aplikacija nije nađena. Instaliraj te\?</string>
<string name="play_audio">Zvuk</string>
<string name="default_audio_format_title">Zadani zvučni format</string>
<string name="default_video_format_title">Zadani video format</string>
<string name="dark_theme_title">Mračno</string>
<string name="settings_category_appearance_title">Izgled</string>
<string name="settings_category_other_title">Drugo</string>
<string name="downloads">Skinuto</string>
<string name="caption_auto_generated">Auto-generirano</string>
<string name="error_path_creation">Fajl ne može biti krejiran</string>
<string name="error_http_no_content">Ovaj server ne šalje podatke</string>
</resources>

View File

@ -216,8 +216,6 @@
<string name="info_dir_created">S\'ha creat el directori de baixades «%1$s»</string>
<string name="retry">Torna a intentar-ho</string>
<string name="storage_permission_denied">S\'ha denegat el permís d\'accés a l\'emmagatzematge</string>
<string name="use_old_player_title">Fes servir el reproductor antic</string>
<string name="use_old_player_summary">Antic reproductor integrat de Mediaframework</string>
<string name="no_subscribers">Sense subscriptors</string>
<string name="no_views">Sense reproduccions</string>
<plurals name="views">

View File

@ -59,7 +59,6 @@
<string name="dark_theme_title">酷黑</string>
<string name="black_theme_title">黑色</string>
<string name="popup_remember_size_pos_title">记住悬浮窗的尺寸与位置</string>
<string name="m4a_description">M4A — 更好的音质</string>
<string name="popup_remember_size_pos_summary">记住上一次悬浮窗的位置以及大小</string>
<string name="thumbnail_cache_wipe_complete_notice">已清除图像缓存</string>
<string name="minimize_on_exit_popup_description">最小化悬浮窗播放器</string>
@ -108,7 +107,6 @@
<string name="minimize_on_exit_summary">从主视频播放器切换到其他应用时的操作 - %s</string>
<string name="minimize_on_exit_none_description">没有</string>
<string name="minimize_on_exit_background_description">最小化后台播放</string>
<string name="webm_description">WebM — 自由视频格式</string>
<string name="use_inexact_seek_title">使用快速粗略定位</string>
<string name="use_inexact_seek_summary">粗略定位功能允许播放器以略低的精确度为代价换取更快的定位速度</string>
<string name="download_thumbnail_title">下载缩略图</string>
@ -243,8 +241,6 @@
<string name="audio">音频</string>
<string name="retry">重试</string>
<string name="storage_permission_denied">手机存储访问权限被拒绝</string>
<string name="use_old_player_title">使用旧的播放器</string>
<string name="use_old_player_summary">旧的内置 Mediaframework 播放器</string>
<string name="short_thousand"></string>
<string name="short_million"></string>
<string name="short_billion">亿</string>

View File

@ -120,7 +120,6 @@
<string name="short_million">mil.</string>
<string name="msg_popup_permission">Toto oprávnění je vyžadováno pro
otevření ve vyskakovacím okně</string>
<string name="use_old_player_title">Použít starý přehrávač</string>
<string name="use_external_video_player_summary">Odstraňuje zvuk v některých rozlišeních</string>
<string name="show_higher_resolutions_title">Zobrazovat vyšší rozlišení</string>
<string name="show_higher_resolutions_summary">Pouze některá zařízení podporují přehrávání 2K/4K videí</string>
@ -166,7 +165,6 @@ otevření ve vyskakovacím okně</string>
<string name="notification_channel_description">Notifikace pro NewPipe přehrávače v pozadí a v okně</string>
<string name="search_no_results">Žádné výsledky</string>
<string name="empty_subscription_feed_subtitle">Je tu sranda jak v márnici</string>
<string name="use_old_player_summary">Starý zabudovaný Mediaframework přehrávač</string>
<string name="short_billion">mld.</string>
<string name="no_subscribers">Žádní odběratelé</string>
<plurals name="subscribers">
@ -282,7 +280,6 @@ otevření ve vyskakovacím okně</string>
<string name="video_player">Video přehrávač</string>
<string name="background_player">Přehrávač na pozadí</string>
<string name="popup_player">Přehrávač v okně</string>
<string name="always_ask_player">Vždy se ptát</string>
<string name="preferred_player_fetcher_notification_title">Získávám informace…</string>
<string name="preferred_player_fetcher_notification_message">Načítání požadovaného obsahu</string>
<string name="controls_download_desc">Stáhnout soubor streamu</string>
@ -322,6 +319,7 @@ otevření ve vyskakovacím okně</string>
<string name="smaller_caption_font_size">Menší písmo</string>
<string name="normal_caption_font_size">Normální písmo</string>
<string name="larger_caption_font_size">Větší písmo</string>
<string name="toggle_leak_canary">Sledovat únik paměti</string>
<string name="disable_leak_canary_notice">Sledování úniku paměti vypnuto</string>
<string name="enable_leak_canary_notice">Sledování úniku paměti povoleno, aplikace může při zátěži přestat reagovat</string>

View File

@ -120,13 +120,11 @@
<string name="all">Alle</string>
<string name="channel">Kanal</string>
<string name="disabled">Deaktiviert</string>
<string name="use_old_player_title">Alten Player benutzen</string>
<string name="open_in_popup_mode">Im Pop-up-Modus öffnen</string>
<string name="default_video_format_title">Bevorzugtes Videoformat</string>
<string name="popup_playing_toast">Spiele im Pop-up Modus ab</string>
<string name="popup_mode_share_menu_title">NewPipe-Pop-up-Modus</string>
<string name="msg_popup_permission">Diese Berechtigung ist für das Öffnen im Pop-up-Modus erforderlich</string>
<string name="use_old_player_summary">Alter eingebauter Mediaframework-Player</string>
<string name="default_popup_resolution_title">Standardauflösung des Pop-ups</string>
<string name="show_higher_resolutions_title">Höhere Auflösungen anzeigen</string>
<string name="show_higher_resolutions_summary">Nur manche Geräte unterstützen das Abspielen von 2K-/4K-Videos</string>
@ -270,7 +268,6 @@
<string name="video_player">Video-Player</string>
<string name="background_player">Hintergrund-Player</string>
<string name="popup_player">Popup-Player</string>
<string name="always_ask_player">Immer fragen</string>
<string name="preferred_player_fetcher_notification_title">Informationen werden abgerufen…</string>
<string name="preferred_player_fetcher_notification_message">Gewünschten Inhalt laden</string>
<string name="import_data_title">Datenbank importieren</string>

View File

@ -29,8 +29,6 @@
<string name="show_play_with_kodi_summary">Προβολή μιας επιλογής για αναπαραγωγή με το Kodi media center</string>
<string name="play_audio">Ήχος</string>
<string name="default_audio_format_title">Προεπιλεγμένη μορφή ήχου</string>
<string name="webm_description">WebM — δωρεάν μορφή</string>
<string name="m4a_description">Μ4Α — καλύτερη ποιότητα</string>
<string name="theme_title">Θέμα</string>
<string name="dark_theme_title">Σκοτεινό</string>
<string name="light_theme_title">Φωτεινό</string>
@ -216,8 +214,6 @@
<string name="detail_drag_description">Σύρετε για ταξινόμηση</string>
<string name="retry">Προσπάθεια εκ νέου</string>
<string name="storage_permission_denied">Δεν δώθηκε άδεια εγγραφής στην εσωτερική μνήμη</string>
<string name="use_old_player_title">Χρήση παλαιάς συσκευής αναπαραγωγής</string>
<string name="use_old_player_summary">Παλαιά συσκευή αναπαραγωγής Mediaframework</string>
<string name="short_thousand">χιλ</string>
<string name="short_million">Εκ</string>
<string name="no_subscribers">Κανένας εγγεγραμένος χρήστης</string>

View File

@ -268,7 +268,6 @@ abrir en modo popup</string>
<string name="video_player">Reproductor de vídeo</string>
<string name="background_player">Reproductor de fondo</string>
<string name="popup_player">Reproductor de popup</string>
<string name="always_ask_player">Preguntar siempre</string>
<string name="preferred_player_fetcher_notification_title">Obteniendo información…</string>
<string name="preferred_player_fetcher_notification_message">Cargando contenido solicitado</string>
<string name="import_data_title">Importar base de datos</string>

View File

@ -195,8 +195,6 @@
<string name="audio">Audio</string>
<string name="retry">Proovi uuesti</string>
<string name="storage_permission_denied">Pääsuõigused salvestile puuduvad</string>
<string name="use_old_player_title">Kasuta vana pleierit</string>
<string name="use_old_player_summary">Vana sisseehitatud mediaframework pleier</string>
<string name="short_thousand">K</string>
<string name="short_million">M</string>
<string name="short_billion">B</string>

View File

@ -117,8 +117,6 @@
<string name="audio">Audioa</string>
<string name="retry">Saiatu berriro</string>
<string name="storage_permission_denied">Biltegia atzitzeko baimena ukatu da</string>
<string name="use_old_player_title">Erabili erreproduzigailu zaharra</string>
<string name="use_old_player_summary">Barne Media Framework erreproduzigailu zaharra</string>
<string name="short_thousand">K</string>
<string name="short_million">M</string>
<string name="short_billion">MM</string>
@ -270,7 +268,6 @@
<string name="video_player">Bideo erreproduzigailua</string>
<string name="background_player">Bigarren planoko erreproduzigailua</string>
<string name="popup_player">Laster-leiho erreproduzigailua</string>
<string name="always_ask_player">Galdetu beti</string>
<string name="preferred_player_fetcher_notification_title">Informazioa eskuratzen…</string>
<string name="preferred_player_fetcher_notification_message">Kargatzen eskatutako edukia</string>
<string name="controls_download_desc">Deskargatu jario fitxategia</string>

View File

@ -32,8 +32,6 @@
<string name="show_play_with_kodi_summary">نمایش گزینه‌ای برای پخش ویدیو با مرکز رسانهٔ کودی</string>
<string name="play_audio">صدا</string>
<string name="default_audio_format_title">قالب صدای پیش‌گزیده</string>
<string name="webm_description">قالب آزاد — WebM</string>
<string name="m4a_description">کیفیت بهتر — M4A</string>
<string name="theme_title">زمینه</string>
<string name="dark_theme_title">تیره</string>
<string name="light_theme_title">روشن</string>
@ -189,7 +187,6 @@
<string name="error_occurred_detail">خطایی رخ داد: %1$s</string>
<string name="no_streams_available_download">جریانی برای بارگیری در دسترس نیست</string>
<string name="search_no_results">بدون نتیجه</string>
<string name="use_old_player_title">استفاده از پخش‌کننده قدیمی</string>
<string name="short_thousand">K</string>
<string name="short_million">M</string>
<string name="short_billion">B</string>

View File

@ -148,8 +148,6 @@
<string name="audio">Ääni</string>
<string name="retry">Toista uudelleen</string>
<string name="storage_permission_denied">Oikeus tallennustilan hallintaan evätty</string>
<string name="use_old_player_title">Käytä vanhaa soitinta</string>
<string name="use_old_player_summary">Käytä vanhaa sisäänrakennettua Mediaframework-soitinta</string>
<string name="short_thousand">t.</string>
<string name="short_million">milj.</string>

View File

@ -119,8 +119,6 @@
<string name="later">Plus tard</string>
<string name="disabled">Désactivé</string>
<string name="info_labels">Quoi :\\nRequête :\\nLangue du contenu :\\nService :\\nHeure GMT :\\nPaquet :\\nVersion :\\nVersion du système :</string>
<string name="use_old_player_title">Utiliser l\'ancien lecteur</string>
<string name="use_old_player_summary">Ancienne version du lecteur Mediaframework</string>
<string name="short_thousand">K</string>
<string name="short_million">M</string>
<string name="msg_popup_permission">Cette autorisation est nécessaire pour
@ -270,7 +268,6 @@
<string name="video_player">Lecteur vidéo</string>
<string name="background_player">Lecteur en arrière-plan</string>
<string name="popup_player">Lecteur en fenêtré</string>
<string name="always_ask_player">Toujours demander</string>
<string name="preferred_player_fetcher_notification_title">Obtention des infos…</string>
<string name="preferred_player_fetcher_notification_message">Chargement du contenu</string>
<string name="import_data_title">Importer les données</string>

View File

@ -59,8 +59,6 @@
<string name="play_audio">Audio</string>
<string name="default_audio_format_title">Formato de audio predeterminado</string>
<string name="default_video_format_title">Formato de vídeo predeterminado</string>
<string name="webm_description">WebM — formato libre</string>
<string name="m4a_description">M4A — mellor calidade</string>
<string name="theme_title">Tema</string>
<string name="light_theme_title">Claro</string>
<string name="dark_theme_title">Escuro</string>
@ -218,8 +216,6 @@
<string name="audio">Audio</string>
<string name="retry">Tentar de novo</string>
<string name="storage_permission_denied">A permisión de acceso ao almacenamento foi denegada</string>
<string name="use_old_player_title">Usar o reprodutor antigo</string>
<string name="use_old_player_summary">Versión interna anticuada do reprodutor Mediaframework</string>
<string name="short_thousand">K</string>
<string name="short_million">M</string>

View File

@ -149,8 +149,6 @@
<string name="audio">שמע</string>
<string name="retry">ניסיון חוזר</string>
<string name="storage_permission_denied">הגישה לאחסון נדחתה</string>
<string name="use_old_player_title">השתמש בנגן הישן</string>
<string name="use_old_player_summary">השתמש בנגן המובנה Mediaframework</string>
<string name="short_thousand">K</string>
<string name="short_million">M</string>
<string name="short_billion">B</string>

View File

@ -80,8 +80,6 @@
<string name="show_play_with_kodi_summary">कोडी मीडिया सेंटर के माध्यम से वीडियो चलाने के लिए एक विकल्प प्रदर्शित करें</string>
<string name="default_audio_format_title">डिफ़ॉल्ट ऑडियो का फॉर्मेट</string>
<string name="default_video_format_title">डिफ़ॉल्ट विडियो का फॉर्मेट</string>
<string name="webm_description">WebM - libre फॉर्मेट</string>
<string name="m4a_description">M4A - बेहतर क्वालिटी</string>
<string name="theme_title">एप्प का नया रूप</string>
<string name="dark_theme_title">काला</string>
<string name="popup_remember_size_pos_title">विडियो पॉपअप की आकर और उसकी स्थति को याद रखे</string>
@ -174,8 +172,6 @@
<string name="audio">ऑडियो</string>
<string name="retry">फिर से कोशिश करे</string>
<string name="storage_permission_denied">स्टोरेज में प्रवेश के लिए अनुमति नहीं मिली</string>
<string name="use_old_player_title">पुराना विडियो प्लेयर प्रयोग करे</string>
<string name="use_old_player_summary">पुराना Mediaframework Player जो फ़ोन में बना हुआ है</string>
<string name="short_thousand">हज़ार</string>
<string name="short_million">करोड़</string>
<string name="short_billion">अरब</string>
@ -319,9 +315,6 @@
<string name="smaller_caption_font_size">छोटे फ़ॉंट</string>
<string name="normal_caption_font_size">सामांय फ़ॉंट</string>
<string name="larger_caption_font_size">बड़ा फ़ॉंट</string>
<string name="toggle_leak_canary">मॉनिटर लीक</string>
<string name="enable_leak_canary_notice">मेमोरी लीक मॉनिटरिंग सक्षम है, हीप डंपिंग के समय ऐप अप्रतिसादी हो सकती है</string>
<string name="disable_leak_canary_notice">मेमोरी लीक मॉनिटरिंग अक्षम है</string>
<string name="settings_category_debug_title">डीबग करें</string>
<string name="caption_auto_generated">ऑटो-जनरेटेड</string>
<string name="enable_leak_canary_title">LeakCanary सक्षम करें</string>

View File

@ -131,8 +131,6 @@
<string name="audio">Zvuk</string>
<string name="retry">Ponovno pokušaj</string>
<string name="storage_permission_denied">Dozvola za pisanje po pohrani je odbijena</string>
<string name="use_old_player_title">Koristi stari reproduktor</string>
<string name="use_old_player_summary">Stari ugrađeni Mediaframework reproduktor</string>
<string name="short_thousand">tis</string>
<string name="short_million">mil</string>
<string name="short_billion">mlrd</string>

View File

@ -24,8 +24,6 @@
<string name="show_play_with_kodi_summary">Opció mutatása a videók Kodi médiaközponttal való lejátszására</string>
<string name="play_audio">Hang</string>
<string name="default_audio_format_title">Alapértelmezett hang formátum</string>
<string name="webm_description">WebM — szabad formátum</string>
<string name="m4a_description">M4A — jobb minőség</string>
<string name="download_dialog_title">Letöltés</string>
<string name="next_video_title">Következő</string>
<string name="url_not_supported_toast">Nem támogatott webcím</string>
@ -193,7 +191,6 @@
<string name="info_labels">Mi:\\nKérés:\\nTartalom nyelve:\\nSzolgáltatás:\\nGMT Idő:\\nCsomag:\\nVerzió:\\nOperációs Rendszer verzió:</string>
<string name="search_no_results">Nincs találat</string>
<string name="use_old_player_title">Régi lejátszó használata</string>
<string name="controls_download_desc">Adatfolyam fájl letöltése</string>
<string name="controls_add_to_playlist_title">Hozzáadás</string>
@ -236,8 +233,6 @@
<string name="empty_subscription_feed_subtitle">Itt nincs semmi</string>
<string name="detail_drag_description">Húzza az átrendezéshez</string>
<string name="use_old_player_summary">Régi beépített Mediaframework lejátszó</string>
<string name="short_thousand">e</string>
<string name="short_million">M</string>
<string name="short_billion">Mrd</string>

View File

@ -29,8 +29,6 @@
<string name="show_play_with_kodi_summary">Tampilkan opsi untuk memutar video via Kodi</string>
<string name="play_audio">Audio</string>
<string name="default_audio_format_title">Format audio</string>
<string name="webm_description">WebM — format bebas</string>
<string name="m4a_description">M4A — kualitas lebih baik</string>
<string name="dark_theme_title">Gelap</string>
<string name="light_theme_title">Terang</string>
<string name="download_dialog_title">Unduh</string>
@ -124,8 +122,6 @@
\nmembuka di mode popup</string>
<string name="popup_mode_share_menu_title">Mode popup NewPipe</string>
<string name="popup_playing_toast">Memutar dalam mode popup</string>
<string name="use_old_player_title">Gunakan pemutar lama</string>
<string name="use_old_player_summary">Pemutar Mediaframework bawaan versi lama</string>
<string name="disabled">Dinonaktifkan</string>
<string name="default_video_format_title">Format video</string>
<string name="default_popup_resolution_title">Resolusi popup</string>

View File

@ -122,7 +122,6 @@
<string name="popup_mode_share_menu_title">Modalità Popup di NewPipe</string>
<string name="popup_playing_toast">Riproduzione in modalità popup</string>
<string name="disabled">Disattivato</string>
<string name="use_old_player_title">Usa il vecchio lettore multimediale</string>
<string name="use_external_video_player_summary">Non riproduce l\'audio con ALCUNE risoluzioni</string>
<string name="controls_background_title">In sottofondo</string>
<string name="controls_popup_title">Popup</string>
@ -142,8 +141,7 @@
<string name="clear">Cancella</string>
<string name="popup_resizing_indicator_title">Ridimensionamento</string>
<string name="best_resolution">Risoluzione migliore</string>
<string name="use_old_player_summary">Precedente lettore multimediale Mediaframework integrato</string>
<string name="msg_popup_permission">Questo permesso è necessario
<string name="msg_popup_permission">Questo permesso è necessario
\nper riprodurre in modalità popup</string>
<string name="action_settings">Impostazioni</string>
<string name="action_about">Informazioni</string>
@ -270,7 +268,6 @@
<string name="video_player">Lettore video</string>
<string name="background_player">Riproduzione in sottofondo</string>
<string name="popup_player">Riproduzione in modalità popup</string>
<string name="always_ask_player">Chiedi sempre</string>
<string name="preferred_player_fetcher_notification_title">Raccogliendo informazioni…</string>
<string name="preferred_player_fetcher_notification_message">Caricamento del contenuto richiesto</string>
<string name="import_data_title">Importa database</string>

View File

@ -123,8 +123,6 @@
このアクセス許可が必要です</string>
<string name="popup_mode_share_menu_title">NewPipe ポップアップモード</string>
<string name="popup_playing_toast">ポップアップモードで再生中</string>
<string name="use_old_player_title">古いプレーヤーを使用する</string>
<string name="use_old_player_summary">古い内蔵のMediaframeworkプレーヤー</string>
<string name="disabled">無効</string>
<string name="default_video_format_title">デフォルトの動画形式</string>
<string name="default_popup_resolution_title">デフォルトのポップアップ解像度</string>
@ -253,7 +251,6 @@
<string name="video_player">動画プレーヤー</string>
<string name="background_player">バックグラウンドプレーヤー</string>
<string name="popup_player">ポップアッププレーヤー</string>
<string name="always_ask_player">常に尋ねる</string>
<string name="preferred_player_fetcher_notification_title">情報を取得しています…</string>
<string name="preferred_player_fetcher_notification_message">コンテンツを読み込んでいます</string>
<string name="controls_download_desc">動画ファイルをダウンロード</string>

View File

@ -173,9 +173,6 @@
<string name="search_no_results">결과 없음</string>
<string name="empty_subscription_feed_subtitle">구독할 항목을 추가하세요</string>
<string name="use_old_player_title">구형 플레이어 사용</string>
<string name="use_old_player_summary">내장된 구형 Mediaframework 플레이어 사용</string>
<string name="short_thousand"></string>
<string name="short_million">백만</string>
<string name="short_billion">10억</string>

View File

@ -120,8 +120,6 @@
<string name="video">Vaizdas</string>
<string name="audio">Muzika</string>
<string name="retry">Bandyti iš naujo</string>
<string name="use_old_player_title">Naudoti seną grotuvą</string>
<string name="use_old_player_summary">Senas įtaisytas media grotuvas</string>
<plurals name="subscribers">
<item quantity="one">%s prenumeratorius</item>

View File

@ -202,8 +202,6 @@
<string name="audio">Звук</string>
<string name="retry">Пробај повторно</string>
<string name="storage_permission_denied">Нема привилегии за пристап</string>
<string name="use_old_player_title">Користи го стариот плеер</string>
<string name="use_old_player_summary">Користи го стариот Mediaframework плеер</string>
<string name="short_thousand">K</string>
<string name="short_million">M</string>

Some files were not shown because too many files have changed in this diff Show More