From cb9bf8e8218f0c68567fd141dbec3fac53501099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Wn=C4=99k?= <86932087+fnwk@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:06:41 +0100 Subject: [PATCH] feat: add disable audio session management (#4829) * chore(nitro): update generated files * feat: implement disableAudioSessionManagement for IOS --- bun.lock | 10 ++-- example/ios/Podfile.lock | 12 ++--- .../hybrids/videoplayer/HybridVideoPlayer.kt | 3 ++ .../videoplayer/HybridVideoPlayerFactory.kt | 4 ++ .../ios/core/VideoManager.swift | 47 ++++++++++++++----- .../VideoPlayer/HybridVideoPlayer.swift | 6 +++ .../HybridVideoPlayerFactory.swift | 4 ++ .../c++/JHybridVideoPlayerFactorySpec.cpp | 4 ++ .../c++/JHybridVideoPlayerFactorySpec.hpp | 1 + .../android/c++/JHybridVideoPlayerSpec.cpp | 9 ++++ .../android/c++/JHybridVideoPlayerSpec.hpp | 2 + .../video/HybridVideoPlayerFactorySpec.kt | 4 ++ .../nitro/video/HybridVideoPlayerSpec.kt | 6 +++ .../c++/HybridVideoPlayerFactorySpecSwift.hpp | 6 +++ .../ios/c++/HybridVideoPlayerSpecSwift.hpp | 6 +++ .../swift/HybridVideoPlayerFactorySpec.swift | 1 + .../HybridVideoPlayerFactorySpec_cxx.swift | 11 +++++ .../ios/swift/HybridVideoPlayerSpec.swift | 1 + .../ios/swift/HybridVideoPlayerSpec_cxx.swift | 11 +++++ .../c++/HybridVideoPlayerFactorySpec.cpp | 1 + .../c++/HybridVideoPlayerFactorySpec.hpp | 1 + .../shared/c++/HybridVideoPlayerSpec.cpp | 2 + .../shared/c++/HybridVideoPlayerSpec.hpp | 2 + .../src/core/VideoPlayer.ts | 15 ++++++ .../src/core/types/VideoPlayerBase.ts | 10 ++++ .../src/core/utils/playerFactory.ts | 33 +++++++++++++ packages/react-native-video/src/index.tsx | 1 + .../src/spec/nitro/VideoPlayer.nitro.ts | 10 ++++ 28 files changed, 201 insertions(+), 22 deletions(-) diff --git a/bun.lock b/bun.lock index 336b1a27..e2c191e5 100644 --- a/bun.lock +++ b/bun.lock @@ -56,7 +56,7 @@ }, "example": { "name": "react-native-video-example", - "version": "7.0.0-beta.6", + "version": "7.0.0-beta.7", "dependencies": { "@react-native-community/slider": "^4.5.6", "@react-native-video/drm": "*", @@ -84,7 +84,7 @@ }, "packages/drm-plugin": { "name": "@react-native-video/drm", - "version": "7.0.0-beta.6", + "version": "7.0.0-beta.8", "devDependencies": { "@react-native/babel-preset": "0.79.2", "@release-it/conventional-changelog": "^9.0.2", @@ -106,13 +106,13 @@ "peerDependencies": { "react": "*", "react-native": "*", - "react-native-nitro-modules": ">=0.27.2", + "react-native-nitro-modules": ">=0.35.0", "react-native-video": ">=7.0.0-alpha.3", }, }, "packages/react-native-video": { "name": "react-native-video", - "version": "7.0.0-beta.6", + "version": "7.0.0-beta.8", "devDependencies": { "@expo/config-plugins": "^10.0.2", "@react-native/eslint-config": "^0.77.0", @@ -132,7 +132,7 @@ "peerDependencies": { "react": "*", "react-native": "*", - "react-native-nitro-modules": ">=0.27.2", + "react-native-nitro-modules": ">=0.35.0", }, }, }, diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 3144721f..dfc522c1 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -8,7 +8,7 @@ PODS: - hermes-engine (0.77.3): - hermes-engine/Pre-built (= 0.77.3) - hermes-engine/Pre-built (0.77.3) - - NitroModules (0.31.10): + - NitroModules (0.35.0): - DoubleConversion - glog - hermes-engine @@ -1565,7 +1565,7 @@ PODS: - React-logger (= 0.77.3) - React-perflogger (= 0.77.3) - React-utils (= 0.77.3) - - ReactNativeVideo (7.0.0-beta.6): + - ReactNativeVideo (7.0.0-beta.8): - DoubleConversion - glog - hermes-engine @@ -1587,7 +1587,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - ReactNativeVideoDrm (7.0.0-beta.6): + - ReactNativeVideoDrm (7.0.0-beta.8): - DoubleConversion - glog - hermes-engine @@ -1844,7 +1844,7 @@ SPEC CHECKSUMS: fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8 hermes-engine: b2187dbe13edb0db8fcb2a93a69c1987a30d98a4 - NitroModules: 72332b5857c49f2ee0b5390d4aa00fb91db17df7 + NitroModules: bb34ba5ce1c2ba59eafca7ec912dd9a6a5957ce7 RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 RCTDeprecation: 6ee92578d332db1d4e03267d3ae98bcf8b780863 RCTRequired: 5b3da0e0f91fddda935574b81748c3e3d3649ee7 @@ -1904,8 +1904,8 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 31015410a4a53b9fd0a908ad4d6e3e2b9a25086a ReactCodegen: 53316394e985ded1babc7f143c90c77d2bb1b43c ReactCommon: bf4612cba0fa356b529385029f470d5529dddde4 - ReactNativeVideo: de7056e831a46412e81fbf730ef739ec4c7378fe - ReactNativeVideoDrm: a83670242cec729b4fb67e7c804ab3300e848b86 + ReactNativeVideo: c9c7b36a616e95e5905d2b3fb7583c3117f77629 + ReactNativeVideoDrm: de11a8ece7a6c385d4b55a95c1c930ae636cca94 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 92f3bb322c40a86b7233b815854730442e01b8c4 diff --git a/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoplayer/HybridVideoPlayer.kt b/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoplayer/HybridVideoPlayer.kt index 25a99711..e812d753 100644 --- a/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoplayer/HybridVideoPlayer.kt +++ b/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoplayer/HybridVideoPlayer.kt @@ -191,6 +191,9 @@ class HybridVideoPlayer() : HybridVideoPlayerSpec(), AutoCloseable { // iOS only property override var ignoreSilentSwitchMode: IgnoreSilentSwitchMode = IgnoreSilentSwitchMode.AUTO + // iOS only property - no-op on Android + override var disableAudioSessionManagement: Boolean = false + override var playInBackground: Boolean = false set(value) { val shouldRun = (value || showNotificationControls) diff --git a/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoplayer/HybridVideoPlayerFactory.kt b/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoplayer/HybridVideoPlayerFactory.kt index edf00a06..6518f20e 100644 --- a/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoplayer/HybridVideoPlayerFactory.kt +++ b/packages/react-native-video/android/src/main/java/com/twg/video/hybrids/videoplayer/HybridVideoPlayerFactory.kt @@ -11,6 +11,10 @@ class HybridVideoPlayerFactory(): HybridVideoPlayerFactorySpec() { return HybridVideoPlayer(source as HybridVideoPlayerSource) } + override fun setAudioSessionManagementDisabled(disabled: Boolean) { + // No-op on Android - audio session management is iOS-specific + } + override val memorySize: Long get() = 0 } diff --git a/packages/react-native-video/ios/core/VideoManager.swift b/packages/react-native-video/ios/core/VideoManager.swift index bc2869d6..a5fad51d 100644 --- a/packages/react-native-video/ios/core/VideoManager.swift +++ b/packages/react-native-video/ios/core/VideoManager.swift @@ -17,9 +17,34 @@ class VideoManager { private var isAudioSessionActive = false private var remoteControlEventsActive = false - - // TODO: Create Global Config, and expose it there - private var isAudioSessionManagementDisabled: Bool = false + + /// Global flag to force disable audio session management + private var isAudioSessionManagementForcedDisabled: Bool = false + + /// Returns true if audio session management is disabled either globally or by any player + private var isAudioSessionManagementDisabled: Bool { + if isAudioSessionManagementForcedDisabled { + return true + } + + // Check if any player has audio session management disabled + return players.allObjects.contains { player in + player.disableAudioSessionManagement + } + } + + func setAudioSessionManagementDisabled(_ disabled: Bool) { + isAudioSessionManagementForcedDisabled = disabled + + if disabled { + // Deactivate audio session when disabling management + // so other libraries can take control + deactivateAudioSession() + } else { + // Re-enable audio session management + updateAudioSessionConfiguration() + } + } private init() { // Subscribe to audio interruption notifications @@ -110,7 +135,7 @@ class VideoManager { if isAudioSessionActive { return } - + do { try AVAudioSession.sharedInstance().setActive(true) isAudioSessionActive = true @@ -118,12 +143,12 @@ class VideoManager { print("Failed to activate audio session: \(error.localizedDescription)") } } - + private func deactivateAudioSession() { if !isAudioSessionActive { return } - + do { try AVAudioSession.sharedInstance().setActive( false, options: .notifyOthersOnDeactivation @@ -133,8 +158,12 @@ class VideoManager { print("Failed to deactivate audio session: \(error.localizedDescription)") } } - + private func updateAudioSessionConfiguration() { + if isAudioSessionManagementDisabled { + return + } + let isAnyPlayerPlaying = players.allObjects.contains { hybridPlayer in hybridPlayer.player.isMuted == false && hybridPlayer.player.rate != 0 } @@ -180,10 +209,6 @@ class VideoManager { player.showNotificationControls } - if isAudioSessionManagementDisabled { - return - } - let category: AVAudioSession.Category = determineAudioCategory( silentSwitchObey: anyPlayerNeedsSilentSwitchObey, silentSwitchIgnore: anyPlayerNeedsSilentSwitchIgnore, diff --git a/packages/react-native-video/ios/hybrids/VideoPlayer/HybridVideoPlayer.swift b/packages/react-native-video/ios/hybrids/VideoPlayer/HybridVideoPlayer.swift index 433ebdb7..e18a1002 100644 --- a/packages/react-native-video/ios/hybrids/VideoPlayer/HybridVideoPlayer.swift +++ b/packages/react-native-video/ios/hybrids/VideoPlayer/HybridVideoPlayer.swift @@ -158,6 +158,12 @@ class HybridVideoPlayer: HybridVideoPlayerSpec, NativeVideoPlayerSpec { var playWhenInactive: Bool = false + var disableAudioSessionManagement: Bool = false { + didSet { + VideoManager.shared.requestAudioSessionUpdate() + } + } + var wasAutoPaused: Bool = false // Text track selection state diff --git a/packages/react-native-video/ios/hybrids/VideoPlayer/HybridVideoPlayerFactory.swift b/packages/react-native-video/ios/hybrids/VideoPlayer/HybridVideoPlayerFactory.swift index 9ab476dc..afb65a82 100644 --- a/packages/react-native-video/ios/hybrids/VideoPlayer/HybridVideoPlayerFactory.swift +++ b/packages/react-native-video/ios/hybrids/VideoPlayer/HybridVideoPlayerFactory.swift @@ -12,4 +12,8 @@ class HybridVideoPlayerFactory: HybridVideoPlayerFactorySpec { func createPlayer(source: HybridVideoPlayerSourceSpec) throws -> HybridVideoPlayerSpec { return try HybridVideoPlayer(source: source) } + + func setAudioSessionManagementDisabled(disabled: Bool) throws { + VideoManager.shared.setAudioSessionManagementDisabled(disabled) + } } diff --git a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerFactorySpec.cpp b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerFactorySpec.cpp index 564e4c19..48cb41e2 100644 --- a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerFactorySpec.cpp +++ b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerFactorySpec.cpp @@ -56,5 +56,9 @@ namespace margelo::nitro::video { auto __result = method(_javaPart, std::dynamic_pointer_cast(source)->getJavaPart()); return __result->getJHybridVideoPlayerSpec(); } + void JHybridVideoPlayerFactorySpec::setAudioSessionManagementDisabled(bool disabled) { + static const auto method = _javaPart->javaClassStatic()->getMethod("setAudioSessionManagementDisabled"); + method(_javaPart, disabled); + } } // namespace margelo::nitro::video diff --git a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerFactorySpec.hpp b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerFactorySpec.hpp index b3f4df5f..50d1f92b 100644 --- a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerFactorySpec.hpp +++ b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerFactorySpec.hpp @@ -55,6 +55,7 @@ namespace margelo::nitro::video { public: // Methods std::shared_ptr createPlayer(const std::shared_ptr& source) override; + void setAudioSessionManagementDisabled(bool disabled) override; private: jni::global_ref _javaPart; diff --git a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerSpec.cpp b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerSpec.cpp index 425cd2c8..2c5c33ae 100644 --- a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerSpec.cpp +++ b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerSpec.cpp @@ -185,6 +185,15 @@ namespace margelo::nitro::video { static const auto method = _javaPart->javaClassStatic()->getMethod("setPlayWhenInactive"); method(_javaPart, playWhenInactive); } + bool JHybridVideoPlayerSpec::getDisableAudioSessionManagement() { + static const auto method = _javaPart->javaClassStatic()->getMethod("getDisableAudioSessionManagement"); + auto __result = method(_javaPart); + return static_cast(__result); + } + void JHybridVideoPlayerSpec::setDisableAudioSessionManagement(bool disableAudioSessionManagement) { + static const auto method = _javaPart->javaClassStatic()->getMethod("setDisableAudioSessionManagement"); + method(_javaPart, disableAudioSessionManagement); + } bool JHybridVideoPlayerSpec::getIsPlaying() { static const auto method = _javaPart->javaClassStatic()->getMethod("isPlaying"); auto __result = method(_javaPart); diff --git a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerSpec.hpp b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerSpec.hpp index b732ea43..1e69948c 100644 --- a/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerSpec.hpp +++ b/packages/react-native-video/nitrogen/generated/android/c++/JHybridVideoPlayerSpec.hpp @@ -74,6 +74,8 @@ namespace margelo::nitro::video { void setPlayInBackground(bool playInBackground) override; bool getPlayWhenInactive() override; void setPlayWhenInactive(bool playWhenInactive) override; + bool getDisableAudioSessionManagement() override; + void setDisableAudioSessionManagement(bool disableAudioSessionManagement) override; bool getIsPlaying() override; std::optional getSelectedTrack() override; diff --git a/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoPlayerFactorySpec.kt b/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoPlayerFactorySpec.kt index 2440091a..28a3963a 100644 --- a/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoPlayerFactorySpec.kt +++ b/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoPlayerFactorySpec.kt @@ -31,6 +31,10 @@ abstract class HybridVideoPlayerFactorySpec: HybridObject() { @DoNotStrip @Keep abstract fun createPlayer(source: HybridVideoPlayerSourceSpec): HybridVideoPlayerSpec + + @DoNotStrip + @Keep + abstract fun setAudioSessionManagementDisabled(disabled: Boolean): Unit // Default implementation of `HybridObject.toString()` override fun toString(): String { diff --git a/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoPlayerSpec.kt b/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoPlayerSpec.kt index b908b588..c38ffbb7 100644 --- a/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoPlayerSpec.kt +++ b/packages/react-native-video/nitrogen/generated/android/kotlin/com/margelo/nitro/video/HybridVideoPlayerSpec.kt @@ -103,6 +103,12 @@ abstract class HybridVideoPlayerSpec: HybridObject() { @set:Keep abstract var playWhenInactive: Boolean + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var disableAudioSessionManagement: Boolean + @get:DoNotStrip @get:Keep abstract val isPlaying: Boolean diff --git a/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoPlayerFactorySpecSwift.hpp b/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoPlayerFactorySpecSwift.hpp index dc7e93eb..14b00306 100644 --- a/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoPlayerFactorySpecSwift.hpp +++ b/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoPlayerFactorySpecSwift.hpp @@ -79,6 +79,12 @@ namespace margelo::nitro::video { auto __value = std::move(__result.value()); return __value; } + inline void setAudioSessionManagementDisabled(bool disabled) override { + auto __result = _swiftPart.setAudioSessionManagementDisabled(std::forward(disabled)); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + } private: ReactNativeVideo::HybridVideoPlayerFactorySpec_cxx _swiftPart; diff --git a/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoPlayerSpecSwift.hpp b/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoPlayerSpecSwift.hpp index d1681e8d..565075de 100644 --- a/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoPlayerSpecSwift.hpp +++ b/packages/react-native-video/nitrogen/generated/ios/c++/HybridVideoPlayerSpecSwift.hpp @@ -162,6 +162,12 @@ namespace margelo::nitro::video { inline void setPlayWhenInactive(bool playWhenInactive) noexcept override { _swiftPart.setPlayWhenInactive(std::forward(playWhenInactive)); } + inline bool getDisableAudioSessionManagement() noexcept override { + return _swiftPart.getDisableAudioSessionManagement(); + } + inline void setDisableAudioSessionManagement(bool disableAudioSessionManagement) noexcept override { + _swiftPart.setDisableAudioSessionManagement(std::forward(disableAudioSessionManagement)); + } inline bool getIsPlaying() noexcept override { return _swiftPart.isPlaying(); } diff --git a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerFactorySpec.swift b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerFactorySpec.swift index b0e611be..35616256 100644 --- a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerFactorySpec.swift +++ b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerFactorySpec.swift @@ -14,6 +14,7 @@ public protocol HybridVideoPlayerFactorySpec_protocol: HybridObject { // Methods func createPlayer(source: (any HybridVideoPlayerSourceSpec)) throws -> (any HybridVideoPlayerSpec) + func setAudioSessionManagementDisabled(disabled: Bool) throws -> Void } public extension HybridVideoPlayerFactorySpec_protocol { diff --git a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerFactorySpec_cxx.swift b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerFactorySpec_cxx.swift index ec41c381..338ef0c9 100644 --- a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerFactorySpec_cxx.swift +++ b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerFactorySpec_cxx.swift @@ -142,4 +142,15 @@ open class HybridVideoPlayerFactorySpec_cxx { return bridge.create_Result_std__shared_ptr_HybridVideoPlayerSpec__(__exceptionPtr) } } + + @inline(__always) + public final func setAudioSessionManagementDisabled(disabled: Bool) -> bridge.Result_void_ { + do { + try self.__implementation.setAudioSessionManagementDisabled(disabled: disabled) + return bridge.create_Result_void_() + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_void_(__exceptionPtr) + } + } } diff --git a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerSpec.swift b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerSpec.swift index 9de71bcc..6738738a 100644 --- a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerSpec.swift +++ b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerSpec.swift @@ -24,6 +24,7 @@ public protocol HybridVideoPlayerSpec_protocol: HybridObject { var ignoreSilentSwitchMode: IgnoreSilentSwitchMode { get set } var playInBackground: Bool { get set } var playWhenInactive: Bool { get set } + var disableAudioSessionManagement: Bool { get set } var isPlaying: Bool { get } var selectedTrack: TextTrack? { get } diff --git a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerSpec_cxx.swift b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerSpec_cxx.swift index f47f2af8..0262eb77 100644 --- a/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerSpec_cxx.swift +++ b/packages/react-native-video/nitrogen/generated/ios/swift/HybridVideoPlayerSpec_cxx.swift @@ -265,6 +265,17 @@ open class HybridVideoPlayerSpec_cxx { } } + public final var disableAudioSessionManagement: Bool { + @inline(__always) + get { + return self.__implementation.disableAudioSessionManagement + } + @inline(__always) + set { + self.__implementation.disableAudioSessionManagement = newValue + } + } + public final var isPlaying: Bool { @inline(__always) get { diff --git a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerFactorySpec.cpp b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerFactorySpec.cpp index 138485b0..8c4ab726 100644 --- a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerFactorySpec.cpp +++ b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerFactorySpec.cpp @@ -15,6 +15,7 @@ namespace margelo::nitro::video { // load custom methods/properties registerHybrids(this, [](Prototype& prototype) { prototype.registerHybridMethod("createPlayer", &HybridVideoPlayerFactorySpec::createPlayer); + prototype.registerHybridMethod("setAudioSessionManagementDisabled", &HybridVideoPlayerFactorySpec::setAudioSessionManagementDisabled); }); } diff --git a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerFactorySpec.hpp b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerFactorySpec.hpp index e9f83127..820615b0 100644 --- a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerFactorySpec.hpp +++ b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerFactorySpec.hpp @@ -54,6 +54,7 @@ namespace margelo::nitro::video { public: // Methods virtual std::shared_ptr createPlayer(const std::shared_ptr& source) = 0; + virtual void setAudioSessionManagementDisabled(bool disabled) = 0; protected: // Hybrid Setup diff --git a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerSpec.cpp b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerSpec.cpp index 97969174..95dc641e 100644 --- a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerSpec.cpp +++ b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerSpec.cpp @@ -38,6 +38,8 @@ namespace margelo::nitro::video { prototype.registerHybridSetter("playInBackground", &HybridVideoPlayerSpec::setPlayInBackground); prototype.registerHybridGetter("playWhenInactive", &HybridVideoPlayerSpec::getPlayWhenInactive); prototype.registerHybridSetter("playWhenInactive", &HybridVideoPlayerSpec::setPlayWhenInactive); + prototype.registerHybridGetter("disableAudioSessionManagement", &HybridVideoPlayerSpec::getDisableAudioSessionManagement); + prototype.registerHybridSetter("disableAudioSessionManagement", &HybridVideoPlayerSpec::setDisableAudioSessionManagement); prototype.registerHybridGetter("isPlaying", &HybridVideoPlayerSpec::getIsPlaying); prototype.registerHybridGetter("selectedTrack", &HybridVideoPlayerSpec::getSelectedTrack); prototype.registerHybridMethod("replaceSourceAsync", &HybridVideoPlayerSpec::replaceSourceAsync); diff --git a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerSpec.hpp b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerSpec.hpp index 238f355a..fd924c83 100644 --- a/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerSpec.hpp +++ b/packages/react-native-video/nitrogen/generated/shared/c++/HybridVideoPlayerSpec.hpp @@ -90,6 +90,8 @@ namespace margelo::nitro::video { virtual void setPlayInBackground(bool playInBackground) = 0; virtual bool getPlayWhenInactive() = 0; virtual void setPlayWhenInactive(bool playWhenInactive) = 0; + virtual bool getDisableAudioSessionManagement() = 0; + virtual void setDisableAudioSessionManagement(bool disableAudioSessionManagement) = 0; virtual bool getIsPlaying() = 0; virtual std::optional getSelectedTrack() = 0; diff --git a/packages/react-native-video/src/core/VideoPlayer.ts b/packages/react-native-video/src/core/VideoPlayer.ts index 8dbef384..92e569f5 100644 --- a/packages/react-native-video/src/core/VideoPlayer.ts +++ b/packages/react-native-video/src/core/VideoPlayer.ts @@ -223,6 +223,21 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { this.player.playWhenInactive = value; } + // Disable Audio Session Management + get disableAudioSessionManagement(): boolean { + return this.player.disableAudioSessionManagement; + } + + set disableAudioSessionManagement(value: boolean) { + if (__DEV__ && !['ios'].includes(Platform.OS)) { + console.warn( + 'disableAudioSessionManagement is not supported on this platform, it wont have any effect' + ); + } + + this.player.disableAudioSessionManagement = value; + } + // Is Playing get isPlaying(): boolean { return this.player.isPlaying; diff --git a/packages/react-native-video/src/core/types/VideoPlayerBase.ts b/packages/react-native-video/src/core/types/VideoPlayerBase.ts index 8167877d..6b10c4af 100644 --- a/packages/react-native-video/src/core/types/VideoPlayerBase.ts +++ b/packages/react-native-video/src/core/types/VideoPlayerBase.ts @@ -101,6 +101,16 @@ export interface VideoPlayerBase { */ playWhenInactive: boolean; + /** + * Disables the internal audio session management for this player. + * When disabled, react-native-video will not configure or activate the AVAudioSession, + * allowing other libraries (like audio recording libraries) to manage it. + * + * @default false + * @platform iOS + */ + disableAudioSessionManagement: boolean; + /** * Whether the player is playing. * @note This is a read-only property. diff --git a/packages/react-native-video/src/core/utils/playerFactory.ts b/packages/react-native-video/src/core/utils/playerFactory.ts index 7efe0f46..23b90a5a 100644 --- a/packages/react-native-video/src/core/utils/playerFactory.ts +++ b/packages/react-native-video/src/core/utils/playerFactory.ts @@ -1,3 +1,4 @@ +import { Platform } from 'react-native'; import { NitroModules } from 'react-native-nitro-modules'; import type { VideoPlayer, @@ -11,6 +12,38 @@ import { tryParseNativeVideoError } from '../types/VideoError'; const VideoPlayerFactory = NitroModules.createHybridObject('VideoPlayerFactory'); +/** + * Disables the internal audio session management on iOS. + * When disabled, react-native-video will not configure or activate the AVAudioSession, + * allowing other libraries (like audio recording libraries) to manage it. + * + * @param disabled - If true, audio session management is disabled + * @platform iOS + * + * @example + * ```tsx + * // Disable audio session management before recording + * setAudioSessionManagementDisabled(true); + * + * // Record audio using another library... + * + * // Re-enable audio session management after recording + * setAudioSessionManagementDisabled(false); + * ``` + */ +export const setAudioSessionManagementDisabled = (disabled: boolean): void => { + if (Platform.OS !== 'ios') { + if (__DEV__) { + console.warn( + 'setAudioSessionManagementDisabled is only supported on iOS' + ); + } + return; + } + + VideoPlayerFactory.setAudioSessionManagementDisabled(disabled); +}; + /** * @internal * Creates a Native VideoPlayer instance. diff --git a/packages/react-native-video/src/index.tsx b/packages/react-native-video/src/index.tsx index cdaaa843..690246ce 100644 --- a/packages/react-native-video/src/index.tsx +++ b/packages/react-native-video/src/index.tsx @@ -24,3 +24,4 @@ export { type VideoViewRef, } from './core/video-view/VideoView'; export { VideoPlayer } from './core/VideoPlayer'; +export { setAudioSessionManagementDisabled } from './core/utils/playerFactory'; diff --git a/packages/react-native-video/src/spec/nitro/VideoPlayer.nitro.ts b/packages/react-native-video/src/spec/nitro/VideoPlayer.nitro.ts index 83bc3068..bd62104a 100644 --- a/packages/react-native-video/src/spec/nitro/VideoPlayer.nitro.ts +++ b/packages/react-native-video/src/spec/nitro/VideoPlayer.nitro.ts @@ -52,4 +52,14 @@ export interface VideoPlayer export interface VideoPlayerFactory extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { createPlayer(source: VideoPlayerSource): VideoPlayer; + + /** + * Disables the internal audio session management. + * When disabled, react-native-video will not configure or activate the AVAudioSession, + * allowing other libraries (like audio recording libraries) to manage it. + * + * @param disabled - If true, audio session management is disabled + * @platform iOS + */ + setAudioSessionManagementDisabled(disabled: boolean): void; }