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 eb889cb00..222fc4687 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 @@ -11,6 +11,7 @@ import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.database.ContentObserver; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; @@ -102,13 +103,12 @@ import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; +import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.AnimatedProgressBar; import org.schabi.newpipe.views.LargeTextMovementMethod; -import java.io.Serializable; -import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -125,6 +125,7 @@ import io.reactivex.schedulers.Schedulers; import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; +import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired; import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -337,7 +338,7 @@ public class VideoDetailFragment stopPlayerListener(); playerService = null; player = null; - saveCurrentAndRestoreDefaultBrightness(); + restoreDefaultBrightness(); } } @@ -404,7 +405,7 @@ public class VideoDetailFragment settingsContentObserver = new ContentObserver(new Handler()) { @Override public void onChange(final boolean selfChange) { - if (activity != null && !PlayerHelper.globalScreenOrientationLocked(activity)) { + if (activity != null && !globalScreenOrientationLocked(activity)) { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } } @@ -426,7 +427,7 @@ public class VideoDetailFragment if (currentWorker != null) { currentWorker.dispose(); } - saveCurrentAndRestoreDefaultBrightness(); + restoreDefaultBrightness(); PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() .putString(getString(R.string.stream_info_selected_tab_key), @@ -538,31 +539,51 @@ public class VideoDetailFragment super.onSaveInstanceState(outState); if (!isLoading.get() && currentInfo != null && isVisible()) { - outState.putSerializable(INFO_KEY, currentInfo); + final String infoCacheKey = SerializedCache.getInstance() + .put(currentInfo, StreamInfo.class); + if (infoCacheKey != null) { + outState.putString(INFO_KEY, infoCacheKey); + } } if (playQueue != null) { - outState.putSerializable(VideoPlayer.PLAY_QUEUE_KEY, playQueue); + final String queueCacheKey = SerializedCache.getInstance() + .put(playQueue, PlayQueue.class); + if (queueCacheKey != null) { + outState.putString(VideoPlayer.PLAY_QUEUE_KEY, queueCacheKey); + } + } + final String stackCacheKey = SerializedCache.getInstance().put(stack, LinkedList.class); + if (stackCacheKey != null) { + outState.putString(STACK_KEY, stackCacheKey); } - outState.putSerializable(STACK_KEY, stack); } @Override protected void onRestoreInstanceState(@NonNull final Bundle savedState) { super.onRestoreInstanceState(savedState); - Serializable serializable = savedState.getSerializable(INFO_KEY); - if (serializable instanceof StreamInfo) { - currentInfo = (StreamInfo) serializable; - InfoCache.getInstance().putInfo(serviceId, url, currentInfo, InfoItem.InfoType.STREAM); + final String infoCacheKey = savedState.getString(INFO_KEY); + if (infoCacheKey != null) { + currentInfo = SerializedCache.getInstance().take(infoCacheKey, StreamInfo.class); + if (currentInfo != null) { + InfoCache.getInstance() + .putInfo(serviceId, url, currentInfo, InfoItem.InfoType.STREAM); + } } - serializable = savedState.getSerializable(STACK_KEY); - if (serializable instanceof Collection) { - //noinspection unchecked - stack.addAll((Collection) serializable); + final String stackCacheKey = savedState.getString(STACK_KEY); + if (stackCacheKey != null) { + final LinkedList cachedStack = + SerializedCache.getInstance().take(stackCacheKey, LinkedList.class); + if (cachedStack != null) { + stack.addAll(cachedStack); + } + } + final String queueCacheKey = savedState.getString(VideoPlayer.PLAY_QUEUE_KEY); + if (queueCacheKey != null) { + playQueue = SerializedCache.getInstance().take(queueCacheKey, PlayQueue.class); } - playQueue = (PlayQueue) savedState.getSerializable(VideoPlayer.PLAY_QUEUE_KEY); } /*////////////////////////////////////////////////////////////////////////// @@ -1808,9 +1829,6 @@ public class VideoDetailFragment setOverlayPlayPauseImage(); switch (state) { - case BasePlayer.STATE_COMPLETED: - restoreDefaultOrientation(); - break; case BasePlayer.STATE_PLAYING: if (positionView.getAlpha() != 1.0f && player.getPlayQueue() != null @@ -1869,10 +1887,11 @@ public class VideoDetailFragment public void onPlayerError(final ExoPlaybackException error) { if (error.type == ExoPlaybackException.TYPE_SOURCE || error.type == ExoPlaybackException.TYPE_UNEXPECTED) { - hideMainPlayer(); + // Properly exit from fullscreen if (playerService != null && player.isFullscreen()) { player.toggleFullscreen(); } + hideMainPlayer(); } } @@ -1911,7 +1930,13 @@ public class VideoDetailFragment } scrollToTop(); - addVideoPlayerView(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + addVideoPlayerView(); + } else { + // KitKat needs a delay before addVideoPlayerView call or it reports wrong height in + // activity.getWindow().getDecorView().getHeight() + new Handler().post(this::addVideoPlayerView); + } } @Override @@ -1919,13 +1944,15 @@ public class VideoDetailFragment // In tablet user experience will be better if screen will not be rotated // from landscape to portrait every time. // Just turn on fullscreen mode in landscape orientation - if (isLandscape() && DeviceUtils.isTablet(activity)) { + // or portrait & unlocked global orientation + if (DeviceUtils.isTablet(activity) + && (!globalScreenOrientationLocked(activity) || isLandscape())) { player.toggleFullscreen(); return; } final int newOrientation = isLandscape() - ? ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; activity.setRequestedOrientation(newOrientation); @@ -1970,7 +1997,11 @@ public class VideoDetailFragment WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; } activity.getWindow().getDecorView().setSystemUiVisibility(0); - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().setStatusBarColor(ThemeHelper.resolveColorFromAttr( + requireContext(), android.R.attr.colorPrimary)); + } } private void hideSystemUi() { @@ -1985,18 +2016,26 @@ public class VideoDetailFragment // Prevent jumping of the player on devices with cutout if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { activity.getWindow().getAttributes().layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; } - final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + // In multiWindow mode status bar is not transparent for devices with cutout + // if I include this flag. So without it is better in this case + if (!isInMultiWindow()) { + visibility |= View.SYSTEM_UI_FLAG_FULLSCREEN; + } activity.getWindow().getDecorView().setSystemUiVisibility(visibility); - activity.getWindow().setFlags( - WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, - WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && (isInMultiWindow() || (player != null && player.isFullscreen()))) { + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); + } + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } // Listener implementation @@ -2014,13 +2053,11 @@ public class VideoDetailFragment && player.getPlayer().getPlaybackState() != Player.STATE_IDLE; } - private void saveCurrentAndRestoreDefaultBrightness() { + private void restoreDefaultBrightness() { final WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); if (lp.screenBrightness == -1) { return; } - // Save current brightness level - PlayerHelper.setScreenBrightness(activity, lp.screenBrightness); // Restore the old brightness when fragment.onPause() called or // when a player is in portrait @@ -2039,7 +2076,7 @@ public class VideoDetailFragment || !player.isFullscreen() || bottomSheetState != BottomSheetBehavior.STATE_EXPANDED) { // Apply system brightness when the player is not in fullscreen - saveCurrentAndRestoreDefaultBrightness(); + restoreDefaultBrightness(); } else { // Restore already saved brightness level final float brightnessLevel = PlayerHelper.getScreenBrightness(activity); @@ -2058,11 +2095,9 @@ public class VideoDetailFragment } player.checkLandscape(); - final boolean orientationLocked = PlayerHelper.globalScreenOrientationLocked(activity); // Let's give a user time to look at video information page if video is not playing - if (orientationLocked && !player.isPlaying()) { + if (globalScreenOrientationLocked(activity) && !player.isPlaying()) { player.onPlay(); - player.showControlsThenHide(); } } @@ -2265,6 +2300,7 @@ public class VideoDetailFragment && player.videoPlayerSelected()) { player.toggleFullscreen(); } + setOverlayLook(appBarLayout, behavior, 1); break; case BottomSheetBehavior.STATE_COLLAPSED: moveFocusToMainFragment(true); @@ -2274,6 +2310,7 @@ public class VideoDetailFragment if (player != null) { player.onQueueClosed(); } + setOverlayLook(appBarLayout, behavior, 0); break; case BottomSheetBehavior.STATE_DRAGGING: case BottomSheetBehavior.STATE_SETTLING: 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 6c33f1d47..0aed3469f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java @@ -147,6 +147,7 @@ public final class MainPlayer extends Service { // Android TV will handle back button in case controls will be visible // (one more additional unneeded click while the player is hidden) playerImpl.hideControls(0, 0); + playerImpl.onQueueClosed(); // Notification shows information about old stream but if a user selects // a stream from backStack it's not actual anymore // So we should hide the notification at all. @@ -195,6 +196,10 @@ public final class MainPlayer extends Service { } if (playerImpl != null) { + // Exit from fullscreen when user closes the player via notification + if (playerImpl.isFullscreen()) { + playerImpl.toggleFullscreen(); + } removeViewFromParent(); playerImpl.setRecovery(); 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 a5a53f7a9..67ea673c3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -128,6 +128,8 @@ public abstract class VideoPlayer extends BasePlayer private View controlsRoot; private TextView currentDisplaySeek; + private View playerTopShadow; + private View playerBottomShadow; private View bottomControlsRoot; private SeekBar playbackSeekBar; @@ -190,6 +192,8 @@ public abstract class VideoPlayer extends BasePlayer this.controlAnimationView = view.findViewById(R.id.controlAnimationView); this.controlsRoot = view.findViewById(R.id.playbackControlRoot); this.currentDisplaySeek = view.findViewById(R.id.currentDisplaySeek); + this.playerTopShadow = view.findViewById(R.id.playerTopShadow); + this.playerBottomShadow = view.findViewById(R.id.playerBottomShadow); this.playbackSeekBar = view.findViewById(R.id.playbackSeekBar); this.playbackCurrentTime = view.findViewById(R.id.playbackCurrentTime); this.playbackEndTime = view.findViewById(R.id.playbackEndTime); @@ -356,11 +360,11 @@ public abstract class VideoPlayer extends BasePlayer return true; }); // apply caption language from previous user preference - if (userPreferredLanguage != null && (captionLanguage.equals(userPreferredLanguage) - || searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage) - || userPreferredLanguage.contains("(") && captionLanguage.startsWith( - userPreferredLanguage - .substring(0, userPreferredLanguage.indexOf('('))))) { + if (userPreferredLanguage != null + && (captionLanguage.equals(userPreferredLanguage) + || (searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage)) + || (userPreferredLanguage.contains("(") && captionLanguage.startsWith( + userPreferredLanguage.substring(0, userPreferredLanguage.indexOf('(')))))) { final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT); if (textRendererIndex != RENDERER_UNAVAILABLE) { trackSelector.setPreferredTextLanguage(captionLanguage); @@ -754,7 +758,6 @@ public abstract class VideoPlayer extends BasePlayer } qualityPopupMenu.show(); isSomePopupMenuVisible = true; - showControls(DEFAULT_CONTROLS_DURATION); final VideoStream videoStream = getSelectedVideoStream(); if (videoStream != null) { @@ -772,7 +775,6 @@ public abstract class VideoPlayer extends BasePlayer } playbackSpeedPopupMenu.show(); isSomePopupMenuVisible = true; - showControls(DEFAULT_CONTROLS_DURATION); } private void onCaptionClicked() { @@ -781,7 +783,6 @@ public abstract class VideoPlayer extends BasePlayer } captionPopupMenu.show(); isSomePopupMenuVisible = true; - showControls(DEFAULT_CONTROLS_DURATION); } void onResizeClicked() { @@ -958,6 +959,7 @@ public abstract class VideoPlayer extends BasePlayer ? DEFAULT_CONTROLS_HIDE_TIME : DPAD_CONTROLS_HIDE_TIME; + showHideShadow(true, DEFAULT_CONTROLS_DURATION, 0); animateView(controlsRoot, true, DEFAULT_CONTROLS_DURATION, 0, () -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime)); } @@ -967,6 +969,7 @@ public abstract class VideoPlayer extends BasePlayer Log.d(TAG, "showControls() called"); } controlsVisibilityHandler.removeCallbacksAndMessages(null); + showHideShadow(true, duration, 0); animateView(controlsRoot, true, duration); } @@ -986,8 +989,10 @@ public abstract class VideoPlayer extends BasePlayer Log.d(TAG, "hideControls() called with: delay = [" + delay + "]"); } controlsVisibilityHandler.removeCallbacksAndMessages(null); - controlsVisibilityHandler.postDelayed(() -> - animateView(controlsRoot, false, duration), delay); + controlsVisibilityHandler.postDelayed(() -> { + showHideShadow(false, duration, 0); + animateView(controlsRoot, false, duration); + }, delay); } public void hideControlsAndButton(final long duration, final long delay, final View button) { @@ -1006,6 +1011,11 @@ public abstract class VideoPlayer extends BasePlayer }; } + void showHideShadow(final boolean show, final long duration, final long delay) { + animateView(playerTopShadow, show, duration, delay, null); + animateView(playerBottomShadow, show, duration, delay, null); + } + public abstract void hideSystemUIIfNeeded(); /*////////////////////////////////////////////////////////////////////////// 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 fc91089b1..299fd4d81 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java @@ -27,22 +27,21 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.database.ContentObserver; import android.graphics.Bitmap; +import android.graphics.Color; import android.graphics.PixelFormat; -import android.graphics.Point; import android.net.Uri; import android.os.Build; import android.os.Handler; +import android.view.DisplayCutout; import androidx.preference.PreferenceManager; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; -import android.view.Display; import android.view.GestureDetector; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -104,7 +103,6 @@ import org.schabi.newpipe.util.ShareUtils; import java.util.List; import static android.content.Context.WINDOW_SERVICE; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD; import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND; @@ -116,6 +114,7 @@ import static org.schabi.newpipe.player.MainPlayer.ACTION_RECREATE_NOTIFICATION; import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT; import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; +import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; import static org.schabi.newpipe.util.AnimationUtils.Type.SLIDE_AND_ALPHA; import static org.schabi.newpipe.util.AnimationUtils.animateRotation; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -138,7 +137,6 @@ public class VideoPlayerImpl extends VideoPlayer static final String POPUP_SAVED_WIDTH = "popup_saved_width"; static final String POPUP_SAVED_X = "popup_saved_x"; static final String POPUP_SAVED_Y = "popup_saved_y"; - private static final int MINIMUM_SHOW_EXTRA_WIDTH_DP = 300; private static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; private static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS @@ -254,6 +252,7 @@ public class VideoPlayerImpl extends VideoPlayer } else { getRootView().setVisibility(View.VISIBLE); initVideoPlayer(); + onQueueClosed(); // Android TV: without it focus will frame the whole player playPauseButton.requestFocus(); } @@ -310,6 +309,9 @@ public class VideoPlayerImpl extends VideoPlayer titleTextView.setSelected(true); channelTextView.setSelected(true); + + // Prevent hiding of bottom sheet via swipe inside queue + this.itemsList.setNestedScrollingEnabled(false); } @Override @@ -334,7 +336,6 @@ 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()) { @@ -469,6 +470,17 @@ public class VideoPlayerImpl extends VideoPlayer Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, settingsContentObserver); getRootView().addOnLayoutChangeListener(this); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + queueLayout.setOnApplyWindowInsetsListener((view, windowInsets) -> { + final DisplayCutout cutout = windowInsets.getDisplayCutout(); + if (cutout != null) { + view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(), + cutout.getSafeInsetRight(), cutout.getSafeInsetBottom()); + } + return windowInsets; + }); + } } public boolean onKeyDown(final int keyCode) { @@ -688,53 +700,33 @@ public class VideoPlayerImpl extends VideoPlayer } /*////////////////////////////////////////////////////////////////////////// - // Player Overrides - //////////////////////////////////////////////////////////////////////////*/ + // Player Overrides + //////////////////////////////////////////////////////////////////////////*/ @Override public void toggleFullscreen() { if (DEBUG) { Log.d(TAG, "toggleFullscreen() called"); } - if (simpleExoPlayer == null || getCurrentMetadata() == null) { + if (popupPlayerSelected() + || simpleExoPlayer == null + || getCurrentMetadata() == null + || fragmentListener == null) { return; } - if (popupPlayerSelected()) { - setRecovery(); - service.removeViewFromParent(); - final Intent intent = NavigationHelper.getPlayerIntent( - service, - MainActivity.class, - this.getPlayQueue(), - this.getRepeatMode(), - this.getPlaybackSpeed(), - this.getPlaybackPitch(), - this.getPlaybackSkipSilence(), - null, - true, - !isPlaying(), - isMuted() - ); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Constants.KEY_SERVICE_ID, - getCurrentMetadata().getMetadata().getServiceId()); - intent.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM); - intent.putExtra(Constants.KEY_URL, getVideoUrl()); - intent.putExtra(Constants.KEY_TITLE, getVideoTitle()); - intent.putExtra(VideoDetailFragment.AUTO_PLAY, true); - service.onDestroy(); - context.startActivity(intent); - return; + isFullscreen = !isFullscreen; + if (!isFullscreen) { + // Apply window insets because Android will not do it when orientation changes + // from landscape to portrait (open vertical video to reproduce) + getControlsRoot().setPadding(0, 0, 0, 0); } else { - if (fragmentListener == null) { - return; - } - - isFullscreen = !isFullscreen; - setControlsSize(); - fragmentListener.onFullscreenStateChanged(isFullscreen()); + // Android needs tens milliseconds to send new insets but a user is able to see + // how controls changes it's position from `0` to `nav bar height` padding. + // So just hide the controls to hide this visual inconsistency + hideControls(0, 0); } + fragmentListener.onFullscreenStateChanged(isFullscreen()); if (!isFullscreen()) { titleTextView.setVisibility(View.GONE); @@ -748,6 +740,40 @@ public class VideoPlayerImpl extends VideoPlayer setupScreenRotationButton(); } + public void switchFromPopupToMain() { + if (DEBUG) { + Log.d(TAG, "switchFromPopupToMain() called"); + } + if (!popupPlayerSelected() || simpleExoPlayer == null || getCurrentMetadata() == null) { + return; + } + + setRecovery(); + service.removeViewFromParent(); + final Intent intent = NavigationHelper.getPlayerIntent( + service, + MainActivity.class, + this.getPlayQueue(), + this.getRepeatMode(), + this.getPlaybackSpeed(), + this.getPlaybackPitch(), + this.getPlaybackSkipSilence(), + null, + true, + !isPlaying(), + isMuted() + ); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(Constants.KEY_SERVICE_ID, + getCurrentMetadata().getMetadata().getServiceId()); + intent.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM); + intent.putExtra(Constants.KEY_URL, getVideoUrl()); + intent.putExtra(Constants.KEY_TITLE, getVideoTitle()); + intent.putExtra(VideoDetailFragment.AUTO_PLAY, true); + service.onDestroy(); + context.startActivity(intent); + } + @Override public void onClick(final View v) { super.onClick(v); @@ -775,9 +801,12 @@ public class VideoPlayerImpl extends VideoPlayer } else if (v.getId() == openInBrowser.getId()) { onOpenInBrowserClicked(); } else if (v.getId() == fullscreenButton.getId()) { - toggleFullscreen(); + switchFromPopupToMain(); } else if (v.getId() == screenRotationButton.getId()) { - if (!isVerticalVideo) { + // Only if it's not a vertical video or vertical video but in landscape with locked + // orientation a screen orientation can be changed automatically + if (!isVerticalVideo + || (service.isLandscape() && globalScreenOrientationLocked(service))) { fragmentListener.onScreenRotationButtonClicked(); } else { toggleFullscreen(); @@ -790,9 +819,12 @@ public class VideoPlayerImpl extends VideoPlayer if (getCurrentState() != STATE_COMPLETED) { getControlsVisibilityHandler().removeCallbacksAndMessages(null); + showHideShadow(true, DEFAULT_CONTROLS_DURATION, 0); animateView(getControlsRoot(), true, DEFAULT_CONTROLS_DURATION, 0, () -> { if (getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible()) { - if (v.getId() == playPauseButton.getId()) { + if (v.getId() == playPauseButton.getId() + // Hide controls in fullscreen immediately + || (v.getId() == screenRotationButton.getId() && isFullscreen)) { hideControls(0, 0); } else { hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); @@ -819,7 +851,7 @@ public class VideoPlayerImpl extends VideoPlayer buildQueue(); updatePlaybackButtons(); - getControlsRoot().setVisibility(View.INVISIBLE); + hideControls(0, 0); queueLayout.requestFocus(); animateView(queueLayout, SLIDE_AND_ALPHA, true, DEFAULT_CONTROLS_DURATION); @@ -911,9 +943,8 @@ public class VideoPlayerImpl extends VideoPlayer private void setupScreenRotationButton() { final boolean orientationLocked = PlayerHelper.globalScreenOrientationLocked(service); - final boolean tabletInLandscape = DeviceUtils.isTablet(service) && service.isLandscape(); final boolean showButton = videoPlayerSelected() - && (orientationLocked || isVerticalVideo || tabletInLandscape); + && (orientationLocked || isVerticalVideo || DeviceUtils.isTablet(service)); screenRotationButton.setVisibility(showButton ? View.VISIBLE : View.GONE); screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(service, isFullscreen() ? R.drawable.ic_fullscreen_exit_white_24dp @@ -925,6 +956,8 @@ public class VideoPlayerImpl extends VideoPlayer if (orientationLocked && isFullscreen() && service.isLandscape() == isVerticalVideo + && !DeviceUtils.isTv(service) + && !DeviceUtils.isTablet(service) && fragmentListener != null) { fragmentListener.onScreenRotationButtonClicked(); } @@ -955,6 +988,7 @@ public class VideoPlayerImpl extends VideoPlayer super.onDismiss(menu); if (isPlaying()) { hideControls(DEFAULT_CONTROLS_DURATION, 0); + hideSystemUIIfNeeded(); } } @@ -979,15 +1013,6 @@ public class VideoPlayerImpl extends VideoPlayer setInitialGestureValues(); queueLayout.getLayoutParams().height = height - queueLayout.getTop(); - - if (popupPlayerSelected()) { - final 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); - } } } @@ -1146,6 +1171,9 @@ public class VideoPlayerImpl extends VideoPlayer updateWindowFlags(IDLE_WINDOW_FLAGS); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); + if (isFullscreen) { + toggleFullscreen(); + } super.onCompleted(); } @@ -1255,20 +1283,6 @@ public class VideoPlayerImpl extends VideoPlayer updatePopupSize(getPopupLayoutParams().width, -1); checkPopupPositionBounds(); } - - // The only situation I need to re-calculate elements sizes is - // when a user rotates a device from landscape to landscape - // because in that case the controls should be aligned to another side of a screen. - // The problem is when user leaves the app and returns back - // (while the app in landscape) Android reports via DisplayMetrics that orientation - // is portrait and it gives wrong sizes calculations. - // Let's skip re-calculation in every case but landscape - final boolean reportedOrientationIsLandscape = service.isLandscape(); - final boolean actualOrientationIsLandscape = context.getResources() - .getConfiguration().orientation == ORIENTATION_LANDSCAPE; - if (reportedOrientationIsLandscape && actualOrientationIsLandscape) { - setControlsSize(); - } // Close it because when changing orientation from portrait // (in fullscreen mode) the size of queue layout can be larger than the screen size onQueueClosed(); @@ -1278,18 +1292,14 @@ public class VideoPlayerImpl extends VideoPlayer // Interrupt playback only when screen turns on // and user is watching video in popup player. // Same actions for video player will be handled in ACTION_VIDEO_FRAGMENT_RESUMED - if (backgroundPlaybackEnabled() - && popupPlayerSelected() - && (isPlaying() || isLoading())) { + if (popupPlayerSelected() && (isPlaying() || isLoading())) { useVideoSource(true); } break; case Intent.ACTION_SCREEN_OFF: shouldUpdateOnProgress = false; // Interrupt playback only when screen turns off with popup player working - if (backgroundPlaybackEnabled() - && popupPlayerSelected() - && (isPlaying() || isLoading())) { + if (popupPlayerSelected() && (isPlaying() || isLoading())) { useVideoSource(false); } break; @@ -1426,9 +1436,10 @@ public class VideoPlayerImpl extends VideoPlayer showOrHideButtons(); getControlsVisibilityHandler().removeCallbacksAndMessages(null); - getControlsVisibilityHandler().postDelayed(() -> - animateView(getControlsRoot(), false, duration, 0, - this::hideSystemUIIfNeeded), delay + getControlsVisibilityHandler().postDelayed(() -> { + showHideShadow(false, duration, 0); + animateView(getControlsRoot(), false, duration, 0, this::hideSystemUIIfNeeded); + }, delay ); } @@ -1456,11 +1467,17 @@ public class VideoPlayerImpl extends VideoPlayer } private void showSystemUIPartially() { - if (isFullscreen() && getParentActivity() != null) { + final AppCompatActivity activity = getParentActivity(); + if (isFullscreen() && activity != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); + } final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - getParentActivity().getWindow().getDecorView().setSystemUiVisibility(visibility); + activity.getWindow().getDecorView().setSystemUiVisibility(visibility); + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } } @@ -1475,92 +1492,6 @@ public class VideoPlayerImpl extends VideoPlayer getLoadController().disablePreloadingOfCurrentTrack(); } - /** - * 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() { - final Point size = new Point(); - final Display display = getRootView().getDisplay(); - 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); - - final boolean isLandscape = service.isLandscape(); - final int width = isFullscreen - ? (isLandscape ? size.x : size.y) - : ViewGroup.LayoutParams.MATCH_PARENT; - final int gravity = isFullscreen - ? (display.getRotation() == Surface.ROTATION_90 - ? Gravity.START : Gravity.END) - : Gravity.TOP; - - getTopControlsRoot().getLayoutParams().width = width; - final 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(); - - getBottomControlsRoot().getLayoutParams().width = width; - final RelativeLayout.LayoutParams bottomParams = - ((RelativeLayout.LayoutParams) getBottomControlsRoot().getLayoutParams()); - bottomParams.removeRule(RelativeLayout.ALIGN_PARENT_START); - bottomParams.removeRule(RelativeLayout.ALIGN_PARENT_END); - bottomParams.addRule(gravity == Gravity.END - ? RelativeLayout.ALIGN_PARENT_END - : RelativeLayout.ALIGN_PARENT_START); - getBottomControlsRoot().requestLayout(); - - final ViewGroup controlsRoot = getRootView().findViewById(R.id.playbackWindowRoot); - // In tablet navigationBar located at the bottom of the screen. - // And the situations when we need to set custom height is - // in fullscreen mode in tablet in non-multiWindow mode or with vertical video. - // Other than that MATCH_PARENT is good - final boolean navBarAtTheBottom = DeviceUtils.isTablet(service) || !isLandscape; - controlsRoot.getLayoutParams().height = isFullscreen && !isInMultiWindow() - && navBarAtTheBottom ? size.y : ViewGroup.LayoutParams.MATCH_PARENT; - controlsRoot.requestLayout(); - - final DisplayMetrics metrics = getRootView().getResources().getDisplayMetrics(); - int topPadding = isFullscreen && !isInMultiWindow() ? getStatusBarHeight() : 0; - topPadding = !isLandscape && DeviceUtils.hasCutout(topPadding, metrics) ? 0 : topPadding; - getRootView().findViewById(R.id.playbackWindowRoot).setTranslationY(topPadding); - getBottomControlsRoot().setTranslationY(-topPadding); - } - - /** - * @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; - final int resourceId = service.isLandscape() - ? service.getResources().getIdentifier( - "status_bar_height_landscape", "dimen", "android") - : service.getResources().getIdentifier( - "status_bar_height", "dimen", "android"); - - if (resourceId > 0) { - statusBarHeight = service.getResources().getDimensionPixelSize(resourceId); - } - if (statusBarHeight == 0) { - // Some devices provide wrong value for status bar height in landscape mode, - // this is workaround - final DisplayMetrics metrics = getRootView().getResources().getDisplayMetrics(); - statusBarHeight = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 24, metrics); - } - return statusBarHeight; - } - protected void setMuteButton(final ImageButton button, final boolean isMuted) { button.setImageDrawable(AppCompatResources.getDrawable(service, isMuted ? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp)); @@ -1603,8 +1534,6 @@ public class VideoPlayerImpl extends VideoPlayer && !DeviceUtils.isTablet(service)) { toggleFullscreen(); } - - setControlsSize(); } private void buildQueue() { @@ -2001,6 +1930,12 @@ public class VideoPlayerImpl extends VideoPlayer public void setFragmentListener(final PlayerServiceEventListener listener) { fragmentListener = listener; fragmentIsVisible = true; + // Apply window insets because Android will not do it when orientation changes + // from landscape to portrait + if (!isFullscreen) { + getControlsRoot().setPadding(0, 0, 0, 0); + } + queueLayout.setPadding(0, 0, 0, 0); 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 19c621221..47c0624b8 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 @@ -39,12 +39,13 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior } // Found that user still swiping, continue following - if (skippingInterception) { + if (skippingInterception || getState() == BottomSheetBehavior.STATE_SETTLING) { return false; } // Don't need to do anything if bottomSheet isn't expanded - if (getState() == BottomSheetBehavior.STATE_EXPANDED) { + if (getState() == BottomSheetBehavior.STATE_EXPANDED + && event.getAction() == MotionEvent.ACTION_DOWN) { // Without overriding scrolling will not work when user touches these elements for (final Integer element : skipInterceptionOfElements) { final ViewGroup viewGroup = child.findViewById(element); diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java index 4aa6070eb..a2def2a64 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java @@ -9,6 +9,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.Window; import android.view.WindowManager; +import android.widget.ProgressBar; import androidx.appcompat.content.res.AppCompatResources; import org.schabi.newpipe.R; import org.schabi.newpipe.player.BasePlayer; @@ -264,14 +265,19 @@ public class PlayerGestureListener } final Window window = parent.getWindow(); - - playerImpl.getBrightnessProgressBar().incrementProgressBy((int) distanceY); - final float currentProgressPercent = (float) playerImpl.getBrightnessProgressBar() - .getProgress() / playerImpl.getMaxGestureLength(); final WindowManager.LayoutParams layoutParams = window.getAttributes(); + final ProgressBar bar = playerImpl.getBrightnessProgressBar(); + final float oldBrightness = layoutParams.screenBrightness; + bar.setProgress((int) (bar.getMax() * Math.max(0, Math.min(1, oldBrightness)))); + bar.incrementProgressBy((int) distanceY); + + final float currentProgressPercent = (float) bar.getProgress() / bar.getMax(); layoutParams.screenBrightness = currentProgressPercent; window.setAttributes(layoutParams); + // Save current brightness level + PlayerHelper.setScreenBrightness(parent, currentProgressPercent); + if (DEBUG) { Log.d(TAG, "onScroll().brightnessControl, " + "currentBrightness = " + currentProgressPercent); diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index 7592d2f35..d852c2296 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -6,8 +6,6 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.BatteryManager; import android.os.Build; -import android.util.DisplayMetrics; -import android.util.TypedValue; import android.view.KeyEvent; import androidx.annotation.NonNull; @@ -74,17 +72,4 @@ public final class DeviceUtils { return false; } } - - /* - * Compares current status bar height with default status bar height in Android and decides, - * does the device has cutout or not - * */ - public static boolean hasCutout(final float statusBarHeight, final DisplayMetrics metrics) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - final float defaultStatusBarHeight = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 25, metrics); - return statusBarHeight > defaultStatusBarHeight; - } - return false; - } } diff --git a/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java b/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java new file mode 100644 index 000000000..23e16ff58 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java @@ -0,0 +1,39 @@ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.util.AttributeSet; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; +import com.google.android.material.appbar.CollapsingToolbarLayout; + +public class CustomCollapsingToolbarLayout extends CollapsingToolbarLayout { + public CustomCollapsingToolbarLayout(@NonNull final Context context) { + super(context); + overrideListener(); + } + + public CustomCollapsingToolbarLayout(@NonNull final Context context, + @Nullable final AttributeSet attrs) { + super(context, attrs); + overrideListener(); + } + + public CustomCollapsingToolbarLayout(@NonNull final Context context, + @Nullable final AttributeSet attrs, + final int defStyleAttr) { + super(context, attrs, defStyleAttr); + overrideListener(); + } + + /** + * CollapsingToolbarLayout sets it's own setOnApplyInsetsListener which consumes + * system insets {@link CollapsingToolbarLayout#onWindowInsetChanged(WindowInsetsCompat)} + * so we will not receive them in subviews with fitsSystemWindows = true. + * Override Google's behavior + * */ + public void overrideListener() { + ViewCompat.setOnApplyWindowInsetsListener(this, (v, insets) -> insets); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java b/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java index 798712b6b..a23172bd3 100644 --- a/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java +++ b/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java @@ -1,14 +1,17 @@ package org.schabi.newpipe.views; import android.content.Context; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.util.AttributeSet; import android.view.SurfaceView; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT; import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM; public class ExpandableSurfaceView extends SurfaceView { - private int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; + private int resizeMode = RESIZE_MODE_FIT; private int baseHeight = 0; private int maxHeight = 0; private float videoAspectRatio = 0.0f; @@ -30,7 +33,7 @@ public class ExpandableSurfaceView extends SurfaceView { final 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 + && resizeMode != RESIZE_MODE_FIT && verticalVideo ? maxHeight : baseHeight; if (height == 0) { @@ -42,26 +45,22 @@ public class ExpandableSurfaceView extends SurfaceView { 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; + if (resizeMode == RESIZE_MODE_FIT + // KitKat doesn't work well when a view has a scale like needed for ZOOM + || (resizeMode == RESIZE_MODE_ZOOM && VERSION.SDK_INT < VERSION_CODES.LOLLIPOP)) { + if (aspectDeformation > 0) { + height = (int) (width / videoAspectRatio); + } else { + width = (int) (height * videoAspectRatio); + } + } else if (resizeMode == RESIZE_MODE_ZOOM) { + if (aspectDeformation < 0) { + scaleY = viewAspectRatio / videoAspectRatio; + } else { + scaleX = videoAspectRatio / viewAspectRatio; + } } + super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java index 1ffb7d069..f400b62b1 100644 --- a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java @@ -17,15 +17,19 @@ */ package org.schabi.newpipe.views; +import android.annotation.TargetApi; import android.content.Context; import android.graphics.Rect; +import android.os.Build; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; +import org.schabi.newpipe.R; public final class FocusAwareCoordinator extends CoordinatorLayout { private final Rect childFocus = new Rect(); @@ -63,4 +67,41 @@ public final class FocusAwareCoordinator extends CoordinatorLayout { requestChildRectangleOnScreen(child, childFocus, false); } } + + /** + * Applies window insets to all children, not just for the first who consume the insets. + * Makes possible for multiple fragments to co-exist. Without this code + * the first ViewGroup who consumes will be the last who receive the insets + */ + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public WindowInsets dispatchApplyWindowInsets(final WindowInsets insets) { + boolean consumed = false; + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + final WindowInsets res = child.dispatchApplyWindowInsets(insets); + if (res.isConsumed()) { + consumed = true; + } + } + + if (consumed) { + insets.consumeSystemWindowInsets(); + } + return insets; + } + + /** + * Adjusts player's controls manually because fitsSystemWindows doesn't work when multiple + * receivers adjust its bounds. So when two listeners are present (like in profile page) + * the player's controls will not receive insets. This method fixes it + */ + @Override + protected boolean fitSystemWindows(final Rect insets) { + final ViewGroup controls = findViewById(R.id.playbackControlRoot); + if (controls != null) { + controls.setPadding(insets.left, insets.top, insets.right, insets.bottom); + } + return super.fitSystemWindows(insets); + } } diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index f69832b81..e2d18434d 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -20,7 +20,6 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="5" - android:fitsSystemWindows="true" android:isScrollContainer="true"> - @@ -162,7 +160,7 @@ - + + + + + - - - - + android:layout_height="match_parent"> -