feat(android): add live video label configuration (#4190)

This commit is contained in:
Seyed Mostafa Hasani 2024-10-03 01:07:18 +03:30 committed by GitHub
parent 82dc4cf3a0
commit 149924ffcb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 146 additions and 97 deletions

View File

@ -16,27 +16,29 @@ class ControlsConfig {
var hideFullscreen: Boolean = false var hideFullscreen: Boolean = false
var hideNavigationBarOnFullScreenMode: Boolean = true var hideNavigationBarOnFullScreenMode: Boolean = true
var hideNotificationBarOnFullScreenMode: Boolean = true var hideNotificationBarOnFullScreenMode: Boolean = true
var liveLabel: String? = null
var seekIncrementMS: Int = 10000 var seekIncrementMS: Int = 10000
companion object { companion object {
@JvmStatic @JvmStatic
fun parse(src: ReadableMap?): ControlsConfig { fun parse(controlsConfig: ReadableMap?): ControlsConfig {
val config = ControlsConfig() val config = ControlsConfig()
if (src != null) { if (controlsConfig != null) {
config.hideSeekBar = ReactBridgeUtils.safeGetBool(src, "hideSeekBar", false) config.hideSeekBar = ReactBridgeUtils.safeGetBool(controlsConfig, "hideSeekBar", false)
config.hideDuration = ReactBridgeUtils.safeGetBool(src, "hideDuration", false) config.hideDuration = ReactBridgeUtils.safeGetBool(controlsConfig, "hideDuration", false)
config.hidePosition = ReactBridgeUtils.safeGetBool(src, "hidePosition", false) config.hidePosition = ReactBridgeUtils.safeGetBool(controlsConfig, "hidePosition", false)
config.hidePlayPause = ReactBridgeUtils.safeGetBool(src, "hidePlayPause", false) config.hidePlayPause = ReactBridgeUtils.safeGetBool(controlsConfig, "hidePlayPause", false)
config.hideForward = ReactBridgeUtils.safeGetBool(src, "hideForward", false) config.hideForward = ReactBridgeUtils.safeGetBool(controlsConfig, "hideForward", false)
config.hideRewind = ReactBridgeUtils.safeGetBool(src, "hideRewind", false) config.hideRewind = ReactBridgeUtils.safeGetBool(controlsConfig, "hideRewind", false)
config.hideNext = ReactBridgeUtils.safeGetBool(src, "hideNext", false) config.hideNext = ReactBridgeUtils.safeGetBool(controlsConfig, "hideNext", false)
config.hidePrevious = ReactBridgeUtils.safeGetBool(src, "hidePrevious", false) config.hidePrevious = ReactBridgeUtils.safeGetBool(controlsConfig, "hidePrevious", false)
config.hideFullscreen = ReactBridgeUtils.safeGetBool(src, "hideFullscreen", false) config.hideFullscreen = ReactBridgeUtils.safeGetBool(controlsConfig, "hideFullscreen", false)
config.seekIncrementMS = ReactBridgeUtils.safeGetInt(src, "seekIncrementMS", 10000) config.seekIncrementMS = ReactBridgeUtils.safeGetInt(controlsConfig, "seekIncrementMS", 10000)
config.hideNavigationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(src, "hideNavigationBarOnFullScreenMode", true) config.hideNavigationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(controlsConfig, "hideNavigationBarOnFullScreenMode", true)
config.hideNotificationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(src, "hideNotificationBarOnFullScreenMode", true) config.hideNotificationBarOnFullScreenMode = ReactBridgeUtils.safeGetBool(controlsConfig, "hideNotificationBarOnFullScreenMode", true)
config.liveLabel = ReactBridgeUtils.safeGetString(controlsConfig, "liveLabel", null)
} }
return config return config
} }

View File

@ -10,6 +10,7 @@ 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 android.widget.LinearLayout
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
@ -216,5 +217,13 @@ class FullScreenPlayerView(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE 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
}
}
} }
} }

View File

