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:
parent
3cd7ab60b2
commit
95e6140eea
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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://");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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.
|
||||||
|
@ -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>;
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user