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>
This commit is contained in:
Seyed Mostafa Hasani 2024-05-20 14:15:18 +03:30 committed by GitHub
parent 3cd7ab60b2
commit 95e6140eea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 101 additions and 2 deletions

View File

@ -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
}
}
}

View File

@ -29,6 +29,8 @@ import android.view.Window;
import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -102,10 +104,12 @@ import androidx.media3.extractor.metadata.emsg.EventMessage;
import androidx.media3.extractor.metadata.id3.Id3Frame; import androidx.media3.extractor.metadata.id3.Id3Frame;
import androidx.media3.extractor.metadata.id3.TextInformationFrame; import androidx.media3.extractor.metadata.id3.TextInformationFrame;
import androidx.media3.session.MediaSessionService; import androidx.media3.session.MediaSessionService;
import androidx.media3.ui.DefaultTimeBar;
import androidx.media3.ui.LegacyPlayerControlView; import androidx.media3.ui.LegacyPlayerControlView;
import com.brentvatne.common.api.BufferConfig; import com.brentvatne.common.api.BufferConfig;
import com.brentvatne.common.api.BufferingStrategy; import com.brentvatne.common.api.BufferingStrategy;
import com.brentvatne.common.api.ControlsConfig;
import com.brentvatne.common.api.ResizeMode; import com.brentvatne.common.api.ResizeMode;
import com.brentvatne.common.api.SideLoadedTextTrack; import com.brentvatne.common.api.SideLoadedTextTrack;
import com.brentvatne.common.api.SideLoadedTextTrackList; import com.brentvatne.common.api.SideLoadedTextTrackList;
@ -120,6 +124,7 @@ import com.brentvatne.react.R;
import com.brentvatne.receiver.AudioBecomingNoisyReceiver; import com.brentvatne.receiver.AudioBecomingNoisyReceiver;
import com.brentvatne.receiver.BecomingNoisyListener; import com.brentvatne.receiver.BecomingNoisyListener;
import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ThemedReactContext;
import com.google.ads.interactivemedia.v3.api.AdError; import com.google.ads.interactivemedia.v3.api.AdError;
@ -212,6 +217,7 @@ public class ReactExoplayerView extends FrameLayout implements
private Handler mainHandler; private Handler mainHandler;
private Runnable mainRunnable; private Runnable mainRunnable;
private DataSource.Factory cacheDataSourceFactory; private DataSource.Factory cacheDataSourceFactory;
private ControlsConfig controlsConfig = new ControlsConfig();
// Props from React // Props from React
private Uri srcUri; private Uri srcUri;
@ -451,6 +457,7 @@ public class ReactExoplayerView extends FrameLayout implements
final ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen); final ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
fullScreenButton.setOnClickListener(v -> setFullscreen(!isFullscreen)); fullScreenButton.setOnClickListener(v -> setFullscreen(!isFullscreen));
updateFullScreenButtonVisbility(); updateFullScreenButtonVisbility();
refreshProgressBarVisibility();
// Invoking onPlaybackStateChanged and onPlayWhenReadyChanged events for Player // Invoking onPlaybackStateChanged and onPlayWhenReadyChanged events for Player
eventListener = new Player.Listener() { eventListener = new Player.Listener() {
@ -509,6 +516,35 @@ 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 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() { private void reLayoutControls() {
reLayout(exoPlayerView); reLayout(exoPlayerView);
reLayout(playerControlView); reLayout(playerControlView);
@ -2296,4 +2332,9 @@ public class ReactExoplayerView extends FrameLayout implements
AdError error = adErrorEvent.getError(); AdError error = adErrorEvent.getError();
eventEmitter.receiveAdErrorEvent(error.getMessage(), String.valueOf(error.getErrorCode()), String.valueOf(error.getErrorType())); eventEmitter.receiveAdErrorEvent(error.getMessage(), String.valueOf(error.getErrorCode()), String.valueOf(error.getErrorType()));
} }
public void setControlsStyles(ControlsConfig controlsStyles) {
controlsConfig = controlsStyles;
refreshProgressBarVisibility();
}
} }

View File

