Chore/rework fullscreen configuration (#4142)

* feat(android): handle navigation bar status in full-screen mode
* chore: update default value of prop
* chore(android): rework fullscreen configuration

---------

Co-authored-by: mostafahasani <seyedmostafahassani@gmail.com>
This commit is contained in:
Olivier Bouillet 2024-09-04 09:53:30 +02:00 committed by GitHub
parent d6bae3cd07
commit 9707081ab9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 141 additions and 23 deletions

View File

@ -7,6 +7,8 @@ class ControlsConfig {
var hideSeekBar: Boolean = false
var seekIncrementMS: Int = 10000
var hideDuration: Boolean = false
var hideNavigationBarOnFullScreenMode: Boolean = true
var hideNotificationBarOnFullScreenMode: Boolean = true
companion object {
@JvmStatic
@ -17,8 +19,9 @@ class ControlsConfig {
config.hideSeekBar = ReactBridgeUtils.safeGetBool(src, "hideSeekBar", false)
config.seekIncrementMS = ReactBridgeUtils.safeGetInt(src, "seekIncrementMS", 10000)
config.hideDuration = ReactBridgeUtils.safeGetBool(src, "hideDuration", false)
config.hideNavigationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(src, "hideNavigationBarOnFullScreenMode", true)
config.hideNotificationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(src, "hideNotificationBarOnFullScreenMode", true)
}
return config
}
}

View File

@ -6,11 +6,16 @@ import android.content.Context
import android.os.Handler
import android.os.Looper
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.ImageButton
import androidx.activity.OnBackPressedCallback
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.media3.ui.LegacyPlayerControlView
import com.brentvatne.common.api.ControlsConfig
import com.brentvatne.common.toolbox.DebugLog
import java.lang.ref.WeakReference
@ -20,14 +25,22 @@ class FullScreenPlayerView(
private val exoPlayerView: ExoPlayerView,
private val reactExoplayerView: ReactExoplayerView,
private val playerControlView: LegacyPlayerControlView?,
private val onBackPressedCallback: OnBackPressedCallback
) : Dialog(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen) {
private val onBackPressedCallback: OnBackPressedCallback,
private val controlsConfig: ControlsConfig
) : Dialog(context, android.R.style.Theme_Black_NoTitleBar) {
private var parent: ViewGroup? = null
private val containerView = FrameLayout(context)
private val mKeepScreenOnHandler = Handler(Looper.getMainLooper())
private val mKeepScreenOnUpdater = KeepScreenOnUpdater(this)
// As this view is fullscreen we need to save initial state and restore it afterward
// Following variables save UI state when open the view
// restoreUIState, will reapply these values
private var initialSystemBarsBehavior: Int? = null
private var initialNavigationBarIsVisible: Boolean? = null
private var initialNotificationBarIsVisible: Boolean? = null
private class KeepScreenOnUpdater(fullScreenPlayerView: FullScreenPlayerView) : Runnable {
private val mFullscreenPlayer = WeakReference(fullScreenPlayerView)
@ -59,6 +72,15 @@ class FullScreenPlayerView(
init {
setContentView(containerView, generateDefaultLayoutParams())
window?.let {
val inset = WindowInsetsControllerCompat(it, it.decorView)
initialSystemBarsBehavior = inset.systemBarsBehavior
initialNavigationBarIsVisible = ViewCompat.getRootWindowInsets(it.decorView)
?.isVisible(WindowInsetsCompat.Type.navigationBars()) == true
initialNotificationBarIsVisible = ViewCompat.getRootWindowInsets(it.decorView)
?.isVisible(WindowInsetsCompat.Type.statusBars()) == true
}
}
override fun onBackPressed() {
super.onBackPressed()
@ -75,6 +97,7 @@ class FullScreenPlayerView(
parent?.removeView(it)
containerView.addView(it, generateDefaultLayoutParams())
}
updateNavigationBarVisibility()
}
override fun onStop() {
@ -89,6 +112,19 @@ class FullScreenPlayerView(
}
parent?.requestLayout()
parent = null
restoreSystemUI()
}
// restore system UI state
private fun restoreSystemUI() {
window?.let {
updateNavigationBarVisibility(
it,
initialNavigationBarIsVisible,
initialNotificationBarIsVisible,
initialSystemBarsBehavior
)
}
}
private fun getFullscreenIconResource(isFullscreen: Boolean): Int =
@ -127,4 +163,61 @@ class FullScreenPlayerView(
layoutParams.setMargins(0, 0, 0, 0)
return layoutParams
}
private fun updateBarVisibility(
inset: WindowInsetsControllerCompat,
type: Int,
shouldHide: Boolean?,
initialVisibility: Boolean?,
systemBarsBehavior: Int? = null
) {
shouldHide?.takeIf { it != initialVisibility }?.let {
if (it) {
inset.hide(type)
systemBarsBehavior?.let { behavior -> inset.systemBarsBehavior = behavior }
} else {
inset.show(type)
}
}
}
// Move the UI to fullscreen.
// if you change this code, remember to check that the UI is well restored in restoreUIState
private fun updateNavigationBarVisibility(
window: Window,
hideNavigationBarOnFullScreenMode: Boolean?,
hideNotificationBarOnFullScreenMode: Boolean?,
systemBarsBehavior: Int?
) {
// Configure the behavior of the hidden system bars.
val inset = WindowInsetsControllerCompat(window, window.decorView)
// Update navigation bar visibility and apply systemBarsBehavior if hiding
updateBarVisibility(
inset,
WindowInsetsCompat.Type.navigationBars(),
hideNavigationBarOnFullScreenMode,
initialNavigationBarIsVisible,
systemBarsBehavior
)
// Update notification bar visibility (no need for systemBarsBehavior here)
updateBarVisibility(
inset,
WindowInsetsCompat.Type.statusBars(),
hideNotificationBarOnFullScreenMode,
initialNotificationBarIsVisible
)
}
private fun updateNavigationBarVisibility() {
window?.let {
updateNavigationBarVisibility(
it,
controlsConfig.hideNavigationBarOnFullScreenMode,
controlsConfig.hideNotificationBarOnFullScreenMode,
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
)
}
}
}

View File

@ -427,15 +427,6 @@ public class ReactExoplayerView extends FrameLayout implements
});
}
if (fullScreenPlayerView == null) {
fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, this, playerControlView, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
setFullscreen(false);
}
});
}
// Setting the player for the playerControlView
playerControlView.setPlayer(player);
playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container);
@ -2261,6 +2252,12 @@ public class ReactExoplayerView extends FrameLayout implements
}
if (isFullscreen) {
fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, this, playerControlView, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
setFullscreen(false);
}
}, controlsConfig);
eventEmitter.onVideoFullscreenPlayerWillPresent.invoke();
if (fullScreenPlayerView != null) {
fullScreenPlayerView.show();

View File

@ -144,11 +144,13 @@ If needed, you can also add your controls or use a package like [react-native-vi
Adjust the control styles. This prop is need only if `controls={true}` and is an object. See the list of prop supported below.
| Property | Type | Description |
|-----------------|---------|-----------------------------------------------------------------------------------------|
| hideSeekBar | boolean | The default value is `false`, allowing you to hide the seek bar for live broadcasts. |
| hideDuration | boolean | The default value is `false`, allowing you to hide the duration. |
| seekIncrementMS | number | The default value is `10000`. You can change the value to increment forward and rewind. |
| Property | Type | Description |
|-----------------------------------|---------|--------------------------------------------------------------------------------------------|
| hideSeekBar | boolean | The default value is `false`, allowing you to hide the seek bar for live broadcasts. |
| hideDuration | boolean | The default value is `false`, allowing you to hide the duration. |
| seekIncrementMS | number | The default value is `10000`. You can change the value to increment forward and rewind. |
| hideNavigationBarOnFullScreenMode | boolean | The default value is `true`, allowing you to hide the navigation bar on full-screen mode. |
| hideNotificationBarOnFullScreenMode | boolean | The default value is `true`, allowing you to hide the notification bar on full-screen mode. |
Example with default values:
@ -157,6 +159,8 @@ controlsStyles={{
hideSeekBar: false,
hideDuration: false,
seekIncrementMS: 10000,
hideNavigationBarOnFullScreenMode: true,
hideNotificationBarOnFullScreenMode: true,
}}
```

View File

@ -17,6 +17,7 @@
"expo": "^51.0.31",
"expo-asset": "~10.0.10",
"expo-image": "^1.12.15",
"expo-navigation-bar": "~3.0.7",
"react": "18.2.0",
"react-native": "0.74.5",
"react-native-windows": "0.74.19"

View File

@ -1,8 +1,8 @@
'use strict';
import React, {type FC, useCallback, useRef, useState} from 'react';
import React, {type FC, useCallback, useRef, useState, useEffect} from 'react';
import {Platform, TouchableOpacity, View} from 'react-native';
import {Platform, TouchableOpacity, View, StatusBar} from 'react-native';
import Video, {
VideoRef,
@ -36,6 +36,7 @@ import styles from './styles';
import {type AdditionalSourceInfo} from './types';
import {bufferConfig, srcList, textTracksSelectionBy} from './constants';
import {Overlay, toast, VideoLoader} from './components';
import * as NavigationBar from 'expo-navigation-bar';
type Props = NonNullable<unknown>;
@ -104,6 +105,10 @@ const VideoPlayer: FC<Props> = ({}) => {
goToChannel((srcListId + srcList.length - 1) % srcList.length);
}, [goToChannel, srcListId]);
useEffect(() => {
NavigationBar.setVisibilityAsync('visible');
}, []);
const onAudioTracks = (data: OnAudioTracksData) => {
const selectedTrack = data.audioTracks?.find((x: AudioTrack) => {
return x.selected;
@ -226,6 +231,8 @@ const VideoPlayer: FC<Props> = ({}) => {
return (
<View style={styles.container}>
<StatusBar animated={true} backgroundColor="black" hidden={false} />
{(srcList[srcListId] as AdditionalSourceInfo)?.noView ? null : (
<TouchableOpacity style={viewStyle}>
<Video
@ -276,6 +283,7 @@ const VideoPlayer: FC<Props> = ({}) => {
bufferingStrategy={BufferingStrategyType.DEFAULT}
debug={{enable: true, thread: true}}
subtitleStyle={{subtitlesFollowVideo: true}}
controlsStyles={{hideNavigationBarOnFullScreenMode: true, hideNotificationBarOnFullScreenMode: true}}
/>
</TouchableOpacity>
)}

View File

@ -4893,6 +4893,14 @@ expo-modules-core@1.12.23:
dependencies:
invariant "^2.2.4"
expo-navigation-bar@~3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/expo-navigation-bar/-/expo-navigation-bar-3.0.7.tgz#1830a302a89fa5c26cb27ce4cf6ac6c1d22907ff"
integrity sha512-KCNHyZ58zoN4xdy7D1lUdJvveCYNVQHGSX4M6xO/SZypvI6GZbLzKSN6Lx4GDGEFxG6Kb+EAckZl48tSiNeGYQ==
dependencies:
"@react-native/normalize-colors" "0.74.85"
debug "^4.3.2"
expo@^51.0.31:
version "51.0.31"
resolved "https://registry.yarnpkg.com/expo/-/expo-51.0.31.tgz#edd623e718705d88681406e72869076dfeb485ff"

View File

@ -294,9 +294,11 @@ export type OnAudioFocusChangedData = Readonly<{
}>;
type ControlsStyles = Readonly<{
hideSeekBar?: boolean;
hideDuration?: boolean;
hideSeekBar?: WithDefault<boolean, false>;
hideDuration?: WithDefault<boolean, false>;
seekIncrementMS?: Int32;
hideNavigationBarOnFullScreenMode?: WithDefault<boolean, true>;
hideNotificationBarOnFullScreenMode?: WithDefault<boolean, true>;
}>;
export type OnControlsVisibilityChange = Readonly<{

View File

@ -248,6 +248,8 @@ export type ControlsStyles = {
hideSeekBar?: boolean;
hideDuration?: boolean;
seekIncrementMS?: number;
hideNavigationBarOnFullScreenMode?: boolean;
hideNotificationBarOnFullScreenMode?: boolean;
};
export interface ReactVideoProps extends ReactVideoEvents, ViewProps {