Merge pull request #5459 from Stypox/fullscreen-autoplay

Add option to directly open fullscreen when the main player starts
This commit is contained in:
Tobi 2021-09-01 23:01:09 +02:00 committed by GitHub
commit 81fa0c1558
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 130 additions and 49 deletions

View File

@ -201,7 +201,7 @@ public final class VideoDetailFragment
@Nullable
private MainPlayer playerService;
private Player player;
private PlayerHolder playerHolder = PlayerHolder.getInstance();
private final PlayerHolder playerHolder = PlayerHolder.getInstance();
/*//////////////////////////////////////////////////////////////////////////
// Service management
@ -220,7 +220,7 @@ public final class VideoDetailFragment
return;
}
if (isLandscape()) {
if (DeviceUtils.isLandscape(requireContext())) {
// If the video is playing but orientation changed
// let's make the video in fullscreen again
checkLandscape();
@ -241,7 +241,7 @@ public final class VideoDetailFragment
&& isAutoplayEnabled()
&& player.getParentActivity() == null)) {
autoPlayEnabled = true; // forcefully start playing
openVideoPlayer();
openVideoPlayerAutoFullscreen();
}
}
@ -499,7 +499,7 @@ public final class VideoDetailFragment
break;
case R.id.detail_thumbnail_root_layout:
autoPlayEnabled = true; // forcefully start playing
openVideoPlayer();
openVideoPlayerAutoFullscreen();
break;
case R.id.detail_title_root_layout:
toggleTitleAndSecondaryControls();
@ -516,7 +516,7 @@ public final class VideoDetailFragment
showSystemUi();
} else {
autoPlayEnabled = true; // forcefully start playing
openVideoPlayer();
openVideoPlayer(false);
}
setOverlayPlayPauseImage(isPlayerAvailable() && player.isPlaying());
@ -762,7 +762,7 @@ public final class VideoDetailFragment
private void setupFromHistoryItem(final StackItem item) {
setAutoPlay(false);
hideMainPlayer();
hideMainPlayerOnLoadingNewStream();
setInitialData(item.getServiceId(), item.getUrl(),
item.getTitle() == null ? "" : item.getTitle(), item.getPlayQueue());
@ -882,7 +882,7 @@ public final class VideoDetailFragment
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
isLoading.set(false);
hideMainPlayer();
hideMainPlayerOnLoadingNewStream();
if (result.getAgeLimit() != NO_AGE_LIMIT && !prefs.getBoolean(
getString(R.string.show_age_restricted_content), false)) {
hideAgeRestrictedContent();
@ -897,8 +897,9 @@ public final class VideoDetailFragment
stack.push(new StackItem(serviceId, url, title, playQueue));
}
}
if (isAutoplayEnabled()) {
openVideoPlayer();
openVideoPlayerAutoFullscreen();
}
}
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
@ -1103,7 +1104,29 @@ public final class VideoDetailFragment
}
}
public void openVideoPlayer() {
/**
* Opens the video player, in fullscreen if needed. In order to open fullscreen, the activity
* is toggled to landscape orientation (which will then cause fullscreen mode).
*
* @param directlyFullscreenIfApplicable whether to open fullscreen if we are not already
* in landscape and screen orientation is locked
*/
public void openVideoPlayer(final boolean directlyFullscreenIfApplicable) {
if (directlyFullscreenIfApplicable
&& !DeviceUtils.isLandscape(requireContext())
&& PlayerHelper.globalScreenOrientationLocked(requireContext())) {
// Make sure the bottom sheet turns out expanded. When this code kicks in the bottom
// sheet could not have fully expanded yet, and thus be in the STATE_SETTLING state.
// When the activity is rotated, and its state is saved and then restored, the bottom
// sheet would forget what it was doing, since even if STATE_SETTLING is restored, it
// doesn't tell which state it was settling to, and thus the bottom sheet settles to
// STATE_COLLAPSED. This can be solved by manually setting the state that will be
// restored (i.e. bottomSheetState) to STATE_EXPANDED.
bottomSheetState = BottomSheetBehavior.STATE_EXPANDED;
// toggle landscape in order to open directly in fullscreen
onScreenRotationButtonClicked();
}
if (PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
showExternalPlaybackDialog();
@ -1112,6 +1135,18 @@ public final class VideoDetailFragment
}
}
/**
* If the option to start directly fullscreen is enabled, calls
* {@link #openVideoPlayer(boolean)} with {@code directlyFullscreenIfApplicable = true}, so that
* if the user is not already in landscape and he has screen orientation locked the activity
* rotates and fullscreen starts. Otherwise, if the option to start directly fullscreen is
* disabled, calls {@link #openVideoPlayer(boolean)} with {@code directlyFullscreenIfApplicable
* = false}, hence preventing it from going directly fullscreen.
*/
public void openVideoPlayerAutoFullscreen() {
openVideoPlayer(PlayerHelper.isStartMainPlayerFullscreenEnabled(requireContext()));
}
private void openNormalBackgroundPlayer(final boolean append) {
// See UI changes while remote playQueue changes
if (!isPlayerAvailable()) {
@ -1145,12 +1180,19 @@ public final class VideoDetailFragment
}
addVideoPlayerView();
final Intent playerIntent = NavigationHelper
.getPlayerIntent(requireContext(), MainPlayer.class, queue, true, autoPlayEnabled);
final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(),
MainPlayer.class, queue, true, autoPlayEnabled);
ContextCompat.startForegroundService(activity, playerIntent);
}
private void hideMainPlayer() {
/**
* When the video detail fragment is already showing details for a video and the user opens a
* new one, the video detail fragment changes all of its old data to the new stream, so if there
* is a video player currently open it should be hidden. This method does exactly that. If
* autoplay is enabled, the underlying player is not stopped completely, since it is going to
* be reused in a few milliseconds and the flickering would be annoying.
*/
private void hideMainPlayerOnLoadingNewStream() {
if (!isPlayerServiceAvailable()
|| playerService.getView() == null
|| !player.videoPlayerSelected()) {
@ -1158,8 +1200,12 @@ public final class VideoDetailFragment
}
removeVideoPlayerView();
playerService.stop(isAutoplayEnabled());
playerService.getView().setVisibility(View.GONE);
if (isAutoplayEnabled()) {
playerService.stopForImmediateReusing();
playerService.getView().setVisibility(View.GONE);
} else {
playerHolder.stopService();
}
}
private PlayQueue setupPlayQueueForIntent(final boolean append) {
@ -1252,7 +1298,7 @@ public final class VideoDetailFragment
final DisplayMetrics metrics = getResources().getDisplayMetrics();
if (getView() != null) {
final int height = (isInMultiWindow()
final int height = (DeviceUtils.isInMultiWindow(activity)
? requireView()
: activity.getWindow().getDecorView()).getHeight();
setHeightThumbnail(height, metrics);
@ -1275,7 +1321,7 @@ public final class VideoDetailFragment
requireView().getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
if (isPlayerAvailable() && player.isFullscreen()) {
final int height = (isInMultiWindow()
final int height = (DeviceUtils.isInMultiWindow(activity)
? requireView()
: activity.getWindow().getDecorView()).getHeight();
// Height is zero when the view is not yet displayed like after orientation change
@ -1808,7 +1854,7 @@ public final class VideoDetailFragment
|| error.type == ExoPlaybackException.TYPE_UNEXPECTED) {
// Properly exit from fullscreen
toggleFullscreenIfInFullscreenMode();
hideMainPlayer();
hideMainPlayerOnLoadingNewStream();
}
}
@ -1864,13 +1910,14 @@ public final class VideoDetailFragment
// from landscape to portrait every time.
// Just turn on fullscreen mode in landscape orientation
// or portrait & unlocked global orientation
final boolean isLandscape = DeviceUtils.isLandscape(requireContext());
if (DeviceUtils.isTablet(activity)
&& (!globalScreenOrientationLocked(activity) || isLandscape())) {
&& (!globalScreenOrientationLocked(activity) || isLandscape)) {
player.toggleFullscreen();
return;
}
final int newOrientation = isLandscape()
final int newOrientation = isLandscape
? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
: ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
@ -1942,15 +1989,17 @@ public final class VideoDetailFragment
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| 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()) {
final boolean isInMultiWindow = DeviceUtils.isInMultiWindow(activity);
if (!isInMultiWindow) {
visibility |= View.SYSTEM_UI_FLAG_FULLSCREEN;
}
activity.getWindow().getDecorView().setSystemUiVisibility(visibility);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& (isInMultiWindow() || (isPlayerAvailable() && player.isFullscreen()))) {
&& (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen()))) {
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
activity.getWindow().setNavigationBarColor(Color.TRANSPARENT);
}
@ -2022,15 +2071,6 @@ public final class VideoDetailFragment
}
}
private boolean isLandscape() {
return getResources().getDisplayMetrics().heightPixels < getResources()
.getDisplayMetrics().widthPixels;
}
private boolean isInMultiWindow() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && activity.isInMultiWindowMode();
}
/*
* Means that the player fragment was swiped away via BottomSheetLayout
* and is empty but ready for any new actions. See cleanUp()
@ -2213,7 +2253,7 @@ public final class VideoDetailFragment
setOverlayElementsClickable(false);
hideSystemUiIfNeeded();
// Conditions when the player should be expanded to fullscreen
if (isLandscape()
if (DeviceUtils.isLandscape(requireContext())
&& isPlayerAvailable()
&& player.isPlaying()
&& !player.isFullscreen()

View File

@ -24,7 +24,6 @@ import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@ -36,6 +35,7 @@ import androidx.core.content.ContextCompat;
import org.schabi.newpipe.App;
import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@ -133,32 +133,29 @@ public final class MainPlayer extends Service {
return START_NOT_STICKY;
}
public void stop(final boolean autoplayEnabled) {
public void stopForImmediateReusing() {
if (DEBUG) {
Log.d(TAG, "stop() called");
Log.d(TAG, "stopForImmediateReusing() called");
}
if (!player.exoPlayerIsNull()) {
player.saveWasPlaying();
// Releases wifi & cpu, disables keepScreenOn, etc.
if (!autoplayEnabled) {
player.pause();
}
// We can't just pause the player here because it will make transition
// from one stream to a new stream not smooth
player.smoothStopPlayer();
player.setRecovery();
// Android TV will handle back button in case controls will be visible
// (one more additional unneeded click while the player is hidden)
player.hideControls(0, 0);
player.closeItemsList();
// 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.
// When autoplay enabled such notification flashing is annoying so skip this case
if (!autoplayEnabled) {
NotificationUtil.getInstance().cancelNotificationAndStopForeground(this);
}
}
}
@ -222,11 +219,8 @@ public final class MainPlayer extends Service {
boolean isLandscape() {
// DisplayMetrics from activity context knows about MultiWindow feature
// while DisplayMetrics from app context doesn't
final DisplayMetrics metrics = (player != null
&& player.getParentActivity() != null
? player.getParentActivity().getResources()
: getResources()).getDisplayMetrics();
return metrics.heightPixels < metrics.widthPixels;
return DeviceUtils.isLandscape(player != null && player.getParentActivity() != null
? player.getParentActivity() : this);
}
@Nullable

View File

@ -621,6 +621,9 @@ public final class Player implements
return;
}
// needed for tablets, check the function for a better explanation
directlyOpenFullscreenIfNeeded();
final PlaybackParameters savedParameters = retrievePlaybackParametersFromPrefs(this);
final float playbackSpeed = savedParameters.speed;
final float playbackPitch = savedParameters.pitch;
@ -672,6 +675,7 @@ public final class Player implements
&& isPlaybackResumeEnabled(this)
&& !samePlayQueue
&& !newQueue.isEmpty()
&& newQueue.getItem() != null
&& newQueue.getItem().getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
databaseUpdateDisposable.add(recordManager.loadStreamState(newQueue.getItem())
.observeOn(AndroidSchedulers.mainThread())
@ -743,6 +747,22 @@ public final class Player implements
NavigationHelper.sendPlayerStartedEvent(context);
}
/**
* Open fullscreen on tablets where the option to have the main player start automatically in
* fullscreen mode is on. Rotating the device to landscape is already done in {@link
* VideoDetailFragment#openVideoPlayer(boolean)} when the thumbnail is clicked, and that's
* enough for phones, but not for tablets since the mini player can be also shown in landscape.
*/
private void directlyOpenFullscreenIfNeeded() {
if (fragmentListener != null
&& PlayerHelper.isStartMainPlayerFullscreenEnabled(service)
&& DeviceUtils.isTablet(service)
&& videoPlayerSelected()
&& PlayerHelper.globalScreenOrientationLocked(service)) {
fragmentListener.onScreenRotationButtonClicked();
}
}
private void initPlayback(@NonNull final PlayQueue queue,
@RepeatMode final int repeatMode,
final float playbackSpeed,
@ -3855,11 +3875,9 @@ public final class Player implements
if (DEBUG) {
Log.d(TAG, "toggleFullscreen() called");
}
if (popupPlayerSelected() || exoPlayerIsNull() || currentMetadata == null
|| fragmentListener == null) {
if (popupPlayerSelected() || exoPlayerIsNull() || fragmentListener == null) {
return;
}
//changeState(STATE_BLOCKED); TODO check what this does
isFullscreen = !isFullscreen;
if (!isFullscreen) {

View File

@ -239,6 +239,11 @@ public final class PlayerHelper {
.getBoolean(context.getString(R.string.brightness_gesture_control_key), true);
}
public static boolean isStartMainPlayerFullscreenEnabled(@NonNull final Context context) {
return getPreferences(context)
.getBoolean(context.getString(R.string.start_main_player_fullscreen_key), false);
}
public static boolean isAutoQueueEnabled(@NonNull final Context context) {
return getPreferences(context)
.getBoolean(context.getString(R.string.auto_queue_key), false);

View File

@ -11,6 +11,7 @@ import android.view.KeyEvent;
import androidx.annotation.Dimension;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
@ -130,4 +131,13 @@ public final class DeviceUtils {
&& !HI3798MV200
&& !CVT_MT5886_EU_1G;
}
public static boolean isLandscape(final Context context) {
return context.getResources().getDisplayMetrics().heightPixels < context.getResources()
.getDisplayMetrics().widthPixels;
}
public static boolean isInMultiWindow(final AppCompatActivity activity) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && activity.isInMultiWindowMode();
}
}

View File

@ -366,7 +366,9 @@ public final class NavigationHelper {
if (switchingPlayers) {
// Situation when user switches from players to main player. All needed data is
// here, we can start watching (assuming newQueue equals playQueue).
detailFragment.openVideoPlayer();
// Starting directly in fullscreen if the previous player type was popup.
detailFragment.openVideoPlayer(playerType == MainPlayer.PlayerType.POPUP
|| PlayerHelper.isStartMainPlayerFullscreenEnabled(context));
} else {
detailFragment.selectAndLoadVideo(serviceId, url, title, playQueue);
}

View File

@ -73,6 +73,8 @@
<item>@string/minimize_on_exit_popup_description</item>
</string-array>
<string name="start_main_player_fullscreen_key" translatable="false">start_main_player_fullscreen_key</string>
<string name="autoplay_key" translatable="false">autoplay_key</string>
<string name="autoplay_value" translatable="false">@string/autoplay_wifi_key</string>
<string name="autoplay_always_key" translatable="false">autoplay_always_key</string>

View File

@ -132,6 +132,8 @@
<string name="resume_on_audio_focus_gain_title">Resume playing</string>
<string name="resume_on_audio_focus_gain_summary">Continue playing after interruptions (e.g. phonecalls)</string>
<string name="download_dialog_title">Download</string>
<string name="start_main_player_fullscreen_title">Start main player in fullscreen</string>
<string name="start_main_player_fullscreen_summary">Do not start videos in the mini player, but turn to fullscreen mode directly, if auto rotation is locked. You can still access the mini player by exiting fullscreen.</string>
<string name="autoplay_title">Autoplay</string>
<string name="show_hold_to_append_title">Show \"Hold to append\" tip</string>
<string name="show_hold_to_append_summary">Show tip when pressing the background or the popup button in video \"Details:\"</string>

View File

@ -129,6 +129,14 @@
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/start_main_player_fullscreen_key"
android:summary="@string/start_main_player_fullscreen_summary"
android:title="@string/start_main_player_fullscreen_title"
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
<ListPreference
android:defaultValue="@string/autoplay_value"
android:entries="@array/autoplay_type_description"