feat(android): add live video label configuration (#4190)
This commit is contained in:
parent
82dc4cf3a0
commit
149924ffcb
@ -16,27 +16,29 @@ class ControlsConfig {
|
||||
var hideFullscreen: Boolean = false
|
||||
var hideNavigationBarOnFullScreenMode: Boolean = true
|
||||
var hideNotificationBarOnFullScreenMode: Boolean = true
|
||||
var liveLabel: String? = null
|
||||
|
||||
var seekIncrementMS: Int = 10000
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun parse(src: ReadableMap?): ControlsConfig {
|
||||
fun parse(controlsConfig: ReadableMap?): ControlsConfig {
|
||||
val config = ControlsConfig()
|
||||
|
||||
if (src != null) {
|
||||
config.hideSeekBar = ReactBridgeUtils.safeGetBool(src, "hideSeekBar", false)
|
||||
config.hideDuration = ReactBridgeUtils.safeGetBool(src, "hideDuration", false)
|
||||
config.hidePosition = ReactBridgeUtils.safeGetBool(src, "hidePosition", false)
|
||||
config.hidePlayPause = ReactBridgeUtils.safeGetBool(src, "hidePlayPause", false)
|
||||
config.hideForward = ReactBridgeUtils.safeGetBool(src, "hideForward", false)
|
||||
config.hideRewind = ReactBridgeUtils.safeGetBool(src, "hideRewind", false)
|
||||
config.hideNext = ReactBridgeUtils.safeGetBool(src, "hideNext", false)
|
||||
config.hidePrevious = ReactBridgeUtils.safeGetBool(src, "hidePrevious", false)
|
||||
config.hideFullscreen = ReactBridgeUtils.safeGetBool(src, "hideFullscreen", false)
|
||||
config.seekIncrementMS = ReactBridgeUtils.safeGetInt(src, "seekIncrementMS", 10000)
|
||||
config.hideNavigationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(src, "hideNavigationBarOnFullScreenMode", true)
|
||||
config.hideNotificationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(src, "hideNotificationBarOnFullScreenMode", true)
|
||||
if (controlsConfig != null) {
|
||||
config.hideSeekBar = ReactBridgeUtils.safeGetBool(controlsConfig, "hideSeekBar", false)
|
||||
config.hideDuration = ReactBridgeUtils.safeGetBool(controlsConfig, "hideDuration", false)
|
||||
config.hidePosition = ReactBridgeUtils.safeGetBool(controlsConfig, "hidePosition", false)
|
||||
config.hidePlayPause = ReactBridgeUtils.safeGetBool(controlsConfig, "hidePlayPause", false)
|
||||
config.hideForward = ReactBridgeUtils.safeGetBool(controlsConfig, "hideForward", false)
|
||||
config.hideRewind = ReactBridgeUtils.safeGetBool(controlsConfig, "hideRewind", false)
|
||||
config.hideNext = ReactBridgeUtils.safeGetBool(controlsConfig, "hideNext", false)
|
||||
config.hidePrevious = ReactBridgeUtils.safeGetBool(controlsConfig, "hidePrevious", false)
|
||||
config.hideFullscreen = ReactBridgeUtils.safeGetBool(controlsConfig, "hideFullscreen", false)
|
||||
config.seekIncrementMS = ReactBridgeUtils.safeGetInt(controlsConfig, "seekIncrementMS", 10000)
|
||||
config.hideNavigationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(controlsConfig, "hideNavigationBarOnFullScreenMode", true)
|
||||
config.hideNotificationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(controlsConfig, "hideNotificationBarOnFullScreenMode", true)
|
||||
config.liveLabel = ReactBridgeUtils.safeGetString(controlsConfig, "liveLabel", null)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
@ -216,5 +217,13 @@ class FullScreenPlayerView(
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
)
|
||||
}
|
||||
if (controlsConfig.hideNotificationBarOnFullScreenMode) {
|
||||
val liveContainer = playerControlView?.findViewById<LinearLayout?>(com.brentvatne.react.R.id.exo_live_container)
|
||||
liveContainer?.let {
|
||||
val layoutParams = it.layoutParams as LinearLayout.LayoutParams
|
||||
layoutParams.topMargin = 40
|
||||
it.layoutParams = layoutParams
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -527,88 +527,78 @@ public class ReactExoplayerView extends FrameLayout implements
|
||||
view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight());
|
||||
}
|
||||
|
||||
private void refreshControlsStyles (){
|
||||
if(playerControlView == null) return;
|
||||
private void refreshControlsStyles() {
|
||||
if (playerControlView == null || player == null || !controls) return;
|
||||
updateLiveContent();
|
||||
updatePlayPauseButtons();
|
||||
updateButtonVisibility(controlsConfig.getHideForward(), R.id.exo_ffwd);
|
||||
updateButtonVisibility(controlsConfig.getHideRewind(), R.id.exo_rew);
|
||||
updateButtonVisibility(controlsConfig.getHideNext(), R.id.exo_next);
|
||||
updateButtonVisibility(controlsConfig.getHidePrevious(), R.id.exo_prev);
|
||||
updateViewVisibility(playerControlView.findViewById(R.id.exo_fullscreen), controlsConfig.getHideFullscreen(), GONE);
|
||||
updateViewVisibility(playerControlView.findViewById(R.id.exo_position), controlsConfig.getHidePosition(), GONE);
|
||||
updateViewVisibility(playerControlView.findViewById(R.id.exo_progress), controlsConfig.getHideSeekBar(), INVISIBLE);
|
||||
updateViewVisibility(playerControlView.findViewById(R.id.exo_duration), controlsConfig.getHideDuration(), GONE);
|
||||
}
|
||||
|
||||
private void updateLiveContent() {
|
||||
LinearLayout exoLiveContainer = playerControlView.findViewById(R.id.exo_live_container);
|
||||
TextView exoLiveLabel = playerControlView.findViewById(R.id.exo_live_label);
|
||||
|
||||
boolean isLive = false;
|
||||
Timeline timeline = player.getCurrentTimeline();
|
||||
|
||||
// Determine if the content is live
|
||||
if (!timeline.isEmpty()) {
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
timeline.getWindow(player.getCurrentMediaItemIndex(), window);
|
||||
isLive = window.isLive();
|
||||
}
|
||||
|
||||
if (isLive && controlsConfig.getLiveLabel() != null) {
|
||||
exoLiveLabel.setText(controlsConfig.getLiveLabel());
|
||||
exoLiveContainer.setVisibility(VISIBLE);
|
||||
} else {
|
||||
exoLiveContainer.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlayPauseButtons() {
|
||||
final ImageButton playButton = playerControlView.findViewById(R.id.exo_play);
|
||||
final ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause);
|
||||
|
||||
if (controlsConfig.getHidePlayPause()) {
|
||||
playPauseControlContainer.setAlpha(0);
|
||||
|
||||
playButton.setClickable(false);
|
||||
pauseButton.setClickable(false);
|
||||
} else {
|
||||
playPauseControlContainer.setAlpha(1.0f);
|
||||
|
||||
playButton.setClickable(true);
|
||||
pauseButton.setClickable(true);
|
||||
}
|
||||
}
|
||||
|
||||
final ImageButton forwardButton = playerControlView.findViewById(R.id.exo_ffwd);
|
||||
if (controlsConfig.getHideForward()) {
|
||||
forwardButton.setImageAlpha(0);
|
||||
forwardButton.setClickable(false);
|
||||
private void updateButtonVisibility(boolean hide, int buttonID) {
|
||||
ImageButton button = playerControlView.findViewById(buttonID);
|
||||
if (hide) {
|
||||
button.setImageAlpha(0);
|
||||
button.setClickable(false);
|
||||
} else {
|
||||
forwardButton.setImageAlpha(255);
|
||||
forwardButton.setClickable(true);
|
||||
}
|
||||
|
||||
final ImageButton rewindButton = playerControlView.findViewById(R.id.exo_rew);
|
||||
if (controlsConfig.getHideRewind()) {
|
||||
rewindButton.setImageAlpha(0);
|
||||
rewindButton.setClickable(false);
|
||||
} else {
|
||||
rewindButton.setImageAlpha(255);
|
||||
rewindButton.setClickable(true);
|
||||
}
|
||||
|
||||
final ImageButton nextButton = playerControlView.findViewById(R.id.exo_next);
|
||||
if (controlsConfig.getHideNext()) {
|
||||
nextButton.setClickable(false);
|
||||
nextButton.setImageAlpha(0);
|
||||
} else {
|
||||
nextButton.setImageAlpha(255);
|
||||
nextButton.setClickable(true);
|
||||
}
|
||||
|
||||
final ImageButton previousButton = playerControlView.findViewById(R.id.exo_prev);
|
||||
if (controlsConfig.getHidePrevious()) {
|
||||
previousButton.setImageAlpha(0);
|
||||
previousButton.setClickable(false);
|
||||
} else {
|
||||
previousButton.setImageAlpha(255);
|
||||
previousButton.setClickable(true);
|
||||
}
|
||||
|
||||
final ImageButton fullscreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
|
||||
if (controlsConfig.getHideFullscreen()) {
|
||||
fullscreenButton.setVisibility(GONE);
|
||||
} else if (fullscreenButton.getVisibility() == GONE) {
|
||||
fullscreenButton.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
final TextView positionText = playerControlView.findViewById(R.id.exo_position);
|
||||
if(controlsConfig.getHidePosition()){
|
||||
positionText.setVisibility(GONE);
|
||||
} else if (positionText.getVisibility() == GONE){
|
||||
positionText.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
final DefaultTimeBar progressBar = playerControlView.findViewById(R.id.exo_progress);
|
||||
if (controlsConfig.getHideSeekBar()) {
|
||||
progressBar.setVisibility(INVISIBLE);
|
||||
} else if (progressBar.getVisibility() == INVISIBLE) {
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
final TextView durationText = playerControlView.findViewById(R.id.exo_duration);
|
||||
if (controlsConfig.getHideDuration()) {
|
||||
durationText.setVisibility(GONE);
|
||||
} else if (durationText.getVisibility() == GONE) {
|
||||
durationText.setVisibility(VISIBLE);
|
||||
button.setImageAlpha(255);
|
||||
button.setClickable(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateViewVisibility(View view, boolean hide, int hideVisibility) {
|
||||
if (hide) {
|
||||
view.setVisibility(hideVisibility);
|
||||
} else if (view.getVisibility() == hideVisibility) {
|
||||
view.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void reLayoutControls() {
|
||||
reLayout(exoPlayerView);
|
||||
reLayout(playerControlView);
|
||||
@ -1508,6 +1498,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
||||
|
||||
eventEmitter.onVideoLoad.invoke(duration, currentPosition, width, height,
|
||||
audioTracks, textTracks, videoTracks, trackId);
|
||||
refreshControlsStyles();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2406,6 +2397,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
||||
removeViewAt(indexOfPC);
|
||||
}
|
||||
}
|
||||
refreshControlsStyles();
|
||||
}
|
||||
|
||||
public void setSubtitleStyle(SubtitleStyle style) {
|
||||
|
6
android/src/main/res/drawable/circle.xml
Normal file
6
android/src/main/res/drawable/circle.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/red"/>
|
||||
<size android:width="10dp" android:height="10dp"/>
|
||||
</shape>
|
@ -1,17 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_height="match_parent"
|
||||
android:layoutDirection="ltr"
|
||||
android:background="@color/midnight_black"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="@dimen/live_wrapper_margin_top"
|
||||
android:id="@+id/exo_live_container">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/exo_live_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/position_duration_horizontal_padding"
|
||||
android:src="@drawable/circle" />
|
||||
|
||||
<TextView android:id="@+id/exo_live_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/position_duration_text_size"
|
||||
android:textStyle="bold"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/white"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingTop="@dimen/controller_wrapper_padding_top"
|
||||
android:layout_gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton android:id="@+id/exo_prev"
|
||||
|
@ -2,4 +2,6 @@
|
||||
<resources>
|
||||
<color name="silver_gray">#FFBEBEBE</color>
|
||||
<color name="midnight_black">#CC000000</color>
|
||||
<color name="white">#FFFFFF</color>
|
||||
<color name="red">#FF0000</color>
|
||||
</resources>
|
@ -3,6 +3,7 @@
|
||||
<!-- margin & padding-->
|
||||
<dimen name="controller_wrapper_padding_top">4dp</dimen>
|
||||
<dimen name="seekBar_wrapper_margin_top">4dp</dimen>
|
||||
<dimen name="live_wrapper_margin_top">12dp</dimen>
|
||||
<dimen name="position_duration_horizontal_padding">4dp</dimen>
|
||||
<dimen name="full_screen_margin">4dp</dimen>
|
||||
|
||||
|
@ -144,20 +144,21 @@ 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 |
|
||||
|-----------------------------------|---------|--------------------------------------------------------------------------------------------|
|
||||
| hidePosition | boolean | Hides the position indicator. Default is `false`. |
|
||||
| hidePlayPause | boolean | Hides the play/pause button. Default is `false`. |
|
||||
| hideForward | boolean | Hides the forward button. Default is `false`. |
|
||||
| hideRewind | boolean | Hides the rewind button. Default is `false`. |
|
||||
| hideNext | boolean | Hides the next button. Default is `false`. |
|
||||
| hidePrevious | boolean | Hides the previous button. Default is `false`. |
|
||||
| hideFullscreen | boolean | Hides the fullscreen button. Default is `false`. |
|
||||
| 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. |
|
||||
| hideNavigationBarOnFullScreenMode | boolean | The default value is `true`, allowing you to hide the navigation bar on full-screen mode. |
|
||||
| Property | Type | Description |
|
||||
|-------------------------------------|---------|---------------------------------------------------------------------------------------------|
|
||||
| hidePosition | boolean | Hides the position indicator. Default is `false`. |
|
||||
| hidePlayPause | boolean | Hides the play/pause button. Default is `false`. |
|
||||
| hideForward | boolean | Hides the forward button. Default is `false`. |
|
||||
| hideRewind | boolean | Hides the rewind button. Default is `false`. |
|
||||
| hideNext | boolean | Hides the next button. Default is `false`. |
|
||||
| hidePrevious | boolean | Hides the previous button. Default is `false`. |
|
||||
| hideFullscreen | boolean | Hides the fullscreen button. Default is `false`. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| liveLabel | string | Allowing you to set a label for live video. |
|
||||
|
||||
Example with default values:
|
||||
|
||||
@ -175,6 +176,7 @@ controlsStyles={{
|
||||
hideNavigationBarOnFullScreenMode: true,
|
||||
hideNotificationBarOnFullScreenMode: true,
|
||||
seekIncrementMS: 10000,
|
||||
liveLabel: "LIVE"
|
||||
}}
|
||||
```
|
||||
|
||||
|
@ -31,6 +31,7 @@ import Video, {
|
||||
type SelectedVideoTrack,
|
||||
type EnumValues,
|
||||
OnBandwidthUpdateData,
|
||||
ControlsStyles,
|
||||
} from 'react-native-video';
|
||||
import styles from './styles';
|
||||
import {type AdditionalSourceInfo} from './types';
|
||||
@ -241,9 +242,10 @@ const VideoPlayer: FC<Props> = ({}) => {
|
||||
const _renderLoader = showPoster ? () => <VideoLoader /> : undefined;
|
||||
|
||||
const _subtitleStyle = {subtitlesFollowVideo: true};
|
||||
const _controlsStyles = {
|
||||
const _controlsStyles : ControlsStyles = {
|
||||
hideNavigationBarOnFullScreenMode: true,
|
||||
hideNotificationBarOnFullScreenMode: true,
|
||||
liveLabel: "LIVE"
|
||||
};
|
||||
const _bufferConfig = {
|
||||
...bufferConfig,
|
||||
|
@ -309,6 +309,7 @@ type ControlsStyles = Readonly<{
|
||||
hideNavigationBarOnFullScreenMode?: WithDefault<boolean, true>;
|
||||
hideNotificationBarOnFullScreenMode?: WithDefault<boolean, true>;
|
||||
seekIncrementMS?: Int32;
|
||||
liveLabel?: string;
|
||||
}>;
|
||||
|
||||
export type OnControlsVisibilityChange = Readonly<{
|
||||
|
@ -261,6 +261,7 @@ export type ControlsStyles = {
|
||||
hideNavigationBarOnFullScreenMode?: boolean;
|
||||
hideNotificationBarOnFullScreenMode?: boolean;
|
||||
seekIncrementMS?: number;
|
||||
liveLabel?: string;
|
||||
};
|
||||
|
||||
export interface ReactVideoRenderLoaderProps {
|
||||
|
Loading…
Reference in New Issue
Block a user