@ -527,88 +527,78 @@ public class ReactExoplayerView extends FrameLayout implements
view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight()); view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight());
} }
private void refreshControlsStyles (){ private void refreshControlsStyles() {
if(playerControlView == null) return; 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 playButton = playerControlView.findViewById(R.id.exo_play);
final ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause); final ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause);
if (controlsConfig.getHidePlayPause()) { if (controlsConfig.getHidePlayPause()) {
playPauseControlContainer.setAlpha(0); playPauseControlContainer.setAlpha(0);
playButton.setClickable(false); playButton.setClickable(false);
pauseButton.setClickable(false); pauseButton.setClickable(false);
} else { } else {
playPauseControlContainer.setAlpha(1.0f); playPauseControlContainer.setAlpha(1.0f);
playButton.setClickable(true); playButton.setClickable(true);
pauseButton.setClickable(true); pauseButton.setClickable(true);
} }
}
final ImageButton forwardButton = playerControlView.findViewById(R.id.exo_ffwd); private void updateButtonVisibility(boolean hide, int buttonID) {
if (controlsConfig.getHideForward()) { ImageButton button = playerControlView.findViewById(buttonID);
forwardButton.setImageAlpha(0); if (hide) {
forwardButton.setClickable(false); button.setImageAlpha(0);
button.setClickable(false);
} else { } else {
forwardButton.setImageAlpha(255); button.setImageAlpha(255);
forwardButton.setClickable(true); button.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);
} }
} }
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() { private void reLayoutControls() {
reLayout(exoPlayerView); reLayout(exoPlayerView);
reLayout(playerControlView); reLayout(playerControlView);
@ -1508,6 +1498,7 @@ public class ReactExoplayerView extends FrameLayout implements
eventEmitter.onVideoLoad.invoke(duration, currentPosition, width, height, eventEmitter.onVideoLoad.invoke(duration, currentPosition, width, height,
audioTracks, textTracks, videoTracks, trackId); audioTracks, textTracks, videoTracks, trackId);
refreshControlsStyles();
} }
} }
@ -1757,7 +1748,7 @@ public class ReactExoplayerView extends FrameLayout implements
} }
eventEmitter.onVideoPlaybackStateChanged.invoke(isPlaying, isSeeking); eventEmitter.onVideoPlaybackStateChanged.invoke(isPlaying, isSeeking);
if (isPlaying) { if (isPlaying) {
isSeeking = false; isSeeking = false;
} }
@ -2406,6 +2397,7 @@ public class ReactExoplayerView extends FrameLayout implements
removeViewAt(indexOfPC); removeViewAt(indexOfPC);
} }
} }
refreshControlsStyles();
} }
public void setSubtitleStyle(SubtitleStyle style) { public void setSubtitleStyle(SubtitleStyle style) {
@ -2440,4 +2432,4 @@ public class ReactExoplayerView extends FrameLayout implements
controlsConfig = controlsStyles; controlsConfig = controlsStyles;
refreshControlsStyles(); refreshControlsStyles();
} }
} }

View 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>

View File

@ -1,17 +1,48 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="bottom"
android:layoutDirection="ltr" android:layoutDirection="ltr"
android:background="@color/midnight_black" android:background="@color/midnight_black"
android:orientation="vertical"> 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 <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:paddingTop="@dimen/controller_wrapper_padding_top" android:paddingTop="@dimen/controller_wrapper_padding_top"
android:layout_gravity="bottom"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageButton android:id="@+id/exo_prev" <ImageButton android:id="@+id/exo_prev"

View File

@ -2,4 +2,6 @@
<resources> <resources>
<color name="silver_gray">#FFBEBEBE</color> <color name="silver_gray">#FFBEBEBE</color>
<color name="midnight_black">#CC000000</color> <color name="midnight_black">#CC000000</color>
<color name="white">#FFFFFF</color>
<color name="red">#FF0000</color>
</resources> </resources>

View File

@ -3,6 +3,7 @@
<!-- margin & padding--> <!-- margin & padding-->
<dimen name="controller_wrapper_padding_top">4dp</dimen> <dimen name="controller_wrapper_padding_top">4dp</dimen>
<dimen name="seekBar_wrapper_margin_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="position_duration_horizontal_padding">4dp</dimen>
<dimen name="full_screen_margin">4dp</dimen> <dimen name="full_screen_margin">4dp</dimen>

