From 100aefc8d47b1abffe11b34d8b66b2c16adf0f58 Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Sun, 21 Sep 2025 13:12:33 +0200 Subject: [PATCH] feat: add `keepScreenAwake` prop (#4700) --- docs/docs/video-view.md | 1 + .../HybridVideoViewViewManager.kt | 6 +++++ .../main/java/com/twg/video/view/VideoView.kt | 6 +++++ .../HybridVideoViewViewManager.swift | 19 +++++++++++++++ .../ios/view/VideoComponentView.swift | 23 +++++++++++++++++++ .../c++/JHybridVideoViewViewManagerSpec.cpp | 9 ++++++++ .../c++/JHybridVideoViewViewManagerSpec.hpp | 2 ++ .../video/HybridVideoViewViewManagerSpec.kt | 6 +++++ .../HybridVideoViewViewManagerSpecSwift.hpp | 6 +++++ .../HybridVideoViewViewManagerSpec.swift | 1 + .../HybridVideoViewViewManagerSpec_cxx.swift | 11 +++++++++ .../c++/HybridVideoViewViewManagerSpec.cpp | 2 ++ .../c++/HybridVideoViewViewManagerSpec.hpp | 2 ++ .../src/core/video-view/VideoView.tsx | 5 ++++ .../spec/nitro/VideoViewViewManager.nitro.ts | 1 + 15 files changed, 100 insertions(+) diff --git a/docs/docs/video-view.md b/docs/docs/video-view.md index cf53e8eb..862b8e09 100644 --- a/docs/docs/video-view.md +++ b/docs/docs/video-view.md @@ -52,6 +52,7 @@ export default App; | `pictureInPicture` | `boolean` | No | `false` | Whether to enable and show the picture-in-picture (PiP) button in the native controls (if supported by the platform and controls are visible). | | `autoEnterPictureInPicture` | `boolean` | No | `false` | Whether the video should automatically enter PiP mode when it starts playing and the app is backgrounded (behavior might vary by platform). | | `resizeMode` | `'contain' \| 'cover' \| 'stretch' \| 'none'` | No | `'none'` | How the video should be resized to fit the view. | +| `keepScreenAwake` | `boolean` | No | `true` | Whether to keep the device screen awake while the video view is mounted. | ## Events diff --git a/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoviewviewmanager/HybridVideoViewViewManager.kt b/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoviewviewmanager/HybridVideoViewViewManager.kt index 76fc675c..ecbc8e8c 100644 --- a/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoviewviewmanager/HybridVideoViewViewManager.kt +++ b/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoviewviewmanager/HybridVideoViewViewManager.kt @@ -72,6 +72,12 @@ class HybridVideoViewViewManager(nitroId: Int): HybridVideoViewViewManagerSpec() videoView.get()?.resizeMode = value } + override var keepScreenAwake: Boolean + get() = videoView.get()?.keepScreenAwake == true + set(value) { + videoView.get()?.keepScreenAwake = value + } + // View callbacks override var onPictureInPictureChange: ((Boolean) -> Unit)? = null set(value) { diff --git a/packages/react-native-video/android/src/main/java/com/twg/video/view/VideoView.kt b/packages/react-native-video/android/src/main/java/com/twg/video/view/VideoView.kt index 92177a01..b56cd63a 100644 --- a/packages/react-native-video/android/src/main/java/com/twg/video/view/VideoView.kt +++ b/packages/react-native-video/android/src/main/java/com/twg/video/view/VideoView.kt @@ -98,6 +98,12 @@ class VideoView @JvmOverloads constructor( } } + var keepScreenAwake: Boolean + get() = playerView.keepScreenOn + set(value) { + playerView.keepScreenOn = value + } + var events = object : VideoViewEvents { override var onPictureInPictureChange: ((Boolean) -> Unit)? = {} override var onFullscreenChange: ((Boolean) -> Unit)? = {} diff --git a/packages/react-native-video/ios/hybrids/VideoViewViewManager/HybridVideoViewViewManager.swift b/packages/react-native-video/ios/hybrids/VideoViewViewManager/HybridVideoViewViewManager.swift index 58de7ec2..08d7dfe5 100644 --- a/packages/react-native-video/ios/hybrids/VideoViewViewManager/HybridVideoViewViewManager.swift +++ b/packages/react-native-video/ios/hybrids/VideoViewViewManager/HybridVideoViewViewManager.swift @@ -119,6 +119,25 @@ class HybridVideoViewViewManager: HybridVideoViewViewManagerSpec { } } + var keepScreenAwake: Bool { + get { + guard let view else { + print(DEALOCATED_WARNING) + return false + } + + return view.keepScreenAwake + } + set { + guard let view else { + print(DEALOCATED_WARNING) + return + } + + view.keepScreenAwake = newValue + } + } + func enterFullscreen() throws { guard let view else { throw VideoViewError.viewIsDeallocated.error() diff --git a/packages/react-native-video/ios/view/VideoComponentView.swift b/packages/react-native-video/ios/view/VideoComponentView.swift index 3d7782c5..120f64bb 100644 --- a/packages/react-native-video/ios/view/VideoComponentView.swift +++ b/packages/react-native-video/ios/view/VideoComponentView.swift @@ -28,6 +28,19 @@ import AVKit } } + private var _keepScreenAwake: Bool = false + var keepScreenAwake: Bool { + get { + guard let player = player as? HybridVideoPlayer else { return false } + return player.player.preventsDisplaySleepDuringVideoPlayback + } + set { + guard let player = player as? HybridVideoPlayer else { return } + player.player.preventsDisplaySleepDuringVideoPlayback = newValue + _keepScreenAwake = newValue + } + } + var playerViewController: AVPlayerViewController? { didSet { guard let observer, let playerViewController else { return } @@ -168,8 +181,18 @@ import AVKit if newSuperview == nil { PluginsRegistry.shared.notifyVideoViewDestroyed(view: self) + + // We want to disable this when view is about to unmount + if keepScreenAwake { + keepScreenAwake = false + } } else { PluginsRegistry.shared.notifyVideoViewCreated(view: self) + + // We want to restore keepScreenAwake after component remount + if _keepScreenAwake { + keepScreenAwake = true + } } } diff --git a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoViewViewManagerSpec.cpp b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoViewViewManagerSpec.cpp index 72760303..c98ca687 100644 --- a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoViewViewManagerSpec.cpp +++ b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoViewViewManagerSpec.cpp @@ -90,6 +90,15 @@ namespace margelo::nitro::video { static const auto method = javaClassStatic()->getMethod /* resizeMode */)>("setResizeMode"); method(_javaPart, JResizeMode::fromCpp(resizeMode)); } + bool JHybridVideoViewViewManagerSpec::getKeepScreenAwake() { + static const auto method = javaClassStatic()->getMethod("getKeepScreenAwake"); + auto __result = method(_javaPart); + return static_cast(__result); + } + void JHybridVideoViewViewManagerSpec::setKeepScreenAwake(bool keepScreenAwake) { + static const auto method = javaClassStatic()->getMethod("setKeepScreenAwake"); + method(_javaPart, keepScreenAwake); + } std::optional> JHybridVideoViewViewManagerSpec::getOnPictureInPictureChange() { static const auto method = javaClassStatic()->getMethod()>("getOnPictureInPictureChange_cxx"); auto __result = method(_javaPart); diff --git a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoViewViewManagerSpec.hpp b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoViewViewManagerSpec.hpp index 2265d9e3..e8a15bd3 100644 --- a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoViewViewManagerSpec.hpp +++ b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoViewViewManagerSpec.hpp @@ -59,6 +59,8 @@ namespace margelo::nitro::video { void setAutoEnterPictureInPicture(bool autoEnterPictureInPicture) override; ResizeMode getResizeMode() override; void setResizeMode(ResizeMode resizeMode) override; + bool getKeepScreenAwake() override; + void setKeepScreenAwake(bool keepScreenAwake) override; std::optional> getOnPictureInPictureChange() override; void setOnPictureInPictureChange(const std::optional>& onPictureInPictureChange) override; std::optional> getOnFullscreenChange() override; diff --git a/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoViewViewManagerSpec.kt b/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoViewViewManagerSpec.kt index 59948fd6..27d4801c 100644 --- a/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoViewViewManagerSpec.kt +++ b/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoViewViewManagerSpec.kt @@ -67,6 +67,12 @@ abstract class HybridVideoViewViewManagerSpec: HybridObject() { @set:Keep abstract var resizeMode: ResizeMode + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var keepScreenAwake: Boolean + abstract var onPictureInPictureChange: ((isInPictureInPicture: Boolean) -> Unit)? private var onPictureInPictureChange_cxx: Func_void_bool? diff --git a/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoViewViewManagerSpecSwift.hpp b/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoViewViewManagerSpecSwift.hpp index 0ba2fb85..6c1e8109 100644 --- a/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoViewViewManagerSpecSwift.hpp +++ b/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoViewViewManagerSpecSwift.hpp @@ -92,6 +92,12 @@ namespace margelo::nitro::video { inline void setResizeMode(ResizeMode resizeMode) noexcept override { _swiftPart.setResizeMode(static_cast(resizeMode)); } + inline bool getKeepScreenAwake() noexcept override { + return _swiftPart.getKeepScreenAwake(); + } + inline void setKeepScreenAwake(bool keepScreenAwake) noexcept override { + _swiftPart.setKeepScreenAwake(std::forward(keepScreenAwake)); + } inline std::optional> getOnPictureInPictureChange() noexcept override { auto __result = _swiftPart.getOnPictureInPictureChange(); return __result; diff --git a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoViewViewManagerSpec.swift b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoViewViewManagerSpec.swift index 18b22309..b950b899 100644 --- a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoViewViewManagerSpec.swift +++ b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoViewViewManagerSpec.swift @@ -16,6 +16,7 @@ public protocol HybridVideoViewViewManagerSpec_protocol: HybridObject { var pictureInPicture: Bool { get set } var autoEnterPictureInPicture: Bool { get set } var resizeMode: ResizeMode { get set } + var keepScreenAwake: Bool { get set } var onPictureInPictureChange: ((_ isInPictureInPicture: Bool) -> Void)? { get set } var onFullscreenChange: ((_ fullscreen: Bool) -> Void)? { get set } var willEnterFullscreen: (() -> Void)? { get set } diff --git a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoViewViewManagerSpec_cxx.swift b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoViewViewManagerSpec_cxx.swift index e200c106..68f61a1c 100644 --- a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoViewViewManagerSpec_cxx.swift +++ b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoViewViewManagerSpec_cxx.swift @@ -180,6 +180,17 @@ open class HybridVideoViewViewManagerSpec_cxx { } } + public final var keepScreenAwake: Bool { + @inline(__always) + get { + return self.__implementation.keepScreenAwake + } + @inline(__always) + set { + self.__implementation.keepScreenAwake = newValue + } + } + public final var onPictureInPictureChange: bridge.std__optional_std__function_void_bool____isInPictureInPicture______ { @inline(__always) get { diff --git a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoViewViewManagerSpec.cpp b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoViewViewManagerSpec.cpp index 99f1667b..0eda1260 100644 --- a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoViewViewManagerSpec.cpp +++ b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoViewViewManagerSpec.cpp @@ -24,6 +24,8 @@ namespace margelo::nitro::video { prototype.registerHybridSetter("autoEnterPictureInPicture", &HybridVideoViewViewManagerSpec::setAutoEnterPictureInPicture); prototype.registerHybridGetter("resizeMode", &HybridVideoViewViewManagerSpec::getResizeMode); prototype.registerHybridSetter("resizeMode", &HybridVideoViewViewManagerSpec::setResizeMode); + prototype.registerHybridGetter("keepScreenAwake", &HybridVideoViewViewManagerSpec::getKeepScreenAwake); + prototype.registerHybridSetter("keepScreenAwake", &HybridVideoViewViewManagerSpec::setKeepScreenAwake); prototype.registerHybridGetter("onPictureInPictureChange", &HybridVideoViewViewManagerSpec::getOnPictureInPictureChange); prototype.registerHybridSetter("onPictureInPictureChange", &HybridVideoViewViewManagerSpec::setOnPictureInPictureChange); prototype.registerHybridGetter("onFullscreenChange", &HybridVideoViewViewManagerSpec::getOnFullscreenChange); diff --git a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoViewViewManagerSpec.hpp b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoViewViewManagerSpec.hpp index 86776e6c..4ed6bb8b 100644 --- a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoViewViewManagerSpec.hpp +++ b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoViewViewManagerSpec.hpp @@ -61,6 +61,8 @@ namespace margelo::nitro::video { virtual void setAutoEnterPictureInPicture(bool autoEnterPictureInPicture) = 0; virtual ResizeMode getResizeMode() = 0; virtual void setResizeMode(ResizeMode resizeMode) = 0; + virtual bool getKeepScreenAwake() = 0; + virtual void setKeepScreenAwake(bool keepScreenAwake) = 0; virtual std::optional> getOnPictureInPictureChange() = 0; virtual void setOnPictureInPictureChange(const std::optional>& onPictureInPictureChange) = 0; virtual std::optional> getOnFullscreenChange() = 0; diff --git a/packages/react-native-video/src/core/video-view/VideoView.tsx b/packages/react-native-video/src/core/video-view/VideoView.tsx index 715b2692..8a78e8d8 100644 --- a/packages/react-native-video/src/core/video-view/VideoView.tsx +++ b/packages/react-native-video/src/core/video-view/VideoView.tsx @@ -40,6 +40,10 @@ export interface VideoViewProps extends Partial, ViewProps { * - 'none': Do not resize the video */ resizeMode?: ResizeMode; + /** + * Whether to keep the screen awake while the video view is mounted. Defaults to true. + */ + keepScreenAwake?: boolean; } export interface VideoViewRef { @@ -99,6 +103,7 @@ const updateProps = (manager: VideoViewViewManager, props: VideoViewProps) => { manager.willExitFullscreen = props.willExitFullscreen; manager.willEnterPictureInPicture = props.willEnterPictureInPicture; manager.willExitPictureInPicture = props.willExitPictureInPicture; + manager.keepScreenAwake = props.keepScreenAwake ?? true; }; /** diff --git a/packages/react-native-video/src/spec/nitro/VideoViewViewManager.nitro.ts b/packages/react-native-video/src/spec/nitro/VideoViewViewManager.nitro.ts index 2ecbbcda..86a7e11a 100644 --- a/packages/react-native-video/src/spec/nitro/VideoViewViewManager.nitro.ts +++ b/packages/react-native-video/src/spec/nitro/VideoViewViewManager.nitro.ts @@ -17,6 +17,7 @@ export interface VideoViewViewManager enterPictureInPicture(): void; exitPictureInPicture(): void; canEnterPictureInPicture(): boolean; + keepScreenAwake: boolean; } // @internal