feat: add disable audio session management (#4829)

* chore(nitro): update generated files

* feat: implement disableAudioSessionManagement for IOS
This commit is contained in:
Filip Wnęk
2026-03-26 16:06:41 +01:00
committed by GitHub
parent a01d203bcb
commit cb9bf8e821
28 changed files with 201 additions and 22 deletions
+5 -5
View File
@@ -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",
},
},
},
+6 -6
View File
@@ -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
@@ -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)
@@ -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
}
@@ -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,
@@ -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
@@ -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)
}
}
@@ -56,5 +56,9 @@ namespace margelo::nitro::video {
auto __result = method(_javaPart, std::dynamic_pointer_cast<JHybridVideoPlayerSourceSpec>(source)->getJavaPart());
return __result->getJHybridVideoPlayerSpec();
}
void JHybridVideoPlayerFactorySpec::setAudioSessionManagementDisabled(bool disabled) {
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jboolean /* disabled */)>("setAudioSessionManagementDisabled");
method(_javaPart, disabled);
}
} // namespace margelo::nitro::video
@@ -55,6 +55,7 @@ namespace margelo::nitro::video {
public:
// Methods
std::shared_ptr<HybridVideoPlayerSpec> createPlayer(const std::shared_ptr<HybridVideoPlayerSourceSpec>& source) override;
void setAudioSessionManagementDisabled(bool disabled) override;
private:
jni::global_ref<JHybridVideoPlayerFactorySpec::JavaPart> _javaPart;
@@ -185,6 +185,15 @@ namespace margelo::nitro::video {
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jboolean /* playWhenInactive */)>("setPlayWhenInactive");
method(_javaPart, playWhenInactive);
}
bool JHybridVideoPlayerSpec::getDisableAudioSessionManagement() {
static const auto method = _javaPart->javaClassStatic()->getMethod<jboolean()>("getDisableAudioSessionManagement");
auto __result = method(_javaPart);
return static_cast<bool>(__result);
}
void JHybridVideoPlayerSpec::setDisableAudioSessionManagement(bool disableAudioSessionManagement) {
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jboolean /* disableAudioSessionManagement */)>("setDisableAudioSessionManagement");
method(_javaPart, disableAudioSessionManagement);
}
bool JHybridVideoPlayerSpec::getIsPlaying() {
static const auto method = _javaPart->javaClassStatic()->getMethod<jboolean()>("isPlaying");
auto __result = method(_javaPart);
@@ -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<TextTrack> getSelectedTrack() override;
@@ -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 {
@@ -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
@@ -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<decltype(disabled)>(disabled));
if (__result.hasError()) [[unlikely]] {
std::rethrow_exception(__result.error());
}
}
private:
ReactNativeVideo::HybridVideoPlayerFactorySpec_cxx _swiftPart;
@@ -162,6 +162,12 @@ namespace margelo::nitro::video {
inline void setPlayWhenInactive(bool playWhenInactive) noexcept override {
_swiftPart.setPlayWhenInactive(std::forward<decltype(playWhenInactive)>(playWhenInactive));
}
inline bool getDisableAudioSessionManagement() noexcept override {
return _swiftPart.getDisableAudioSessionManagement();
}
inline void setDisableAudioSessionManagement(bool disableAudioSessionManagement) noexcept override {
_swiftPart.setDisableAudioSessionManagement(std::forward<decltype(disableAudioSessionManagement)>(disableAudioSessionManagement));
}
inline bool getIsPlaying() noexcept override {
return _swiftPart.isPlaying();
}
@@ -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 {
@@ -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)
}
}
}
@@ -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 }
@@ -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 {
@@ -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);
});
}
@@ -54,6 +54,7 @@ namespace margelo::nitro::video {
public:
// Methods
virtual std::shared_ptr<HybridVideoPlayerSpec> createPlayer(const std::shared_ptr<HybridVideoPlayerSourceSpec>& source) = 0;
virtual void setAudioSessionManagementDisabled(bool disabled) = 0;
protected:
// Hybrid Setup
@@ -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);
@@ -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<TextTrack> getSelectedTrack() = 0;
@@ -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;
@@ -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.
@@ -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>('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.
@@ -24,3 +24,4 @@ export {
type VideoViewRef,
} from './core/video-view/VideoView';
export { VideoPlayer } from './core/VideoPlayer';
export { setAudioSessionManagementDisabled } from './core/utils/playerFactory';
@@ -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;
}