View File

@ -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. 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 |
|-----------------------------------|---------|--------------------------------------------------------------------------------------------| |-------------------------------------|---------|---------------------------------------------------------------------------------------------|
| hidePosition | boolean | Hides the position indicator. Default is `false`. | | hidePosition | boolean | Hides the position indicator. Default is `false`. |
| hidePlayPause | boolean | Hides the play/pause button. Default is `false`. | | hidePlayPause | boolean | Hides the play/pause button. Default is `false`. |
| hideForward | boolean | Hides the forward button. Default is `false`. | | hideForward | boolean | Hides the forward button. Default is `false`. |
| hideRewind | boolean | Hides the rewind button. Default is `false`. | | hideRewind | boolean | Hides the rewind button. Default is `false`. |
| hideNext | boolean | Hides the next button. Default is `false`. | | hideNext | boolean | Hides the next button. Default is `false`. |
| hidePrevious | boolean | Hides the previous button. Default is `false`. | | hidePrevious | boolean | Hides the previous button. Default is `false`. |
| hideFullscreen | boolean | Hides the fullscreen 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. | | 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. |
| hideNavigationBarOnFullScreenMode | boolean | The default value is `true`, allowing you to hide the navigation bar on full-screen mode. | | 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. | | 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: Example with default values:
@ -175,6 +176,7 @@ controlsStyles={{
hideNavigationBarOnFullScreenMode: true, hideNavigationBarOnFullScreenMode: true,
hideNotificationBarOnFullScreenMode: true, hideNotificationBarOnFullScreenMode: true,
seekIncrementMS: 10000, seekIncrementMS: 10000,
liveLabel: "LIVE"
}} }}
``` ```

View File

@ -31,6 +31,7 @@ import Video, {
type SelectedVideoTrack, type SelectedVideoTrack,
type EnumValues, type EnumValues,
OnBandwidthUpdateData, OnBandwidthUpdateData,
ControlsStyles,
} from 'react-native-video'; } from 'react-native-video';
import styles from './styles'; import styles from './styles';
import {type AdditionalSourceInfo} from './types'; import {type AdditionalSourceInfo} from './types';
@ -241,9 +242,10 @@ const VideoPlayer: FC<Props> = ({}) => {
const _renderLoader = showPoster ? () => <VideoLoader /> : undefined; const _renderLoader = showPoster ? () => <VideoLoader /> : undefined;
const _subtitleStyle = {subtitlesFollowVideo: true}; const _subtitleStyle = {subtitlesFollowVideo: true};
const _controlsStyles = { const _controlsStyles : ControlsStyles = {
hideNavigationBarOnFullScreenMode: true, hideNavigationBarOnFullScreenMode: true,
hideNotificationBarOnFullScreenMode: true, hideNotificationBarOnFullScreenMode: true,
liveLabel: "LIVE"
}; };
const _bufferConfig = { const _bufferConfig = {
...bufferConfig, ...bufferConfig,

View File

@ -309,6 +309,7 @@ type ControlsStyles = Readonly<{
hideNavigationBarOnFullScreenMode?: WithDefault<boolean, true>; hideNavigationBarOnFullScreenMode?: WithDefault<boolean, true>;
hideNotificationBarOnFullScreenMode?: WithDefault<boolean, true>; hideNotificationBarOnFullScreenMode?: WithDefault<boolean, true>;
seekIncrementMS?: Int32; seekIncrementMS?: Int32;
liveLabel?: string;
}>; }>;
export type OnControlsVisibilityChange = Readonly<{ export type OnControlsVisibilityChange = Readonly<{

View File

@ -261,6 +261,7 @@ export type ControlsStyles = {
hideNavigationBarOnFullScreenMode?: boolean; hideNavigationBarOnFullScreenMode?: boolean;
hideNotificationBarOnFullScreenMode?: boolean; hideNotificationBarOnFullScreenMode?: boolean;
seekIncrementMS?: number; seekIncrementMS?: number;
liveLabel?: string;
}; };
export interface ReactVideoRenderLoaderProps { export interface ReactVideoRenderLoaderProps {