mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-10-27 13:28:01 +00:00
Merge remote-tracking branch 'TeamNewPipe/dev' into close_button
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -47,7 +47,7 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
updateFragments();
|
||||
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -230,21 +230,4 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
|
||||
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void openUrlInBrowser(String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
|
||||
}
|
||||
|
||||
protected void shareUrl(String subject, String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class EmptyFragment extends BaseFragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_empty, container, false);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -61,6 +62,18 @@ public class TabAdaptor extends FragmentPagerAdapter {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
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;
|
||||
@@ -85,6 +86,7 @@ import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
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;
|
||||
|
||||
@@ -121,6 +123,7 @@ public class VideoDetailFragment
|
||||
private boolean autoPlayEnabled;
|
||||
private boolean showRelatedStreams;
|
||||
private boolean showComments;
|
||||
private String selectedTabTag;
|
||||
|
||||
@State
|
||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||
@@ -179,6 +182,7 @@ public class VideoDetailFragment
|
||||
|
||||
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;
|
||||
@@ -211,6 +215,9 @@ public class VideoDetailFragment
|
||||
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);
|
||||
}
|
||||
@@ -224,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
|
||||
@@ -537,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;
|
||||
@@ -626,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;
|
||||
}
|
||||
@@ -813,6 +824,9 @@ public class VideoDetailFragment
|
||||
}
|
||||
|
||||
private void initTabs() {
|
||||
if (pageAdapter.getCount() != 0) {
|
||||
selectedTabTag = pageAdapter.getItemTitle(viewPager.getCurrentItem());
|
||||
}
|
||||
pageAdapter.clearAllItems();
|
||||
|
||||
if(shouldShowComments()){
|
||||
@@ -824,11 +838,17 @@ public class VideoDetailFragment
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,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;
|
||||
@@ -255,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),
|
||||
@@ -264,19 +266,22 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
NavigationHelper.playOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 2:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 3:
|
||||
if (getFragmentManager() != null) {
|
||||
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
|
||||
.show(getFragmentManager(), TAG);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
shareUrl(item.getName(), item.getUrl());
|
||||
case 4:
|
||||
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -190,7 +191,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
shareUrl(item.getName(), item.getUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -233,10 +234,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
openRssFeed();
|
||||
break;
|
||||
case R.id.menu_item_openInBrowser:
|
||||
openUrlInBrowser(currentInfo.getOriginalUrl());
|
||||
ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl());
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
shareUrl(name, currentInfo.getOriginalUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), name, currentInfo.getOriginalUrl());
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -168,7 +169,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
||||
break;
|
||||
case 5:
|
||||
shareUrl(item.getName(), item.getUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -230,10 +231,10 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_item_openInBrowser:
|
||||
openUrlInBrowser(url);
|
||||
ShareUtils.openUrlInBrowser(this.getContext(), url);
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
shareUrl(name, url);
|
||||
ShareUtils.shareUrl(this.getContext(), name, url);
|
||||
break;
|
||||
case R.id.menu_item_bookmark:
|
||||
onBookmarkClicked();
|
||||
@@ -305,6 +306,16 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
|
||||
headerBackgroundButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
|
||||
|
||||
headerPopupButton.setOnLongClickListener(view -> {
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue());
|
||||
return true;
|
||||
});
|
||||
|
||||
headerBackgroundButton.setOnLongClickListener(view -> {
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue() {
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.TooltipCompat;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
@@ -45,10 +46,9 @@ import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.LayoutManagerSmoothScroller;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
|
||||
@@ -73,8 +73,8 @@ import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
import static android.support.v7.widget.helper.ItemTouchHelper.Callback.makeMovementFlags;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class SearchFragment
|
||||
@@ -298,7 +298,23 @@ public class SearchFragment
|
||||
suggestionsPanel = rootView.findViewById(R.id.suggestions_panel);
|
||||
suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list);
|
||||
suggestionsRecyclerView.setAdapter(suggestionListAdapter);
|
||||
suggestionsRecyclerView.setLayoutManager(new LayoutManagerSmoothScroller(activity));
|
||||
new ItemTouchHelper(new ItemTouchHelper.Callback() {
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
return getSuggestionMovementFlags(recyclerView, viewHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
onSuggestionItemSwiped(viewHolder, i);
|
||||
}
|
||||
}).attachToRecyclerView(suggestionsRecyclerView);
|
||||
|
||||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||
@@ -901,4 +917,28 @@ public class SearchFragment
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Suggestion item touch helper
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public int getSuggestionMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final SuggestionItem item = suggestionListAdapter.getItem(position);
|
||||
return item.fromHistory ? makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
|
||||
}
|
||||
|
||||
public void onSuggestionItemSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final String query = suggestionListAdapter.getItem(position).query;
|
||||
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> suggestionPublisher
|
||||
.onNext(searchEditText.getText().toString()),
|
||||
throwable -> showSnackBarError(throwable,
|
||||
UserAction.DELETE_FROM_HISTORY, "none",
|
||||
"Deleting item failed", R.string.general_error));
|
||||
disposables.add(onDelete);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
||||
});
|
||||
}
|
||||
|
||||
private SuggestionItem getItem(int position) {
|
||||
SuggestionItem getItem(int position) {
|
||||
return items.get(position);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
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 {
|
||||
@@ -26,6 +31,25 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
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);
|
||||
|
||||
@@ -53,6 +77,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
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(
|
||||
@@ -66,34 +91,60 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
}
|
||||
});
|
||||
|
||||
// ellipsize if not already ellipsized
|
||||
if (null == itemContentView.getEllipsize()) {
|
||||
itemContentView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
itemContentView.setMaxLines(commentDefaultLines);
|
||||
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();
|
||||
}
|
||||
|
||||
itemContentView.setText(item.getCommentText());
|
||||
if (null != item.getLikeCount()) {
|
||||
itemLikesCountView.setText(String.valueOf(item.getLikeCount()));
|
||||
}
|
||||
itemPublishedTime.setText(item.getPublishedTime());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
toggleEllipsize(item.getCommentText());
|
||||
toggleEllipsize();
|
||||
if (itemBuilder.getOnCommentsSelectedListener() != null) {
|
||||
itemBuilder.getOnCommentsSelectedListener().selected(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleEllipsize(String text) {
|
||||
// toggle ellipsize
|
||||
if (null == itemContentView.getEllipsize()) {
|
||||
itemContentView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
itemContentView.setMaxLines(commentDefaultLines);
|
||||
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 {
|
||||
itemContentView.setEllipsize(null);
|
||||
itemContentView.setMaxLines(commentExpandedLines);
|
||||
expand();
|
||||
}
|
||||
}
|
||||
|
||||
private void expand() {
|
||||
itemContentView.setMaxLines(commentExpandedLines);
|
||||
itemContentView.setText(commentText);
|
||||
linkify();
|
||||
}
|
||||
|
||||
private void linkify(){
|
||||
Linkify.addLinks(itemContentView, Linkify.WEB_URLS);
|
||||
Linkify.addLinks(itemContentView, pattern, null, null, timestampLink);
|
||||
itemContentView.setMovementMethod(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,11 @@ import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
@@ -21,13 +25,16 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -104,6 +111,12 @@ public class StatisticsPlaylistFragment
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.menu_history, menu);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Views
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@@ -155,6 +168,53 @@ public class StatisticsPlaylistFragment
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_history_clear:
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.delete_view_history_alert)
|
||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
||||
final Disposable onDelete = recordManager.deleteWholeStreamHistory()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> Toast.makeText(getContext(),
|
||||
R.string.view_history_deleted,
|
||||
Toast.LENGTH_SHORT).show(),
|
||||
throwable -> ErrorActivity.reportError(getContext(),
|
||||
throwable,
|
||||
SettingsActivity.class, null,
|
||||
ErrorActivity.ErrorInfo.make(
|
||||
UserAction.DELETE_FROM_HISTORY,
|
||||
"none",
|
||||
"Delete view history",
|
||||
R.string.general_error)));
|
||||
|
||||
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> {},
|
||||
throwable -> ErrorActivity.reportError(getContext(),
|
||||
throwable,
|
||||
SettingsActivity.class, null,
|
||||
ErrorActivity.ErrorInfo.make(
|
||||
UserAction.DELETE_FROM_HISTORY,
|
||||
"none",
|
||||
"Delete search history",
|
||||
R.string.general_error)));
|
||||
disposables.add(onClearOrphans);
|
||||
disposables.add(onDelete);
|
||||
}))
|
||||
.create()
|
||||
.show();
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Loading
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@@ -335,7 +395,7 @@ public class StatisticsPlaylistFragment
|
||||
deleteEntry(index);
|
||||
break;
|
||||
case 6:
|
||||
shareUrl(item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -555,7 +556,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
deleteItem(item);
|
||||
break;
|
||||
case 7:
|
||||
shareUrl(item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -57,6 +57,7 @@ import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.CollapsibleView;
|
||||
|
||||
@@ -425,7 +426,7 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
}
|
||||
|
||||
private void shareChannel (ChannelInfoItem selectedItem) {
|
||||
shareUrl(selectedItem.getName(), selectedItem.getUrl());
|
||||
ShareUtils.shareUrl(this.getContext(), selectedItem.getName(), selectedItem.getUrl());
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
|
||||
@@ -147,11 +147,16 @@ public class SubscriptionService {
|
||||
}
|
||||
|
||||
private boolean isSubscriptionUpToDate(final ChannelInfo info, final SubscriptionEntity entity) {
|
||||
return info.getUrl().equals(entity.getUrl()) &&
|
||||
return equalsAndNotNull(info.getUrl(), entity.getUrl()) &&
|
||||
info.getServiceId() == entity.getServiceId() &&
|
||||
info.getName().equals(entity.getName()) &&
|
||||
info.getAvatarUrl().equals(entity.getAvatarUrl()) &&
|
||||
info.getDescription().equals(entity.getDescription()) &&
|
||||
equalsAndNotNull(info.getAvatarUrl(), entity.getAvatarUrl()) &&
|
||||
equalsAndNotNull(info.getDescription(), entity.getDescription()) &&
|
||||
info.getSubscriberCount() == entity.getSubscriberCount();
|
||||
}
|
||||
|
||||
private boolean equalsAndNotNull(final Object o1, final Object o2) {
|
||||
return (o1 != null && o2 != null)
|
||||
&& o1.equals(o2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.schabi.newpipe.player;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
|
||||
/**
|
||||
* Fixes a leak caused by AudioManager using an Activity context.
|
||||
* Tracked at https://android-review.googlesource.com/#/c/140481/1 and
|
||||
* https://github.com/square/leakcanary/issues/205
|
||||
* Source:
|
||||
* https://gist.github.com/jankovd/891d96f476f7a9ce24e2
|
||||
*/
|
||||
public class AudioServiceLeakFix extends ContextWrapper {
|
||||
|
||||
AudioServiceLeakFix(Context base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
public static ContextWrapper preventLeakOf(Context base) {
|
||||
return new AudioServiceLeakFix(base);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSystemService(String name) {
|
||||
if (Context.AUDIO_SERVICE.equals(name)) {
|
||||
return getApplicationContext().getSystemService(name);
|
||||
}
|
||||
return super.getSystemService(name);
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,11 @@ public final class BackgroundPlayer extends Service {
|
||||
onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
@@ -270,6 +275,8 @@ public final class BackgroundPlayer extends Service {
|
||||
protected class BasePlayerImpl extends BasePlayer {
|
||||
|
||||
@NonNull final private AudioPlaybackResolver resolver;
|
||||
private int cachedDuration;
|
||||
private String cachedDurationString;
|
||||
|
||||
BasePlayerImpl(Context context) {
|
||||
super(context);
|
||||
@@ -344,10 +351,14 @@ public final class BackgroundPlayer extends Service {
|
||||
|
||||
if (!shouldUpdateOnProgress) return;
|
||||
resetNotification();
|
||||
if(Build.VERSION.SDK_INT >= 26 /*Oreo*/) updateNotificationThumbnail();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Oreo*/) updateNotificationThumbnail();
|
||||
if (bigNotRemoteView != null) {
|
||||
if(cachedDuration != duration) {
|
||||
cachedDuration = duration;
|
||||
cachedDurationString = getTimeString(duration);
|
||||
}
|
||||
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
|
||||
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration));
|
||||
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + cachedDurationString);
|
||||
}
|
||||
if (notRemoteView != null) {
|
||||
notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
|
||||
|
||||
@@ -44,6 +44,7 @@ import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
@@ -63,7 +64,6 @@ import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.mediasource.FailedMediaSource;
|
||||
import org.schabi.newpipe.player.playback.BasePlayerMediaSession;
|
||||
import org.schabi.newpipe.player.playback.CustomTrackSelector;
|
||||
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
||||
import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
@@ -113,7 +113,7 @@ public abstract class BasePlayer implements
|
||||
final protected HistoryRecordManager recordManager;
|
||||
|
||||
@NonNull
|
||||
final protected CustomTrackSelector trackSelector;
|
||||
final protected DefaultTrackSelector trackSelector;
|
||||
@NonNull
|
||||
final protected PlayerDataSource dataSource;
|
||||
|
||||
@@ -207,9 +207,8 @@ 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);
|
||||
this.trackSelector = new CustomTrackSelector(trackSelectionFactory);
|
||||
final TrackSelection.Factory trackSelectionFactory = PlayerHelper.getQualitySelector(context);
|
||||
this.trackSelector = new DefaultTrackSelector(trackSelectionFactory);
|
||||
|
||||
this.loadControl = new LoadController(context);
|
||||
this.renderFactory = new DefaultRenderersFactory(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);
|
||||
|
||||
@@ -76,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;
|
||||
|
||||
@@ -242,6 +243,11 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
isBackPressed = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context newBase) {
|
||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(newBase));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// State Saving
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -278,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 =
|
||||
@@ -354,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() {
|
||||
@@ -408,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;
|
||||
@@ -443,6 +441,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
|
||||
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);
|
||||
@@ -489,6 +488,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
closeButton.setOnClickListener(this);
|
||||
|
||||
moreOptionsButton.setOnClickListener(this);
|
||||
shareButton.setOnClickListener(this);
|
||||
toggleOrientationButton.setOnClickListener(this);
|
||||
switchBackgroundButton.setOnClickListener(this);
|
||||
switchPopupButton.setOnClickListener(this);
|
||||
@@ -639,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();
|
||||
|
||||
@@ -695,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();
|
||||
@@ -861,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
|
||||
);
|
||||
}
|
||||
@@ -1066,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)
|
||||
@@ -1092,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)
|
||||
|
||||
@@ -181,6 +181,11 @@ public final class PopupVideoPlayer extends Service {
|
||||
closePopup();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.schabi.newpipe.player;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.provider.Settings;
|
||||
@@ -653,11 +652,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
final int shuffleAlpha = shuffled ? 255 : 77;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
shuffleButton.setImageAlpha(shuffleAlpha);
|
||||
} else {
|
||||
shuffleButton.setAlpha(shuffleAlpha);
|
||||
}
|
||||
shuffleButton.setImageAlpha(shuffleAlpha);
|
||||
}
|
||||
|
||||
private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
|
||||
|
||||
@@ -212,7 +212,6 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
|
||||
@Override
|
||||
public void initListeners() {
|
||||
super.initListeners();
|
||||
playbackSeekBar.setOnSeekBarChangeListener(this);
|
||||
playbackSpeedTextView.setOnClickListener(this);
|
||||
qualityTextView.setOnClickListener(this);
|
||||
@@ -306,9 +305,9 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
captionItem.setOnMenuItemClickListener(menuItem -> {
|
||||
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
||||
if (textRendererIndex != RENDERER_UNAVAILABLE) {
|
||||
trackSelector.setPreferredTextLanguage(captionLanguage);
|
||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||
.setRendererDisabled(textRendererIndex, false));
|
||||
.setRendererDisabled(textRendererIndex, false)
|
||||
.setPreferredTextLanguage(captionLanguage));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -509,12 +508,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 +521,15 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
captionTextView.setVisibility(availableLanguages.isEmpty() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
// workaround to match normalized captions like english to English or deutsch to Deutsch
|
||||
private static boolean containsCaseInsensitive(List<String> list, String toFind) {
|
||||
for(String i : list){
|
||||
if(i.equalsIgnoreCase(toFind))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// General Player
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@@ -12,13 +12,11 @@ import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||||
|
||||
public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
AudioRendererEventListener {
|
||||
AnalyticsListener {
|
||||
|
||||
private static final String TAG = "AudioFocusReactor";
|
||||
|
||||
@@ -42,7 +40,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
this.player = player;
|
||||
this.context = context;
|
||||
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
player.addAudioDebugListener(this);
|
||||
player.addAnalyticsListener(this);
|
||||
|
||||
if (SHOULD_BUILD_FOCUS_REQUEST) {
|
||||
request = new AudioFocusRequest.Builder(FOCUS_GAIN_TYPE)
|
||||
@@ -57,7 +55,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
|
||||
public void dispose() {
|
||||
abandonAudioFocus();
|
||||
player.removeAudioDebugListener(this);
|
||||
player.removeAnalyticsListener(this);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -164,29 +162,12 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onAudioSessionId(int i) {
|
||||
public void onAudioSessionId(EventTime eventTime, int audioSessionId) {
|
||||
if (!PlayerHelper.isUsingDSP(context)) return;
|
||||
|
||||
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
|
||||
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, i);
|
||||
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId);
|
||||
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioEnabled(DecoderCounters decoderCounters) {}
|
||||
|
||||
@Override
|
||||
public void onAudioDecoderInitialized(String s, long l, long l1) {}
|
||||
|
||||
@Override
|
||||
public void onAudioInputFormatChanged(Format format) {}
|
||||
|
||||
@Override
|
||||
public void onAudioSinkUnderrun(int bufferSize,
|
||||
long bufferSizeMs,
|
||||
long elapsedSinceLastFeedMs) {}
|
||||
|
||||
@Override
|
||||
public void onAudioDisabled(DecoderCounters decoderCounters) {}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,14 @@ import java.io.File;
|
||||
|
||||
public CacheFactory(@NonNull final Context context,
|
||||
@NonNull final String userAgent,
|
||||
@NonNull final TransferListener<? super DataSource> transferListener) {
|
||||
@NonNull final TransferListener transferListener) {
|
||||
this(context, userAgent, transferListener, PlayerHelper.getPreferredCacheSize(context),
|
||||
PlayerHelper.getPreferredFileSize(context));
|
||||
}
|
||||
|
||||
private CacheFactory(@NonNull final Context context,
|
||||
@NonNull final String userAgent,
|
||||
@NonNull final TransferListener<? super DataSource> transferListener,
|
||||
@NonNull final TransferListener transferListener,
|
||||
final long maxCacheSize,
|
||||
final long maxFileSize) {
|
||||
this.maxFileSize = maxFileSize;
|
||||
|
||||
@@ -2,17 +2,12 @@ package org.schabi.newpipe.player.helper;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.LoadControl;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
||||
|
||||
import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS;
|
||||
import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_TARGET_BUFFER_BYTES;
|
||||
|
||||
public class LoadController implements LoadControl {
|
||||
|
||||
@@ -36,15 +31,10 @@ public class LoadController implements LoadControl {
|
||||
final int optimalPlaybackBufferMs) {
|
||||
this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000;
|
||||
|
||||
final DefaultAllocator allocator = new DefaultAllocator(true,
|
||||
C.DEFAULT_BUFFER_SEGMENT_SIZE);
|
||||
|
||||
internalLoadControl = new DefaultLoadControl(allocator,
|
||||
/*minBufferMs=*/minimumPlaybackbufferMs,
|
||||
/*maxBufferMs=*/optimalPlaybackBufferMs,
|
||||
/*bufferForPlaybackMs=*/initialPlaybackBufferMs,
|
||||
/*bufferForPlaybackAfterRebufferMs=*/initialPlaybackBufferMs,
|
||||
DEFAULT_TARGET_BUFFER_BYTES, DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS);
|
||||
DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
|
||||
builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs,
|
||||
initialPlaybackBufferMs, initialPlaybackBufferMs);
|
||||
internalLoadControl = builder.createDefaultLoadControl();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
public class PlayerDataSource {
|
||||
@@ -24,7 +25,7 @@ public class PlayerDataSource {
|
||||
|
||||
public PlayerDataSource(@NonNull final Context context,
|
||||
@NonNull final String userAgent,
|
||||
@NonNull final TransferListener<? super DataSource> transferListener) {
|
||||
@NonNull final TransferListener transferListener) {
|
||||
cacheDataSourceFactory = new CacheFactory(context, userAgent, transferListener);
|
||||
cachelessDataSourceFactory = new DefaultDataSourceFactory(context, userAgent, transferListener);
|
||||
}
|
||||
@@ -32,21 +33,21 @@ public class PlayerDataSource {
|
||||
public SsMediaSource.Factory getLiveSsMediaSourceFactory() {
|
||||
return new SsMediaSource.Factory(new DefaultSsChunkSource.Factory(
|
||||
cachelessDataSourceFactory), cachelessDataSourceFactory)
|
||||
.setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY)
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
|
||||
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
|
||||
}
|
||||
|
||||
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
|
||||
return new HlsMediaSource.Factory(cachelessDataSourceFactory)
|
||||
.setAllowChunklessPreparation(true)
|
||||
.setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY);
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
|
||||
}
|
||||
|
||||
public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
|
||||
return new DashMediaSource.Factory(new DefaultDashChunkSource.Factory(
|
||||
cachelessDataSourceFactory), cachelessDataSourceFactory)
|
||||
.setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY)
|
||||
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
|
||||
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS, true);
|
||||
}
|
||||
|
||||
public SsMediaSource.Factory getSsMediaSourceFactory() {
|
||||
@@ -65,7 +66,7 @@ public class PlayerDataSource {
|
||||
|
||||
public ExtractorMediaSource.Factory getExtractorMediaSourceFactory() {
|
||||
return new ExtractorMediaSource.Factory(cacheDataSourceFactory)
|
||||
.setMinLoadableRetryCount(EXTRACTOR_MINIMUM_RETRY);
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
|
||||
}
|
||||
|
||||
public ExtractorMediaSource.Factory getExtractorMediaSourceFactory(@NonNull final String key) {
|
||||
|
||||
@@ -14,7 +14,6 @@ import com.google.android.exoplayer2.text.CaptionStyleCompat;
|
||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
@@ -70,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()
|
||||
@@ -240,9 +239,8 @@ public class PlayerHelper {
|
||||
return 60000;
|
||||
}
|
||||
|
||||
public static TrackSelection.Factory getQualitySelector(@NonNull final Context context,
|
||||
@NonNull final BandwidthMeter meter) {
|
||||
return new AdaptiveTrackSelection.Factory(meter,
|
||||
public static TrackSelection.Factory getQualitySelector(@NonNull final Context context) {
|
||||
return new AdaptiveTrackSelection.Factory(
|
||||
/*bufferDurationRequiredForQualityIncrease=*/1000,
|
||||
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
@@ -79,7 +80,7 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -88,7 +89,7 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo
|
||||
|
||||
|
||||
@Override
|
||||
protected void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {
|
||||
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
Log.e(TAG, "Loading failed source: ", error);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@ package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
@@ -36,9 +37,8 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareSource(ExoPlayer player, boolean isTopLevelSource,
|
||||
SourceInfoRefreshListener listener) {
|
||||
source.prepareSource(player, isTopLevelSource, listener);
|
||||
public void prepareSource(SourceInfoRefreshListener listener, @Nullable TransferListener mediaTransferListener) {
|
||||
source.prepareSource(listener, mediaTransferListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,8 +47,8 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
|
||||
return source.createPeriod(id, allocator);
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||
return source.createPeriod(id, allocator, startPositionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
@@ -86,21 +86,22 @@ public class ManagedMediaSourcePlaylist {
|
||||
/**
|
||||
* Invalidates the {@link ManagedMediaSource} at the given index by replacing it
|
||||
* with a {@link PlaceholderMediaSource}.
|
||||
* @see #update(int, ManagedMediaSource, Runnable)
|
||||
* @see #update(int, ManagedMediaSource, Handler, Runnable)
|
||||
* */
|
||||
public synchronized void invalidate(final int index,
|
||||
@Nullable final Handler handler,
|
||||
@Nullable final Runnable finalizingAction) {
|
||||
if (get(index) instanceof PlaceholderMediaSource) return;
|
||||
update(index, new PlaceholderMediaSource(), finalizingAction);
|
||||
update(index, new PlaceholderMediaSource(), handler, finalizingAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
|
||||
* at the given index with a given {@link ManagedMediaSource}.
|
||||
* @see #update(int, ManagedMediaSource, Runnable)
|
||||
* @see #update(int, ManagedMediaSource, Handler, Runnable)
|
||||
* */
|
||||
public synchronized void update(final int index, @NonNull final ManagedMediaSource source) {
|
||||
update(index, source, /*doNothing=*/null);
|
||||
update(index, source, null, /*doNothing=*/null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,9 +109,10 @@ public class ManagedMediaSourcePlaylist {
|
||||
* at the given index with a given {@link ManagedMediaSource}. If the index is out of bound,
|
||||
* then the replacement is ignored.
|
||||
* @see ConcatenatingMediaSource#addMediaSource
|
||||
* @see ConcatenatingMediaSource#removeMediaSource(int, Runnable)
|
||||
* @see ConcatenatingMediaSource#removeMediaSource(int, Handler, Runnable)
|
||||
* */
|
||||
public synchronized void update(final int index, @NonNull final ManagedMediaSource source,
|
||||
@Nullable final Handler handler,
|
||||
@Nullable final Runnable finalizingAction) {
|
||||
if (index < 0 || index >= internalSource.getSize()) return;
|
||||
|
||||
@@ -126,6 +128,6 @@ public class ManagedMediaSourcePlaylist {
|
||||
|
||||
// Because of the above race condition, it is thus only safe to synchronize the player
|
||||
// in the finalizing action AFTER the removal is complete and the timeline has changed.
|
||||
internalSource.removeMediaSource(index, finalizingAction);
|
||||
internalSource.removeMediaSource(index, handler, finalizingAction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
public class PlaceholderMediaSource extends BaseMediaSource implements ManagedMediaSource {
|
||||
// Do nothing, so this will stall the playback
|
||||
@Override public void maybeThrowSourceInfoRefreshError() {}
|
||||
@Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { return null; }
|
||||
@Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { return null; }
|
||||
@Override public void releasePeriod(MediaPeriod mediaPeriod) {}
|
||||
@Override protected void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {}
|
||||
@Override protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {}
|
||||
@Override protected void releaseSourceInternal() {}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
package org.schabi.newpipe.player.playback;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
|
||||
/**
|
||||
* This class allows irregular text language labels for use when selecting text captions and
|
||||
* 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.
|
||||
* */
|
||||
public class CustomTrackSelector extends DefaultTrackSelector {
|
||||
private static final int WITHIN_RENDERER_CAPABILITIES_BONUS = 1000;
|
||||
|
||||
private String preferredTextLanguage;
|
||||
|
||||
public CustomTrackSelector(TrackSelection.Factory adaptiveTrackSelectionFactory) {
|
||||
super(adaptiveTrackSelectionFactory);
|
||||
}
|
||||
|
||||
public String getPreferredTextLanguage() {
|
||||
return preferredTextLanguage;
|
||||
}
|
||||
|
||||
public void setPreferredTextLanguage(@NonNull final String label) {
|
||||
Assertions.checkNotNull(label);
|
||||
if (!label.equals(preferredTextLanguage)) {
|
||||
preferredTextLanguage = label;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/** @see DefaultTrackSelector#formatHasLanguage(Format, String)*/
|
||||
protected static boolean formatHasLanguage(Format format, String language) {
|
||||
return language != null && TextUtils.equals(language, format.language);
|
||||
}
|
||||
|
||||
/** @see DefaultTrackSelector#formatHasNoLanguage(Format)*/
|
||||
protected static boolean formatHasNoLanguage(Format format) {
|
||||
return TextUtils.isEmpty(format.language) || formatHasLanguage(format, C.LANGUAGE_UNDETERMINED);
|
||||
}
|
||||
|
||||
/** @see DefaultTrackSelector#selectTextTrack(TrackGroupArray, int[][], Parameters) */
|
||||
@Override
|
||||
protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport,
|
||||
Parameters params) {
|
||||
TrackGroup selectedGroup = null;
|
||||
int selectedTrackIndex = 0;
|
||||
int selectedTrackScore = 0;
|
||||
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
|
||||
TrackGroup trackGroup = groups.get(groupIndex);
|
||||
int[] trackFormatSupport = formatSupport[groupIndex];
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
if (isSupported(trackFormatSupport[trackIndex],
|
||||
params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
Format format = trackGroup.getFormat(trackIndex);
|
||||
int maskedSelectionFlags =
|
||||
format.selectionFlags & ~params.disabledTextTrackSelectionFlags;
|
||||
boolean isDefault = (maskedSelectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
|
||||
boolean isForced = (maskedSelectionFlags & C.SELECTION_FLAG_FORCED) != 0;
|
||||
int trackScore;
|
||||
boolean preferredLanguageFound = formatHasLanguage(format, preferredTextLanguage);
|
||||
if (preferredLanguageFound
|
||||
|| (params.selectUndeterminedTextLanguage && formatHasNoLanguage(format))) {
|
||||
if (isDefault) {
|
||||
trackScore = 8;
|
||||
} else if (!isForced) {
|
||||
// Prefer non-forced to forced if a preferred text language has been specified. Where
|
||||
// both are provided the non-forced track will usually contain the forced subtitles as
|
||||
// a subset.
|
||||
trackScore = 6;
|
||||
} else {
|
||||
trackScore = 4;
|
||||
}
|
||||
trackScore += preferredLanguageFound ? 1 : 0;
|
||||
} else if (isDefault) {
|
||||
trackScore = 3;
|
||||
} else if (isForced) {
|
||||
if (formatHasLanguage(format, params.preferredAudioLanguage)) {
|
||||
trackScore = 2;
|
||||
} else {
|
||||
trackScore = 1;
|
||||
}
|
||||
} else {
|
||||
// Track should not be selected.
|
||||
continue;
|
||||
}
|
||||
if (isSupported(trackFormatSupport[trackIndex], false)) {
|
||||
trackScore += WITHIN_RENDERER_CAPABILITIES_BONUS;
|
||||
}
|
||||
if (trackScore > selectedTrackScore) {
|
||||
selectedGroup = trackGroup;
|
||||
selectedTrackIndex = trackIndex;
|
||||
selectedTrackScore = trackScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return selectedGroup == null ? null
|
||||
: new FixedTrackSelection(selectedGroup, selectedTrackIndex);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
package org.schabi.newpipe.player.playback;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.util.ArraySet;
|
||||
@@ -103,6 +103,8 @@ public class MediaSourceManager {
|
||||
|
||||
@NonNull private ManagedMediaSourcePlaylist playlist;
|
||||
|
||||
private Handler removeMediaSourceHandler = new Handler();
|
||||
|
||||
public MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue) {
|
||||
this(listener, playQueue, /*loadDebounceMillis=*/400L,
|
||||
@@ -395,7 +397,7 @@ public class MediaSourceManager {
|
||||
if (isCorrectionNeeded(item)) {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - Updating index=[" + itemIndex + "] with " +
|
||||
"title=[" + item.getTitle() + "] at url=[" + item.getUrl() + "]");
|
||||
playlist.update(itemIndex, mediaSource, this::maybeSynchronizePlayer);
|
||||
playlist.update(itemIndex, mediaSource, removeMediaSourceHandler, this::maybeSynchronizePlayer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,7 +443,7 @@ public class MediaSourceManager {
|
||||
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - Reloading currently playing, " +
|
||||
"index=[" + currentIndex + "], item=[" + currentItem.getTitle() + "]");
|
||||
playlist.invalidate(currentIndex, this::loadImmediate);
|
||||
playlist.invalidate(currentIndex, removeMediaSourceHandler, this::loadImmediate);
|
||||
}
|
||||
|
||||
private void maybeClearLoaders() {
|
||||
|
||||
@@ -16,6 +16,11 @@ public final class SinglePlayQueue extends PlayQueue {
|
||||
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
||||
}
|
||||
|
||||
public SinglePlayQueue(final StreamInfo info, final long startPosition) {
|
||||
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
||||
getItem().setRecoveryPosition(startPosition);
|
||||
}
|
||||
|
||||
public SinglePlayQueue(final List<StreamInfoItem> items, final int index) {
|
||||
super(index, playQueueItemsOf(items));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -34,11 +35,11 @@ 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.fragments.list.comments.CommentsFragment;
|
||||
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
|
||||
import org.schabi.newpipe.local.feed.FeedFragment;
|
||||
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;
|
||||
@@ -422,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);
|
||||
|
||||
22
app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
Normal file
22
app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class ShareUtils {
|
||||
public static void openUrlInBrowser(Context context, String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title)));
|
||||
}
|
||||
|
||||
public static void shareUrl(Context context, String subject, String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user