From 95e6140eead56efab83871e29b57d30f4f11a77a Mon Sep 17 00:00:00 2001 From: Seyed Mostafa Hasani Date: Mon, 20 May 2024 14:15:18 +0330 Subject: [PATCH] feat(android): add possibility to hide seekBar (#3789) * feat: hide seekBar on Android when a video is a live broadcast * refactor: prop name & code * chore: update the document for a new prop (controlsStyles) * refactor: code * remove: additional variables * fix: indent issues * remove: duplicate function * Update android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java revert change1 * Update android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java revert change2 * Update android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java * Update android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java chore: revert indent change * Update android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java chore: revert indent change * Update android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java chore: revert indent change * Update android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java chore: revert indent change * Update android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java chore: revert indent change * fix: eslint errors * Update android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java chore: revert indent change --------- Co-authored-by: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> --- .../brentvatne/common/api/ControlsConfig.kt | 21 ++++++++++ .../exoplayer/ReactExoplayerView.java | 41 +++++++++++++++++++ .../exoplayer/ReactExoplayerViewManager.java | 10 ++++- docs/pages/component/props.mdx | 21 +++++++++- src/specs/VideoNativeComponent.ts | 5 +++ src/types/video.ts | 5 +++ 6 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 android/src/main/java/com/brentvatne/common/api/ControlsConfig.kt diff --git a/android/src/main/java/com/brentvatne/common/api/ControlsConfig.kt b/android/src/main/java/com/brentvatne/common/api/ControlsConfig.kt new file mode 100644 index 0000000000..cdc282f76e --- /dev/null +++ b/android/src/main/java/com/brentvatne/common/api/ControlsConfig.kt @@ -0,0 +1,21 @@ +package com.brentvatne.common.api + +import com.brentvatne.common.toolbox.ReactBridgeUtils +import com.facebook.react.bridge.ReadableMap + +class ControlsConfig { + var hideSeekBar: Boolean = false + + companion object { + @JvmStatic + fun parse(src: ReadableMap?): ControlsConfig { + val config = ControlsConfig() + + if (src != null) { + config.hideSeekBar = ReactBridgeUtils.safeGetBool(src, "hideSeekBar", false) + } + + return config + } + } +} diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 9bd900f4f9..2d505e139d 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -29,6 +29,8 @@ import android.view.accessibility.CaptioningManager; import android.widget.FrameLayout; import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; @@ -102,10 +104,12 @@ import androidx.media3.extractor.metadata.id3.Id3Frame; import androidx.media3.extractor.metadata.id3.TextInformationFrame; import androidx.media3.session.MediaSessionService; +import androidx.media3.ui.DefaultTimeBar; import androidx.media3.ui.LegacyPlayerControlView; import com.brentvatne.common.api.BufferConfig; import com.brentvatne.common.api.BufferingStrategy; +import com.brentvatne.common.api.ControlsConfig; import com.brentvatne.common.api.ResizeMode; import com.brentvatne.common.api.SideLoadedTextTrack; import com.brentvatne.common.api.SideLoadedTextTrackList; @@ -120,6 +124,7 @@ import com.brentvatne.receiver.AudioBecomingNoisyReceiver; import com.brentvatne.receiver.BecomingNoisyListener; import com.facebook.react.bridge.LifecycleEventListener; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.uimanager.ThemedReactContext; import com.google.ads.interactivemedia.v3.api.AdError; @@ -212,6 +217,7 @@ public class ReactExoplayerView extends FrameLayout implements private Handler mainHandler; private Runnable mainRunnable; private DataSource.Factory cacheDataSourceFactory; + private ControlsConfig controlsConfig = new ControlsConfig(); // Props from React private Uri srcUri; @@ -451,6 +457,7 @@ public void handleOnBackPressed() { final ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen); fullScreenButton.setOnClickListener(v -> setFullscreen(!isFullscreen)); updateFullScreenButtonVisbility(); + refreshProgressBarVisibility(); // Invoking onPlaybackStateChanged and onPlayWhenReadyChanged events for Player eventListener = new Player.Listener() { @@ -509,6 +516,35 @@ private void reLayout(View view) { view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight()); } + private void refreshProgressBarVisibility (){ + if(playerControlView == null) return; + DefaultTimeBar exoProgress; + TextView exoDuration; + TextView exoPosition; + exoProgress = playerControlView.findViewById(R.id.exo_progress); + exoDuration = playerControlView.findViewById(R.id.exo_duration); + exoPosition = playerControlView.findViewById(R.id.exo_position); + if(controlsConfig.getHideSeekBar()){ + LinearLayout.LayoutParams param = new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT, + 1.0f + ); + exoProgress.setVisibility(GONE); + exoDuration.setVisibility(GONE); + exoPosition.setLayoutParams(param); + }else{ + exoProgress.setVisibility(VISIBLE); + exoDuration.setVisibility(VISIBLE); + // Reset the layout parameters of exoPosition to their default state + LinearLayout.LayoutParams defaultParam = new LinearLayout.LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + ); + exoPosition.setLayoutParams(defaultParam); + } + } + private void reLayoutControls() { reLayout(exoPlayerView); reLayout(playerControlView); @@ -2296,4 +2332,9 @@ public void onAdError(AdErrorEvent adErrorEvent) { AdError error = adErrorEvent.getError(); eventEmitter.receiveAdErrorEvent(error.getMessage(), String.valueOf(error.getErrorCode()), String.valueOf(error.getErrorType())); } + + public void setControlsStyles(ControlsConfig controlsStyles) { + controlsConfig = controlsStyles; + refreshProgressBarVisibility(); + } } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 74f818f5c0..fd4efd85f2 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -13,6 +13,7 @@ import com.brentvatne.common.api.BufferConfig; import com.brentvatne.common.api.BufferingStrategy; +import com.brentvatne.common.api.ControlsConfig; import com.brentvatne.common.api.ResizeMode; import com.brentvatne.common.api.SideLoadedTextTrackList; import com.brentvatne.common.api.SubtitleStyle; @@ -88,6 +89,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager + +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. | + +Example with default values: + +```javascript +controlsStyles={{ + hideSeekBar: false, +}} +``` + + ### `bufferConfig` @@ -797,7 +816,7 @@ textTracks={[ -Controls whether to show media controls in the notification area. +Controls whether to show media controls in the notification area. For Android each Video component will have its own notification controls and for iOS only one notification control will be shown for the last Active Video component. You propably want also set `playInBackground` to `true` to keep the video playing when the app is in the background or `playWhenInactive` to `true` to keep the video playing when notifications or the Control Center are in front of the video. diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index 4f1c738fb7..53cec03b46 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -273,6 +273,10 @@ export type OnAudioFocusChangedData = Readonly<{ hasAudioFocus: boolean; }>; +type ControlsStyles = Readonly<{ + hideSeekBar?: boolean; +}>; + export interface VideoNativeProps extends ViewProps { src?: VideoSrc; drm?: Drm; @@ -320,6 +324,7 @@ export interface VideoNativeProps extends ViewProps { useTextureView?: boolean; // Android useSecureView?: boolean; // Android bufferingStrategy?: BufferingStrategyType; // Android + controlsStyles?: ControlsStyles; // Android onVideoLoad?: DirectEventHandler; onVideoLoadStart?: DirectEventHandler; onVideoAspectRatio?: DirectEventHandler; diff --git a/src/types/video.ts b/src/types/video.ts index e174bbbf75..56694ecd24 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -193,6 +193,10 @@ export enum PosterResizeModeType { export type AudioOutput = 'speaker' | 'earpiece'; +export type ControlsStyles = { + hideSeekBar?: boolean; +}; + export interface ReactVideoProps extends ReactVideoEvents, ViewProps { source?: ReactVideoSource; drm?: Drm; @@ -247,4 +251,5 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps { localSourceEncryptionKeyScheme?: string; debug?: DebugConfig; allowsExternalPlayback?: boolean; // iOS + controlsStyles?: ControlsStyles; // Android }