Skip to content

Commit 7eff1a9

Browse files
authored
feat: allow specifying the Audio Session Category for iOS and Usage for Android (#28)
* feat: allow ios to specify the audio session category when setting up the stream * feat: allow specifying use in android * chore: update docs
1 parent 191a9b0 commit 7eff1a9

17 files changed

+201
-41
lines changed

README.md

+17-4
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ For iOS, run `pod install` in the `ios` directory.
2424

2525
## Usage
2626

27-
1. Setup an Audio Stream using the singleton `AudioManager`'s `shared` static property and calling its `setupAudioStream(sampleRate: number, channelCount: number): void`
27+
1. Setup an Audio Stream using the singleton `AudioManager`'s `shared` static property and calling its `setupAudioStream`.
28+
`setupAudioStream` takes in an optional options argument where you can pass in the `sampleRate` and `channelCount` of your audio files, or do not specify them and it will default to `44100` and `2` respectively.
2829

2930
[How do I know what `sampleRate` and `channelCount` I need to pass?](#sample-rates-and-channel-counts)
3031

3132
```ts
3233
import { AudioManager } from 'react-native-audio-playback';
3334

34-
AudioManager.shared.setupAudioStream(44100, 2);
35+
AudioManager.shared.setupAudioStream({ sampleRate: 44100, channelCount: 2 });
3536
```
3637

3738
2. Load in your audio sounds as such:
@@ -96,8 +97,20 @@ AudioManager.shared.<some-method>
9697

9798
#### Methods:
9899

99-
- `setupAudioStream(sampleRate: number = 44100, channelCount: number = 2): void`: sets up the Audio Stream to allow it later be opened.
100-
Note: You shouldn't setup multiple streams simultaneously because you only need one stream. Trying to setup another one will simply fails because there is already one setup.
100+
- `setupAudioStream(options?: {
101+
sampleRate?: number;
102+
channelCount?: number;
103+
ios?: {
104+
audioSessionCategory?: IosAudioSessionCategory;
105+
};
106+
android?: {
107+
usage?: AndroidAudioStreamUsage;
108+
};
109+
}): void`: sets up the Audio Stream to allow it later be opened.
110+
Notes:
111+
1. You shouldn't setup multiple streams simultaneously because you only need one stream. Trying to setup another one will simply fails because there is already one setup.
112+
2. You can change the ios audio session category using the `audioSessionCategory` option in the `ios` object. Check [apple docs](https://developer.apple.com/documentation/avfaudio/avaudiosession/category-swift.struct#Getting-Standard-Categories) for more info on the different audio session categories.
113+
3. You can change the android usage using the `usage` option in the `android` object. Check [here](https://github.com/google/oboe/blob/11afdfcd3e1c46dc2ea4b86c83519ebc2d44a1d4/include/oboe/Definitions.h#L316-L377) for the list of options.
101114
- `openAudioStream(): void`: Opens the audio stream to allow audio to be played
102115
Note: You should have called `setupAudioStream` before calling this method. You can't open a stream that hasn't been setup
103116
- `pauseAudioStream(): void`: Pauses the audio stream (An example of when to use this is when user puts app to background)

android/src/main/cpp/AudioEngine.cpp

+23-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88

99
#include "audio/AAssetDataSource.h"
1010

11-
SetupAudioStreamResult AudioEngine::setupAudioStream(double sampleRate, double channelCount) {
11+
SetupAudioStreamResult AudioEngine::setupAudioStream(
12+
double sampleRate,
13+
double channelCount,
14+
int usage) {
1215
if(mAudioStream) {
1316
return { .error = "Setting up an audio stream while one is already available"};
1417
}
@@ -18,6 +21,7 @@ SetupAudioStreamResult AudioEngine::setupAudioStream(double sampleRate, double c
1821

1922
oboe::AudioStreamBuilder builder {};
2023

24+
builder.setUsage(getUsageFromInt(usage));
2125
builder.setFormat(oboe::AudioFormat::Float);
2226
builder.setFormatConversionAllowed(true);
2327
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
@@ -185,3 +189,21 @@ void AudioEngine::unloadSounds(const std::optional<std::vector<std::string>> &id
185189
mPlayers.clear();
186190
}
187191
}
192+
193+
oboe::Usage AudioEngine::getUsageFromInt(int usage) {
194+
switch(usage) {
195+
case 0: return oboe::Usage::Media;
196+
case 1: return oboe::Usage::VoiceCommunication;
197+
case 2: return oboe::Usage::VoiceCommunicationSignalling;
198+
case 3: return oboe::Usage::Alarm;
199+
case 4: return oboe::Usage::Notification;
200+
case 5: return oboe::Usage::NotificationRingtone;
201+
case 6: return oboe::Usage::NotificationEvent;
202+
case 7: return oboe::Usage::AssistanceAccessibility;
203+
case 8: return oboe::Usage::AssistanceNavigationGuidance;
204+
case 9: return oboe::Usage::AssistanceSonification;
205+
case 10: return oboe::Usage::Game;
206+
case 11: return oboe::Usage::Assistant;
207+
default: return oboe::Usage::Media;
208+
}
209+
}

android/src/main/cpp/AudioEngine.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
class AudioEngine : public oboe::AudioStreamDataCallback{
1818
public:
19-
SetupAudioStreamResult setupAudioStream(double sampleRate, double channelCount);
19+
SetupAudioStreamResult setupAudioStream(double sampleRate, double channelCount, int usage);
2020
OpenAudioStreamResult openAudioStream();
2121
PauseAudioStreamResult pauseAudioStream();
2222
CloseAudioStreamResult closeAudioStream();
@@ -34,6 +34,8 @@ class AudioEngine : public oboe::AudioStreamDataCallback{
3434
std::map<std::string, std::unique_ptr<Player>> mPlayers;
3535
int32_t mDesiredSampleRate{};
3636
int mDesiredChannelCount{};
37+
38+
oboe::Usage getUsageFromInt(int usage);
3739
};
3840

3941
#endif //AUDIOPLAYBACK_AUDIOENGINE_H

android/src/main/cpp/native-lib.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,13 @@ std::vector<std::string> jniStringArrayToStringVector(JNIEnv* env, jobjectArray
8383

8484
extern "C" {
8585
JNIEXPORT jobject JNICALL
86-
Java_com_audioplayback_AudioPlaybackModule_setupAudioStreamNative(JNIEnv *env, jobject thiz, jdouble sample_rate, jdouble channel_count) {
87-
auto result = audioEngine->setupAudioStream(sample_rate, channel_count);
86+
Java_com_audioplayback_AudioPlaybackModule_setupAudioStreamNative(
87+
JNIEnv *env,
88+
jobject thiz,
89+
jdouble sample_rate,
90+
jdouble channel_count,
91+
jint usage) {
92+
auto result = audioEngine->setupAudioStream(sample_rate, channel_count, usage);
8893

8994
jclass structClass = env->FindClass("com/audioplayback/models/SetupAudioStreamResult");
9095
jmethodID constructor = env->GetMethodID(structClass, "<init>", "(Ljava/lang/String;)V");

android/src/main/java/com/audioplayback/AudioPlaybackModule.kt

+8-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.audioplayback.models.OpenAudioStreamResult
1313
import com.audioplayback.models.PauseAudioStreamResult
1414
import com.audioplayback.models.SetupAudioStreamResult
1515
import com.facebook.react.bridge.Arguments
16+
import com.facebook.react.bridge.ReadableMap
1617
import com.facebook.react.bridge.WritableMap
1718
import kotlinx.coroutines.CoroutineScope
1819
import kotlinx.coroutines.Dispatchers
@@ -29,8 +30,12 @@ class AudioPlaybackModule internal constructor(context: ReactApplicationContext)
2930
}
3031

3132
@ReactMethod(isBlockingSynchronousMethod = true)
32-
override fun setupAudioStream(sampleRate: Double, channelCount: Double): WritableMap {
33-
val result = setupAudioStreamNative(sampleRate, channelCount)
33+
override fun setupAudioStream(options: ReadableMap): WritableMap {
34+
val sampleRate = options.getDouble("sampleRate")
35+
val channelCount = options.getDouble("channelCount")
36+
val usage = options.getMap("android")!!.getInt("usage")
37+
38+
val result = setupAudioStreamNative(sampleRate, channelCount, usage)
3439
val map = Arguments.createMap()
3540
result.error?.let { map.putString("error", it) } ?: map.putNull("error")
3641
return map
@@ -185,7 +190,7 @@ class AudioPlaybackModule internal constructor(context: ReactApplicationContext)
185190
unloadSoundsNative(null)
186191
}
187192

188-
private external fun setupAudioStreamNative(sampleRate: Double, channelCount: Double): SetupAudioStreamResult
193+
private external fun setupAudioStreamNative(sampleRate: Double, channelCount: Double, usage: Int): SetupAudioStreamResult
189194
private external fun openAudioStreamNative(): OpenAudioStreamResult
190195
private external fun pauseAudioStreamNative(): PauseAudioStreamResult
191196
private external fun closeAudioStreamNative(): CloseAudioStreamResult

example/Gemfile.lock

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ GEM
6666
netrc (~> 0.11)
6767
cocoapods-try (1.2.0)
6868
colored2 (3.1.2)
69-
concurrent-ruby (1.3.4)
69+
concurrent-ruby (1.3.3)
7070
connection_pool (2.4.1)
7171
drb (2.2.1)
7272
escape (0.0.4)
@@ -110,6 +110,7 @@ PLATFORMS
110110
DEPENDENCIES
111111
activesupport (>= 6.1.7.5, != 7.1.0)
112112
cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
113+
concurrent-ruby (< 1.3.4)
113114
xcodeproj (< 1.26.0)
114115

115116
RUBY VERSION

example/android/gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
3232
# your application. You should enable this flag either if you want
3333
# to write custom TurboModules/Fabric components OR use libraries that
3434
# are providing them.
35-
newArchEnabled=false
35+
newArchEnabled=true
3636

3737
# Use this property to enable or disable the Hermes JS engine.
3838
# If set to false, you will be using JSC instead.

example/ios/Podfile.lock

+3-3
Original file line numberDiff line numberDiff line change
@@ -1210,7 +1210,7 @@ PODS:
12101210
- React-jsiexecutor
12111211
- React-RCTFBReactNativeSpec
12121212
- ReactCommon/turbomodule/core
1213-
- react-native-audio-playback (1.0.5):
1213+
- react-native-audio-playback (1.0.6):
12141214
- DoubleConversion
12151215
- glog
12161216
- hermes-engine
@@ -1818,7 +1818,7 @@ SPEC CHECKSUMS:
18181818
React-logger: e7eeebaed32b88dcc29b10901aa8c5822dc397c4
18191819
React-Mapbuffer: 73dd1210c4ecf0dfb4e2d4e06f2a13f824a801a9
18201820
React-microtasksnativemodule: d03753688e2abf135edcd4160ab3ce7526da8b0d
1821-
react-native-audio-playback: 6b828a31071736a3beee00128fd445c6fb3016ef
1821+
react-native-audio-playback: e3b40982c3b6dff7dc0ed0680d7481d43a8f54dc
18221822
react-native-slider: 54e7f67e9e4c92c0edac77bbf58abfaf1f60055f
18231823
React-nativeconfig: cb207ebba7cafce30657c7ad9f1587a8f32e4564
18241824
React-NativeModulesApple: 8411d548b1ad9d2b3e597beb9348e715c8020e0c
@@ -1854,4 +1854,4 @@ SPEC CHECKSUMS:
18541854

18551855
PODFILE CHECKSUM: 0de3c0d5ec96ef8679c10cf5c8e600381b2b0ac8
18561856

1857-
COCOAPODS: 1.14.3
1857+
COCOAPODS: 1.15.2

example/src/components/StreamControl.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { AudioManager } from 'react-native-audio-playback';
1+
import {
2+
AndroidAudioStreamUsage,
3+
AudioManager,
4+
} from 'react-native-audio-playback';
25

36
import { Button } from './Button';
47
import { Section } from './Section';
@@ -9,7 +12,11 @@ interface StreamControlProps {
912

1013
export function StreamControl({ onLoadSounds }: StreamControlProps) {
1114
function onSetupStream() {
12-
AudioManager.shared.setupAudioStream();
15+
AudioManager.shared.setupAudioStream({
16+
android: {
17+
usage: AndroidAudioStreamUsage.Alarm,
18+
},
19+
});
1320
}
1421

1522
function onOpenStream() {

ios/AudioEngine.swift

+10-5
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,6 @@ class AudioEngine {
5959
private var interruptionState: InterruptionState?
6060

6161
init() {
62-
// configure Audio Session
63-
let audioSession = AVAudioSession.sharedInstance()
64-
try? audioSession.setCategory(.playback)
65-
try? audioSession.setActive(true)
6662

6763
NotificationCenter.default.addObserver(
6864
self,
@@ -101,10 +97,19 @@ class AudioEngine {
10197
}
10298
}
10399

104-
public func setupAudioStream(sampleRate: Double, channelCount: Int) throws {
100+
public func setupAudioStream(
101+
sampleRate: Double,
102+
channelCount: Int,
103+
audioSessionCategory: AVAudioSession.Category
104+
) throws {
105105
guard audioStreamState != .initialized else {
106106
throw AudioEngineError.audioStreamAlreadyInitialized
107107
}
108+
109+
// configure Audio Session
110+
let audioSession = AVAudioSession.sharedInstance()
111+
try? audioSession.setCategory(audioSessionCategory)
112+
try? audioSession.setActive(true)
108113

109114
self.desiredSampleRate = sampleRate
110115
self.desiredChannelCount = channelCount

ios/AudioPlayback.mm

+7-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,13 @@ - (instancetype) init {
2020

2121
RCT_EXPORT_MODULE()
2222

23-
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, setupAudioStream:(double)sampleRate channelCount:(double)channelCount) {
24-
NSString *error = [moduleImpl setupAudioStreamWithSampleRate:sampleRate channelCount:channelCount];
23+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, setupAudioStream:(JS::NativeAudioPlayback::SpecSetupAudioStreamOptions &)options) {
24+
double sampleRate = options.sampleRate();
25+
double channelCount = options.channelCount();
26+
double audioSessionCategory = options.ios().audioSessionCategory();
27+
28+
29+
NSString *error = [moduleImpl setupAudioStreamWithSampleRate:sampleRate channelCount:channelCount audioSessionCategory: audioSessionCategory];
2530

2631
return @{@"error":error?: [NSNull null]};
2732
}

ios/AudioPlaybackImpl.swift

+26-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,21 @@ import AudioToolbox
66
private static let unknownError: String = "An unknown error occurred while loading the audio file. Please create an issue with a reproducible"
77
let audioEngine = AudioEngine()
88

9-
@objc public func setupAudioStream(sampleRate: Double, channelCount: Double) -> String? {
9+
@objc public func setupAudioStream(
10+
sampleRate: Double,
11+
channelCount: Double,
12+
audioSessionCategory: Double
13+
) -> String? {
14+
guard let audioSessionCategoryEnum = getAudoSessionCategoryEnumMemeberFromRawValue(audioSessionCategory) else {
15+
return "Invalid audio session category"
16+
}
17+
1018
do {
11-
try audioEngine.setupAudioStream(sampleRate: sampleRate, channelCount: Int(channelCount))
19+
try audioEngine.setupAudioStream(
20+
sampleRate: sampleRate,
21+
channelCount: Int(channelCount),
22+
audioSessionCategory: audioSessionCategoryEnum
23+
)
1224
return nil
1325
} catch let error as AudioEngineError {
1426
return error.localizedDescription
@@ -151,5 +163,17 @@ import AudioToolbox
151163
}
152164
return result
153165
}
166+
167+
private func getAudoSessionCategoryEnumMemeberFromRawValue(_ rawValue: Double) -> AVAudioSession.Category? {
168+
switch Int(rawValue) {
169+
case 0: return .ambient
170+
case 1: return .multiRoute
171+
case 2: return .playAndRecord
172+
case 3: return .playback
173+
case 4: return .record
174+
case 5: return .soloAmbient
175+
default: return nil
176+
}
177+
}
154178
}
155179

src/NativeAudioPlayback.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@ import type { TurboModule } from 'react-native';
22
import { TurboModuleRegistry } from 'react-native';
33

44
export interface Spec extends TurboModule {
5-
setupAudioStream: (
6-
sampleRate: number,
7-
channelCount: number
8-
) => { error: string | null };
5+
setupAudioStream: (options: {
6+
sampleRate: number;
7+
channelCount: number;
8+
ios: {
9+
audioSessionCategory: number;
10+
};
11+
android: {
12+
usage: number;
13+
};
14+
}) => { error: string | null };
915
openAudioStream: () => { error: string | null };
1016
pauseAudioStream: () => { error: string | null };
1117
closeAudioStream: () => { error: string | null };

src/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { AudioManager, Player } from './models';
2+
export { IosAudioSessionCategory, AndroidAudioStreamUsage } from './types';

src/models/AudioManager.ts

+29-5
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,42 @@ import {
99
setSoundsVolume,
1010
setupAudioStream,
1111
} from '../module';
12+
13+
import { AndroidAudioStreamUsage, IosAudioSessionCategory } from '../types';
1214
import { Player } from './Player';
1315

1416
export class AudioManager {
1517
public static shared = new AudioManager();
1618

1719
private constructor() {}
1820

19-
public setupAudioStream(
20-
sampleRate: number = 44100,
21-
channelCount: number = 2
22-
) {
23-
setupAudioStream(sampleRate, channelCount);
21+
public setupAudioStream(options?: {
22+
sampleRate?: number;
23+
channelCount?: number;
24+
ios?: {
25+
audioSessionCategory?: IosAudioSessionCategory;
26+
};
27+
android?: {
28+
usage?: AndroidAudioStreamUsage;
29+
};
30+
}) {
31+
const sampleRate = options?.sampleRate ?? 44100;
32+
const channelCount = options?.channelCount ?? 2;
33+
const iosAudioSessionCategory =
34+
options?.ios?.audioSessionCategory ?? IosAudioSessionCategory.Playback;
35+
const androidUsage =
36+
options?.android?.usage ?? AndroidAudioStreamUsage.Media;
37+
38+
setupAudioStream({
39+
channelCount,
40+
sampleRate,
41+
ios: {
42+
audioSessionCategory: iosAudioSessionCategory,
43+
},
44+
android: {
45+
usage: androidUsage,
46+
},
47+
});
2448
}
2549

2650
public openAudioStream(): void {

0 commit comments

Comments
 (0)