From 2fa6c43615c1bc0a3bbcb5f472ffaeb8ae16a1af Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:13:06 +0200 Subject: [PATCH] fix(android): add subtitleStyle.subtitlesFollowVideo prop to control subtitles positionning (#4133) * fix(android): add subtitleStyle.subtitlesFollowVideo prop to control subtitles positionning * docs: add new prop description * docs: add supported platform for subtitleStyle * chore: use constructor instead of parse --- .../brentvatne/common/api/SubtitleStyle.kt | 6 ++- .../brentvatne/exoplayer/ExoPlayerView.java | 26 ++++++++++-- docs/pages/component/props.mdx | 41 +++++++++++++++---- examples/basic/src/VideoPlayer.tsx | 1 + src/specs/VideoNativeComponent.ts | 1 + src/types/video.ts | 1 + 6 files changed, 63 insertions(+), 13 deletions(-) diff --git a/android/src/main/java/com/brentvatne/common/api/SubtitleStyle.kt b/android/src/main/java/com/brentvatne/common/api/SubtitleStyle.kt index efb4da0a81..1ac0fd03ac 100644 --- a/android/src/main/java/com/brentvatne/common/api/SubtitleStyle.kt +++ b/android/src/main/java/com/brentvatne/common/api/SubtitleStyle.kt @@ -6,7 +6,7 @@ import com.facebook.react.bridge.ReadableMap /** * Helper file to parse SubtitleStyle prop and build a dedicated class */ -class SubtitleStyle private constructor() { +class SubtitleStyle public constructor() { var fontSize = -1 private set var paddingLeft = 0 @@ -19,6 +19,8 @@ class SubtitleStyle private constructor() { private set var opacity = 1f private set + var subtitlesFollowVideo = true + private set companion object { private const val PROP_FONT_SIZE_TRACK = "fontSize" @@ -27,6 +29,7 @@ class SubtitleStyle private constructor() { private const val PROP_PADDING_LEFT = "paddingLeft" private const val PROP_PADDING_RIGHT = "paddingRight" private const val PROP_OPACITY = "opacity" + private const val PROP_SUBTITLES_FOLLOW_VIDEO = "subtitlesFollowVideo" @JvmStatic fun parse(src: ReadableMap?): SubtitleStyle { @@ -37,6 +40,7 @@ class SubtitleStyle private constructor() { subtitleStyle.paddingLeft = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_LEFT, 0) subtitleStyle.paddingRight = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_RIGHT, 0) subtitleStyle.opacity = ReactBridgeUtils.safeGetFloat(src, PROP_OPACITY, 1f) + subtitleStyle.subtitlesFollowVideo = ReactBridgeUtils.safeGetBool(src, PROP_SUBTITLES_FOLLOW_VIDEO, true) return subtitleStyle } } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index dd18dbfd5b..45624d6fa6 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -48,6 +48,8 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { private @ViewType.ViewType int viewType = ViewType.VIEW_TYPE_SURFACE; private boolean hideShutterView = false; + private SubtitleStyle localStyle = new SubtitleStyle(); + public ExoPlayerView(Context context) { super(context, null, 0); @@ -80,10 +82,15 @@ public ExoPlayerView(Context context) { adOverlayFrameLayout = new FrameLayout(context); layout.addView(shutterView, 1, layoutParams); - layout.addView(adOverlayFrameLayout, 2, layoutParams); + if (localStyle.getSubtitlesFollowVideo()) { + layout.addView(subtitleLayout, layoutParams); + layout.addView(adOverlayFrameLayout, layoutParams); + } addViewInLayout(layout, 0, aspectRatioParams); - addViewInLayout(subtitleLayout, 1, layoutParams); + if (!localStyle.getSubtitlesFollowVideo()) { + addViewInLayout(subtitleLayout, 1, layoutParams); + } } private void clearVideoView() { @@ -107,7 +114,7 @@ public boolean isPlaying() { } public void setSubtitleStyle(SubtitleStyle style) { - // ensure we reset subtile style before reapplying it + // ensure we reset subtitle style before reapplying it subtitleLayout.setUserDefaultStyle(); subtitleLayout.setUserDefaultTextSize(); @@ -121,7 +128,18 @@ public void setSubtitleStyle(SubtitleStyle style) { } else { subtitleLayout.setVisibility(View.GONE); } - + if (localStyle.getSubtitlesFollowVideo() != style.getSubtitlesFollowVideo()) { + // No need to manipulate layout if value didn't change + if (style.getSubtitlesFollowVideo()) { + removeViewInLayout(subtitleLayout); + layout.addView(subtitleLayout, layoutParams); + } else { + layout.removeViewInLayout(subtitleLayout); + addViewInLayout(subtitleLayout, 1, layoutParams, false); + } + requestLayout(); + } + localStyle = style; } public void setShutterColor(Integer color) { diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index e32865969e..47971ba83e 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -846,14 +846,18 @@ source={{ ### `subtitleStyle` -| Property | Description | Platforms | -| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -| fontSize | Adjust the font size of the subtitles. Default: font size of the device | Android | -| paddingTop | Adjust the top padding of the subtitles. Default: 0 | Android | -| paddingBottom | Adjust the bottom padding of the subtitles. Default: 0 | Android | -| paddingLeft | Adjust the left padding of the subtitles. Default: 0 | Android | -| paddingRight | Adjust the right padding of the subtitles. Default: 0 | Android | -| opacity | Adjust the visibility of subtitles with 0 hiding and 1 fully showing them. Android supports float values between 0 and 1 for varying opacity levels, whereas iOS supports only 0 or 1. Default: 1. | Android, iOS | + + +| Property | Platform | Description | Platforms | +| ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| fontSize | Android | Adjust the font size of the subtitles. Default: font size of the device | Android | +| paddingTop | Android | Adjust the top padding of the subtitles. Default: 0 | Android | +| paddingBottom | Android | Adjust the bottom padding of the subtitles. Default: 0 | Android | +| paddingLeft | Android | Adjust the left padding of the subtitles. Default: 0 | Android | +| paddingRight | Android | Adjust the right padding of the subtitles. Default: 0 | Android | +| opacity | Android, iOS | Adjust the visibility of subtitles with 0 hiding and 1 fully showing them. Android supports float values between 0 and 1 for varying opacity levels, whereas iOS supports only 0 or 1. Default: 1. | Android, iOS | +| subtitlesFollowVideo | Android | Boolean to adjust position of subtitles. Default: true | + Example: @@ -861,6 +865,27 @@ Example: subtitleStyle={{ paddingBottom: 50, fontSize: 20, opacity: 0 }} ``` +Note for `subtitlesFollowVideo` + +`subtitlesFollowVideo` helps to determine how the subtitles are positionned. +To understand this prop you need to understand how views management works. +The main View style passed to react native video is the position reserved to display the video component. +It may not match exactly the real video size. +For exemple, you can pass a 4:3 video view and render a 16:9 video inside. +So there is a second view, the video view. + +Subtitles are managed in a third view. + +First react-native-video resize the video to keep aspect ratio (depending on `resizeMode` property) and put it in main view. + +* When putting subtitlesFollowVideo to true, the subtitle view will be adapt to the video view. +It means that if the video is displayed out of screen, the subtitles may also be displayed out of screen. + +* When putting subtitlesFollowVideo to false, the subtitle view will keep adapting to the main view. +It means that if the video is displayed out of screen, the subtitles may also be displayed out of screen. + +This prop can be changed on runtime. + ### `textTracks` diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx index 243d2fa8f5..b77f97c9da 100644 --- a/examples/basic/src/VideoPlayer.tsx +++ b/examples/basic/src/VideoPlayer.tsx @@ -268,6 +268,7 @@ const VideoPlayer: FC = ({}) => { onPlaybackStateChanged={onPlaybackStateChanged} bufferingStrategy={BufferingStrategyType.DEFAULT} debug={{enable: true, thread: true}} + subtitleStyle={{subtitlesFollowVideo: true}} /> )} diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index a5eba10c38..82b92745a8 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -131,6 +131,7 @@ type SubtitleStyle = Readonly<{ paddingLeft?: WithDefault; paddingRight?: WithDefault; opacity?: WithDefault; + subtitlesFollowVideo?: WithDefault; }>; type OnLoadData = Readonly<{ diff --git a/src/types/video.ts b/src/types/video.ts index d29d0108a9..b34becb5e0 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -168,6 +168,7 @@ export type SubtitleStyle = { paddingLeft?: number; paddingRight?: number; opacity?: number; + subtitlesFollowVideo?: boolean; }; export enum TextTrackType {