Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Center videos correctly & fix padding for controls #4255

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager;
import android.provider.Settings;
import android.text.Spanned;
import android.text.TextUtils;
Expand Down Expand Up @@ -46,7 +44,9 @@
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.ViewPager;

import com.google.android.exoplayer2.ExoPlaybackException;
Expand Down Expand Up @@ -1998,7 +1998,7 @@ private void hideSystemUi() {
// 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
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
Expand Down
158 changes: 139 additions & 19 deletions app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
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.DisplayCutout;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.KeyEvent;
Expand All @@ -56,12 +56,15 @@
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.preference.PreferenceManager;
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;
Expand All @@ -71,6 +74,7 @@
import com.google.android.exoplayer2.ui.SubtitleView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.nostra13.universalimageloader.core.assist.FailReason;

import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.StreamingService;
Expand All @@ -91,9 +95,9 @@
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.KoreUtil;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
Expand Down Expand Up @@ -203,6 +207,11 @@ public class VideoPlayerImpl extends VideoPlayer
private int cachedDuration;
private String cachedDurationString;

private boolean gotCutout;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private boolean gotCutout;
private boolean hasCutout;

private int insetLeft;
private int insetRight;
private int insetBottom;

// Popup
private WindowManager.LayoutParams popupLayoutParams;
public WindowManager windowManager;
Expand Down Expand Up @@ -1490,13 +1499,13 @@ public void hideSystemUIIfNeeded() {
* 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.
final Point size = new Point();
display.getSize(size);

final boolean isLandscape = service.isLandscape();
Expand All @@ -1508,7 +1517,84 @@ public void setControlsSize() {
? Gravity.START : Gravity.END)
: Gravity.TOP;

getTopControlsRoot().getLayoutParams().width = width;
final int navBarHeight = DeviceUtils.hasNavBar(windowManager) ? getNavBarHeight() : 0;
final int statusBarHeight = getStatusBarHeight();
final int controlsPadding = service.getResources()
.getDimensionPixelSize(R.dimen.player_main_controls_padding);
final int playerTopPadding = service.getResources()
.getDimensionPixelSize(R.dimen.player_main_top_padding);

// Handle rotation
final int ctrlPadLeft;
final int ctrlPadRight;
final int ctrlPadTop = playerTopPadding + statusBarHeight;
final int ctrlPadBottom = navBarHeight + insetBottom;
final int bottom = ctrlPadBottom + (navBarHeight == 0 ? controlsPadding : playerTopPadding);
final int rotation = windowManager.getDefaultDisplay().getRotation();

if (DeviceUtils.isTv(service)) {
gotCutout = false;
ctrlPadLeft = controlsPadding;
ctrlPadRight = controlsPadding;
} else if (DeviceUtils.isTablet(service)) {
if (gotCutout) {
ctrlPadLeft = controlsPadding + insetLeft;
ctrlPadRight = controlsPadding + insetRight;
} else {
ctrlPadLeft = controlsPadding;
ctrlPadRight = controlsPadding;
}
} else {
if (rotation == Surface.ROTATION_90) {
if (gotCutout) {
ctrlPadLeft = controlsPadding + insetLeft;
ctrlPadRight = controlsPadding
+ (getNavBarMode() == 2 ? 0 : navBarHeight) + insetRight;
} else {
ctrlPadLeft = controlsPadding;
ctrlPadRight = controlsPadding;
}
} else {
if (gotCutout) {
ctrlPadLeft = controlsPadding
+ (getNavBarMode() == 2 ? 0 : navBarHeight) + insetLeft;
ctrlPadRight = controlsPadding + insetRight;
} else {
ctrlPadLeft = controlsPadding;
ctrlPadRight = controlsPadding;
}
}
Comment on lines +1548 to +1566
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you only check for 90 deg, but not for 180?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TobiGr When it's 90° in landscape then the navbar is on the right side of your screen, so the else statement handles the opposite. And this orientation check only applies to phones in landscape, so it always seems to work.

If you look at #4272 then you'll see that avently worked on a cleaner method, but we had a disagreement about handling a navbar feature that's found on a lot of custom Android 10 ROMs (LineageOS, ArrowOS, Resurrection Remix, PixelExperience, AICP etc).

}

if (gotCutout) {
// We need the real screen size to position the controls correctly
getRealScreenSize(display, size);
getTopControlsRoot().getLayoutParams().width = size.x;
getBottomControlsRoot().getLayoutParams().width = size.x;
} else {
getBottomControlsRoot().getLayoutParams().width = width;
getTopControlsRoot().getLayoutParams().width = width;
}

if (isLandscape && isFullscreen && !isInMultiWindow()) {
getTopControlsRoot().setPaddingRelative(ctrlPadLeft, ctrlPadTop, ctrlPadRight,
0);
getBottomControlsRoot().setPaddingRelative(ctrlPadLeft, 0, ctrlPadRight,
getNavBarMode() == 2 ? bottom : (DeviceUtils.isTablet(service) ? ctrlPadBottom
: 0));
} else if (!isLandscape && isFullscreen && !isInMultiWindow()) {
getTopControlsRoot().setPaddingRelative(controlsPadding, ctrlPadTop, controlsPadding,
0);
getBottomControlsRoot().setPaddingRelative(controlsPadding, 0, controlsPadding,
getNavBarMode() == 2 ? bottom : ctrlPadBottom);
} else {
final int topPad = isFullscreen && !isInMultiWindow() ? statusBarHeight : 0;
getTopControlsRoot().setPaddingRelative(controlsPadding, playerTopPadding + topPad,
controlsPadding, 0);
getBottomControlsRoot().setPaddingRelative(controlsPadding, 0,
controlsPadding, 0);
}

final RelativeLayout.LayoutParams topParams =
((RelativeLayout.LayoutParams) getTopControlsRoot().getLayoutParams());
topParams.removeRule(RelativeLayout.ALIGN_PARENT_START);
Expand All @@ -1518,7 +1604,6 @@ public void setControlsSize() {
: RelativeLayout.ALIGN_PARENT_START);
getTopControlsRoot().requestLayout();

getBottomControlsRoot().getLayoutParams().width = width;
final RelativeLayout.LayoutParams bottomParams =
((RelativeLayout.LayoutParams) getBottomControlsRoot().getLayoutParams());
bottomParams.removeRule(RelativeLayout.ALIGN_PARENT_START);
Expand All @@ -1527,22 +1612,42 @@ public void setControlsSize() {
? 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();
@SuppressWarnings({"ConstantConditions", "JavaReflectionMemberAccess"})
@SuppressLint("ObsoleteSdkInt")
private void getRealScreenSize(final Display display, final Point size) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
display.getRealSize(size);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
try {
size.x = (Integer) Display.class.getMethod("getRawWidth").invoke(display);
size.y = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
} catch (final Exception e) {
// This should never happen
Log.e(TAG, "getRealScreenSize() failed", e);
}
}
}

private int getNavBarMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
final int resourceId = service.getResources().getIdentifier(
"config_navBarInteractionMode", "integer", "android");
if (resourceId > 0) {
return service.getResources().getInteger(resourceId);
}
}
return 0;
}

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);
private int getNavBarHeight() {
final int resourceId = service.getResources().getIdentifier(
"navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
return service.getResources().getDimensionPixelSize(resourceId);
}
return 0;
}

