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:
parent
d6bae3cd07
commit
9707081ab9
@ -7,6 +7,8 @@ class ControlsConfig {
|
|||||||
var hideSeekBar: Boolean = false
|
var hideSeekBar: Boolean = false
|
||||||
var seekIncrementMS: Int = 10000
|
var seekIncrementMS: Int = 10000
|
||||||
var hideDuration: Boolean = false
|
var hideDuration: Boolean = false
|
||||||
|
var hideNavigationBarOnFullScreenMode: Boolean = true
|
||||||
|
var hideNotificationBarOnFullScreenMode: Boolean = true
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -17,8 +19,9 @@ class ControlsConfig {
|
|||||||
config.hideSeekBar = ReactBridgeUtils.safeGetBool(src, "hideSeekBar", false)
|
config.hideSeekBar = ReactBridgeUtils.safeGetBool(src, "hideSeekBar", false)
|
||||||
config.seekIncrementMS = ReactBridgeUtils.safeGetInt(src, "seekIncrementMS", 10000)
|
config.seekIncrementMS = ReactBridgeUtils.safeGetInt(src, "seekIncrementMS", 10000)
|
||||||
config.hideDuration = ReactBridgeUtils.safeGetBool(src, "hideDuration", false)
|
config.hideDuration = ReactBridgeUtils.safeGetBool(src, "hideDuration", false)
|
||||||
|
config.hideNavigationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(src, "hideNavigationBarOnFullScreenMode", true)
|
||||||
|
config.hideNotificationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(src, "hideNotificationBarOnFullScreenMode", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,16 @@ import android.content.Context
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.Window
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import androidx.activity.OnBackPressedCallback
|
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 androidx.media3.ui.LegacyPlayerControlView
|
||||||
|
import com.brentvatne.common.api.ControlsConfig
|
||||||
import com.brentvatne.common.toolbox.DebugLog
|
import com.brentvatne.common.toolbox.DebugLog
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
@ -20,14 +25,22 @@ class FullScreenPlayerView(
|
|||||||
private val exoPlayerView: ExoPlayerView,
|
private val exoPlayerView: ExoPlayerView,
|
||||||
private val reactExoplayerView: ReactExoplayerView,
|
private val reactExoplayerView: ReactExoplayerView,
|
||||||
private val playerControlView: LegacyPlayerControlView?,
|
private val playerControlView: LegacyPlayerControlView?,
|
||||||
private val onBackPressedCallback: OnBackPressedCallback
|
private val onBackPressedCallback: OnBackPressedCallback,
|
||||||
) : Dialog(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen) {
|
private val controlsConfig: ControlsConfig
|
||||||
|
) : Dialog(context, android.R.style.Theme_Black_NoTitleBar) {
|
||||||
|
|
||||||
private var parent: ViewGroup? = null
|
private var parent: ViewGroup? = null
|
||||||
private val containerView = FrameLayout(context)
|
private val containerView = FrameLayout(context)
|
||||||
private val mKeepScreenOnHandler = Handler(Looper.getMainLooper())
|
private val mKeepScreenOnHandler = Handler(Looper.getMainLooper())
|
||||||
private val mKeepScreenOnUpdater = KeepScreenOnUpdater(this)
|
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 class KeepScreenOnUpdater(fullScreenPlayerView: FullScreenPlayerView) : Runnable {
|
||||||
private val mFullscreenPlayer = WeakReference(fullScreenPlayerView)
|
private val mFullscreenPlayer = WeakReference(fullScreenPlayerView)
|
||||||
|
|
||||||
@ -59,6 +72,15 @@ class FullScreenPlayerView(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
setContentView(containerView, generateDefaultLayoutParams())
|
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() {
|
override fun onBackPressed() {
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
@ -75,6 +97,7 @@ class FullScreenPlayerView(
|
|||||||
parent?.removeView(it)
|
parent?.removeView(it)
|
||||||
containerView.addView(it, generateDefaultLayoutParams())
|
containerView.addView(it, generateDefaultLayoutParams())
|
||||||
}
|
}
|
||||||
|
updateNavigationBarVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
@ -89,6 +112,19 @@ class FullScreenPlayerView(
|
|||||||
}
|
}
|
||||||
parent?.requestLayout()
|
parent?.requestLayout()
|
||||||
parent = null
|
parent = null
|
||||||
|
restoreSystemUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore system UI state
|
||||||
|
private fun restoreSystemUI() {
|
||||||
|
window?.let {
|
||||||
|
updateNavigationBarVisibility(
|
||||||
|
it,
|
||||||
|
initialNavigationBarIsVisible,
|
||||||
|
initialNotificationBarIsVisible,
|
||||||
|
initialSystemBarsBehavior
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFullscreenIconResource(isFullscreen: Boolean): Int =
|
private fun getFullscreenIconResource(isFullscreen: Boolean): Int =
|
||||||
@ -127,4 +163,61 @@ class FullScreenPlayerView(
|
|||||||
layoutParams.setMargins(0, 0, 0, 0)
|
layoutParams.setMargins(0, 0, 0, 0)
|
||||||
return layoutParams
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
// Setting the player for the playerControlView
|
||||||
playerControlView.setPlayer(player);
|
playerControlView.setPlayer(player);
|
||||||
playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container);
|
playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container);
|
||||||
@ -2261,6 +2252,12 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isFullscreen) {
|
if (isFullscreen) {
|
||||||
|
fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, this, playerControlView, new OnBackPressedCallback(true) {
|
||||||
|
@Override
|
||||||
|
public void handleOnBackPressed() {
|
||||||
|
setFullscreen(false);
|
||||||
|
}
|
||||||
|
}, controlsConfig);
|
||||||
eventEmitter.onVideoFullscreenPlayerWillPresent.invoke();
|
eventEmitter.onVideoFullscreenPlayerWillPresent.invoke();
|
||||||
if (fullScreenPlayerView != null) {
|
if (fullScreenPlayerView != null) {
|
||||||
fullScreenPlayerView.show();
|
fullScreenPlayerView.show();
|
||||||
@ -2383,4 +2380,4 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
controlsConfig = controlsStyles;
|
controlsConfig = controlsStyles;
|
||||||
refreshProgressBarVisibility();
|
refreshProgressBarVisibility();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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.
|
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 |
|
| Property | Type | Description |
|
||||||
|-----------------|---------|-----------------------------------------------------------------------------------------|
|
|-----------------------------------|---------|--------------------------------------------------------------------------------------------|
|
||||||
| hideSeekBar | boolean | The default value is `false`, allowing you to hide the seek bar for live broadcasts. |
|
| 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. |
|
| 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. |
|
| 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:
|
Example with default values:
|
||||||
|
|
||||||
@ -157,6 +159,8 @@ controlsStyles={{
|
|||||||
hideSeekBar: false,
|
hideSeekBar: false,
|
||||||
hideDuration: false,
|
hideDuration: false,
|
||||||
seekIncrementMS: 10000,
|
seekIncrementMS: 10000,
|
||||||
|
hideNavigationBarOnFullScreenMode: true,
|
||||||
|
hideNotificationBarOnFullScreenMode: true,
|
||||||
}}
|
}}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -1089,4 +1093,4 @@ Example:
|
|||||||
}}
|
}}
|
||||||
// or other video props
|
// or other video props
|
||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"expo": "^51.0.31",
|
"expo": "^51.0.31",
|
||||||
"expo-asset": "~10.0.10",
|
"expo-asset": "~10.0.10",
|
||||||
"expo-image": "^1.12.15",
|
"expo-image": "^1.12.15",
|
||||||
|
"expo-navigation-bar": "~3.0.7",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-native": "0.74.5",
|
"react-native": "0.74.5",
|
||||||
"react-native-windows": "0.74.19"
|
"react-native-windows": "0.74.19"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
'use strict';
|
'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, {
|
import Video, {
|
||||||
VideoRef,
|
VideoRef,
|
||||||
@ -36,6 +36,7 @@ import styles from './styles';
|
|||||||
import {type AdditionalSourceInfo} from './types';
|
import {type AdditionalSourceInfo} from './types';
|
||||||
import {bufferConfig, srcList, textTracksSelectionBy} from './constants';
|
import {bufferConfig, srcList, textTracksSelectionBy} from './constants';
|
||||||
import {Overlay, toast, VideoLoader} from './components';
|
import {Overlay, toast, VideoLoader} from './components';
|
||||||
|
import * as NavigationBar from 'expo-navigation-bar';
|
||||||
|
|
||||||
type Props = NonNullable<unknown>;
|
type Props = NonNullable<unknown>;
|
||||||
|
|
||||||
@ -104,6 +105,10 @@ const VideoPlayer: FC<Props> = ({}) => {
|
|||||||
goToChannel((srcListId + srcList.length - 1) % srcList.length);
|
goToChannel((srcListId + srcList.length - 1) % srcList.length);
|
||||||
}, [goToChannel, srcListId]);
|
}, [goToChannel, srcListId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
NavigationBar.setVisibilityAsync('visible');
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onAudioTracks = (data: OnAudioTracksData) => {
|
const onAudioTracks = (data: OnAudioTracksData) => {
|
||||||
const selectedTrack = data.audioTracks?.find((x: AudioTrack) => {
|
const selectedTrack = data.audioTracks?.find((x: AudioTrack) => {
|
||||||
return x.selected;
|
return x.selected;
|
||||||
@ -226,6 +231,8 @@ const VideoPlayer: FC<Props> = ({}) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
<StatusBar animated={true} backgroundColor="black" hidden={false} />
|
||||||
|
|
||||||
{(srcList[srcListId] as AdditionalSourceInfo)?.noView ? null : (
|
{(srcList[srcListId] as AdditionalSourceInfo)?.noView ? null : (
|
||||||
<TouchableOpacity style={viewStyle}>
|
<TouchableOpacity style={viewStyle}>
|
||||||
<Video
|
<Video
|
||||||
@ -276,6 +283,7 @@ const VideoPlayer: FC<Props> = ({}) => {
|
|||||||
bufferingStrategy={BufferingStrategyType.DEFAULT}
|
bufferingStrategy={BufferingStrategyType.DEFAULT}
|
||||||
debug={{enable: true, thread: true}}
|
debug={{enable: true, thread: true}}
|
||||||
subtitleStyle={{subtitlesFollowVideo: true}}
|
subtitleStyle={{subtitlesFollowVideo: true}}
|
||||||
|
controlsStyles={{hideNavigationBarOnFullScreenMode: true, hideNotificationBarOnFullScreenMode: true}}
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
|
@ -4893,6 +4893,14 @@ expo-modules-core@1.12.23:
|
|||||||
dependencies:
|
dependencies:
|
||||||
invariant "^2.2.4"
|
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:
|
expo@^51.0.31:
|
||||||
version "51.0.31"
|
version "51.0.31"
|
||||||
resolved "https://registry.yarnpkg.com/expo/-/expo-51.0.31.tgz#edd623e718705d88681406e72869076dfeb485ff"
|
resolved "https://registry.yarnpkg.com/expo/-/expo-51.0.31.tgz#edd623e718705d88681406e72869076dfeb485ff"
|
||||||
|
@ -294,9 +294,11 @@ export type OnAudioFocusChangedData = Readonly<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
type ControlsStyles = Readonly<{
|
type ControlsStyles = Readonly<{
|
||||||
hideSeekBar?: boolean;
|
hideSeekBar?: WithDefault<boolean, false>;
|
||||||
hideDuration?: boolean;
|
hideDuration?: WithDefault<boolean, false>;
|
||||||
seekIncrementMS?: Int32;
|
seekIncrementMS?: Int32;
|
||||||
|
hideNavigationBarOnFullScreenMode?: WithDefault<boolean, true>;
|
||||||
|
hideNotificationBarOnFullScreenMode?: WithDefault<boolean, true>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type OnControlsVisibilityChange = Readonly<{
|
export type OnControlsVisibilityChange = Readonly<{
|
||||||
|
@ -248,6 +248,8 @@ export type ControlsStyles = {
|
|||||||
hideSeekBar?: boolean;
|
hideSeekBar?: boolean;
|
||||||
hideDuration?: boolean;
|
hideDuration?: boolean;
|
||||||
seekIncrementMS?: number;
|
seekIncrementMS?: number;
|
||||||
|
hideNavigationBarOnFullScreenMode?: boolean;
|
||||||
|
hideNotificationBarOnFullScreenMode?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
|
export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
|
||||||
|
Loading…
Reference in New Issue
Block a user