Skip to content

Commit

Permalink
Chore/rework fullscreen configuration (#4142)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
freeboub and seyedmostafahasani authored Sep 4, 2024
1 parent d6bae3c commit 9707081
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)

Expand Down Expand Up @@ -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()
Expand All @@ -75,6 +97,7 @@ class FullScreenPlayerView(
parent?.removeView(it)
containerView.addView(it, generateDefaultLayoutParams())
}
updateNavigationBarVisibility()
}

override fun onStop() {
Expand All @@ -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 =
Expand Down Expand Up @@ -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
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -427,15 +427,6 @@ public void onVisibilityChange(int visibility) {
});
}

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);
Expand Down Expand Up @@ -2261,6 +2252,12 @@ public void setFullscreen(boolean fullscreen) {
}

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();
Expand Down Expand Up @@ -2383,4 +2380,4 @@ public void setControlsStyles(ControlsConfig controlsStyles) {
controlsConfig = controlsStyles;
refreshProgressBarVisibility();
}
}
}
16 changes: 10 additions & 6 deletions docs/pages/component/props.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -157,6 +159,8 @@ controlsStyles={{
hideSeekBar: false,
hideDuration: false,
seekIncrementMS: 10000,
hideNavigationBarOnFullScreenMode: true,
hideNotificationBarOnFullScreenMode: true,
}}
```

Expand Down Expand Up @@ -1089,4 +1093,4 @@ Example:
}}
// or other video props
/>
```
```
1 change: 1 addition & 0 deletions examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
12 changes: 10 additions & 2 deletions examples/basic/src/VideoPlayer.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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>;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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>
)}
Expand Down
8 changes: 8 additions & 0 deletions examples/basic/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4893,6 +4893,14 @@ [email protected]:
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"
Expand Down
6 changes: 4 additions & 2 deletions src/specs/VideoNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<{
Expand Down
2 changes: 2 additions & 0 deletions src/types/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ export type ControlsStyles = {
hideSeekBar?: boolean;
hideDuration?: boolean;
seekIncrementMS?: number;
hideNavigationBarOnFullScreenMode?: boolean;
hideNotificationBarOnFullScreenMode?: boolean;
};

export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
Expand Down

0 comments on commit 9707081

Please sign in to comment.