From f334a2740f95cd5fe093860e83483b6511b4542e Mon Sep 17 00:00:00 2001 From: Avently <7953703+avently@users.noreply.github.com> Date: Wed, 5 Feb 2020 08:59:30 +0300 Subject: [PATCH] Mini player, ExpandableSurfaceView with ZOOM support, popup - mini player's title, image and author information will be updated in many situations but the main idea is that the info will be the same as currently playing stream. If nothing played then you'll see the info about currently opened stream in fragment. When MainPlayer service stops the info updates too - made ExpandableSurfaceView to replace AspectRatioFrameLayout. The reason for that is to make possible to use aspect ratio mode ZOOM. It's impossible to show a stream inside AspectRatioFrameLayout with ZOOM mode and to fit the video view to a screen space at the same time. Now the new view able to do that and to show vertical videos in a slightly wide space for them - refactored some methods to make the code more understandable - made fixes for player view for landscape-to-landscape orientation change - added Java docs - adapted swipe tracking inside bottom sheet - fixed PlayQueue crashes on clearing - paddings for popup player now as small as possible --- .../material/appbar/FlingBehavior.java | 2 +- .../fragments/detail/VideoDetailFragment.java | 79 +++++--- .../org/schabi/newpipe/player/BasePlayer.java | 17 +- .../org/schabi/newpipe/player/MainPlayer.java | 1 + .../schabi/newpipe/player/VideoPlayer.java | 32 ++- .../newpipe/player/VideoPlayerImpl.java | 184 ++++++++++++------ .../event/CustomBottomSheetBehavior.java | 24 ++- .../newpipe/player/playqueue/PlayQueue.java | 14 +- .../newpipe/views/ExpandableSurfaceView.java | 102 ++++++++++ .../activity_main_player.xml | 81 +++----- .../fragment_video_detail.xml | 2 +- .../main/res/layout/activity_main_player.xml | 81 +++----- .../main/res/layout/fragment_video_detail.xml | 2 +- app/src/main/res/values/dimens.xml | 9 + 14 files changed, 405 insertions(+), 225 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index ff2860558..f1038faa1 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -30,7 +30,7 @@ public final class FlingBehavior extends AppBarLayout.Behavior { ViewGroup playQueue = child.findViewById(R.id.playQueue); if (playQueue != null) { playQueue.getGlobalVisibleRect(playQueueRect); - if (playQueueRect.contains((int) ev.getX(), (int) ev.getY())) { + if (playQueueRect.contains((int) ev.getRawX(), (int) ev.getRawY())) { allowScroll = false; return false; } 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 dca7126da..ce113a93d 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 @@ -5,7 +5,6 @@ import android.app.Activity; import android.content.*; import android.content.pm.ActivityInfo; import android.database.ContentObserver; -import android.graphics.Bitmap; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; @@ -108,6 +107,7 @@ public class VideoDetailFragment private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4; private static final int COMMENTS_UPDATE_FLAG = 0x8; private static final float MAX_OVERLAY_ALPHA = 0.9f; + private static final float MAX_PLAYER_HEIGHT = 0.7f; public static final String ACTION_SHOW_MAIN_PLAYER = "org.schabi.newpipe.fragments.VideoDetailFragment.ACTION_SHOW_MAIN_PLAYER"; public static final String ACTION_HIDE_MAIN_PLAYER = "org.schabi.newpipe.fragments.VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER"; @@ -235,7 +235,7 @@ public class VideoDetailFragment // It will do nothing if the player is not in fullscreen mode hideSystemUIIfNeeded(); - if (!player.videoPlayerSelected()) return; + if (!player.videoPlayerSelected() && !playAfterConnect) return; // STATE_IDLE means the player is stopped if (player.getPlayer() != null && player.getPlayer().getPlaybackState() != Player.STATE_IDLE) addVideoPlayerView(); @@ -282,6 +282,9 @@ public class VideoDetailFragment } private void startService(boolean playAfterConnect) { + // startService() can be called concurrently and it will give a random crashes and NullPointerExceptions + // inside the service because the service will be bound twice. Prevent it with unbinding first + unbind(); getContext().startService(new Intent(getContext(), MainPlayer.class)); serviceConnection = getServiceConnection(playAfterConnect); bind(); @@ -708,7 +711,6 @@ public class VideoDetailFragment private void initThumbnailViews(@NonNull StreamInfo info) { thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark); - overlayThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark); if (!TextUtils.isEmpty(info.getThumbnailUrl())) { final String infoServiceName = NewPipe.getNameOfService(info.getServiceId()); @@ -718,11 +720,6 @@ public class VideoDetailFragment showSnackBarError(failReason.getCause(), UserAction.LOAD_IMAGE, infoServiceName, imageUri, R.string.could_not_load_thumbnails); } - - @Override - public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { - overlayThumbnailImageView.setImageBitmap(loadedImage); - } }; imageLoader.displayImage(info.getThumbnailUrl(), thumbnailImageView, @@ -855,7 +852,7 @@ public class VideoDetailFragment */ protected final LinkedList stack = new LinkedList<>(); - public void setTitleToUrl(int serviceId, String videoUrl, String name) { + /*public void setTitleToUrl(int serviceId, String videoUrl, String name) { if (name != null && !name.isEmpty()) { for (StackItem stackItem : stack) { if (stack.peek().getServiceId() == serviceId @@ -864,7 +861,7 @@ public class VideoDetailFragment } } } - } + }*/ @Override public boolean onBackPressed() { @@ -887,7 +884,9 @@ public class VideoDetailFragment } // If we have something in history of played items we replay it here - if (player != null && player.getPlayQueue() != null && player.getPlayQueue().previous()) { + boolean isPreviousCanBePlayed = player != null && player.getPlayQueue() != null && player.videoPlayerSelected() + && player.getPlayQueue().previous(); + if (isPreviousCanBePlayed) { return true; } // That means that we are on the start of the stack, @@ -914,6 +913,12 @@ public class VideoDetailFragment item.getUrl(), !TextUtils.isEmpty(item.getTitle()) ? item.getTitle() : "", item.getPlayQueue()); + + PlayQueueItem playQueueItem = item.getPlayQueue().getItem(); + // Update title, url, uploader from the last item in the stack (it's current now) + boolean isPlayerStopped = player == null || player.isPlayerStopped(); + if (playQueueItem != null && isPlayerStopped) + updateOverlayData(playQueueItem.getTitle(), playQueueItem.getUploader(), playQueueItem.getThumbnailUrl()); } /*////////////////////////////////////////////////////////////////////////// @@ -1199,7 +1204,7 @@ public class VideoDetailFragment FrameLayout viewHolder = getView().findViewById(R.id.player_placeholder); // Check if viewHolder already contains a child - if (player.getRootView() != viewHolder) removeVideoPlayerView(); + if (player.getRootView().getParent() != viewHolder) removeVideoPlayerView(); setHeightThumbnail(); // Prevent from re-adding a view multiple times @@ -1250,6 +1255,11 @@ public class VideoDetailFragment })); } + /** + * Method which controls the size of thumbnail and the size of main player inside a layout with thumbnail. + * It decides what height the player should have in both screen orientations. It knows about multiWindow feature + * and about videos with aspectRatio ZOOM (the height for them will be a bit higher, {@link #MAX_PLAYER_HEIGHT}) + */ private void setHeightThumbnail() { final DisplayMetrics metrics = getResources().getDisplayMetrics(); boolean isPortrait = metrics.heightPixels > metrics.widthPixels; @@ -1260,11 +1270,14 @@ public class VideoDetailFragment else height = isPortrait ? (int) (metrics.widthPixels / (16.0f / 9.0f)) - : (int) (metrics.heightPixels / 2f);; + : (int) (metrics.heightPixels / 2f); - thumbnailImageView.setLayoutParams( - new FrameLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height)); + thumbnailImageView.setLayoutParams(new FrameLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height)); thumbnailImageView.setMinimumHeight(height); + if (player != null) { + int maxHeight = (int) (metrics.heightPixels * MAX_PLAYER_HEIGHT); + player.getSurfaceView().setHeights(height, player.isInFullscreen() ? height : maxHeight); + } } private void showContent() { @@ -1393,13 +1406,11 @@ public class VideoDetailFragment animateView(thumbnailPlayButton, true, 200); videoTitleTextView.setText(name); - overlayTitleTextView.setText(name); if (!TextUtils.isEmpty(info.getUploaderName())) { uploaderTextView.setText(info.getUploaderName()); uploaderTextView.setVisibility(View.VISIBLE); uploaderTextView.setSelected(true); - overlayChannelTextView.setText(info.getUploaderName()); } else { uploaderTextView.setVisibility(View.GONE); } @@ -1481,8 +1492,9 @@ public class VideoDetailFragment setupActionBar(info); initThumbnailViews(info); - setTitleToUrl(info.getServiceId(), info.getUrl(), info.getName()); - setTitleToUrl(info.getServiceId(), info.getOriginalUrl(), info.getName()); + if (player == null || player.isPlayerStopped()) + updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl()); + if (!info.getErrors().isEmpty()) { showSnackBarError(info.getErrors(), @@ -1682,7 +1694,8 @@ public class VideoDetailFragment peek.setUrl(info.getUrl()); } - if (currentInfo == info) return; + updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl()); + if (currentInfo != null && info.getUrl().equals(currentInfo.getUrl())) return; currentInfo = info; setAutoplay(false); @@ -1702,6 +1715,8 @@ public class VideoDetailFragment public void onServiceStopped() { unbind(); setOverlayPlayPauseImage(); + if (currentInfo != null) + updateOverlayData(currentInfo.getName(), currentInfo.getUploaderName(), currentInfo.getThumbnailUrl()); } @Override @@ -1858,9 +1873,11 @@ public class VideoDetailFragment private void cleanUp() { // New beginning stack.clear(); + if (currentWorker != null) currentWorker.dispose(); stopService(); setInitialData(0,null,"", null); currentInfo = null; + updateOverlayData(null, null, null); } /*////////////////////////////////////////////////////////////////////////// @@ -1899,16 +1916,20 @@ public class VideoDetailFragment // Disable click because overlay buttons located on top of buttons from the player setOverlayElementsClickable(false); hideSystemUIIfNeeded(); - if (isLandscape() && player != null && player.isPlaying() && !player.isInFullscreen()) - player.toggleFullscreen(); + boolean needToExpand = isLandscape() + && player != null + && player.isPlaying() + && !player.isInFullscreen() + && player.videoPlayerSelected(); + if (needToExpand) player.toggleFullscreen(); break; case BottomSheetBehavior.STATE_COLLAPSED: // Re-enable clicks setOverlayElementsClickable(true); - if (player != null && player.isInFullscreen()) showSystemUi(); break; case BottomSheetBehavior.STATE_DRAGGING: case BottomSheetBehavior.STATE_SETTLING: + if (player != null && player.isInFullscreen()) showSystemUi(); if (player != null && player.isControlsVisible()) player.hideControls(0, 0); break; } @@ -1925,6 +1946,15 @@ public class VideoDetailFragment }); } + private void updateOverlayData(@Nullable String title, @Nullable String uploader, @Nullable String thumbnailUrl) { + overlayTitleTextView.setText(!TextUtils.isEmpty(title) ? title : ""); + overlayChannelTextView.setText(!TextUtils.isEmpty(uploader) ? uploader : ""); + overlayThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark); + if (!TextUtils.isEmpty(thumbnailUrl)) + imageLoader.displayImage(thumbnailUrl, overlayThumbnailImageView, + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, null); + } + private void setOverlayPlayPauseImage() { boolean playing = player != null && player.getPlayer().getPlayWhenReady(); int attr = playing ? R.attr.pause : R.attr.play; @@ -1935,7 +1965,8 @@ public class VideoDetailFragment if (behavior != null) { overlay.setAlpha(Math.min(MAX_OVERLAY_ALPHA, 1 - slideOffset)); - behavior.setTopAndBottomOffset((int)(-appBarLayout.getTotalScrollRange() * (1 - slideOffset) / 3)); + // These numbers are not special. They just do a cool transition + behavior.setTopAndBottomOffset((int)(-thumbnailImageView.getHeight() * 2 * (1 - slideOffset) / 3)); appBarLayout.requestLayout(); } } 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 70ab82fb8..815cbb8ab 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -276,14 +276,6 @@ public abstract class BasePlayer implements boolean same = playQueue != null && playQueue.equals(queue); - // Do not re-init the same PlayQueue. Save time - if (same && !playQueue.isDisposed()) { - // Player can have state = IDLE when playback is stopped or failed and we should retry() in this case - if (simpleExoPlayer != null && simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) - simpleExoPlayer.retry(); - return; - } - final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode()); final float playbackSpeed = intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()); final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch()); @@ -298,10 +290,17 @@ public abstract class BasePlayer implements && playQueue.getItem() != null && queue.getItem().getUrl().equals(playQueue.getItem().getUrl()) && queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET - && !same) { + && simpleExoPlayer.getPlaybackState() != Player.STATE_IDLE) { + // Player can have state = IDLE when playback is stopped or failed and we should retry() in this case + if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) simpleExoPlayer.retry(); simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition()); return; + } else if (same && !playQueue.isDisposed() && simpleExoPlayer != null) { + // Do not re-init the same PlayQueue. Save time + // Player can have state = IDLE when playback is stopped or failed and we should retry() in this case + if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) simpleExoPlayer.retry(); + return; } else if (intent.getBooleanExtra(RESUME_PLAYBACK, false) && isPlaybackResumeEnabled() && !same) { diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java index 63c2f195f..771d6d7e1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java @@ -179,6 +179,7 @@ public final class MainPlayer extends Service { playerImpl.savePlaybackState(); playerImpl.stopActivityBinding(); + playerImpl.removePopupFromView(); playerImpl.destroy(); } if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID); 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 07f485e7a..c29cfd19c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -34,10 +34,7 @@ import android.os.Build; import android.os.Handler; import android.preference.PreferenceManager; import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.SurfaceView; -import android.view.View; +import android.view.*; import android.widget.*; import androidx.annotation.NonNull; @@ -65,6 +62,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.resolver.MediaSourceTag; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.util.AnimationUtils; +import org.schabi.newpipe.views.ExpandableSurfaceView; import java.util.ArrayList; import java.util.List; @@ -109,8 +107,7 @@ public abstract class VideoPlayer extends BasePlayer private View rootView; - private AspectRatioFrameLayout aspectRatioFrameLayout; - private SurfaceView surfaceView; + private ExpandableSurfaceView surfaceView; private View surfaceForeground; private View loadingPanel; @@ -163,7 +160,6 @@ public abstract class VideoPlayer extends BasePlayer public void initViews(View rootView) { this.rootView = rootView; - this.aspectRatioFrameLayout = rootView.findViewById(R.id.aspectRatioLayout); this.surfaceView = rootView.findViewById(R.id.surfaceView); this.surfaceForeground = rootView.findViewById(R.id.surfaceForeground); this.loadingPanel = rootView.findViewById(R.id.loading_panel); @@ -187,12 +183,10 @@ public abstract class VideoPlayer extends BasePlayer setupSubtitleView(subtitleView, captionScale, captionStyle); this.resizeView = rootView.findViewById(R.id.resizeTextView); - resizeView.setText(PlayerHelper.resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode())); + resizeView.setText(PlayerHelper.resizeTypeOf(context, getSurfaceView().getResizeMode())); this.captionTextView = rootView.findViewById(R.id.captionTextView); - //this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN); this.playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY); @@ -501,7 +495,7 @@ public abstract class VideoPlayer extends BasePlayer if (DEBUG) { Log.d(TAG, "onVideoSizeChanged() called with: width / height = [" + width + " / " + height + " = " + (((float) width) / height) + "], unappliedRotationDegrees = [" + unappliedRotationDegrees + "], pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]"); } - aspectRatioFrameLayout.setAspectRatio(((float) width) / height); + getSurfaceView().setAspectRatio(((float) width) / height); } @Override @@ -715,15 +709,15 @@ public abstract class VideoPlayer extends BasePlayer } void onResizeClicked() { - if (getAspectRatioFrameLayout() != null) { - final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode(); + if (getSurfaceView() != null) { + final int currentResizeMode = getSurfaceView().getResizeMode(); final int newResizeMode = nextResizeMode(currentResizeMode); setResizeMode(newResizeMode); } } protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { - getAspectRatioFrameLayout().setResizeMode(resizeMode); + getSurfaceView().setResizeMode(resizeMode); getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode)); } @@ -894,11 +888,7 @@ public abstract class VideoPlayer extends BasePlayer return resolver.getPlaybackQuality(); } - public AspectRatioFrameLayout getAspectRatioFrameLayout() { - return aspectRatioFrameLayout; - } - - public SurfaceView getSurfaceView() { + public ExpandableSurfaceView getSurfaceView() { return surfaceView; } @@ -969,6 +959,10 @@ public abstract class VideoPlayer extends BasePlayer return qualityPopupMenu; } + public TextView getPlaybackSpeedTextView() { + return playbackSpeedTextView; + } + public PopupMenu getPlaybackSpeedPopupMenu() { return playbackSpeedPopupMenu; } diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java index 4c2740edc..72c3b71ee 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java @@ -46,6 +46,7 @@ import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.text.CaptionStyleCompat; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; @@ -195,6 +196,7 @@ public class VideoPlayerImpl extends VideoPlayer } setupElementsVisibility(); + setupElementsSize(); if (audioPlayerSelected()) { service.removeViewFromParent(); @@ -280,11 +282,16 @@ public class VideoPlayerImpl extends VideoPlayer } } + /** + * This method ensures that popup and main players have different look. We use one layout for both players and + * need to decide what to show and what to hide. Additional measuring should be done inside {@link #setupElementsSize}. + * {@link #setControlsSize} is used to adapt the UI to fullscreen mode, multiWindow, navBar, etc + */ private void setupElementsVisibility() { if (popupPlayerSelected()) { fullscreenButton.setVisibility(View.VISIBLE); screenRotationButton.setVisibility(View.GONE); - getRootView().findViewById(R.id.spaceBeforeControls).setVisibility(View.GONE); + getResizeView().setVisibility(View.GONE); getRootView().findViewById(R.id.metadataView).setVisibility(View.GONE); queueButton.setVisibility(View.GONE); moreOptionsButton.setVisibility(View.GONE); @@ -297,14 +304,16 @@ public class VideoPlayerImpl extends VideoPlayer playWithKodi.setVisibility(View.GONE); openInBrowser.setVisibility(View.GONE); playerCloseButton.setVisibility(View.GONE); + getTopControlsRoot().bringToFront(); + getBottomControlsRoot().bringToFront(); } else { fullscreenButton.setVisibility(View.GONE); setupScreenRotationButton(service.isLandscape()); - getRootView().findViewById(R.id.spaceBeforeControls).setVisibility(View.VISIBLE); + getResizeView().setVisibility(View.VISIBLE); getRootView().findViewById(R.id.metadataView).setVisibility(View.VISIBLE); moreOptionsButton.setVisibility(View.VISIBLE); getTopControlsRoot().setOrientation(LinearLayout.VERTICAL); - primaryControls.getLayoutParams().width = secondaryControls.getLayoutParams().width; + primaryControls.getLayoutParams().width = LinearLayout.LayoutParams.MATCH_PARENT; secondaryControls.setVisibility(View.GONE); moreOptionsButton.setImageDrawable(service.getResources().getDrawable( R.drawable.ic_expand_more_white_24dp)); @@ -325,6 +334,37 @@ public class VideoPlayerImpl extends VideoPlayer animateRotation(moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0); } + /** + * Changes padding, size of elements based on player selected right now. Popup player has small padding in comparison with the + * main player + */ + private void setupElementsSize() { + if (popupPlayerSelected()) { + int controlsPadding = service.getResources().getDimensionPixelSize(R.dimen.player_popup_controls_padding); + int buttonsPadding = service.getResources().getDimensionPixelSize(R.dimen.player_popup_buttons_padding); + getTopControlsRoot().setPaddingRelative(controlsPadding, 0, controlsPadding, 0); + getBottomControlsRoot().setPaddingRelative(controlsPadding, 0, controlsPadding, 0); + getQualityTextView().setPadding(buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding); + getPlaybackSpeedTextView().setPadding(buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding); + getQualityTextView().setPadding(buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding); + getCaptionTextView().setPadding(buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding); + getQualityTextView().setMinimumWidth(0); + getPlaybackSpeedTextView().setMinimumWidth(0); + } else if (videoPlayerSelected()) { + int buttonsMinWidth = service.getResources().getDimensionPixelSize(R.dimen.player_main_buttons_min_width); + int playerTopPadding = service.getResources().getDimensionPixelSize(R.dimen.player_main_top_padding); + int controlsPadding = service.getResources().getDimensionPixelSize(R.dimen.player_main_controls_padding); + int buttonsPadding = service.getResources().getDimensionPixelSize(R.dimen.player_main_buttons_padding); + getTopControlsRoot().setPaddingRelative(controlsPadding, playerTopPadding, controlsPadding, 0); + getBottomControlsRoot().setPaddingRelative(controlsPadding, 0, controlsPadding, 0); + getQualityTextView().setPadding(buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding); + getPlaybackSpeedTextView().setPadding(buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding); + getQualityTextView().setMinimumWidth(buttonsMinWidth); + getPlaybackSpeedTextView().setMinimumWidth(buttonsMinWidth); + getCaptionTextView().setPadding(buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding); + } + } + @Override public void initListeners() { super.initListeners(); @@ -357,24 +397,7 @@ public class VideoPlayerImpl extends VideoPlayer service.getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, settingsContentObserver); - - getRootView().addOnLayoutChangeListener((view, l, t, r, b, ol, ot, or, ob) -> { - if (l != ol || t != ot || r != or || b != ob) { - // Use smaller value to be consistent between screen orientations - // (and to make usage easier) - int width = r - l, height = b - t; - int min = Math.min(width, height); - maxGestureLength = (int) (min * MAX_GESTURE_LENGTH); - - if (DEBUG) Log.d(TAG, "maxGestureLength = " + maxGestureLength); - - volumeProgressBar.setMax(maxGestureLength); - brightnessProgressBar.setMax(maxGestureLength); - - setInitialGestureValues(); - queueLayout.getLayoutParams().height = min - queueLayout.getTop(); - } - }); + getRootView().addOnLayoutChangeListener(this); } public AppCompatActivity getParentActivity() { @@ -553,19 +576,15 @@ public class VideoPlayerImpl extends VideoPlayer intent.putExtra(Constants.KEY_URL, getVideoUrl()); intent.putExtra(Constants.KEY_TITLE, getVideoTitle()); intent.putExtra(VideoDetailFragment.AUTO_PLAY, true); + service.onDestroy(); context.startActivity(intent); + return; } else { if (fragmentListener == null) return; isFullscreen = !isFullscreen; setControlsSize(); fragmentListener.onFullscreenStateChanged(isInFullscreen()); - // When user presses back button in landscape mode and in fullscreen and uses ZOOM mode - // a video can be larger than screen. Prevent it like this - if (getAspectRatioFrameLayout().getResizeMode() == AspectRatioFrameLayout.RESIZE_MODE_ZOOM - && !isInFullscreen() - && service.isLandscape()) - onResizeClicked(); } if (!isInFullscreen()) { @@ -741,18 +760,28 @@ public class VideoPlayerImpl extends VideoPlayer } @Override - public void onLayoutChange(final View view, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - if (popupPlayerSelected()) { - float widthDp = Math.abs(right - left) / service.getResources().getDisplayMetrics().density; - final int visibility = widthDp > MINIMUM_SHOW_EXTRA_WIDTH_DP ? View.VISIBLE : View.GONE; - secondaryControls.setVisibility(visibility); - } else if (videoPlayerSelected() - && !isInFullscreen() - && getAspectRatioFrameLayout().getMeasuredHeight() > service.getResources().getDisplayMetrics().heightPixels * 0.8) { - // Resize mode is ZOOM probably. In this mode video will grow down and it will be weird. - // So let's open it in fullscreen - toggleFullscreen(); + public void onLayoutChange(final View view, int l, int t, int r, int b, + int ol, int ot, int or, int ob) { + if (l != ol || t != ot || r != or || b != ob) { + // Use smaller value to be consistent between screen orientations + // (and to make usage easier) + int width = r - l, height = b - t; + int min = Math.min(width, height); + maxGestureLength = (int) (min * MAX_GESTURE_LENGTH); + + if (DEBUG) Log.d(TAG, "maxGestureLength = " + maxGestureLength); + + volumeProgressBar.setMax(maxGestureLength); + brightnessProgressBar.setMax(maxGestureLength); + + setInitialGestureValues(); + queueLayout.getLayoutParams().height = min - queueLayout.getTop(); + + if (popupPlayerSelected()) { + float widthDp = Math.abs(r - l) / service.getResources().getDisplayMetrics().density; + final int visibility = widthDp > MINIMUM_SHOW_EXTRA_WIDTH_DP ? View.VISIBLE : View.GONE; + secondaryControls.setVisibility(visibility); + } } } @@ -781,6 +810,11 @@ public class VideoPlayerImpl extends VideoPlayer .apply(); } + private void restoreResizeMode() { + setResizeMode(defaultPreferences.getInt( + service.getString(R.string.last_resize_mode), AspectRatioFrameLayout.RESIZE_MODE_FIT)); + } + @Override protected VideoPlaybackResolver.QualityResolver getQualityResolver() { return new VideoPlaybackResolver.QualityResolver() { @@ -933,6 +967,7 @@ public class VideoPlayerImpl extends VideoPlayer intentFilter.addAction(ACTION_FAST_REWIND); intentFilter.addAction(ACTION_FAST_FORWARD); + intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); @@ -969,6 +1004,9 @@ public class VideoPlayerImpl extends VideoPlayer case ACTION_REPEAT: onRepeatClicked(); break; + case Intent.ACTION_CONFIGURATION_CHANGED: + setControlsSize(); + break; case Intent.ACTION_SCREEN_ON: shouldUpdateOnProgress = true; // Interrupt playback only when screen turns on and user is watching video in fragment @@ -1054,6 +1092,10 @@ public class VideoPlayerImpl extends VideoPlayer return playerType == MainPlayer.PlayerType.POPUP; } + public boolean isPlayerStopped() { + return getPlayer() == null || getPlayer().getPlaybackState() == SimpleExoPlayer.STATE_IDLE; + } + private int distanceFromCloseButton(MotionEvent popupMotionEvent) { final int closeOverlayButtonX = closeOverlayButton.getLeft() + closeOverlayButton.getWidth() / 2; final int closeOverlayButtonY = closeOverlayButton.getTop() + closeOverlayButton.getHeight() / 2; @@ -1143,32 +1185,29 @@ public class VideoPlayerImpl extends VideoPlayer fragmentListener.hideSystemUIIfNeeded(); } - /* - * This method measures width and height of controls visible on screen. It ensures that controls will be side-by-side with - * NavigationBar and notches but not under them. Tablets have only bottom NavigationBar - * */ - private void setControlsSize() { + /** + * Measures width and height of controls visible on screen. It ensures that controls will be side-by-side with + * NavigationBar and notches but not under them. Tablets have only bottom NavigationBar + */ + public void setControlsSize() { Point size = new Point(); Display display = getRootView().getDisplay(); - if (display == null) return; + if (display == null || !videoPlayerSelected()) return; // This method will give a correct size of a usable area of a window. // It doesn't include NavigationBar, notches, etc. display.getSize(size); - int spaceBeforeTopControls = getRootView().findViewById(R.id.spaceBeforeControls).getWidth(); - int widthForTopControls = isFullscreen ? size.x - spaceBeforeTopControls : ViewGroup.LayoutParams.MATCH_PARENT; - int widthForBottomControls = isFullscreen ? size.x : ViewGroup.LayoutParams.MATCH_PARENT; + int width = isFullscreen ? size.x : ViewGroup.LayoutParams.MATCH_PARENT; int gravity = isFullscreen ? (display.getRotation() == Surface.ROTATION_90 ? Gravity.START : Gravity.END) : Gravity.TOP; - primaryControls.getLayoutParams().width = widthForTopControls; - ((LinearLayout.LayoutParams) primaryControls.getLayoutParams()).gravity = gravity; - primaryControls.requestLayout(); + getTopControlsRoot().getLayoutParams().width = width; + RelativeLayout.LayoutParams topParams = ((RelativeLayout.LayoutParams) getTopControlsRoot().getLayoutParams()); + topParams.removeRule(RelativeLayout.ALIGN_PARENT_START); + topParams.removeRule(RelativeLayout.ALIGN_PARENT_END); + topParams.addRule(gravity == Gravity.END ? RelativeLayout.ALIGN_PARENT_END : RelativeLayout.ALIGN_PARENT_START); + getTopControlsRoot().requestLayout(); - secondaryControls.getLayoutParams().width = widthForTopControls; - ((LinearLayout.LayoutParams) secondaryControls.getLayoutParams()).gravity = gravity; - secondaryControls.requestLayout(); - - getBottomControlsRoot().getLayoutParams().width = widthForBottomControls; + getBottomControlsRoot().getLayoutParams().width = width; RelativeLayout.LayoutParams bottomParams = ((RelativeLayout.LayoutParams) getBottomControlsRoot().getLayoutParams()); bottomParams.removeRule(RelativeLayout.ALIGN_PARENT_START); bottomParams.removeRule(RelativeLayout.ALIGN_PARENT_END); @@ -1188,6 +1227,9 @@ public class VideoPlayerImpl extends VideoPlayer getRootView().findViewById(R.id.playbackWindowRoot).requestLayout(); } + /** + * @return statusBar height that was found inside system resources or default value if no value was provided inside resources + */ private int getStatusBarHeight() { int statusBarHeight = 0; int resourceId = service.getResources().getIdentifier("status_bar_height_landscape", "dimen", "android"); @@ -1200,6 +1242,9 @@ public class VideoPlayerImpl extends VideoPlayer return statusBarHeight; } + /** + * @return true if main player is attached to activity and activity inside multiWindow mode + */ private boolean isInMultiWindow() { AppCompatActivity parent = getParentActivity(); return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && parent != null && parent.isInMultiWindowMode(); @@ -1308,7 +1353,7 @@ public class VideoPlayerImpl extends VideoPlayer if (DEBUG) Log.d(TAG, "initPopup() called"); // Popup is already added to windowManager - if (getRootView().getLayoutParams() instanceof WindowManager.LayoutParams) return; + if (isPopupHasParent()) return; updateScreenSize(); @@ -1316,18 +1361,19 @@ public class VideoPlayerImpl extends VideoPlayer final float defaultSize = service.getResources().getDimension(R.dimen.popup_default_width); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(service); popupWidth = popupRememberSizeAndPos ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize; - + popupHeight = getMinimumVideoHeight(popupWidth); final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_PHONE : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; popupLayoutParams = new WindowManager.LayoutParams( - (int) popupWidth, (int) getMinimumVideoHeight(popupWidth), + (int) popupWidth, (int) popupHeight, layoutParamType, IDLE_WINDOW_FLAGS, PixelFormat.TRANSLUCENT); popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + getSurfaceView().setHeights((int) popupHeight, (int) popupHeight); int centerX = (int) (screenWidth / 2f - popupWidth / 2f); int centerY = (int) (screenHeight / 2f - popupHeight / 2f); @@ -1342,8 +1388,8 @@ public class VideoPlayerImpl extends VideoPlayer service.removeViewFromParent(); windowManager.addView(getRootView(), popupLayoutParams); - if (getAspectRatioFrameLayout().getResizeMode() == AspectRatioFrameLayout.RESIZE_MODE_ZOOM) - onResizeClicked(); + // Popup doesn't have aspectRatio selector, using FIT automatically + setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); } @SuppressLint("RtlHardcoded") @@ -1375,6 +1421,7 @@ public class VideoPlayerImpl extends VideoPlayer } private void initVideoPlayer() { + restoreResizeMode(); getRootView().setLayoutParams(new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); } @@ -1471,6 +1518,7 @@ public class VideoPlayerImpl extends VideoPlayer popupLayoutParams.height = height; popupWidth = width; popupHeight = height; + getSurfaceView().setHeights((int) popupHeight, (int) popupHeight); if (DEBUG) Log.d(TAG, "updatePopupSize() updated values: width = [" + width + "], height = [" + height + "]"); windowManager.updateViewLayout(getRootView(), popupLayoutParams); @@ -1499,6 +1547,14 @@ public class VideoPlayerImpl extends VideoPlayer animateOverlayAndFinishService(); } + public void removePopupFromView() { + boolean isCloseOverlayHasParent = closeOverlayView != null && closeOverlayView.getParent() != null; + if (isPopupHasParent()) + windowManager.removeView(getRootView()); + if (isCloseOverlayHasParent) + windowManager.removeView(closeOverlayView); + } + private void animateOverlayAndFinishService() { final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight() - closeOverlayButton.getY()); @@ -1527,12 +1583,18 @@ public class VideoPlayerImpl extends VideoPlayer }).start(); } + private boolean isPopupHasParent() { + View root = getRootView(); + return root != null && root.getLayoutParams() instanceof WindowManager.LayoutParams && root.getParent() != null; + } + /////////////////////////////////////////////////////////////////////////// // Manipulations with listener /////////////////////////////////////////////////////////////////////////// public void setFragmentListener(PlayerServiceEventListener listener) { fragmentListener = listener; + updateMetadata(); updatePlayback(); triggerProgressUpdate(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java index 6f4cf41de..f0178853f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java @@ -16,6 +16,8 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior { super(context, attrs); } + boolean skippingInterception = false; + @Override public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent event) { // Behavior of globalVisibleRect is different on different APIs. @@ -24,24 +26,40 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior { boolean visible; Rect rect = new Rect(); + // Drop folowing when actions ends + if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) + skippingInterception = false; + + // Found that user still swipping, continue folowing + if (skippingInterception) return false; + // Without overriding scrolling will not work in detail_content_root_layout ViewGroup controls = child.findViewById(R.id.detail_content_root_layout); if (controls != null) { visible = controls.getGlobalVisibleRect(rect); - if (rect.contains((int) event.getX(), (int) event.getY()) && visible) return false; + if (rect.contains((int) event.getRawX(), (int) event.getRawY()) && visible) { + skippingInterception = true; + return false; + } } // Without overriding scrolling will not work on relatedStreamsLayout ViewGroup relatedStreamsLayout = child.findViewById(R.id.relatedStreamsLayout); if (relatedStreamsLayout != null) { visible = relatedStreamsLayout.getGlobalVisibleRect(rect); - if (rect.contains((int) event.getX(), (int) event.getY()) && visible) return false; + if (rect.contains((int) event.getRawX(), (int) event.getRawY()) && visible) { + skippingInterception = true; + return false; + } } ViewGroup playQueue = child.findViewById(R.id.playQueue); if (playQueue != null) { visible = playQueue.getGlobalVisibleRect(rect); - if (rect.contains((int) event.getX(), (int) event.getY()) && visible) return false; + if (rect.contains((int) event.getRawX(), (int) event.getRawY()) && visible) { + skippingInterception = true; + return false; + } } return super.onInterceptTouchEvent(parent, child, event); diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index 12454bde9..e739b8f33 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -59,7 +59,7 @@ public abstract class PlayQueue implements Serializable { streams = new ArrayList<>(); streams.addAll(startWith); history = new ArrayList<>(); - history.add(streams.get(index)); + if (streams.size() > index) history.add(streams.get(index)); queueIndex = new AtomicInteger(index); disposed = false; @@ -305,7 +305,9 @@ public abstract class PlayQueue implements Serializable { } history.remove(streams.remove(removeIndex)); - history.add(streams.get(queueIndex.get())); + if (streams.size() > queueIndex.get()) { + history.add(streams.get(queueIndex.get())); + } } /** @@ -379,7 +381,9 @@ public abstract class PlayQueue implements Serializable { streams.add(0, streams.remove(newIndex)); } queueIndex.set(0); - history.add(streams.get(0)); + if (streams.size() > 0) { + history.add(streams.get(0)); + } broadcast(new ReorderEvent(originIndex, queueIndex.get())); } @@ -407,7 +411,9 @@ public abstract class PlayQueue implements Serializable { } else { queueIndex.set(0); } - history.add(streams.get(queueIndex.get())); + if (streams.size() > queueIndex.get()) { + history.add(streams.get(queueIndex.get())); + } broadcast(new ReorderEvent(originIndex, queueIndex.get())); } diff --git a/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java b/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java new file mode 100644 index 000000000..df012eafd --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java @@ -0,0 +1,102 @@ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.SurfaceView; +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; + +import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.*; + +public class ExpandableSurfaceView extends SurfaceView { + private int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; + private int baseHeight = 0; + private int maxHeight = 0; + private float videoAspectRatio = 0f; + private float scaleX = 1.0f; + private float scaleY = 1.0f; + + public ExpandableSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (videoAspectRatio == 0f) return; + + int width = MeasureSpec.getSize(widthMeasureSpec); + boolean verticalVideo = videoAspectRatio < 1; + // Use maxHeight only on non-fit resize mode and in vertical videos + int height = maxHeight != 0 && resizeMode != AspectRatioFrameLayout.RESIZE_MODE_FIT && verticalVideo ? maxHeight : baseHeight; + + if (height == 0) return; + + float viewAspectRatio = width / ((float) height); + float aspectDeformation = videoAspectRatio / viewAspectRatio - 1; + scaleX = 1.0f; + scaleY = 1.0f; + + switch (resizeMode) { + case AspectRatioFrameLayout.RESIZE_MODE_FIT: + if (aspectDeformation > 0) { + height = (int) (width / videoAspectRatio); + } else { + width = (int) (height * videoAspectRatio); + } + + break; + case RESIZE_MODE_ZOOM: + if (aspectDeformation < 0) { + scaleY = viewAspectRatio / videoAspectRatio; + } else { + scaleX = videoAspectRatio / viewAspectRatio; + } + + break; + default: + break; + } + super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } + + /** + * Scale view only in {@link #onLayout} to make transition for ZOOM mode as smooth as possible + */ + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + setScaleX(scaleX); + setScaleY(scaleY); + } + + /** + * @param base The height that will be used in every resize mode as a minimum height + * @param max The max height for vertical videos in non-FIT resize modes + */ + public void setHeights(int base, int max) { + if (baseHeight == base && maxHeight == max) return; + baseHeight = base; + maxHeight = max; + requestLayout(); + } + + @AspectRatioFrameLayout.ResizeMode + public void setResizeMode(int newResizeMode) { + if (resizeMode == newResizeMode) return; + + resizeMode = newResizeMode; + requestLayout(); + } + + @AspectRatioFrameLayout.ResizeMode + public int getResizeMode() { + return resizeMode; + } + + public void setAspectRatio(float aspectRatio) { + if (videoAspectRatio == aspectRatio) return; + + videoAspectRatio = aspectRatio; + requestLayout(); + } +} \ No newline at end of file diff --git a/app/src/main/res/layout-large-land/activity_main_player.xml b/app/src/main/res/layout-large-land/activity_main_player.xml index 73a1113c6..d434fe1ac 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 @@ -8,27 +8,18 @@ android:background="@color/black" android:gravity="center"> - + android:layout_centerHorizontal="true"/> - - - - - - + - - - @@ -235,11 +222,9 @@ android:id="@+id/qualityTextView" android:layout_width="wrap_content" android:layout_height="35dp" - android:padding="6dp" - android:layout_marginStart="5dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:gravity="center" - android:minWidth="50dp" android:text="720p" android:textColor="@android:color/white" android:textStyle="bold" @@ -251,11 +236,10 @@ android:id="@+id/playbackSpeed" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:gravity="center" android:minHeight="35dp" - android:minWidth="40dp" android:textColor="@android:color/white" android:textStyle="bold" android:background="?attr/selectableItemBackground" @@ -266,7 +250,7 @@ android:id="@+id/queueButton" android:layout_width="30dp" android:layout_height="35dp" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:clickable="true" android:focusable="true" @@ -280,8 +264,7 @@ android:id="@+id/moreOptionsButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:padding="8dp" - android:layout_marginEnd="8dp" + android:padding="@dimen/player_main_buttons_padding" android:clickable="true" android:focusable="true" android:scaleType="fitXY" @@ -304,7 +287,7 @@ android:id="@+id/resizeTextView" android:layout_width="wrap_content" android:layout_height="35dp" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:gravity="center" android:minWidth="50dp" @@ -318,7 +301,7 @@ android:id="@+id/captionTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:gravity="center|left" android:minHeight="35dp" @@ -341,7 +324,7 @@ android:id="@+id/playWithKodi" android:layout_width="wrap_content" android:layout_height="35dp" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:clickable="true" android:focusable="true" @@ -355,7 +338,7 @@ android:id="@+id/openInBrowser" android:layout_width="wrap_content" android:layout_height="35dp" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:clickable="true" android:focusable="true" @@ -369,8 +352,7 @@ android:id="@+id/share" android:layout_width="wrap_content" android:layout_height="35dp" - android:padding="6dp" - android:layout_marginEnd="8dp" + android:padding="@dimen/player_main_buttons_padding" android:clickable="true" android:focusable="true" android:scaleType="fitXY" @@ -387,9 +369,7 @@ android:id="@+id/fullScreenButton" android:layout_width="40dp" android:layout_height="40dp" - android:layout_marginTop="2dp" - android:layout_marginEnd="2dp" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_alignParentRight="true" android:background="?attr/selectableItemBackground" android:clickable="true" @@ -413,9 +393,8 @@ android:layout_alignParentBottom="true" android:gravity="center" android:orientation="horizontal" - android:paddingBottom="6dp" - android:paddingLeft="16dp" - android:paddingRight="16dp"> + android:paddingLeft="@dimen/player_main_controls_padding" + android:paddingRight="@dimen/player_main_controls_padding"> diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml index cf44d6bcb..d63108096 100644 --- a/app/src/main/res/layout/activity_main_player.xml +++ b/app/src/main/res/layout/activity_main_player.xml @@ -8,27 +8,18 @@ android:background="@color/black" android:gravity="center"> - + android:layout_centerHorizontal="true"/> - - - - - - + - - - @@ -233,11 +220,9 @@ android:id="@+id/qualityTextView" android:layout_width="wrap_content" android:layout_height="35dp" - android:padding="6dp" - android:layout_marginStart="5dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:gravity="center" - android:minWidth="50dp" android:text="720p" android:textColor="@android:color/white" android:textStyle="bold" @@ -249,11 +234,10 @@ android:id="@+id/playbackSpeed" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:gravity="center" android:minHeight="35dp" - android:minWidth="40dp" android:textColor="@android:color/white" android:textStyle="bold" android:background="?attr/selectableItemBackground" @@ -264,7 +248,7 @@ android:id="@+id/queueButton" android:layout_width="30dp" android:layout_height="35dp" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:clickable="true" android:focusable="true" @@ -278,8 +262,7 @@ android:id="@+id/moreOptionsButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:padding="8dp" - android:layout_marginEnd="8dp" + android:padding="@dimen/player_main_buttons_padding" android:clickable="true" android:focusable="true" android:scaleType="fitXY" @@ -302,7 +285,7 @@ android:id="@+id/resizeTextView" android:layout_width="wrap_content" android:layout_height="35dp" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:gravity="center" android:minWidth="50dp" @@ -316,7 +299,7 @@ android:id="@+id/captionTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:gravity="center|left" android:minHeight="35dp" @@ -339,7 +322,7 @@ android:id="@+id/playWithKodi" android:layout_width="wrap_content" android:layout_height="35dp" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:clickable="true" android:focusable="true" @@ -353,7 +336,7 @@ android:id="@+id/openInBrowser" android:layout_width="wrap_content" android:layout_height="35dp" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_marginEnd="8dp" android:clickable="true" android:focusable="true" @@ -367,8 +350,7 @@ android:id="@+id/share" android:layout_width="wrap_content" android:layout_height="35dp" - android:padding="6dp" - android:layout_marginEnd="8dp" + android:padding="@dimen/player_main_buttons_padding" android:clickable="true" android:focusable="true" android:scaleType="fitXY" @@ -385,9 +367,7 @@ android:id="@+id/fullScreenButton" android:layout_width="40dp" android:layout_height="40dp" - android:layout_marginTop="2dp" - android:layout_marginEnd="2dp" - android:padding="6dp" + android:padding="@dimen/player_main_buttons_padding" android:layout_alignParentRight="true" android:background="?attr/selectableItemBackground" android:clickable="true" @@ -411,9 +391,8 @@ android:layout_alignParentBottom="true" android:gravity="center" android:orientation="horizontal" - android:paddingBottom="6dp" - android:paddingLeft="16dp" - android:paddingRight="16dp"> + android:paddingLeft="@dimen/player_main_controls_padding" + android:paddingRight="@dimen/player_main_controls_padding"> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 07b71e5ee..56928b249 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -24,6 +24,15 @@ 2dp 4dp 8dp + + + 16dp + 6dp + 4dp + 6dp + 1dp + 40dp + 180dp 150dp