diff --git a/README.md b/README.md index 0d615f43c..6c1aa3d4b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@

WebsiteBlogPress


-WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS. +WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY. + +PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS. ## Screenshots diff --git a/app/build.gradle b/app/build.gradle index 2d46fcc90..f89f1d004 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "org.schabi.newpipe" minSdkVersion 19 targetSdkVersion 28 - versionCode 71 - versionName "0.15.1" + versionCode 720 + versionName "0.16.0" 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:8de53111d9' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:aa4f03a' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java index af9b88ac1..6a6d1b9c2 100644 --- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java +++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java @@ -2,11 +2,13 @@ package org.schabi.newpipe; import android.app.Application; import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; +import android.net.ConnectivityManager; import android.net.Uri; import android.os.AsyncTask; import android.preference.PreferenceManager; @@ -68,6 +70,8 @@ public class CheckForNewAppVersionTask extends AsyncTask { @Override protected String doInBackground(Void... voids) { + + if(isCancelled() || !isConnected()) return null; // Make a network request to get latest NewPipe data. if (client == null) { @@ -227,4 +231,12 @@ public class CheckForNewAppVersionTask extends AsyncTask { return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1); } + + private boolean isConnected() { + + ConnectivityManager cm = + (ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE); + return cm.getActiveNetworkInfo() != null + && cm.getActiveNetworkInfo().isConnected(); + } } diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index b8941670f..f040dc8b4 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -36,12 +36,12 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; -import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; @@ -81,10 +81,13 @@ public class RouterActivity extends AppCompatActivity { protected int selectedPreviously = -1; protected String currentUrl; + protected boolean internalRoute = false; protected final CompositeDisposable disposables = new CompositeDisposable(); private boolean selectionIsDownload = false; + public static final String internalRouteKey = "internalRoute"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -94,11 +97,13 @@ public class RouterActivity extends AppCompatActivity { currentUrl = getUrl(getIntent()); if (TextUtils.isEmpty(currentUrl)) { - Toast.makeText(this, R.string.invalid_url_toast, Toast.LENGTH_LONG).show(); + handleText(); finish(); } } + internalRoute = getIntent().getBooleanExtra(internalRouteKey, false); + setTheme(ThemeHelper.isLightThemeSelected(this) ? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark); } @@ -112,7 +117,7 @@ public class RouterActivity extends AppCompatActivity { @Override protected void onStart() { super.onStart(); - + handleUrl(currentUrl); } @@ -353,6 +358,15 @@ public class RouterActivity extends AppCompatActivity { positiveButton.setEnabled(state); } + private void handleText(){ + String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT); + int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0); + Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + NavigationHelper.openSearch(getThemeWrapperContext(),serviceId,searchString); + } + private void handleChoice(final String selectedChoiceKey) { final List validChoicesList = Arrays.asList(getResources().getStringArray(R.array.preferred_open_action_values_list)); if (validChoicesList.contains(selectedChoiceKey)) { @@ -383,8 +397,10 @@ public class RouterActivity extends AppCompatActivity { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(intent -> { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + if(!internalRoute){ + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + } startActivity(intent); finish(); diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java index 251e4c730..7ee686a66 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java @@ -47,7 +47,7 @@ public class DownloadActivity extends AppCompatActivity { @Override public void onGlobalLayout() { updateFragments(); - getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this); + getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this); } }); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index acee1f111..4546483d2 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -230,21 +230,4 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView, ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId)); } - - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - - protected void openUrlInBrowser(String url) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title))); - } - - protected void shareUrl(String subject, String url) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_SUBJECT, subject); - intent.putExtra(Intent.EXTRA_TEXT, url); - startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title))); - } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java new file mode 100644 index 000000000..0666667d6 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java @@ -0,0 +1,17 @@ +package org.schabi.newpipe.fragments; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.schabi.newpipe.BaseFragment; +import org.schabi.newpipe.R; + +public class EmptyFragment extends BaseFragment { + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_empty, container, false); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java index 27cc3ec8a..8314f9539 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java @@ -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(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 269125e27..bbd1a315d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -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); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index b61fe0d02..dbc3dd8a2 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -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 extends BaseStateFragment implem if (context == null || context.getResources() == null || getActivity() == null) return; final String[] commands = new String[]{ + context.getResources().getString(R.string.direct_on_background), context.getResources().getString(R.string.enqueue_on_background), context.getResources().getString(R.string.enqueue_on_popup), context.getResources().getString(R.string.append_playlist), @@ -264,19 +266,22 @@ public abstract class BaseListFragment extends BaseStateFragment implem final DialogInterface.OnClickListener actions = (dialogInterface, i) -> { switch (i) { case 0: - NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); + NavigationHelper.playOnBackgroundPlayer(context, new SinglePlayQueue(item)); break; case 1: - NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item)); + NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); break; case 2: + NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item)); + break; + case 3: if (getFragmentManager() != null) { PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item)) .show(getFragmentManager(), TAG); } break; - case 3: - shareUrl(item.getName(), item.getUrl()); + case 4: + ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl()); break; default: break; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index b9489ffa7..71865b04d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -46,6 +46,7 @@ import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.ShareUtils; import java.util.ArrayList; import java.util.Collections; @@ -190,7 +191,7 @@ public class ChannelFragment extends BaseListInfoFragment { } break; case 6: - shareUrl(item.getName(), item.getUrl()); + ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl()); break; default: break; @@ -233,10 +234,10 @@ public class ChannelFragment extends BaseListInfoFragment { openRssFeed(); break; case R.id.menu_item_openInBrowser: - openUrlInBrowser(currentInfo.getOriginalUrl()); + ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl()); break; case R.id.menu_item_share: - shareUrl(name, currentInfo.getOriginalUrl()); + ShareUtils.shareUrl(this.getContext(), name, currentInfo.getOriginalUrl()); break; default: return super.onOptionsItemSelected(item); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 0019a3819..2a775fe8f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -40,6 +40,7 @@ import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.ThemeHelper; import java.util.ArrayList; @@ -168,7 +169,7 @@ public class PlaylistFragment extends BaseListInfoFragment { NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index)); break; case 5: - shareUrl(item.getName(), item.getUrl()); + ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl()); break; default: break; @@ -230,10 +231,10 @@ public class PlaylistFragment extends BaseListInfoFragment { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_item_openInBrowser: - openUrlInBrowser(url); + ShareUtils.openUrlInBrowser(this.getContext(), url); break; case R.id.menu_item_share: - shareUrl(name, url); + ShareUtils.shareUrl(this.getContext(), name, url); break; case R.id.menu_item_bookmark: onBookmarkClicked(); @@ -305,6 +306,16 @@ public class PlaylistFragment extends BaseListInfoFragment { NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); headerBackgroundButton.setOnClickListener(view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); + + headerPopupButton.setOnLongClickListener(view -> { + NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue()); + return true; + }); + + headerBackgroundButton.setOnLongClickListener(view -> { + NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue()); + return true; + }); } private PlayQueue getPlayQueue() { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index e20d6bad3..a3b01f251 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -12,6 +12,7 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.TooltipCompat; +import android.support.v7.widget.helper.ItemTouchHelper; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -45,10 +46,9 @@ import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; -import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.AnimationUtils; +import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.ExtractorHelper; -import org.schabi.newpipe.util.LayoutManagerSmoothScroller; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ServiceHelper; @@ -73,8 +73,8 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; +import static android.support.v7.widget.helper.ItemTouchHelper.Callback.makeMovementFlags; import static java.util.Arrays.asList; - import static org.schabi.newpipe.util.AnimationUtils.animateView; public class SearchFragment @@ -298,7 +298,23 @@ public class SearchFragment suggestionsPanel = rootView.findViewById(R.id.suggestions_panel); suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list); suggestionsRecyclerView.setAdapter(suggestionListAdapter); - suggestionsRecyclerView.setLayoutManager(new LayoutManagerSmoothScroller(activity)); + new ItemTouchHelper(new ItemTouchHelper.Callback() { + @Override + public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { + return getSuggestionMovementFlags(recyclerView, viewHolder); + } + + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, + @NonNull RecyclerView.ViewHolder viewHolder1) { + return false; + } + + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) { + onSuggestionItemSwiped(viewHolder, i); + } + }).attachToRecyclerView(suggestionsRecyclerView); searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container); searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text); @@ -901,4 +917,28 @@ public class SearchFragment return true; } + + /*////////////////////////////////////////////////////////////////////////// + // Suggestion item touch helper + //////////////////////////////////////////////////////////////////////////*/ + + public int getSuggestionMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { + final int position = viewHolder.getAdapterPosition(); + final SuggestionItem item = suggestionListAdapter.getItem(position); + return item.fromHistory ? makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0; + } + + public void onSuggestionItemSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) { + final int position = viewHolder.getAdapterPosition(); + final String query = suggestionListAdapter.getItem(position).query; + final Disposable onDelete = historyRecordManager.deleteSearchHistory(query) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + howManyDeleted -> suggestionPublisher + .onNext(searchEditText.getText().toString()), + throwable -> showSnackBarError(throwable, + UserAction.DELETE_FROM_HISTORY, "none", + "Deleting item failed", R.string.general_error)); + disposables.add(onDelete); + } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java index 7b5f72c53..3f4e9af0b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java @@ -75,7 +75,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter 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); + } } diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 32083fd42..5a62a3969 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -8,7 +8,11 @@ import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; +import android.support.v7.app.AlertDialog; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; @@ -21,13 +25,16 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import org.schabi.newpipe.extractor.stream.StreamInfoItem; -import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.info_list.InfoItemDialog; +import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; +import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; +import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.ThemeHelper; import java.util.ArrayList; @@ -104,6 +111,12 @@ public class StatisticsPlaylistFragment } } + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.menu_history, menu); + } + /////////////////////////////////////////////////////////////////////////// // Fragment LifeCycle - Views /////////////////////////////////////////////////////////////////////////// @@ -155,6 +168,53 @@ public class StatisticsPlaylistFragment }); } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_history_clear: + new AlertDialog.Builder(activity) + .setTitle(R.string.delete_view_history_alert) + .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss())) + .setPositiveButton(R.string.delete, ((dialog, which) -> { + final Disposable onDelete = recordManager.deleteWholeStreamHistory() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + howManyDeleted -> Toast.makeText(getContext(), + R.string.view_history_deleted, + Toast.LENGTH_SHORT).show(), + throwable -> ErrorActivity.reportError(getContext(), + throwable, + SettingsActivity.class, null, + ErrorActivity.ErrorInfo.make( + UserAction.DELETE_FROM_HISTORY, + "none", + "Delete view history", + R.string.general_error))); + + final Disposable onClearOrphans = recordManager.removeOrphanedRecords() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + howManyDeleted -> {}, + throwable -> ErrorActivity.reportError(getContext(), + throwable, + SettingsActivity.class, null, + ErrorActivity.ErrorInfo.make( + UserAction.DELETE_FROM_HISTORY, + "none", + "Delete search history", + R.string.general_error))); + disposables.add(onClearOrphans); + disposables.add(onDelete); + })) + .create() + .show(); + break; + default: + return super.onOptionsItemSelected(item); + } + return true; + } + /////////////////////////////////////////////////////////////////////////// // Fragment LifeCycle - Loading /////////////////////////////////////////////////////////////////////////// @@ -335,7 +395,7 @@ public class StatisticsPlaylistFragment deleteEntry(index); break; case 6: - shareUrl(item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl()); + ShareUtils.shareUrl(this.getContext(), item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl()); break; default: break; diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index f400061e1..dc101fade 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -34,6 +34,7 @@ import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; +import org.schabi.newpipe.util.ShareUtils; import java.util.ArrayList; import java.util.Collections; @@ -555,7 +556,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment= 26 /*Oreo*/) updateNotificationThumbnail(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Oreo*/) updateNotificationThumbnail(); if (bigNotRemoteView != null) { + if(cachedDuration != duration) { + cachedDuration = duration; + cachedDurationString = getTimeString(duration); + } bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false); - bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration)); + bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + cachedDurationString); } if (notRemoteView != null) { notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false); diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index d1b06c9c5..22c69fdd4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -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); diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index cc906bbca..0bb9c7b2b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -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) diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 8ea3d509c..7578c444c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -181,6 +181,11 @@ public final class PopupVideoPlayer extends Service { closePopup(); } + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base)); + } + @Override public IBinder onBind(Intent intent) { return mBinder; diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 2ec4275fc..3e04f1e3a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.player; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; -import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.provider.Settings; @@ -653,11 +652,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity } final int shuffleAlpha = shuffled ? 255 : 77; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - shuffleButton.setImageAlpha(shuffleAlpha); - } else { - shuffleButton.setAlpha(shuffleAlpha); - } + shuffleButton.setImageAlpha(shuffleAlpha); } private void onPlaybackParameterChanged(final PlaybackParameters parameters) { diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index d30d9b8be..fb60ac473 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -212,7 +212,6 @@ public abstract class VideoPlayer extends BasePlayer @Override public void initListeners() { - super.initListeners(); playbackSeekBar.setOnSeekBarChangeListener(this); playbackSpeedTextView.setOnClickListener(this); qualityTextView.setOnClickListener(this); @@ -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 list, String toFind) { + for(String i : list){ + if(i.equalsIgnoreCase(toFind)) + return true; + } + return false; + } + /*////////////////////////////////////////////////////////////////////////// // General Player //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java index f148aed27..24d1ee1ca 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java @@ -12,13 +12,11 @@ import android.os.Build; import android.support.annotation.NonNull; import android.util.Log; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.audio.AudioRendererEventListener; -import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.analytics.AnalyticsListener; public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, - AudioRendererEventListener { + AnalyticsListener { private static final String TAG = "AudioFocusReactor"; @@ -42,7 +40,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, this.player = player; this.context = context; this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - player.addAudioDebugListener(this); + player.addAnalyticsListener(this); if (SHOULD_BUILD_FOCUS_REQUEST) { request = new AudioFocusRequest.Builder(FOCUS_GAIN_TYPE) @@ -57,7 +55,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, public void dispose() { abandonAudioFocus(); - player.removeAudioDebugListener(this); + player.removeAnalyticsListener(this); } /*////////////////////////////////////////////////////////////////////////// @@ -164,29 +162,12 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAudioSessionId(int i) { + public void onAudioSessionId(EventTime eventTime, int audioSessionId) { if (!PlayerHelper.isUsingDSP(context)) return; final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); - intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, i); + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId); intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName()); context.sendBroadcast(intent); } - - @Override - public void onAudioEnabled(DecoderCounters decoderCounters) {} - - @Override - public void onAudioDecoderInitialized(String s, long l, long l1) {} - - @Override - public void onAudioInputFormatChanged(Format format) {} - - @Override - public void onAudioSinkUnderrun(int bufferSize, - long bufferSizeMs, - long elapsedSinceLastFeedMs) {} - - @Override - public void onAudioDisabled(DecoderCounters decoderCounters) {} } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java index b8d8dc12f..091efc942 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java @@ -33,14 +33,14 @@ import java.io.File; public CacheFactory(@NonNull final Context context, @NonNull final String userAgent, - @NonNull final TransferListener 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 transferListener, + @NonNull final TransferListener transferListener, final long maxCacheSize, final long maxFileSize) { this.maxFileSize = maxFileSize; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java index 7670deb98..4239dd62f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java @@ -2,17 +2,12 @@ package org.schabi.newpipe.player.helper; import android.content.Context; -import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.LoadControl; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.Allocator; -import com.google.android.exoplayer2.upstream.DefaultAllocator; - -import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS; -import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_TARGET_BUFFER_BYTES; public class LoadController implements LoadControl { @@ -36,15 +31,10 @@ public class LoadController implements LoadControl { final int optimalPlaybackBufferMs) { this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000; - final DefaultAllocator allocator = new DefaultAllocator(true, - C.DEFAULT_BUFFER_SEGMENT_SIZE); - - internalLoadControl = new DefaultLoadControl(allocator, - /*minBufferMs=*/minimumPlaybackbufferMs, - /*maxBufferMs=*/optimalPlaybackBufferMs, - /*bufferForPlaybackMs=*/initialPlaybackBufferMs, - /*bufferForPlaybackAfterRebufferMs=*/initialPlaybackBufferMs, - DEFAULT_TARGET_BUFFER_BYTES, DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS); + DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder(); + builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs, + initialPlaybackBufferMs, initialPlaybackBufferMs); + internalLoadControl = builder.createDefaultLoadControl(); } /*////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java index 133121269..5743891c2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java @@ -12,6 +12,7 @@ import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.TransferListener; public class PlayerDataSource { @@ -24,7 +25,7 @@ public class PlayerDataSource { public PlayerDataSource(@NonNull final Context context, @NonNull final String userAgent, - @NonNull final TransferListener transferListener) { + @NonNull final TransferListener transferListener) { cacheDataSourceFactory = new CacheFactory(context, userAgent, transferListener); cachelessDataSourceFactory = new DefaultDataSourceFactory(context, userAgent, transferListener); } @@ -32,21 +33,21 @@ public class PlayerDataSource { public SsMediaSource.Factory getLiveSsMediaSourceFactory() { return new SsMediaSource.Factory(new DefaultSsChunkSource.Factory( cachelessDataSourceFactory), cachelessDataSourceFactory) - .setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY) + .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)) .setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS); } public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() { return new HlsMediaSource.Factory(cachelessDataSourceFactory) .setAllowChunklessPreparation(true) - .setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY); + .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)); } public DashMediaSource.Factory getLiveDashMediaSourceFactory() { return new DashMediaSource.Factory(new DefaultDashChunkSource.Factory( cachelessDataSourceFactory), cachelessDataSourceFactory) - .setMinLoadableRetryCount(MANIFEST_MINIMUM_RETRY) - .setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS); + .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)) + .setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS, true); } public SsMediaSource.Factory getSsMediaSourceFactory() { @@ -65,7 +66,7 @@ public class PlayerDataSource { public ExtractorMediaSource.Factory getExtractorMediaSourceFactory() { return new ExtractorMediaSource.Factory(cacheDataSourceFactory) - .setMinLoadableRetryCount(EXTRACTOR_MINIMUM_RETRY); + .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY)); } public ExtractorMediaSource.Factory getExtractorMediaSourceFactory(@NonNull final String key) { diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index e1960247e..7248857b5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -14,7 +14,6 @@ import com.google.android.exoplayer2.text.CaptionStyleCompat; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; -import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.MimeTypes; import org.schabi.newpipe.R; @@ -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, diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java index 2f233c464..cc9cd36bc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java @@ -1,12 +1,13 @@ package org.schabi.newpipe.player.mediasource; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; -import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.source.BaseMediaSource; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.upstream.Allocator; +import com.google.android.exoplayer2.upstream.TransferListener; import org.schabi.newpipe.player.playqueue.PlayQueueItem; @@ -79,7 +80,7 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { return null; } @@ -88,7 +89,7 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo @Override - protected void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) { + protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { Log.e(TAG, "Loading failed source: ", error); } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java index c39b0a03d..d36a3e305 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java @@ -2,12 +2,13 @@ package org.schabi.newpipe.player.mediasource; import android.os.Handler; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; -import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.upstream.Allocator; +import com.google.android.exoplayer2.upstream.TransferListener; import org.schabi.newpipe.player.playqueue.PlayQueueItem; @@ -36,9 +37,8 @@ public class LoadedMediaSource implements ManagedMediaSource { } @Override - public void prepareSource(ExoPlayer player, boolean isTopLevelSource, - SourceInfoRefreshListener listener) { - source.prepareSource(player, isTopLevelSource, listener); + public void prepareSource(SourceInfoRefreshListener listener, @Nullable TransferListener mediaTransferListener) { + source.prepareSource(listener, mediaTransferListener); } @Override @@ -47,8 +47,8 @@ public class LoadedMediaSource implements ManagedMediaSource { } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { - return source.createPeriod(id, allocator); + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { + return source.createPeriod(id, allocator, startPositionUs); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java index 5fe107657..fe29707fc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java @@ -1,5 +1,5 @@ package org.schabi.newpipe.player.mediasource; - +import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -86,21 +86,22 @@ public class ManagedMediaSourcePlaylist { /** * Invalidates the {@link ManagedMediaSource} at the given index by replacing it * with a {@link PlaceholderMediaSource}. - * @see #update(int, ManagedMediaSource, Runnable) + * @see #update(int, ManagedMediaSource, Handler, Runnable) * */ public synchronized void invalidate(final int index, + @Nullable final Handler handler, @Nullable final Runnable finalizingAction) { if (get(index) instanceof PlaceholderMediaSource) return; - update(index, new PlaceholderMediaSource(), finalizingAction); + update(index, new PlaceholderMediaSource(), handler, finalizingAction); } /** * Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource} * at the given index with a given {@link ManagedMediaSource}. - * @see #update(int, ManagedMediaSource, Runnable) + * @see #update(int, ManagedMediaSource, Handler, Runnable) * */ public synchronized void update(final int index, @NonNull final ManagedMediaSource source) { - update(index, source, /*doNothing=*/null); + update(index, source, null, /*doNothing=*/null); } /** @@ -108,9 +109,10 @@ public class ManagedMediaSourcePlaylist { * at the given index with a given {@link ManagedMediaSource}. If the index is out of bound, * then the replacement is ignored. * @see ConcatenatingMediaSource#addMediaSource - * @see ConcatenatingMediaSource#removeMediaSource(int, Runnable) + * @see ConcatenatingMediaSource#removeMediaSource(int, Handler, Runnable) * */ public synchronized void update(final int index, @NonNull final ManagedMediaSource source, + @Nullable final Handler handler, @Nullable final Runnable finalizingAction) { if (index < 0 || index >= internalSource.getSize()) return; @@ -126,6 +128,6 @@ public class ManagedMediaSourcePlaylist { // Because of the above race condition, it is thus only safe to synchronize the player // in the finalizing action AFTER the removal is complete and the timeline has changed. - internalSource.removeMediaSource(index, finalizingAction); + internalSource.removeMediaSource(index, handler, finalizingAction); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java index bfd734393..377ca55a3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java @@ -1,20 +1,21 @@ package org.schabi.newpipe.player.mediasource; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; -import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.source.BaseMediaSource; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.upstream.Allocator; +import com.google.android.exoplayer2.upstream.TransferListener; import org.schabi.newpipe.player.playqueue.PlayQueueItem; public class PlaceholderMediaSource extends BaseMediaSource implements ManagedMediaSource { // Do nothing, so this will stall the playback @Override public void maybeThrowSourceInfoRefreshError() {} - @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { return null; } + @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { return null; } @Override public void releasePeriod(MediaPeriod mediaPeriod) {} - @Override protected void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {} + @Override protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {} @Override protected void releaseSourceInternal() {} @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java b/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java deleted file mode 100644 index efe6f3a58..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 3c5642d51..db8cc797e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -1,5 +1,5 @@ package org.schabi.newpipe.player.playback; - +import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.ArraySet; @@ -103,6 +103,8 @@ public class MediaSourceManager { @NonNull private ManagedMediaSourcePlaylist playlist; + private Handler removeMediaSourceHandler = new Handler(); + public MediaSourceManager(@NonNull final PlaybackListener listener, @NonNull final PlayQueue playQueue) { this(listener, playQueue, /*loadDebounceMillis=*/400L, @@ -395,7 +397,7 @@ public class MediaSourceManager { if (isCorrectionNeeded(item)) { if (DEBUG) Log.d(TAG, "MediaSource - Updating index=[" + itemIndex + "] with " + "title=[" + item.getTitle() + "] at url=[" + item.getUrl() + "]"); - playlist.update(itemIndex, mediaSource, this::maybeSynchronizePlayer); + playlist.update(itemIndex, mediaSource, removeMediaSourceHandler, this::maybeSynchronizePlayer); } } @@ -441,7 +443,7 @@ public class MediaSourceManager { if (DEBUG) Log.d(TAG, "MediaSource - Reloading currently playing, " + "index=[" + currentIndex + "], item=[" + currentItem.getTitle() + "]"); - playlist.invalidate(currentIndex, this::loadImmediate); + playlist.invalidate(currentIndex, removeMediaSourceHandler, this::loadImmediate); } private void maybeClearLoaders() { diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java index 5993481e2..40d1a11e7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java @@ -16,6 +16,11 @@ public final class SinglePlayQueue extends PlayQueue { super(0, Collections.singletonList(new PlayQueueItem(info))); } + public SinglePlayQueue(final StreamInfo info, final long startPosition) { + super(0, Collections.singletonList(new PlayQueueItem(info))); + getItem().setRecoveryPosition(startPosition); + } + public SinglePlayQueue(final List items, final int index) { super(index, playQueueItemsOf(items)); } diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java new file mode 100644 index 000000000..e1ecc662d --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java @@ -0,0 +1,131 @@ +package org.schabi.newpipe.util; + +import android.content.Context; +import android.text.Layout; +import android.text.Selection; +import android.text.Spannable; +import android.text.Spanned; +import android.text.style.ClickableSpan; +import android.text.style.URLSpan; +import android.view.MotionEvent; +import android.view.View; +import android.widget.TextView; + +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.SinglePlayQueue; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +public class CommentTextOnTouchListener implements View.OnTouchListener { + + public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener(); + + private static final Pattern timestampPattern = Pattern.compile("(.*)#timestamp=(\\d+)"); + + @Override + public boolean onTouch(View v, MotionEvent event) { + if(!(v instanceof TextView)){ + return false; + } + TextView widget = (TextView) v; + Object text = widget.getText(); + if (text instanceof Spanned) { + Spannable buffer = (Spannable) text; + + int action = event.getAction(); + + if (action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_DOWN) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + + x += widget.getScrollX(); + y += widget.getScrollY(); + + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + + ClickableSpan[] link = buffer.getSpans(off, off, + ClickableSpan.class); + + if (link.length != 0) { + if (action == MotionEvent.ACTION_UP) { + boolean handled = false; + if(link[0] instanceof URLSpan){ + handled = handleUrl(v.getContext(), (URLSpan) link[0]); + } + if(!handled) link[0].onClick(widget); + } else if (action == MotionEvent.ACTION_DOWN) { + Selection.setSelection(buffer, + buffer.getSpanStart(link[0]), + buffer.getSpanEnd(link[0])); + } + return true; + } + } + + } + + return false; + } + + private boolean handleUrl(Context context, URLSpan urlSpan) { + String url = urlSpan.getURL(); + int seconds = -1; + Matcher matcher = timestampPattern.matcher(url); + if(matcher.matches()){ + url = matcher.group(1); + seconds = Integer.parseInt(matcher.group(2)); + } + StreamingService service; + StreamingService.LinkType linkType; + try { + service = NewPipe.getServiceByUrl(url); + linkType = service.getLinkTypeByUrl(url); + } catch (ExtractionException e) { + return false; + } + if(linkType == StreamingService.LinkType.NONE){ + return false; + } + if(linkType == StreamingService.LinkType.STREAM && seconds != -1){ + return playOnPopup(context, url, service, seconds); + }else{ + NavigationHelper.openRouterActivity(context, url); + return true; + } + } + + private boolean playOnPopup(Context context, String url, StreamingService service, int seconds) { + LinkHandlerFactory factory = service.getStreamLHFactory(); + String cleanUrl = null; + try { + cleanUrl = factory.getUrl(factory.getId(url)); + } catch (ParsingException e) { + return false; + } + Single single = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false); + single.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(info -> { + PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info, seconds*1000); + NavigationHelper.playOnPopupPlayer(context, playQueue); + }); + return true; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 4b93600ce..98ae3a88a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -21,6 +21,7 @@ import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; +import org.schabi.newpipe.RouterActivity; import org.schabi.newpipe.about.AboutActivity; import org.schabi.newpipe.download.DownloadActivity; import org.schabi.newpipe.extractor.NewPipe; @@ -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); diff --git a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java new file mode 100644 index 000000000..c5c78a726 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java @@ -0,0 +1,22 @@ +package org.schabi.newpipe.util; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +import org.schabi.newpipe.R; + +public class ShareUtils { + public static void openUrlInBrowser(Context context, String url) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title))); + } + + public static void shareUrl(Context context, String subject, String url) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_SUBJECT, subject); + intent.putExtra(Intent.EXTRA_TEXT, url); + context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title))); + } +} diff --git a/app/src/main/res/layout-large-land/activity_main_player.xml b/app/src/main/res/layout-large-land/activity_main_player.xml index d01fb379d..8428d489a 100644 --- a/app/src/main/res/layout-large-land/activity_main_player.xml +++ b/app/src/main/res/layout-large-land/activity_main_player.xml @@ -249,7 +249,7 @@ android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_marginLeft="2dp" - android:padding="5dp" + android:padding="5dp" android:clickable="true" android:focusable="true" android:scaleType="fitXY" @@ -305,7 +305,7 @@ tools:text="English" /> + + diff --git a/app/src/main/res/layout/activity_history.xml b/app/src/main/res/layout/activity_history.xml deleted file mode 100644 index e53b9bff9..000000000 --- a/app/src/main/res/layout/activity_history.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml index 50a6c522b..a55a5df05 100644 --- a/app/src/main/res/layout/activity_main_player.xml +++ b/app/src/main/res/layout/activity_main_player.xml @@ -247,7 +247,7 @@ android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_marginLeft="2dp" - android:padding="5dp" + android:padding="5dp" android:clickable="true" android:focusable="true" android:scaleType="fitXY" @@ -303,7 +303,7 @@ tools:text="English" /> + + - - - -