@ -13,6 +13,7 @@ import androidx.media3.datasource.RawResourceDataSource;
import com.brentvatne.common.api.BufferConfig; import com.brentvatne.common.api.BufferConfig;
import com.brentvatne.common.api.BufferingStrategy; import com.brentvatne.common.api.BufferingStrategy;
import com.brentvatne.common.api.ControlsConfig;
import com.brentvatne.common.api.ResizeMode; import com.brentvatne.common.api.ResizeMode;
import com.brentvatne.common.api.SideLoadedTextTrackList; import com.brentvatne.common.api.SideLoadedTextTrackList;
import com.brentvatne.common.api.SubtitleStyle; import com.brentvatne.common.api.SubtitleStyle;
@ -88,6 +89,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_SHUTTER_COLOR = "shutterColor"; private static final String PROP_SHUTTER_COLOR = "shutterColor";
private static final String PROP_SHOW_NOTIFICATION_CONTROLS = "showNotificationControls"; private static final String PROP_SHOW_NOTIFICATION_CONTROLS = "showNotificationControls";
private static final String PROP_DEBUG = "debug"; private static final String PROP_DEBUG = "debug";
private static final String PROP_CONTROLS_STYLES = "controlsStyles";
private final ReactExoplayerConfig config; private final ReactExoplayerConfig config;
@ -451,6 +453,12 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setDebug(enableDebug); videoView.setDebug(enableDebug);
} }
@ReactProp(name = PROP_CONTROLS_STYLES)
public void setControlsStyles(final ReactExoplayerView videoView, @Nullable ReadableMap controlsStyles) {
ControlsConfig controlsConfig = ControlsConfig.parse(controlsStyles);
videoView.setControlsStyles(controlsConfig);
}
private boolean startsWithValidScheme(String uriString) { private boolean startsWithValidScheme(String uriString) {
String lowerCaseUri = uriString.toLowerCase(); String lowerCaseUri = uriString.toLowerCase();
return lowerCaseUri.startsWith("http://") return lowerCaseUri.startsWith("http://")
@ -460,4 +468,4 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|| lowerCaseUri.startsWith("rtsp://") || lowerCaseUri.startsWith("rtsp://")
|| lowerCaseUri.startsWith("asset://"); || lowerCaseUri.startsWith("asset://");
} }
} }

View File

@ -47,6 +47,25 @@ A Boolean value that indicates whether the player should automatically delay pla
- **false** - Immediately starts playback - **false** - Immediately starts playback
- **true (default)** - Delays playback in order to minimize stalling - **true (default)** - Delays playback in order to minimize stalling
### `controlsStyles`
<PlatformsList types={['Android']} />
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` ### `bufferConfig`
<PlatformsList types={['Android']} /> <PlatformsList types={['Android']} />
@ -797,7 +816,7 @@ textTracks={[
<PlatformsList types={['Android', 'iOS']}/> <PlatformsList types={['Android', 'iOS']}/>
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. 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. 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.

View File

@ -273,6 +273,10 @@ export type OnAudioFocusChangedData = Readonly<{
hasAudioFocus: boolean; hasAudioFocus: boolean;
}>; }>;
type ControlsStyles = Readonly<{
hideSeekBar?: boolean;
}>;
export interface VideoNativeProps extends ViewProps { export interface VideoNativeProps extends ViewProps {
src?: VideoSrc; src?: VideoSrc;
drm?: Drm; drm?: Drm;
@ -320,6 +324,7 @@ export interface VideoNativeProps extends ViewProps {
useTextureView?: boolean; // Android useTextureView?: boolean; // Android
useSecureView?: boolean; // Android useSecureView?: boolean; // Android
bufferingStrategy?: BufferingStrategyType; // Android bufferingStrategy?: BufferingStrategyType; // Android
controlsStyles?: ControlsStyles; // Android
onVideoLoad?: DirectEventHandler<OnLoadData>; onVideoLoad?: DirectEventHandler<OnLoadData>;
onVideoLoadStart?: DirectEventHandler<OnLoadStartData>; onVideoLoadStart?: DirectEventHandler<OnLoadStartData>;
onVideoAspectRatio?: DirectEventHandler<OnVideoAspectRatioData>; onVideoAspectRatio?: DirectEventHandler<OnVideoAspectRatioData>;

View File

@ -193,6 +193,10 @@ export enum PosterResizeModeType {
export type AudioOutput = 'speaker' | 'earpiece'; export type AudioOutput = 'speaker' | 'earpiece';
export type ControlsStyles = {
hideSeekBar?: boolean;
};
export interface ReactVideoProps extends ReactVideoEvents, ViewProps { export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
source?: ReactVideoSource; source?: ReactVideoSource;
drm?: Drm; drm?: Drm;
@ -247,4 +251,5 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
localSourceEncryptionKeyScheme?: string; localSourceEncryptionKeyScheme?: string;
debug?: DebugConfig; debug?: DebugConfig;
allowsExternalPlayback?: boolean; // iOS allowsExternalPlayback?: boolean; // iOS
controlsStyles?: ControlsStyles; // Android
} }