From dde94303d7c0a62ae6ba894f8ca5dfd42e1a38b8 Mon Sep 17 00:00:00 2001 From: Alicja Wojciechowska Date: Fri, 11 Jul 2025 11:37:31 +0200 Subject: [PATCH 1/2] refactor: updated AVAudioSessionCategoryOptions --- .../common-app/src/examples/Record/Record.tsx | 2 +- .../audiodocs/docs/system/audio-manager.mdx | 171 +++++++++--------- .../ios/system/AudioSessionManager.mm | 8 +- .../src/system/types.ts | 3 +- 4 files changed, 95 insertions(+), 89 deletions(-) diff --git a/apps/common-app/src/examples/Record/Record.tsx b/apps/common-app/src/examples/Record/Record.tsx index 6b0f60b63..51844f45d 100644 --- a/apps/common-app/src/examples/Record/Record.tsx +++ b/apps/common-app/src/examples/Record/Record.tsx @@ -19,7 +19,7 @@ const Record: FC = () => { AudioManager.setAudioSessionOptions({ iosCategory: 'playAndRecord', iosMode: 'spokenAudio', - iosOptions: ['allowBluetooth', 'defaultToSpeaker'], + iosOptions: ['allowBluetoothHFP', 'defaultToSpeaker'], }); recorderRef.current = new AudioRecorder({ diff --git a/packages/audiodocs/docs/system/audio-manager.mdx b/packages/audiodocs/docs/system/audio-manager.mdx index 9c2d512d5..544bde6d0 100644 --- a/packages/audiodocs/docs/system/audio-manager.mdx +++ b/packages/audiodocs/docs/system/audio-manager.mdx @@ -16,23 +16,22 @@ import { AudioManager } from 'react-native-audio-api'; import { useEffect } from 'react'; function App() { - // set AVAudioSession example options (iOS only) - AudioManager.setAudioSessionOptions({ - iosCategory: 'playback', - iosMode: 'default', - iosOptions: ['allowBluetooth', 'allowAirPlay'], - }) - - // set info for track to be visible while device is locked - AudioManager.setLockScreenInfo({ - title: 'Audio file', - artist: 'Software Mansion', - album: 'Audio API', - duration: 10, - }); + // set AVAudioSession example options (iOS only) + AudioManager.setAudioSessionOptions({ + iosCategory: 'playback', + iosMode: 'default', + iosOptions: ['allowBluetoothHFP', 'allowAirPlay'], + }); + + // set info for track to be visible while device is locked + AudioManager.setLockScreenInfo({ + title: 'Audio file', + artist: 'Software Mansion', + album: 'Audio API', + duration: 10, + }); useEffect(() => { - // enabling emission of events AudioManager.enableRemoteCommand('remotePlay', true); AudioManager.enableRemoteCommand('remotePause', true); @@ -77,9 +76,9 @@ function App() { ### `setLockScreenInfo` -| Parameters | Type | Description | -| :---: | :---: | :-----: | -| `info` | [`LockScreenInfo`](/system/audio-manager#lockscreeninfo) | Information to be displayed on the lock screen | +| Parameters | Type | Description | +| :--------: | :------------------------------------------------------: | :--------------------------------------------: | +| `info` | [`LockScreenInfo`](/system/audio-manager#lockscreeninfo) | Information to be displayed on the lock screen | #### Returns `undefined` @@ -91,17 +90,17 @@ Resets all of the lock screen data. ### `setAudioSessionOptions` -| Parameters | Type | Description | -| :---: | :---: | :---- | -| options | [`SessionOptions`](/system/audio-manager#sessionoptions) | Options to be set for AVAudioSession | +| Parameters | Type | Description | +| :--------: | :------------------------------------------------------: | :----------------------------------- | +| options | [`SessionOptions`](/system/audio-manager#sessionoptions) | Options to be set for AVAudioSession | #### Returns `undefined` ### `setAudioSessionActivity` -| Parameters | Type | Description | -| :---: | :---: | :---- | -| enabled | `boolean` | It is used to set/unset AVAudioSession activity | +| Parameters | Type | Description | +| :--------: | :-------: | :---------------------------------------------- | +| enabled | `boolean` | It is used to set/unset AVAudioSession activity | #### Returns promise of `boolean` type, which is resolved to `true` if invokation ended with success, `false` otherwise. @@ -111,17 +110,17 @@ Resets all of the lock screen data. ### `observeAudioInterruptions` -| Parameters | Type | Description | -| :---: | :---: | :---- | -| `enabled` | `boolean` | It is used to enable/disable observing audio interruptions | +| Parameters | Type | Description | +| :--------: | :-------: | :--------------------------------------------------------- | +| `enabled` | `boolean` | It is used to enable/disable observing audio interruptions | #### Returns `undefined` ### `observeVolumeChanges` -| Parameters | Type | Description | -| :---: | :---: | :---- | -| `enabled` | `boolean` | It is used to enable/disable observing volume changes | +| Parameters | Type | Description | +| :--------: | :-------: | :---------------------------------------------------- | +| `enabled` | `boolean` | It is used to enable/disable observing volume changes | #### Returns `undefined` @@ -129,10 +128,10 @@ Resets all of the lock screen data. Enables emition of some system events. -| Parameters | Type | Description | -| :---: | :---: | :---- | -| `name` | [`RemoteCommandEventName`](/system/audio-manager#systemeventname--remotecommandeventname) | Name of an event | -| `enabled` | `boolean` | Indicates the start or the end of event emission | +| Parameters | Type | Description | +| :--------: | :---------------------------------------------------------------------------------------: | :----------------------------------------------- | +| `name` | [`RemoteCommandEventName`](/system/audio-manager#systemeventname--remotecommandeventname) | Name of an event | +| `enabled` | `boolean` | Indicates the start or the end of event emission | #### Returns `undefined` @@ -143,14 +142,13 @@ with proper parameters. ::: - ### `addSystemEventListener` Adds callback to be invoked upon hearing an event. -| Parameters | Type | Description | -| :---: | :---: | :---- | -| `name` | [`SystemEventName`](/system/audio-manager#systemeventname--remotecommandeventname) | Name of an event listener | +| Parameters | Type | Description | +| :--------: | :------------------------------------------------------------------------------------: | :-------------------------------------------------- | +| `name` | [`SystemEventName`](/system/audio-manager#systemeventname--remotecommandeventname) | Name of an event listener | | `callback` | [`SystemEventCallback`](/system/audio-manager#systemeventname--remotecommandeventname) | Callback that will be invoked upon hearing an event | #### Returns [`AudioEventSubscription`](/system/audio-manager#audioeventsubscription) if `enabled` is set to true, `undefined` otherwise @@ -189,17 +187,18 @@ interface BaseLockScreenInfo { type MediaState = 'state_playing' | 'state_paused'; interface LockScreenInfo extends BaseLockScreenInfo { - title?: string; //title of the track - artwork?: string; //uri to the artwork - artist?: string; //name of the artist - album?: string; //name of the album - duration?: number; //duration in seconds - description?: string; // android only, description of the track - state?: MediaState; - speed?: number; //playback rate - elapsedTime?: number; //elapsed time of an audio in seconds +title?: string; //title of the track +artwork?: string; //uri to the artwork +artist?: string; //name of the artist +album?: string; //name of the album +duration?: number; //duration in seconds +description?: string; // android only, description of the track +state?: MediaState; +speed?: number; //playback rate +elapsedTime?: number; //elapsed time of an audio in seconds } -``` + +```` ### `SessionOptions` @@ -230,7 +229,8 @@ type IOSOption = | 'duckOthers' | 'allowAirPlay' | 'mixWithOthers' - | 'allowBluetooth' + | 'allowBluetoothHFP' + | 'bluetoothHighQualityRecording' | 'defaultToSpeaker' | 'allowBluetoothA2DP' | 'overrideMutedMicrophoneInterruption' @@ -241,9 +241,9 @@ interface SessionOptions { iosOptions?: IOSOption[]; iosCategory?: IOSCategory; } -``` - +```` + ### `SystemEventName` | `RemoteCommandEventName` @@ -253,55 +253,56 @@ interface SessionOptions { interface EventEmptyType {} interface EventTypeWithValue { - value: number; +value: number; } interface OnInterruptionEventType { - type: 'ended' | 'began'; //if interruption event has started or ended - shouldResume: boolean; //if we should resume playing after interruption +type: 'ended' | 'began'; //if interruption event has started or ended +shouldResume: boolean; //if we should resume playing after interruption } interface OnRouteChangeEventType { - reason: - | 'Unknown' - | 'Override' - | 'CategoryChange' - | 'WakeFromSleep' - | 'NewDeviceAvailable' - | 'OldDeviceUnavailable' - | 'ConfigurationChange' - | 'NoSuitableRouteForCategory'; +reason: +| 'Unknown' +| 'Override' +| 'CategoryChange' +| 'WakeFromSleep' +| 'NewDeviceAvailable' +| 'OldDeviceUnavailable' +| 'ConfigurationChange' +| 'NoSuitableRouteForCategory'; } // visit https://developer.apple.com/documentation/mediaplayer/mpremotecommandcenter?language=objc // for further info interface RemoteCommandEvents { - remotePlay: EventEmptyType; - remotePause: EventEmptyType; - remoteStop: EventEmptyType; - remoteTogglePlayPause: EventEmptyType; // iOS only - remoteChangePlaybackRate: EventTypeWithValue; - remoteNextTrack: EventEmptyType; - remotePreviousTrack: EventEmptyType; - remoteSkipForward: EventTypeWithValue; - remoteSkipBackward: EventTypeWithValue; // iOS only - remoteSeekForward: EventEmptyType; // iOS only - remoteSeekBackward: EventEmptyType; - remoteChangePlaybackPosition: EventTypeWithValue; +remotePlay: EventEmptyType; +remotePause: EventEmptyType; +remoteStop: EventEmptyType; +remoteTogglePlayPause: EventEmptyType; // iOS only +remoteChangePlaybackRate: EventTypeWithValue; +remoteNextTrack: EventEmptyType; +remotePreviousTrack: EventEmptyType; +remoteSkipForward: EventTypeWithValue; +remoteSkipBackward: EventTypeWithValue; // iOS only +remoteSeekForward: EventEmptyType; // iOS only +remoteSeekBackward: EventEmptyType; +remoteChangePlaybackPosition: EventTypeWithValue; } type SystemEvents = RemoteCommandEvents & { - volumeChange: EventTypeWithValue; //triggered when volume level is changed - interruption: OnInterruptionEventType; //triggered when f.e. some app wants to play music when we are playing - routeChange: OnRouteChangeEventType; //change of output f.e. from speaker to headphones, events are always emitted! +volumeChange: EventTypeWithValue; //triggered when volume level is changed +interruption: OnInterruptionEventType; //triggered when f.e. some app wants to play music when we are playing +routeChange: OnRouteChangeEventType; //change of output f.e. from speaker to headphones, events are always emitted! }; type RemoteCommandEventName = keyof RemoteCommandEvents; type SystemEventName = keyof SystemEvents; type SystemEventCallback = ( - event: SystemEvents[Name] +event: SystemEvents[Name] ) => void; -``` + +```` @@ -333,16 +334,16 @@ class AudioEventSubscription { ); } } -``` +```` + ### `PermissionStatus`
-Type definitions -```typescript -type PermissionStatus = 'Undetermined' | 'Denied' | 'Granted'; -``` + Type definitions + ```typescript type PermissionStatus = 'Undetermined' | 'Denied' | 'Granted'; + ```
### `AudioDevicesInfo` diff --git a/packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.mm b/packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.mm index 072e56f2a..b9f5d0069 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.mm @@ -87,8 +87,12 @@ - (void)setAudioSessionOptions:(NSString *)category sessionOptions |= AVAudioSessionCategoryOptionMixWithOthers; } - if ([option isEqualToString:@"allowBluetooth"]) { - sessionOptions |= AVAudioSessionCategoryOptionAllowBluetooth; + if ([option isEqualToString:@"allowBluetoothHFP"]) { + sessionOptions |= AVAudioSessionCategoryOptionAllowBluetoothHFP; + } + + if ([option isEqualToString:@"bluetoothHighQualityRecording"]) { + sessionOptions |= AVAudioSessionCategoryOptionBluetoothHighQualityRecording; } if ([option isEqualToString:@"defaultToSpeaker"]) { diff --git a/packages/react-native-audio-api/src/system/types.ts b/packages/react-native-audio-api/src/system/types.ts index 8625eab25..66e891fdf 100644 --- a/packages/react-native-audio-api/src/system/types.ts +++ b/packages/react-native-audio-api/src/system/types.ts @@ -21,7 +21,8 @@ export type IOSOption = | 'duckOthers' | 'allowAirPlay' | 'mixWithOthers' - | 'allowBluetooth' + | 'allowBluetoothHFP' + | 'bluetoothHighQualityRecording' | 'defaultToSpeaker' | 'allowBluetoothA2DP' | 'overrideMutedMicrophoneInterruption' From 30e1ad49ec19ca27b75cf97f73c022ff25d74870 Mon Sep 17 00:00:00 2001 From: Alicja Wojciechowska Date: Wed, 23 Jul 2025 15:27:34 +0200 Subject: [PATCH 2/2] style: fix formatting --- .../audiodocs/docs/system/audio-manager.mdx | 168 +++++++++--------- .../ios/system/AudioSessionManager.mm | 2 +- 2 files changed, 85 insertions(+), 85 deletions(-) diff --git a/packages/audiodocs/docs/system/audio-manager.mdx b/packages/audiodocs/docs/system/audio-manager.mdx index 544bde6d0..b90ec7858 100644 --- a/packages/audiodocs/docs/system/audio-manager.mdx +++ b/packages/audiodocs/docs/system/audio-manager.mdx @@ -16,22 +16,23 @@ import { AudioManager } from 'react-native-audio-api'; import { useEffect } from 'react'; function App() { - // set AVAudioSession example options (iOS only) - AudioManager.setAudioSessionOptions({ - iosCategory: 'playback', - iosMode: 'default', - iosOptions: ['allowBluetoothHFP', 'allowAirPlay'], - }); - - // set info for track to be visible while device is locked - AudioManager.setLockScreenInfo({ - title: 'Audio file', - artist: 'Software Mansion', - album: 'Audio API', - duration: 10, - }); + // set AVAudioSession example options (iOS only) + AudioManager.setAudioSessionOptions({ + iosCategory: 'playback', + iosMode: 'default', + iosOptions: ['allowBluetoothHFP', 'allowAirPlay'], + }) + + // set info for track to be visible while device is locked + AudioManager.setLockScreenInfo({ + title: 'Audio file', + artist: 'Software Mansion', + album: 'Audio API', + duration: 10, + }); useEffect(() => { + // enabling emission of events AudioManager.enableRemoteCommand('remotePlay', true); AudioManager.enableRemoteCommand('remotePause', true); @@ -76,9 +77,9 @@ function App() { ### `setLockScreenInfo` -| Parameters | Type | Description | -| :--------: | :------------------------------------------------------: | :--------------------------------------------: | -| `info` | [`LockScreenInfo`](/system/audio-manager#lockscreeninfo) | Information to be displayed on the lock screen | +| Parameters | Type | Description | +| :---: | :---: | :-----: | +| `info` | [`LockScreenInfo`](/system/audio-manager#lockscreeninfo) | Information to be displayed on the lock screen | #### Returns `undefined` @@ -90,17 +91,17 @@ Resets all of the lock screen data. ### `setAudioSessionOptions` -| Parameters | Type | Description | -| :--------: | :------------------------------------------------------: | :----------------------------------- | -| options | [`SessionOptions`](/system/audio-manager#sessionoptions) | Options to be set for AVAudioSession | +| Parameters | Type | Description | +| :---: | :---: | :---- | +| options | [`SessionOptions`](/system/audio-manager#sessionoptions) | Options to be set for AVAudioSession | #### Returns `undefined` ### `setAudioSessionActivity` -| Parameters | Type | Description | -| :--------: | :-------: | :---------------------------------------------- | -| enabled | `boolean` | It is used to set/unset AVAudioSession activity | +| Parameters | Type | Description | +| :---: | :---: | :---- | +| enabled | `boolean` | It is used to set/unset AVAudioSession activity | #### Returns promise of `boolean` type, which is resolved to `true` if invokation ended with success, `false` otherwise. @@ -110,17 +111,17 @@ Resets all of the lock screen data. ### `observeAudioInterruptions` -| Parameters | Type | Description | -| :--------: | :-------: | :--------------------------------------------------------- | -| `enabled` | `boolean` | It is used to enable/disable observing audio interruptions | +| Parameters | Type | Description | +| :---: | :---: | :---- | +| `enabled` | `boolean` | It is used to enable/disable observing audio interruptions | #### Returns `undefined` ### `observeVolumeChanges` -| Parameters | Type | Description | -| :--------: | :-------: | :---------------------------------------------------- | -| `enabled` | `boolean` | It is used to enable/disable observing volume changes | +| Parameters | Type | Description | +| :---: | :---: | :---- | +| `enabled` | `boolean` | It is used to enable/disable observing volume changes | #### Returns `undefined` @@ -128,10 +129,10 @@ Resets all of the lock screen data. Enables emition of some system events. -| Parameters | Type | Description | -| :--------: | :---------------------------------------------------------------------------------------: | :----------------------------------------------- | -| `name` | [`RemoteCommandEventName`](/system/audio-manager#systemeventname--remotecommandeventname) | Name of an event | -| `enabled` | `boolean` | Indicates the start or the end of event emission | +| Parameters | Type | Description | +| :---: | :---: | :---- | +| `name` | [`RemoteCommandEventName`](/system/audio-manager#systemeventname--remotecommandeventname) | Name of an event | +| `enabled` | `boolean` | Indicates the start or the end of event emission | #### Returns `undefined` @@ -142,13 +143,14 @@ with proper parameters. ::: + ### `addSystemEventListener` Adds callback to be invoked upon hearing an event. -| Parameters | Type | Description | -| :--------: | :------------------------------------------------------------------------------------: | :-------------------------------------------------- | -| `name` | [`SystemEventName`](/system/audio-manager#systemeventname--remotecommandeventname) | Name of an event listener | +| Parameters | Type | Description | +| :---: | :---: | :---- | +| `name` | [`SystemEventName`](/system/audio-manager#systemeventname--remotecommandeventname) | Name of an event listener | | `callback` | [`SystemEventCallback`](/system/audio-manager#systemeventname--remotecommandeventname) | Callback that will be invoked upon hearing an event | #### Returns [`AudioEventSubscription`](/system/audio-manager#audioeventsubscription) if `enabled` is set to true, `undefined` otherwise @@ -187,18 +189,17 @@ interface BaseLockScreenInfo { type MediaState = 'state_playing' | 'state_paused'; interface LockScreenInfo extends BaseLockScreenInfo { -title?: string; //title of the track -artwork?: string; //uri to the artwork -artist?: string; //name of the artist -album?: string; //name of the album -duration?: number; //duration in seconds -description?: string; // android only, description of the track -state?: MediaState; -speed?: number; //playback rate -elapsedTime?: number; //elapsed time of an audio in seconds + title?: string; //title of the track + artwork?: string; //uri to the artwork + artist?: string; //name of the artist + album?: string; //name of the album + duration?: number; //duration in seconds + description?: string; // android only, description of the track + state?: MediaState; + speed?: number; //playback rate + elapsedTime?: number; //elapsed time of an audio in seconds } - -```` +``` ### `SessionOptions` @@ -241,10 +242,10 @@ interface SessionOptions { iosOptions?: IOSOption[]; iosCategory?: IOSCategory; } -```` - +``` + ### `SystemEventName` | `RemoteCommandEventName`
@@ -253,56 +254,55 @@ interface SessionOptions { interface EventEmptyType {} interface EventTypeWithValue { -value: number; + value: number; } interface OnInterruptionEventType { -type: 'ended' | 'began'; //if interruption event has started or ended -shouldResume: boolean; //if we should resume playing after interruption + type: 'ended' | 'began'; //if interruption event has started or ended + shouldResume: boolean; //if we should resume playing after interruption } interface OnRouteChangeEventType { -reason: -| 'Unknown' -| 'Override' -| 'CategoryChange' -| 'WakeFromSleep' -| 'NewDeviceAvailable' -| 'OldDeviceUnavailable' -| 'ConfigurationChange' -| 'NoSuitableRouteForCategory'; + reason: + | 'Unknown' + | 'Override' + | 'CategoryChange' + | 'WakeFromSleep' + | 'NewDeviceAvailable' + | 'OldDeviceUnavailable' + | 'ConfigurationChange' + | 'NoSuitableRouteForCategory'; } // visit https://developer.apple.com/documentation/mediaplayer/mpremotecommandcenter?language=objc // for further info interface RemoteCommandEvents { -remotePlay: EventEmptyType; -remotePause: EventEmptyType; -remoteStop: EventEmptyType; -remoteTogglePlayPause: EventEmptyType; // iOS only -remoteChangePlaybackRate: EventTypeWithValue; -remoteNextTrack: EventEmptyType; -remotePreviousTrack: EventEmptyType; -remoteSkipForward: EventTypeWithValue; -remoteSkipBackward: EventTypeWithValue; // iOS only -remoteSeekForward: EventEmptyType; // iOS only -remoteSeekBackward: EventEmptyType; -remoteChangePlaybackPosition: EventTypeWithValue; + remotePlay: EventEmptyType; + remotePause: EventEmptyType; + remoteStop: EventEmptyType; + remoteTogglePlayPause: EventEmptyType; // iOS only + remoteChangePlaybackRate: EventTypeWithValue; + remoteNextTrack: EventEmptyType; + remotePreviousTrack: EventEmptyType; + remoteSkipForward: EventTypeWithValue; + remoteSkipBackward: EventTypeWithValue; // iOS only + remoteSeekForward: EventEmptyType; // iOS only + remoteSeekBackward: EventEmptyType; + remoteChangePlaybackPosition: EventTypeWithValue; } type SystemEvents = RemoteCommandEvents & { -volumeChange: EventTypeWithValue; //triggered when volume level is changed -interruption: OnInterruptionEventType; //triggered when f.e. some app wants to play music when we are playing -routeChange: OnRouteChangeEventType; //change of output f.e. from speaker to headphones, events are always emitted! + volumeChange: EventTypeWithValue; //triggered when volume level is changed + interruption: OnInterruptionEventType; //triggered when f.e. some app wants to play music when we are playing + routeChange: OnRouteChangeEventType; //change of output f.e. from speaker to headphones, events are always emitted! }; type RemoteCommandEventName = keyof RemoteCommandEvents; type SystemEventName = keyof SystemEvents; type SystemEventCallback = ( -event: SystemEvents[Name] + event: SystemEvents[Name] ) => void; - -```` +```
@@ -334,16 +334,16 @@ class AudioEventSubscription { ); } } -```` - +``` ### `PermissionStatus`
- Type definitions - ```typescript type PermissionStatus = 'Undetermined' | 'Denied' | 'Granted'; - ``` +Type definitions +```typescript +type PermissionStatus = 'Undetermined' | 'Denied' | 'Granted'; +```
### `AudioDevicesInfo` diff --git a/packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.mm b/packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.mm index b9f5d0069..b67acc5aa 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.mm @@ -91,7 +91,7 @@ - (void)setAudioSessionOptions:(NSString *)category sessionOptions |= AVAudioSessionCategoryOptionAllowBluetoothHFP; } - if ([option isEqualToString:@"bluetoothHighQualityRecording"]) { + if (@available(iOS 26.0, *) && [option isEqualToString:@"bluetoothHighQualityRecording"]) { sessionOptions |= AVAudioSessionCategoryOptionBluetoothHighQualityRecording; }