Skip to content

Commit

Permalink
feat(android): add live video label configuration (#4190)
Browse files Browse the repository at this point in the history
  • Loading branch information
seyedmostafahasani authored Oct 2, 2024
1 parent 82dc4cf commit 149924f
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 94 deletions.
30 changes: 16 additions & 14 deletions android/src/main/java/com/brentvatne/common/api/ControlsConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
}
}
120 changes: 56 additions & 64 deletions android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
Original file line number Diff line number Diff line change
Expand Up @@ -527,87 +527,77 @@ private void reLayout(View view) {
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);
} 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);
private void updateButtonVisibility(boolean hide, int buttonID) {
ImageButton button = playerControlView.findViewById(buttonID);
if (hide) {
button.setImageAlpha(0);
button.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);
button.setImageAlpha(255);
button.setClickable(true);
}
}

final TextView positionText = playerControlView.findViewById(R.id.exo_position);
if(controlsConfig.getHidePosition()){
positionText.setVisibility(GONE);
} else if (positionText.getVisibility() == GONE){
positionText.setVisibility(VISIBLE);
private void updateViewVisibility(View view, boolean hide, int hideVisibility) {
if (hide) {
view.setVisibility(hideVisibility);
} else if (view.getVisibility() == hideVisibility) {
view.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);
}
}

private void reLayoutControls() {
reLayout(exoPlayerView);
Expand Down Expand Up @@ -1508,6 +1498,7 @@ private void videoLoaded() {

eventEmitter.onVideoLoad.invoke(duration, currentPosition, width, height,
audioTracks, textTracks, videoTracks, trackId);
refreshControlsStyles();
}
}

Expand Down Expand Up @@ -1757,7 +1748,7 @@ public void onIsPlayingChanged(boolean isPlaying) {
}

eventEmitter.onVideoPlaybackStateChanged.invoke(isPlaying, isSeeking);

if (isPlaying) {
isSeeking = false;
}
Expand Down Expand Up @@ -2406,6 +2397,7 @@ public void setControls(boolean controls) {
removeViewAt(indexOfPC);
}
}
refreshControlsStyles();
}

public void setSubtitleStyle(SubtitleStyle style) {
Expand Down Expand Up @@ -2440,4 +2432,4 @@ public void setControlsStyles(ControlsConfig controlsStyles) {
controlsConfig = controlsStyles;
refreshControlsStyles();
}
}
}
6 changes: 6 additions & 0 deletions android/src/main/res/drawable/circle.xml
Original file line number Diff line number Diff line change
@@ -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>
35 changes: 33 additions & 2 deletions android/src/main/res/layout/exo_legacy_player_control_view.xml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 2 additions & 0 deletions android/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
1 change: 1 addition & 0 deletions android/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
28 changes: 15 additions & 13 deletions docs/pages/component/props.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -175,6 +176,7 @@ controlsStyles={{
hideNavigationBarOnFullScreenMode: true,
hideNotificationBarOnFullScreenMode: true,
seekIncrementMS: 10000,
liveLabel: "LIVE"
}}
```

Expand Down
4 changes: 3 additions & 1 deletion examples/basic/src/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/specs/VideoNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ type ControlsStyles = Readonly<{
hideNavigationBarOnFullScreenMode?: WithDefault<boolean, true>;
hideNotificationBarOnFullScreenMode?: WithDefault<boolean, true>;
seekIncrementMS?: Int32;
liveLabel?: string;
}>;

export type OnControlsVisibilityChange = Readonly<{
Expand Down
1 change: 1 addition & 0 deletions src/types/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ export type ControlsStyles = {
hideNavigationBarOnFullScreenMode?: boolean;
hideNotificationBarOnFullScreenMode?: boolean;
seekIncrementMS?: number;
liveLabel?: string;
};

export interface ReactVideoRenderLoaderProps {
Expand Down

0 comments on commit 149924f

Please sign in to comment.