diff --git a/README.md b/README.md index c852401c3..557c5d9be 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,11 @@ Project status: [](screenshots/screenshot_3.png) [](screenshots/screenshot_4.png) [](screenshots/screenshot_5.png) +[](screenshots/screenshot_6.png) +[](screenshots/screenshot_7.png) +[](screenshots/screenshot_8.png) +[](screenshots/screenshot_9.png) + ## Description diff --git a/app/build.gradle b/app/build.gradle index 79efd38ef..c5106adbf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "org.schabi.newpipe" minSdkVersion 15 targetSdkVersion 25 - versionCode 31 - versionName "0.9.4" + versionCode 32 + versionName "0.9.5" } buildTypes { release { diff --git a/app/src/main/java/org/schabi/newpipe/ThemableActivity.java b/app/src/main/java/org/schabi/newpipe/ThemableActivity.java deleted file mode 100644 index 8b0d126be..000000000 --- a/app/src/main/java/org/schabi/newpipe/ThemableActivity.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.schabi.newpipe; - -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.v7.app.AppCompatActivity; - -public class ThemableActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (PreferenceManager.getDefaultSharedPreferences(this) - .getString("theme", getResources().getString(R.string.light_theme_title)). - equals(getResources().getString(R.string.dark_theme_title))) { - setTheme(R.style.DarkTheme); - } - } - - @Override - protected void onResume() { - super.onResume(); - if (PreferenceManager.getDefaultSharedPreferences(this) - .getString("theme", getResources().getString(R.string.light_theme_title)). - equals(getResources().getString(R.string.dark_theme_title))) { - setTheme(R.style.DarkTheme); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseFragment.java index d51267104..5337912b8 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseFragment.java @@ -1,7 +1,5 @@ package org.schabi.newpipe.fragments; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; @@ -28,6 +26,8 @@ import org.schabi.newpipe.R; import java.util.concurrent.atomic.AtomicBoolean; +import static org.schabi.newpipe.util.AnimationUtils.animateView; + public abstract class BaseFragment extends Fragment { protected final String TAG = "BaseFragment@" + Integer.toHexString(hashCode()); protected static final boolean DEBUG = MainActivity.DEBUG; @@ -130,70 +130,6 @@ public abstract class BaseFragment extends Fragment { // Utils //////////////////////////////////////////////////////////////////////////*/ - public void animateView(final View view, final boolean enterOrExit, long duration) { - animateView(view, enterOrExit, duration, 0, null); - } - - public void animateView(final View view, final boolean enterOrExit, long duration, final Runnable execOnEnd) { - animateView(view, enterOrExit, duration, 0, execOnEnd); - } - - public void animateView(final View view, final boolean enterOrExit, long duration, long delay) { - animateView(view, enterOrExit, duration, delay, null); - } - - /** - * Animate the view - * - * @param view view that will be animated - * @param enterOrExit true to enter, false to exit - * @param duration how long the animation will take, in milliseconds - * @param delay how long the animation will take to start, in milliseconds - * @param execOnEnd runnable that will be executed when the animation ends - */ - public void animateView(final View view, final boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) { - if (DEBUG) Log.d(TAG, "animateView() called with: view = [" + view + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], execOnEnd = [" + execOnEnd + "]"); - if (view == null) return; - - if (view.getVisibility() == View.VISIBLE && enterOrExit) { - view.animate().setListener(null).cancel(); - view.setVisibility(View.VISIBLE); - view.setAlpha(1f); - if (execOnEnd != null) execOnEnd.run(); - return; - } else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) { - view.animate().setListener(null).cancel(); - view.setVisibility(View.GONE); - view.setAlpha(0f); - if (execOnEnd != null) execOnEnd.run(); - return; - } - - view.animate().setListener(null).cancel(); - view.setVisibility(View.VISIBLE); - - if (enterOrExit) { - view.animate().alpha(1f).setDuration(duration).setStartDelay(delay) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (execOnEnd != null) execOnEnd.run(); - } - }).start(); - } else { - view.animate().alpha(0f) - .setDuration(duration).setStartDelay(delay) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setVisibility(View.GONE); - if (execOnEnd != null) execOnEnd.run(); - } - }) - .start(); - } - } - protected void setErrorMessage(String message, boolean showRetryButton) { if (errorTextView == null || activity == null) return; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java index fa02dbfdb..032212ed6 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java @@ -36,6 +36,8 @@ import java.io.Serializable; import java.text.NumberFormat; import java.util.ArrayList; +import static org.schabi.newpipe.util.AnimationUtils.animateView; + public class ChannelFragment extends BaseFragment implements ChannelExtractorWorker.OnChannelInfoReceive { private final String TAG = "ChannelFragment@" + Integer.toHexString(hashCode()); 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 7ad62d99f..65ddc36ed 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 @@ -19,6 +19,7 @@ import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.util.Log; import android.util.TypedValue; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -39,7 +40,6 @@ import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; import org.schabi.newpipe.ImageErrorLoadingListener; -import org.schabi.newpipe.Localization; import org.schabi.newpipe.R; import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.download.DownloadDialog; @@ -55,6 +55,8 @@ import org.schabi.newpipe.player.MainVideoPlayer; import org.schabi.newpipe.player.PlayVideoActivity; import org.schabi.newpipe.player.PopupVideoPlayer; import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.util.Constants; +import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.Utils; @@ -64,6 +66,8 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Stack; +import static org.schabi.newpipe.util.AnimationUtils.animateView; + public class VideoDetailFragment extends BaseFragment implements StreamExtractorWorker.OnStreamInfoReceivedListener, SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener { private final String TAG = "VideoDetailFragment@" + Integer.toHexString(hashCode()); @@ -71,9 +75,6 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor private static final int INITIAL_RELATED_VIDEOS = 8; private static final String KORE_PACKET = "org.xbmc.kore"; - private static final String SERVICE_ID_KEY = "service_id_key"; - private static final String VIDEO_URL_KEY = "video_url_key"; - private static final String VIDEO_TITLE_KEY = "video_title_key"; private static final String STACK_KEY = "stack_key"; private static final String INFO_KEY = "info_key"; private static final String WAS_RELATED_EXPANDED_KEY = "was_related_expanded_key"; @@ -170,9 +171,9 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); if (savedInstanceState != null) { - videoTitle = savedInstanceState.getString(VIDEO_TITLE_KEY); - videoUrl = savedInstanceState.getString(VIDEO_URL_KEY); - serviceId = savedInstanceState.getInt(SERVICE_ID_KEY, 0); + videoTitle = savedInstanceState.getString(Constants.KEY_TITLE); + videoUrl = savedInstanceState.getString(Constants.KEY_URL); + serviceId = savedInstanceState.getInt(Constants.KEY_SERVICE_ID, 0); wasRelatedStreamsExpanded = savedInstanceState.getBoolean(WAS_RELATED_EXPANDED_KEY, false); Serializable serializable = savedInstanceState.getSerializable(STACK_KEY); if (serializable instanceof Stack) { @@ -289,9 +290,9 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor @Override public void onSaveInstanceState(Bundle outState) { if (DEBUG) Log.d(TAG, "onSaveInstanceState() called with: outState = [" + outState + "]"); - outState.putString(VIDEO_URL_KEY, videoUrl); - outState.putString(VIDEO_TITLE_KEY, videoTitle); - outState.putInt(SERVICE_ID_KEY, serviceId); + outState.putString(Constants.KEY_URL, videoUrl); + outState.putString(Constants.KEY_TITLE, videoTitle); + outState.putInt(Constants.KEY_SERVICE_ID, serviceId); outState.putSerializable(STACK_KEY, stack); int nextCount = currentStreamInfo != null && currentStreamInfo.next_video != null ? 2 : 0; @@ -420,7 +421,10 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor if (isLoading.get()) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(activity)) { - Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG).show(); + Toast toast = Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG); + TextView messageView = (TextView) toast.getView().findViewById(android.R.id.message); + if (messageView != null) messageView.setGravity(Gravity.CENTER); + toast.show(); return; } @@ -800,7 +804,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor * If it is, check if the cache contains the info already.
* If the cache doesn't have the info, load from the network. * - * @param info info to prepare and load, can be null + * @param info info to prepare and load, can be null * @param scrollToTop whether or not scroll the scrollView to y = 0 */ public void prepareAndLoad(StreamInfo info, boolean scrollToTop) { @@ -844,7 +848,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor else parallaxScrollRootView.scrollTo(0, 0); } - animateView(contentRootLayoutHiding, false, greaterThanThreshold ? 250 : 0, new Runnable() { + animateView(contentRootLayoutHiding, false, greaterThanThreshold ? 250 : 0, 0, new Runnable() { @Override public void run() { handleStreamInfo(infoFinal, false); @@ -1052,7 +1056,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor private void setErrorImage(final int imageResource) { if (thumbnailImageView == null || activity == null) return; thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, imageResource)); - animateView(thumbnailImageView, false, 0, new Runnable() { + animateView(thumbnailImageView, false, 0, 0, new Runnable() { @Override public void run() { animateView(thumbnailImageView, true, 500); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java index 8071f4a36..11707c38d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java @@ -37,6 +37,7 @@ import org.schabi.newpipe.extractor.search.SearchResult; import org.schabi.newpipe.fragments.BaseFragment; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoListAdapter; +import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.workers.SearchWorker; import org.schabi.newpipe.workers.SuggestionWorker; @@ -45,12 +46,13 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import static org.schabi.newpipe.util.AnimationUtils.animateView; + public class SearchFragment extends BaseFragment implements SuggestionWorker.OnSuggestionResult, SearchWorker.OnSearchResult { private final String TAG = "SearchFragment@" + Integer.toHexString(hashCode()); // savedInstanceBundle arguments private static final String QUERY_KEY = "query_key"; private static final String PAGE_NUMBER_KEY = "page_number_key"; - private static final String SERVICE_KEY = "service_key"; private static final String INFO_LIST_KEY = "info_list_key"; private static final String WAS_LOADING_KEY = "was_loading_key"; private static final String ERROR_KEY = "error_key"; @@ -101,7 +103,7 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS setHasOptionsMenu(true); if (savedInstanceState != null) { searchQuery = savedInstanceState.getString(QUERY_KEY); - serviceId = savedInstanceState.getInt(SERVICE_KEY, 0); + serviceId = savedInstanceState.getInt(Constants.KEY_SERVICE_ID, 0); pageNumber = savedInstanceState.getInt(PAGE_NUMBER_KEY, 0); wasLoading.set(savedInstanceState.getBoolean(WAS_LOADING_KEY, false)); filterItemCheckedId = savedInstanceState.getInt(FILTER_CHECKED_ID_KEY, 0); @@ -171,7 +173,7 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS String query = searchEditText != null && !TextUtils.isEmpty(searchEditText.getText().toString()) ? searchEditText.getText().toString() : searchQuery; outState.putString(QUERY_KEY, query); - outState.putInt(SERVICE_KEY, serviceId); + outState.putInt(Constants.KEY_SERVICE_ID, serviceId); outState.putInt(PAGE_NUMBER_KEY, pageNumber); outState.putSerializable(INFO_LIST_KEY, ((ArrayList) infoListAdapter.getItemsList())); outState.putBoolean(WAS_LOADING_KEY, curSearchWorker != null && curSearchWorker.isRunning()); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchSuggestionListener.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchSuggestionListener.java deleted file mode 100644 index 9e2a74dad..000000000 --- a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchSuggestionListener.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.schabi.newpipe.fragments.search; - -import android.support.v7.widget.SearchView; - -/** - * Created by Christian Schabesberger on 02.08.16. - * - * Copyright (C) Christian Schabesberger 2016 - * SearchSuggestionListener.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - - -public class SearchSuggestionListener implements SearchView.OnSuggestionListener{ - - private final SearchView searchView; - private final SuggestionListAdapter adapter; - - public SearchSuggestionListener(SearchView searchView, SuggestionListAdapter adapter) { - this.searchView = searchView; - this.adapter = adapter; - } - - @Override - public boolean onSuggestionSelect(int position) { - String suggestion = adapter.getSuggestion(position); - searchView.setQuery(suggestion,true); - return false; - } - - @Override - public boolean onSuggestionClick(int position) { - String suggestion = adapter.getSuggestion(position); - searchView.setQuery(suggestion,true); - return false; - } -} \ No newline at end of file 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 ce3525f17..45221325b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -21,10 +21,13 @@ import android.widget.TextView; import android.widget.Toast; import org.schabi.newpipe.R; +import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.ThemeHelper; +import static org.schabi.newpipe.util.AnimationUtils.animateView; + /** * Activity Player implementing VideoPlayer * @@ -37,14 +40,7 @@ public class MainVideoPlayer extends Activity { private AudioManager audioManager; private GestureDetector gestureDetector; - private final Runnable hideUiRunnable = new Runnable() { - @Override - public void run() { - hideSystemUi(); - } - }; private boolean activityPaused; - private VideoPlayerImpl playerImpl; /*////////////////////////////////////////////////////////////////////////// @@ -265,14 +261,15 @@ public class MainVideoPlayer extends Activity { else if (v.getId() == screenRotationButton.getId()) onScreenRotationClicked(); if (getCurrentState() != STATE_COMPLETED) { + getControlsVisibilityHandler().removeCallbacksAndMessages(null); animateView(playerImpl.getControlsRoot(), true, 300, 0, new Runnable() { @Override public void run() { if (getCurrentState() == STATE_PLAYING && !playerImpl.isQualityMenuVisible()) { - animateView(playerImpl.getControlsRoot(), false, 300, DEFAULT_CONTROLS_HIDE_TIME, true); + hideControls(300, DEFAULT_CONTROLS_HIDE_TIME); } } - }, false); + }); } } @@ -285,14 +282,14 @@ public class MainVideoPlayer extends Activity { public void onStopTrackingTouch(SeekBar seekBar) { super.onStopTrackingTouch(seekBar); if (playerImpl.wasPlaying()) { - animateView(playerImpl.getControlsRoot(), false, 100, 0); + hideControls(100, 0); } } @Override public void onDismiss(PopupMenu menu) { super.onDismiss(menu); - if (isPlaying()) animateView(getControlsRoot(), false, 500, 0); + if (isPlaying()) hideControls(300, 0); } @Override @@ -310,26 +307,23 @@ public class MainVideoPlayer extends Activity { public void onLoading() { super.onLoading(); playPauseButton.setImageResource(R.drawable.ic_pause_white); - animateView(playPauseButton, false, 100, 0); + animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100); } @Override public void onBuffering() { super.onBuffering(); - animateView(playPauseButton, false, 100, 0); + animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100); } @Override public void onPlaying() { super.onPlaying(); - //playPauseButton.setImageResource(R.drawable.ic_pause_white); - //animateView(playPauseButton, true, 500, 0); - - animateView(playPauseButton, false, 80, 0, new Runnable() { + animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, new Runnable() { @Override public void run() { playPauseButton.setImageResource(R.drawable.ic_pause_white); - animateView(playPauseButton, true, 200, 0); + animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, true, 200); } }); @@ -339,14 +333,11 @@ public class MainVideoPlayer extends Activity { @Override public void onPaused() { super.onPaused(); - //playPauseButton.setImageResource(R.drawable.ic_play_arrow_white); - //animateView(playPauseButton, true, 100, 0); - - animateView(playPauseButton, false, 80, 0, new Runnable() { + animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, new Runnable() { @Override public void run() { playPauseButton.setImageResource(R.drawable.ic_play_arrow_white); - animateView(playPauseButton, true, 200, 0); + animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, true, 200); } }); @@ -356,7 +347,7 @@ public class MainVideoPlayer extends Activity { @Override public void onPausedSeek() { super.onPausedSeek(); - animateView(playPauseButton, false, 100, 0); + animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100); } @@ -366,11 +357,11 @@ public class MainVideoPlayer extends Activity { playPauseButton.setImageResource(R.drawable.ic_pause_white); } else { showSystemUi(); - animateView(playPauseButton, false, 0, 0, new Runnable() { + animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, new Runnable() { @Override public void run() { playPauseButton.setImageResource(R.drawable.ic_replay_white); - animateView(playPauseButton, true, 300, 0); + animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, true, 300); } }); } @@ -382,19 +373,20 @@ public class MainVideoPlayer extends Activity { //////////////////////////////////////////////////////////////////////////*/ @Override - public void animateView(View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd, boolean hideUi) { - //if (execOnEnd == null) playerImpl.setDefaultAnimationEnd(hideUiRunnable); - - if (hideUi && execOnEnd != null) { - Runnable combinedRunnable = new Runnable() { - @Override - public void run() { - execOnEnd.run(); - hideUiRunnable.run(); - } - }; - super.animateView(view, enterOrExit, duration, delay, combinedRunnable, true); - } else super.animateView(view, enterOrExit, duration, delay, hideUi ? hideUiRunnable : execOnEnd, hideUi); + public void hideControls(final long duration, long delay) { + if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]"); + getControlsVisibilityHandler().removeCallbacksAndMessages(null); + getControlsVisibilityHandler().postDelayed(new Runnable() { + @Override + public void run() { + animateView(getControlsRoot(), false, duration, 0, new Runnable() { + @Override + public void run() { + hideSystemUi(); + } + }); + } + }, delay); } /////////////////////////////////////////////////////////////////////////// @@ -443,14 +435,9 @@ public class MainVideoPlayer extends Activity { if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]"); if (playerImpl.getCurrentState() != BasePlayer.STATE_PLAYING) return true; - if (playerImpl.isControlsVisible()) playerImpl.animateView(playerImpl.getControlsRoot(), false, 150, 0, true); + if (playerImpl.isControlsVisible()) playerImpl.hideControls(150, 0); else { - playerImpl.animateView(playerImpl.getControlsRoot(), true, 500, 0, new Runnable() { - @Override - public void run() { - playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME, true); - } - }); + playerImpl.showControlsThenHide(); showSystemUi(); } return true; @@ -500,7 +487,7 @@ public class MainVideoPlayer extends Activity { if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume); playerImpl.getVolumeTextView().setText(volumeUnicode + " " + Math.round((((float) currentVolume) / maxVolume) * 100) + "%"); - if (playerImpl.getVolumeTextView().getVisibility() != View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), true, 200, 0); + if (playerImpl.getVolumeTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getVolumeTextView(), true, 200); if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE); } else { WindowManager.LayoutParams lp = getWindow().getAttributes(); @@ -515,7 +502,7 @@ public class MainVideoPlayer extends Activity { playerImpl.getBrightnessTextView().setText(brightnessUnicode + " " + (brightnessNormalized == 1 ? 0 : brightnessNormalized) + "%"); - if (playerImpl.getBrightnessTextView().getVisibility() != View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), true, 200, 0); + if (playerImpl.getBrightnessTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getBrightnessTextView(), true, 200); if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE); } return true; @@ -527,11 +514,11 @@ public class MainVideoPlayer extends Activity { eventsNum = 0; /* if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE); if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);*/ - if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), false, 200, 200); - if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), false, 200, 200); + if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) animateView(playerImpl.getVolumeTextView(), false, 200, 200); + if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) animateView(playerImpl.getBrightnessTextView(), false, 200, 200); if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) { - playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME, true); + playerImpl.hideControls(300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME); } } 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 ded6f34f9..0bc91509b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -7,13 +7,14 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.PixelFormat; import android.os.Build; import android.os.Handler; import android.os.IBinder; +import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.util.DisplayMetrics; import android.util.Log; @@ -24,6 +25,8 @@ import android.view.View; import android.view.WindowManager; import android.widget.PopupMenu; import android.widget.RemoteViews; +import android.widget.SeekBar; +import android.widget.TextView; import android.widget.Toast; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -43,6 +46,8 @@ import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.Utils; import org.schabi.newpipe.workers.StreamExtractorWorker; +import static org.schabi.newpipe.util.AnimationUtils.animateView; + /** * Service Popup Player implementing VideoPlayer * @@ -58,14 +63,19 @@ public class PopupVideoPlayer extends Service { public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_DETAIL"; public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT"; + private static final String POPUP_SAVED_WIDTH = "popup_saved_width"; + private static final String POPUP_SAVED_X = "popup_saved_x"; + private static final String POPUP_SAVED_Y = "popup_saved_y"; + private WindowManager windowManager; private WindowManager.LayoutParams windowLayoutParams; private GestureDetector gestureDetector; private float screenWidth, screenHeight; private float popupWidth, popupHeight; - private float currentPopupHeight = 110.0f * Resources.getSystem().getDisplayMetrics().density; - //private float minimumHeight = 100; // TODO: Use it when implementing the resize of the popup + + private float minimumWidth, minimumHeight; + private float maximumWidth, maximumHeight; private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha"; private NotificationManager notificationManager; @@ -114,6 +124,8 @@ public class PopupVideoPlayer extends Service { @Override public void onConfigurationChanged(Configuration newConfig) { updateScreenSize(); + updatePopupSize(windowLayoutParams.width, -1); + checkPositionBounds(); } @Override @@ -129,6 +141,8 @@ public class PopupVideoPlayer extends Service { currentExtractorWorker.cancel(); currentExtractorWorker = null; } + + savePositionAndSize(); } @Override @@ -144,21 +158,33 @@ public class PopupVideoPlayer extends Service { private void initPopup() { if (DEBUG) Log.d(TAG, "initPopup() called"); View rootView = View.inflate(this, R.layout.player_popup, null); - playerImpl.setup(rootView); updateScreenSize(); + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean popupRememberSizeAndPos = sharedPreferences.getBoolean(getString(R.string.popup_remember_size_pos_key), true); + + float defaultSize = getResources().getDimension(R.dimen.popup_default_width); + popupWidth = popupRememberSizeAndPos ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize; + windowLayoutParams = new WindowManager.LayoutParams( - (int) getMinimumVideoWidth(currentPopupHeight), (int) currentPopupHeight, + (int) popupWidth, (int) getMinimumVideoHeight(popupWidth), WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); - windowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; + int centerX = (int) (screenWidth / 2f - popupWidth / 2f); + int centerY = (int) (screenHeight / 2f - popupHeight / 2f); + windowLayoutParams.x = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX; + windowLayoutParams.y = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY; + + checkPositionBounds(); + MySimpleOnGestureListener listener = new MySimpleOnGestureListener(); gestureDetector = new GestureDetector(this, listener); - gestureDetector.setIsLongpressEnabled(false); + //gestureDetector.setIsLongpressEnabled(false); rootView.setOnTouchListener(listener); playerImpl.getLoadingPanel().setMinimumWidth(windowLayoutParams.width); playerImpl.getLoadingPanel().setMinimumHeight(windowLayoutParams.height); @@ -219,13 +245,13 @@ public class PopupVideoPlayer extends Service { notificationManager.notify(NOTIFICATION_ID, notBuilder.build()); } - /*////////////////////////////////////////////////////////////////////////// // Misc //////////////////////////////////////////////////////////////////////////*/ public void onVideoClose() { if (DEBUG) Log.d(TAG, "onVideoClose() called"); + savePositionAndSize(); stopSelf(); } @@ -245,10 +271,23 @@ public class PopupVideoPlayer extends Service { // Utils //////////////////////////////////////////////////////////////////////////*/ - private float getMinimumVideoWidth(float height) { - float width = height * (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have - if (DEBUG) Log.d(TAG, "getMinimumVideoWidth() called with: height = [" + height + "], returned: " + width); - return width; + private void checkPositionBounds() { + if (windowLayoutParams.x > screenWidth - windowLayoutParams.width) windowLayoutParams.x = (int) (screenWidth - windowLayoutParams.width); + if (windowLayoutParams.x < 0) windowLayoutParams.x = 0; + if (windowLayoutParams.y > screenHeight - windowLayoutParams.height) windowLayoutParams.y = (int) (screenHeight - windowLayoutParams.height); + if (windowLayoutParams.y < 0) windowLayoutParams.y = 0; + } + + private void savePositionAndSize() { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(PopupVideoPlayer.this); + sharedPreferences.edit().putInt(POPUP_SAVED_X, windowLayoutParams.x).apply(); + sharedPreferences.edit().putInt(POPUP_SAVED_Y, windowLayoutParams.y).apply(); + sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, windowLayoutParams.width).apply(); + } + + private float getMinimumVideoHeight(float width) { + //if (DEBUG) Log.d(TAG, "getMinimumVideoHeight() called with: width = [" + width + "], returned: " + height); + return width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have } private void updateScreenSize() { @@ -258,11 +297,39 @@ public class PopupVideoPlayer extends Service { screenWidth = metrics.widthPixels; screenHeight = metrics.heightPixels; if (DEBUG) Log.d(TAG, "updateScreenSize() called > screenWidth = " + screenWidth + ", screenHeight = " + screenHeight); + + popupWidth = getResources().getDimension(R.dimen.popup_default_width); + popupHeight = getMinimumVideoHeight(popupWidth); + + minimumWidth = getResources().getDimension(R.dimen.popup_minimum_width); + minimumHeight = getMinimumVideoHeight(minimumWidth); + + maximumWidth = screenWidth; + maximumHeight = screenHeight; + } + + private void updatePopupSize(int width, int height) { + //if (DEBUG) Log.d(TAG, "updatePopupSize() called with: width = [" + width + "], height = [" + height + "]"); + + width = (int) (width > maximumWidth ? maximumWidth : width < minimumWidth ? minimumWidth : width); + + if (height == -1) height = (int) getMinimumVideoHeight(width); + else height = (int) (height > maximumHeight ? maximumHeight : height < minimumHeight ? minimumHeight : height); + + windowLayoutParams.width = width; + windowLayoutParams.height = height; + popupWidth = width; + popupHeight = height; + + if (DEBUG) Log.d(TAG, "updatePopupSize() updated values: width = [" + width + "], height = [" + height + "]"); + windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); } /////////////////////////////////////////////////////////////////////////// private class VideoPlayerImpl extends VideoPlayer { + private TextView resizingIndicator; + VideoPlayerImpl() { super("VideoPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this); } @@ -271,13 +338,20 @@ public class PopupVideoPlayer extends Service { public void playUrl(String url, String format, boolean autoPlay) { super.playUrl(url, format, autoPlay); - windowLayoutParams.width = (int) getMinimumVideoWidth(currentPopupHeight); + windowLayoutParams.width = (int) popupWidth; + windowLayoutParams.height = (int) getMinimumVideoHeight(popupWidth); windowManager.updateViewLayout(getRootView(), windowLayoutParams); notBuilder = createNotification(); startForeground(NOTIFICATION_ID, notBuilder.build()); } + @Override + public void initViews(View rootView) { + super.initViews(rootView); + resizingIndicator = (TextView) rootView.findViewById(R.id.resizing_indicator); + } + @Override public void destroy() { super.destroy(); @@ -337,7 +411,7 @@ public class PopupVideoPlayer extends Service { @Override public void onDismiss(PopupMenu menu) { super.onDismiss(menu); - if (isPlaying()) animateView(getControlsRoot(), false, 500, 0); + if (isPlaying()) hideControls(500, 0); } @Override @@ -347,7 +421,14 @@ public class PopupVideoPlayer extends Service { stopSelf(); } - /*////////////////////////////////////////////////////////////////////////// + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + super.onStopTrackingTouch(seekBar); + if (playerImpl.wasPlaying()) { + hideControls(100, 0); + } + } +/*////////////////////////////////////////////////////////////////////////// // Broadcast Receiver //////////////////////////////////////////////////////////////////////////*/ @@ -422,12 +503,21 @@ public class PopupVideoPlayer extends Service { showAndAnimateControl(R.drawable.ic_replay_white, false); } + + @SuppressWarnings("WeakerAccess") + public TextView getResizingIndicator() { + return resizingIndicator; + } } private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { private int initialPopupX, initialPopupY; private boolean isMoving; + private int onDownPopupWidth = 0; + private boolean isResizing; + private boolean isResizingRightSide; + @Override public boolean onDoubleTap(MotionEvent e) { if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); @@ -450,15 +540,34 @@ public class PopupVideoPlayer extends Service { if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]"); initialPopupX = windowLayoutParams.x; initialPopupY = windowLayoutParams.y; - popupWidth = playerImpl.getRootView().getWidth(); - popupHeight = playerImpl.getRootView().getHeight(); + popupWidth = windowLayoutParams.width; + popupHeight = windowLayoutParams.height; + onDownPopupWidth = windowLayoutParams.width; return false; } + @Override + public void onLongPress(MotionEvent e) { + if (DEBUG) Log.d(TAG, "onLongPress() called with: e = [" + e + "]"); + playerImpl.showAndAnimateControl(-1, true); + playerImpl.getLoadingPanel().setVisibility(View.GONE); + + playerImpl.hideControls(0, 0); + animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0); + animateView(playerImpl.getResizingIndicator(), true, 200, 0); + + isResizing = true; + isResizingRightSide = e.getRawX() > windowLayoutParams.x + (windowLayoutParams.width / 2f); + } + @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f) playerImpl.animateView(playerImpl.getControlsRoot(), true, 30, 0); + if (isResizing) return false; + + if (playerImpl.getCurrentState() != BasePlayer.STATE_BUFFERING + && (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f)) playerImpl.showControls(0); isMoving = true; + float diffX = (int) (e2.getRawX() - e1.getRawX()), posX = (int) (initialPopupX + diffX); float diffY = (int) (e2.getRawY() - e1.getRawY()), posY = (int) (initialPopupY + diffY); @@ -477,7 +586,7 @@ public class PopupVideoPlayer extends Service { ", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" + ", distanceXy = [" + distanceX + ", " + distanceY + "]" + ", posXy = [" + posX + ", " + posY + "]" + - ", popupWh rootView.get wh = [" + popupWidth + " x " + popupHeight + "]"); + ", popupWh = [" + popupWidth + " x " + popupHeight + "]"); windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); return true; } @@ -485,16 +594,38 @@ public class PopupVideoPlayer extends Service { private void onScrollEnd() { if (DEBUG) Log.d(TAG, "onScrollEnd() called"); if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) { - playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME); + playerImpl.hideControls(300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME); } } @Override public boolean onTouch(View v, MotionEvent event) { gestureDetector.onTouchEvent(event); - if (event.getAction() == MotionEvent.ACTION_UP && isMoving) { - isMoving = false; - onScrollEnd(); + if (event.getAction() == MotionEvent.ACTION_MOVE && isResizing && !isMoving) { + //if (DEBUG) Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]"); + int width; + if (isResizingRightSide) width = (int) event.getRawX() - windowLayoutParams.x; + else { + width = (int) (windowLayoutParams.width + (windowLayoutParams.x - event.getRawX())); + if (width > minimumWidth) windowLayoutParams.x = initialPopupX - (width - onDownPopupWidth); + } + if (width <= maximumWidth && width >= minimumWidth) updatePopupSize(width, -1); + return true; + } + + if (event.getAction() == MotionEvent.ACTION_UP) { + if (DEBUG) Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]"); + if (isMoving) { + isMoving = false; + onScrollEnd(); + } + + if (isResizing) { + isResizing = false; + animateView(playerImpl.getResizingIndicator(), false, 100, 0); + playerImpl.changeState(playerImpl.getCurrentState()); + } + savePositionAndSize(); } return true; } 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 d64e60747..84f2192f9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -12,6 +12,7 @@ import android.graphics.Color; import android.graphics.PorterDuff; import android.net.Uri; import android.os.Build; +import android.os.Handler; import android.support.v4.content.ContextCompat; import android.util.Log; import android.view.Menu; @@ -36,12 +37,15 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream_info.AudioStream; import org.schabi.newpipe.extractor.stream_info.VideoStream; +import org.schabi.newpipe.util.AnimationUtils; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Vector; +import static org.schabi.newpipe.util.AnimationUtils.animateView; + /** * Base for video players * @@ -101,6 +105,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. private ImageButton fullScreenButton; private ValueAnimator controlViewAnimator; + private Handler controlsVisibilityHandler = new Handler(); private boolean isQualityPopupMenuVisible = false; private boolean qualityChanged = false; @@ -235,6 +240,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. if (!isProgressLoopRunning.get()) startProgressLoop(); + controlsVisibilityHandler.removeCallbacksAndMessages(null); + animateView(controlsRoot, false, 300); + showAndAnimateControl(-1, true); playbackSeekBar.setEnabled(true); playbackSeekBar.setProgress(0); @@ -242,10 +250,10 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. // Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN); - animateView(endScreen, false, 0, 0); + animateView(endScreen, false, 0); loadingPanel.setBackgroundColor(Color.BLACK); - animateView(loadingPanel, true, 0, 0); - animateView(surfaceForeground, true, 100, 0); + animateView(loadingPanel, true, 0); + animateView(surfaceForeground, true, 100); } @Override @@ -254,26 +262,21 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. if (!isProgressLoopRunning.get()) startProgressLoop(); showAndAnimateControl(-1, true); loadingPanel.setVisibility(View.GONE); - animateView(controlsRoot, true, 500, 0, new Runnable() { - @Override - public void run() { - animateView(controlsRoot, false, 500, DEFAULT_CONTROLS_HIDE_TIME, true); - } - }); - animateView(currentDisplaySeek, false, 200, 0); + showControlsThenHide(); + animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200); } @Override public void onBuffering() { if (DEBUG) Log.d(TAG, "onBuffering() called"); loadingPanel.setBackgroundColor(Color.TRANSPARENT); - animateView(loadingPanel, true, 500, 0); + animateView(loadingPanel, true, 500); } @Override public void onPaused() { if (DEBUG) Log.d(TAG, "onPaused() called"); - animateView(controlsRoot, true, 500, 100); + showControls(400); loadingPanel.setVisibility(View.GONE); } @@ -289,9 +292,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. if (isProgressLoopRunning.get()) stopProgressLoop(); - animateView(controlsRoot, true, 500, 0); - animateView(endScreen, true, 800, 0); - animateView(currentDisplaySeek, false, 200, 0); + showControls(500); + animateView(endScreen, true, 800); + animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200); loadingPanel.setVisibility(View.GONE); playbackSeekBar.setMax((int) simpleExoPlayer.getDuration()); @@ -302,7 +305,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. // Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN); - animateView(surfaceForeground, true, 100, 0); + animateView(surfaceForeground, true, 100); if (currentRepeatMode == RepeatMode.REPEAT_ONE) { changeState(STATE_LOADING); @@ -324,7 +327,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. @Override public void onRenderedFirstFrame() { - animateView(surfaceForeground, false, 100, 0); + animateView(surfaceForeground, false, 100); } /*////////////////////////////////////////////////////////////////////////// @@ -443,7 +446,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called"); qualityPopupMenu.show(); isQualityPopupMenuVisible = true; - animateView(getControlsRoot(), true, 300, 0); + showControls(300); VideoStream videoStream = getSelectedVideoStream(); qualityTextView.setText(MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution); @@ -469,8 +472,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. wasPlaying = isPlaying(); if (isPlaying()) simpleExoPlayer.setPlayWhenReady(false); - animateView(controlsRoot, true, 0, 0); - animateView(currentDisplaySeek, true, 300, 0); + showControls(0); + animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, true, 300); } @Override @@ -481,7 +484,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) simpleExoPlayer.setPlayWhenReady(true); playbackCurrentTime.setText(getTimeString(seekBar.getProgress())); - animateView(currentDisplaySeek, false, 200, 0); + animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200); if (getCurrentState() == STATE_PAUSED_SEEK) changeState(STATE_BUFFERING); if (!isProgressLoopRunning.get()) startProgressLoop(); @@ -550,107 +553,37 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. controlViewAnimator.start(); } - public void animateView(View view, boolean enterOrExit, long duration, long delay) { - animateView(view, enterOrExit, duration, delay, null, false); - } - - public void animateView(View view, boolean enterOrExit, long duration, long delay, boolean hideUi) { - animateView(view, enterOrExit, duration, delay, null, hideUi); - } - - public void animateView(final View view, final boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) { - animateView(view, enterOrExit, duration, delay, execOnEnd, false); - } - - /** - * Animate the view - * - * @param view view that will be animated - * @param enterOrExit true to enter, false to exit - * @param duration how long the animation will take, in milliseconds - * @param delay how long the animation will wait to start, in milliseconds - * @param execOnEnd runnable that will be executed when the animation ends - * @param hideUi need to hide ui when animation ends, - * just a helper for classes extending this - */ - public void animateView(final View view, final boolean enterOrExit, long duration, long delay, final Runnable execOnEnd, boolean hideUi) { - if (DEBUG) { - Log.d(TAG, "animateView() called with: view = [" + view + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], delay = [" + delay + "], execOnEnd = [" + execOnEnd + "]"); - } - if (view.getVisibility() == View.VISIBLE && enterOrExit) { - if (DEBUG) Log.d(TAG, "animateView() view was already visible > view = [" + view + "]"); - view.animate().setListener(null).cancel(); - view.setVisibility(View.VISIBLE); - view.setAlpha(1f); - if (execOnEnd != null) execOnEnd.run(); - return; - } else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) { - if (DEBUG) Log.d(TAG, "animateView() view was already gone > view = [" + view + "]"); - view.animate().setListener(null).cancel(); - view.setVisibility(View.GONE); - view.setAlpha(0f); - if (execOnEnd != null) execOnEnd.run(); - return; - } - - view.animate().setListener(null).cancel(); - view.setVisibility(View.VISIBLE); - - if (view == controlsRoot) { - if (enterOrExit) { - view.animate().alpha(1f).setDuration(duration).setStartDelay(delay) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (execOnEnd != null) execOnEnd.run(); - } - }).start(); - } else { - view.animate().alpha(0f) - .setDuration(duration).setStartDelay(delay) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setVisibility(View.GONE); - if (execOnEnd != null) execOnEnd.run(); - } - }) - .start(); - } - return; - } - - if (enterOrExit) { - view.setAlpha(0f); - view.setScaleX(.8f); - view.setScaleY(.8f); - view.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(duration).setStartDelay(delay) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (execOnEnd != null) execOnEnd.run(); - } - }).start(); - } else { - view.setAlpha(1f); - view.setScaleX(1f); - view.setScaleY(1f); - view.animate().alpha(0f).scaleX(.8f).scaleY(.8f).setDuration(duration).setStartDelay(delay) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setVisibility(View.GONE); - if (execOnEnd != null) execOnEnd.run(); - } - }) - .start(); - } - } - public boolean isQualityMenuVisible() { return isQualityPopupMenuVisible; } + public void showControlsThenHide() { + if (DEBUG) Log.d(TAG, "showControlsThenHide() called"); + animateView(controlsRoot, true, 300, 0, new Runnable() { + @Override + public void run() { + hideControls(300, DEFAULT_CONTROLS_HIDE_TIME); + } + }); + } + + public void showControls(long duration) { + if (DEBUG) Log.d(TAG, "showControls() called"); + controlsVisibilityHandler.removeCallbacksAndMessages(null); + animateView(controlsRoot, true, duration); + } + + public void hideControls(final long duration, long delay) { + if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]"); + controlsVisibilityHandler.removeCallbacksAndMessages(null); + controlsVisibilityHandler.postDelayed(new Runnable() { + @Override + public void run() { + animateView(controlsRoot, false, duration); + } + }, delay); + } + /*////////////////////////////////////////////////////////////////////////// // Getters and Setters //////////////////////////////////////////////////////////////////////////*/ @@ -711,6 +644,10 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. this.startedFromNewPipe = startedFromNewPipe; } + public Handler getControlsVisibilityHandler() { + return controlsVisibilityHandler; + } + public View getRootView() { return rootView; } diff --git a/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java b/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java new file mode 100644 index 000000000..c37eaa560 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java @@ -0,0 +1,126 @@ +package org.schabi.newpipe.util; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.util.Log; +import android.view.View; + +import org.schabi.newpipe.player.BasePlayer; + +public class AnimationUtils { + private static final String TAG = "AnimationUtils"; + private static final boolean DEBUG = BasePlayer.DEBUG; + + public enum Type { + ALPHA, SCALE_AND_ALPHA + } + + public static void animateView(View view, boolean enterOrExit, long duration) { + animateView(view, Type.ALPHA, enterOrExit, duration, 0, null); + } + + public static void animateView(View view, boolean enterOrExit, long duration, long delay) { + animateView(view, Type.ALPHA, enterOrExit, duration, delay, null); + } + + public static void animateView(View view, boolean enterOrExit, long duration, long delay, Runnable execOnEnd) { + animateView(view, Type.ALPHA, enterOrExit, duration, delay, execOnEnd); + } + + public static void animateView(View view, Type animationType, boolean enterOrExit, long duration) { + animateView(view, animationType, enterOrExit, duration, 0, null); + } + + public static void animateView(View view, Type animationType, boolean enterOrExit, long duration, long delay) { + animateView(view, animationType, enterOrExit, duration, delay, null); + } + + /** + * Animate the view + * + * @param view view that will be animated + * @param animationType {@link Type} of the animation + * @param enterOrExit true to enter, false to exit + * @param duration how long the animation will take, in milliseconds + * @param delay how long the animation will wait to start, in milliseconds + * @param execOnEnd runnable that will be executed when the animation ends + */ + public static void animateView(final View view, Type animationType, boolean enterOrExit, long duration, long delay, Runnable execOnEnd) { + if (DEBUG) { + Log.d(TAG, "animateView() called with: view = [" + view + "], animationType = [" + animationType + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], delay = [" + delay + "], execOnEnd = [" + execOnEnd + "]"); + } + + if (view.getVisibility() == View.VISIBLE && enterOrExit) { + if (DEBUG) Log.d(TAG, "animateView() view was already visible > view = [" + view + "]"); + view.animate().setListener(null).cancel(); + view.setVisibility(View.VISIBLE); + view.setAlpha(1f); + if (execOnEnd != null) execOnEnd.run(); + return; + } else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) { + if (DEBUG) Log.d(TAG, "animateView() view was already gone > view = [" + view + "]"); + view.animate().setListener(null).cancel(); + view.setVisibility(View.GONE); + view.setAlpha(0f); + if (execOnEnd != null) execOnEnd.run(); + return; + } + + view.animate().setListener(null).cancel(); + view.setVisibility(View.VISIBLE); + + switch (animationType) { + case ALPHA: + animateAlpha(view, enterOrExit, duration, delay, execOnEnd); + break; + case SCALE_AND_ALPHA: + animateScaleAndAlpha(view, enterOrExit, duration, delay, execOnEnd); + break; + } + } + + private static void animateScaleAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) { + if (enterOrExit) { + view.setAlpha(0f); + view.setScaleX(.8f); + view.setScaleY(.8f); + view.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (execOnEnd != null) execOnEnd.run(); + } + }).start(); + } else { + view.setAlpha(1f); + view.setScaleX(1f); + view.setScaleY(1f); + view.animate().alpha(0f).scaleX(.8f).scaleY(.8f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(View.GONE); + if (execOnEnd != null) execOnEnd.run(); + } + }).start(); + } + } + + + private static void animateAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) { + if (enterOrExit) { + view.animate().alpha(1f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (execOnEnd != null) execOnEnd.run(); + } + }).start(); + } else { + view.animate().alpha(0f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(View.GONE); + if (execOnEnd != null) execOnEnd.run(); + } + }).start(); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java similarity index 98% rename from app/src/main/java/org/schabi/newpipe/Localization.java rename to app/src/main/java/org/schabi/newpipe/util/Localization.java index 2fa461129..f1eb50c6a 100644 --- a/app/src/main/java/org/schabi/newpipe/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -1,10 +1,12 @@ -package org.schabi.newpipe; +package org.schabi.newpipe.util; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.preference.PreferenceManager; +import org.schabi.newpipe.R; + import java.text.DateFormat; import java.text.NumberFormat; import java.text.ParseException; diff --git a/app/src/main/res/layout/player_popup.xml b/app/src/main/res/layout/player_popup.xml index a3be1511c..9ff3db76a 100644 --- a/app/src/main/res/layout/player_popup.xml +++ b/app/src/main/res/layout/player_popup.xml @@ -5,7 +5,9 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" - android:gravity="center"> + android:gravity="center" + tools:layout_width="@dimen/popup_default_width" + tools:layout_height="101.25dp"> + + + \ No newline at end of file diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml new file mode 100644 index 000000000..587b12ce4 --- /dev/null +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -0,0 +1,227 @@ + + + + শুরু করতে অনুসন্ধান এ আলতো চাপ + NewPipe ব্যাকগ্রাউন্ড প্লেয়ার + + %1$s বার দেখা হয়েছে + প্রকাশকাল %1$s + কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি। তুমি কি VLC ইনস্টল করতে চাও? + ইনস্টল + বাদ দাও + + ব্রাউজারে খোলো + পপআপ মোডে খোলো + শেয়ার + লোড হচ্ছে + ডাউনলোড + খোঁজ + সেটিং + তুমি কি বলতে চাচ্ছ %1$s ? + "পেজ খোঁজ : " + শেয়ার কর + ব্রাউজার পছন্দ কর + রোটেশন + সেটিং + বাহ্যিক ভিডিও প্লেয়ার ব্যবহার করো + বাহ্যিক অডিও প্লেয়ার ব্যবহার করো + NewPipe পপআপ মোড + + ব্যাকগ্রাউন্ড + পপআপ + + Video download path + ডাউনলোড করা ভিডিও সঞ্চয় পাথ করার পাথ + ভিডিওগুলির জন্য ডাউনলোডের পাথ প্রবেশ করাও + + অডিও ডাউনলোড পাথ + ডাউনলোড করা অডিও সঞ্চয় পাথ করার পাথ + অডিও ফাইলগুলির জন্য ডাউনলোডের পাথ প্রবেশ করাও + + স্বয়ংক্রিয়ভাবে প্লে করুন যখন অন্য অ্যাপ্লিকেশন থেকে চালু করা হয় + স্বয়ংক্রিয়ভাবে একটি ভিডিও প্লে করো যখন NewPipe অন্য অ্যাপ্লিকেশন থেকে চালু করা হয়। + ডিফল্ট রেজল্যুশন + ডিফল্ট পপআপ রেজল্যুশন + উচ্চ রেজুলেশন দেখাও + শুধুমাত্র কিছু ডিভাইস 2k / 4k ভিডিও চালানোয় সমর্থন + Kodi এর মাধ্যমে চালাও + Kore অ্যাপ্লিকেশন খুঁজে পাওয়া যায়নি। Kore ইনস্টল করবে? + + দেখাও \"Kodi এর মাধ্যমে চালাও \" বিকল্প + Kodi মিডিয়া সেন্টারে এর মাধ্যমে একটি ভিডিও প্লে করার জন্য একটি বিকল্প প্রদর্শন কর + অডিও + ডিফল্ট অডিও ফরম্যাট + পছন্দের ভিডিও ফরম্যাট + WebM — বিনামূল্য/স্বাধীন ফরম্যাট + m4a — ভালো মানের + থিম + উজ্জ্বল + অন্ধকার + কালো + পপআপ আকার এবং অবস্থান মনে রাখো + শেষ আকার এবং পপআপ সেট অবস্থান মনে রাখো + + ডাউনলোড + + ভিডিও + অডিও + + + + ভিডিও + + + + অডিও + + + + + পরবর্তী ভিডিওগুলি + পরবর্তী এবং অনুরূপ ভিডিওগুলি দেখাও + URL সমর্থিত নয় + অনুরূপ ভিডিওগুলি + Preferred content language + ভিডিও এবং অডিও + পপআপ + এপিয়ারেন্স + অন্যান্য + + ব্যাকগ্রাউন্ডে বাজাও + পপআপ মোডে চালাও + + চালাও + কনটেন্ট + বয়স সীমাবদ্ধ কনটেন্ট দেখাও + ভিডিওটিকে বয়স সীমিত করা হয়েছে। প্রথমে সেটিংসে বয়স সীমাবদ্ধ ভিডিওগুলি সক্ষম করুন। + লাইভ + ডাউনলোড + ডাউনলোড + সেটিং + ত্রুটির তথ্য প্রতিবেদন + সবগুলি + চ্যানেল + হ্যা + পরবর্তী সময়ে + নিস্ক্রীয় + Filter + রিফ্রেশ + পরিষ্কার + আকার পরিবর্তন + + + ত্রুটি + নেটওয়ার্ক ত্রুটি + সব থাম্বনেইল লোড করা যায়নি + ভিডিও URL স্বাক্ষর ডিক্রিপ্ট করা যায়নি। + ওয়েবসাইট বিশ্লেষন করা যায়নি। + ওয়েবসাইট সম্পুর্নভাবে বিশ্লেষন করা যায়নি। + কনটেন্ট উপলব্ধ নয় + GEMA কর্তৃক ব্লক করা হয়েছে + ডাউনলোড মেনু সেটআপ করা যায়নি + এটি একটি লাইভ স্ট্রিম। যা এখনও সমর্থিত নয়। + কোনও স্ট্রিম পাওয়া যায়নি। + চিত্র লোড করা যায়নি + অ্যাপ / UI ক্র্যাশ করেছে + + দুঃখিত এটা ঘটা উচিত ছিল না + + মেলের মাধ্যমে ত্রুটি প্রতিবেদন করুন + দুঃখিত কিছু ত্রুটি ঘটেছে + প্রতিবেদন + তথ্য: + কি হয়েছিল: + + অনুসন্ধান: + অনুরোধ করা স্ট্রিম: + তোমার মন্তব্য (ইংরেজিতে): + বর্ণনা: + + + + ভিডিও প্রাকদর্শন থাম্বনেইল + ভিডিও প্রাকদর্শন থাম্বনেইল + আপলোডারের ইউজারপিক থাম্বনেইল + পছন্দ + অপছন্দ + টর ব্যবহার করো + (পরীক্ষামূলক) গোপনীয়তা বর্ধিত করতে টর এর মাধ্যমে ডাউনলোড ট্রাফিক জোরপুর্বক পাঠাও (ভিডিওগুলি স্ট্রিমিং সমর্থিত নয়) + একটি ত্রুটি রিপোর্ট করো + ব্যবহারকারীর প্রতিবেদন + + \'%1$s\' ডাউনলোড ডিরেক্টরি তৈরি করতে পারছে না + \'%1$s\' ডাউনলোড ডিরেক্টরি তৈরি করা হয়েছে + + ব্যাকগ্রাউন্ড এ চালাও + ভিডিও + অডিও + টেক্সট + লগিং + সাধারণ + বাগাড়ম্বরপূর্ণ + পুনরায় চেষ্টা করো + [বন্ধ] + সুরক্ষিত কনটেন্ট 18 বছরের নিচে API মাত্রাগুলি সমর্থিত নয় + এই ডিভাইসটি প্রয়োজনীয় DRM স্কিমের সমর্থন করে না + একটি অজানা DRM ত্রূটি ঘটেছে + এই ডিভাইসটি %1$s এর জন্য একটি ডিকোডার প্রদান করে না + এই ডিভাইসটি %1$s এর জন্য একটি নিরাপদ ডিকোডার প্রদান করে না + ডিভাইস ডিকোডার জিজ্ঞাসা করতে অক্ষমs + ডিকোডার চালু করতে অক্ষম %1$s + স্টোরেজ অ্যাক্সেস করার অনুমতি অস্বীকার করা হয়েছে + পুরানো প্লেয়ার ব্যবহার করো + মিডিয়াফ্রেমওয়ার্ক প্লেয়ারের পুরানো বিল্ড। + ভিডিওগুলি + গ্রাহক + গ্রাহকরা + সাবস্ক্রাইব + প্রদর্শন + K + M + B + + + শুরু + বিরতি + প্রদর্শন + ডিলেট + চেকসাম + + + নতুন মিশন + ঠিক আছে + তালিকা এবং গ্রিডের মধ্যে পরিবর্তন করো + + + + ডাউনলোড URL + ফাইলের নাম + থ্রেড + ফাইলের নাম আনো + ভুল বার্তা + সার্ভার অসমর্থিত + ফাইল ইতিমধ্যে বিদ্যমান + বিকৃত URL অথবা ইন্টারনেট নেই + NewPipe ডাউনলোড হচ্ছে + বিস্তারিত জানার জন্য আলতো চাপ + অনুগ্রহপূর্বক অপেক্ষা করো… + ক্লিপবোর্ডে অনুলিপি করা হয়েছে + অনুগ্রহ করে একটি ডাউনলোড ডিরেক্টরি নির্বাচন করো + এই অনুমতিটি পপআপ মোডে খুলতে প্রয়োজন + + + + + চ্যানেলের ক্রিয়াকলাপ + সেটিং + reCAPTCHA + reCAPTCHA চ্যালেঞ্জ + reCAPTCHA চ্যালেঞ্জ অনুরোধ করা হয়েছে + + + + diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml index ed40aa04b..8ab553bb6 100644 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -1,6 +1,7 @@ - + 230dp + 140dp 18sp diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index b39348290..5fe9e47aa 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -17,7 +17,8 @@ 5sp 2sp - + 180dp + 120dp 16sp diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 0d2cbbf50..34898377b 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -2,6 +2,7 @@ settings_category_video_audio + settings_category_popup settings_category_appearance settings_content_options settings_category_other @@ -27,6 +28,8 @@ 144p + popup_remember_size_pos_key + default_popup_resolution_key 480p diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bcfa65ed2..e518b7811 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -59,6 +59,8 @@ Light Dark Black + Remember popup size and position + Remember the last size and position set to the popup Download @@ -86,6 +88,7 @@ Similar videos Preferred content language Video & Audio + Popup Appearance Other %1$s - NewPipe @@ -109,6 +112,7 @@ Filter Refresh Clear + Resizing Error diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 55b0c8196..9e76ff57a 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -27,14 +27,6 @@ android:summary="%s" android:defaultValue="@string/default_resolution_value"/> - - + + + + + + + + +