/**
Expand Down Expand Up @@ -2010,6 +2115,21 @@ private boolean popupHasParent() {
public void setFragmentListener(final PlayerServiceEventListener listener) {
fragmentListener = listener;
fragmentIsVisible = true;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
getRootView().setOnApplyWindowInsetsListener((view, windowInsets) -> {
final DisplayCutout cutout = windowInsets.getDisplayCutout();
if (cutout != null) {
gotCutout = true;
insetLeft = cutout.getSafeInsetLeft();
insetRight = cutout.getSafeInsetRight();
insetBottom = cutout.getSafeInsetBottom();
setControlsSize();
}
return windowInsets;
});
}

updateMetadata();
updatePlayback();
triggerProgressUpdate();
Expand Down
50 changes: 38 additions & 12 deletions app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
package org.schabi.newpipe.util;

import android.annotation.SuppressLint;
import android.app.UiModeManager;
import android.content.Context;
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.os.IBinder;
import android.util.Log;
import android.view.Display;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.WindowManager;

import androidx.annotation.NonNull;

import org.schabi.newpipe.App;

import java.lang.reflect.Method;

import static android.content.Context.BATTERY_SERVICE;
import static android.content.Context.UI_MODE_SERVICE;

Expand Down Expand Up @@ -75,16 +82,35 @@ public static boolean isConfirmKey(final int keyCode) {
}
}

/*
* 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;

// This method works on real devices and emulators
@SuppressWarnings("ConstantConditions")
@SuppressLint({"PrivateApi", "ObsoleteSdkInt"})
public static boolean hasNavBar(final WindowManager wm) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
try {
final Class<?> serviceManager = Class.forName("android.os.ServiceManager");
final IBinder serviceBinder = (IBinder) serviceManager
.getMethod("getService", String.class)
.invoke(serviceManager, "window");
final Class<?> stub = Class.forName("android.view.IWindowManager$Stub");
final Object windowManager = stub.getMethod("asInterface", IBinder.class)
.invoke(stub, serviceBinder);
Method hasNavigationBar;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
hasNavigationBar = windowManager.getClass()
.getMethod("hasNavigationBar");
return (boolean) hasNavigationBar.invoke(windowManager);
}
hasNavigationBar = windowManager.getClass()
.getMethod("hasNavigationBar", int.class);
final Display dsp = wm.getDefaultDisplay();
return (boolean) hasNavigationBar.invoke(windowManager, dsp.getDisplayId());
} catch (final Exception e) {
Log.e(".DeviceUtils", "hasNavBar() failed", e);
}
}
return false;
return !(KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK)
&& KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_HOME));
}
}