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
This commit is contained in:
Avently 2020-02-05 08:59:30 +03:00
parent 26e487c01a
commit f334a2740f
14 changed files with 405 additions and 225 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,27 +8,18 @@
android:background="@color/black"
android:gravity="center">
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout
android:id="@+id/aspectRatioLayout"
<org.schabi.newpipe.views.ExpandableSurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_gravity="center">
android:layout_centerHorizontal="true"/>
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
<View
android:id="@+id/surfaceForeground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
<View
android:id="@+id/surfaceForeground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:layout_alignBottom="@+id/surfaceView"/>
<com.google.android.exoplayer2.ui.SubtitleView
android:id="@+id/subtitleView"
@ -147,12 +138,6 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- It will be hidden in popup -->
<Space
android:id="@+id/spaceBeforeControls"
android:layout_width="16dp"
android:layout_height="0dp" />
<LinearLayout
android:id="@+id/topControls"
android:layout_width="match_parent"
@ -160,24 +145,26 @@
android:layout_alignParentTop="true"
android:orientation="vertical"
android:gravity="top"
android:paddingTop="4dp"
android:layout_toEndOf="@id/spaceBeforeControls"
android:paddingTop="@dimen/player_main_top_padding"
android:paddingStart="@dimen/player_main_controls_padding"
android:paddingEnd="@dimen/player_main_controls_padding"
android:baselineAligned="false">
<LinearLayout
android:id="@+id/primaryControls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="50dp"
android:baselineAligned="false"
android:gravity="top"
android:paddingBottom="7dp"
android:layout_marginBottom="7dp"
tools:ignore="RtlHardcoded">
<ImageButton
android:id="@+id/playerCloseButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:padding="6dp"
android:padding="@dimen/player_main_buttons_padding"
android:layout_marginEnd="8dp"
android:clickable="true"
android:focusable="true"
@ -193,8 +180,8 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingTop="6dp"
android:paddingRight="8dp"
android:layout_marginTop="6dp"
android:layout_marginRight="8dp"
tools:ignore="RtlHardcoded"
android:layout_weight="1">
@ -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">
<TextView
android:id="@+id/playbackCurrentTime"
@ -470,7 +449,7 @@
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
android:padding="6dp"
android:padding="@dimen/player_main_buttons_padding"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"

View File

@ -633,7 +633,7 @@
android:layout_marginRight="2dp"
android:padding="10dp"
android:scaleType="center"
android:src="@drawable/ic_pause_white_24dp"
android:src="?attr/play"
android:background="?attr/selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>

View File

@ -8,27 +8,18 @@
android:background="@color/black"
android:gravity="center">
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout
android:id="@+id/aspectRatioLayout"
<org.schabi.newpipe.views.ExpandableSurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_gravity="center">
android:layout_centerHorizontal="true"/>
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
<View
android:id="@+id/surfaceForeground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
<View
android:id="@+id/surfaceForeground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:layout_alignBottom="@+id/surfaceView"/>
<com.google.android.exoplayer2.ui.SubtitleView
android:id="@+id/subtitleView"
@ -145,12 +136,6 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- It will be hidden in popup -->
<Space
android:id="@+id/spaceBeforeControls"
android:layout_width="16dp"
android:layout_height="0dp" />
<LinearLayout
android:id="@+id/topControls"
android:layout_width="match_parent"
@ -158,24 +143,26 @@
android:layout_alignParentTop="true"
android:orientation="vertical"
android:gravity="top"
android:paddingTop="4dp"
android:layout_toEndOf="@id/spaceBeforeControls"
android:paddingTop="@dimen/player_main_top_padding"
android:paddingStart="@dimen/player_main_controls_padding"
android:paddingEnd="@dimen/player_main_controls_padding"
android:baselineAligned="false">
<LinearLayout
android:id="@+id/primaryControls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="50dp"
android:baselineAligned="false"
android:gravity="top"
android:paddingBottom="7dp"
android:layout_marginBottom="7dp"
tools:ignore="RtlHardcoded">
<ImageButton
android:id="@+id/playerCloseButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:padding="6dp"
android:padding="@dimen/player_main_buttons_padding"
android:layout_marginEnd="8dp"
android:clickable="true"
android:focusable="true"
@ -191,8 +178,8 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingTop="6dp"
android:paddingRight="8dp"
android:layout_marginTop="6dp"
android:layout_marginRight="8dp"
tools:ignore="RtlHardcoded"
android:layout_weight="1">
@ -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">
<TextView
android:id="@+id/playbackCurrentTime"
@ -468,7 +447,7 @@
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
android:padding="6dp"
android:padding="@dimen/player_main_buttons_padding"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"

View File

@ -623,7 +623,7 @@
android:layout_marginRight="2dp"
android:padding="10dp"
android:scaleType="center"
android:src="@drawable/ic_pause_white_24dp"
android:src="?attr/play"
android:background="?attr/selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>

View File

@ -24,6 +24,15 @@
<dimen name="video_item_search_duration_margin">2dp</dimen>
<dimen name="video_item_detail_description_to_details_margin">4dp</dimen>
<dimen name="software_component_item_padding">8dp</dimen>
<!-- Players padding & sizes -->
<dimen name="player_main_controls_padding">16dp</dimen>
<dimen name="player_popup_controls_padding">6dp</dimen>
<dimen name="player_main_top_padding">4dp</dimen>
<dimen name="player_main_buttons_padding">6dp</dimen>
<dimen name="player_popup_buttons_padding">1dp</dimen>
<dimen name="player_main_buttons_min_width">40dp</dimen>
<!-- Miscellaneous -->
<dimen name="popup_default_width">180dp</dimen>
<dimen name="popup_minimum_width">150dp</dimen>