Skip to content

Commit 97a3f6d

Browse files
authored
feat: expose stream state (#29)
* feat: expose the state of the audio stream on iOS * feat: expose the state of the audio stream on Android * feat: add docs
1 parent 7eff1a9 commit 97a3f6d

15 files changed

+137
-18
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ AudioManager.shared.<some-method>
121121
- `playSounds(args: ReadonlyArray<[Player, boolean]>): void` Plays/pauses multiple sounds
122122
- `loopSounds(args: ReadonlyArray<[Player, boolean]>): void` Loops/unloops multiple sounds
123123
- `seekSoundsTo(args: ReadonlyArray<[Player, number]>): void` Seeks multiple sounds
124-
- `public setSoundsVolume(args: ReadonlyArray<[Player, number]>): void` Sets the volume of multiple sounds, volume should be a number between 0 and 1.
124+
- `setSoundsVolume(args: ReadonlyArray<[Player, number]>): void` Sets the volume of multiple sounds, volume should be a number between 0 and 1.
125+
- `getStreamState(): StreamState` Returns the current state of the stream.
125126

126127
### Player
127128

android/src/main/cpp/AudioEngine.cpp

+24
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,27 @@ oboe::Usage AudioEngine::getUsageFromInt(int usage) {
207207
default: return oboe::Usage::Media;
208208
}
209209
}
210+
211+
StreamState AudioEngine::getStreamState() {
212+
if(!mAudioStream) {
213+
return StreamState::closed;
214+
}
215+
216+
oboe::StreamState streamState = {mAudioStream->getState()};
217+
switch (streamState) {
218+
case oboe::StreamState::Closing:
219+
case oboe::StreamState::Closed:
220+
case oboe::StreamState::Disconnected:
221+
case oboe::StreamState::Unknown:
222+
case oboe::StreamState::Uninitialized: return StreamState::closed;
223+
case oboe::StreamState::Open: return StreamState::initialized;
224+
case oboe::StreamState::Starting:
225+
case oboe::StreamState::Started: return StreamState::open;
226+
case oboe::StreamState::Flushing:
227+
case oboe::StreamState::Flushed:
228+
case oboe::StreamState::Stopping:
229+
case oboe::StreamState::Stopped:
230+
case oboe::StreamState::Pausing:
231+
case oboe::StreamState::Paused: return StreamState::paused;
232+
}
233+
}

android/src/main/cpp/AudioEngine.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
#include "AudioConstants.h"
1515
#include <android/asset_manager.h>
1616

17+
enum class StreamState {
18+
closed, initialized, open, paused
19+
};
20+
1721
class AudioEngine : public oboe::AudioStreamDataCallback{
1822
public:
1923
SetupAudioStreamResult setupAudioStream(double sampleRate, double channelCount, int usage);
@@ -26,6 +30,7 @@ class AudioEngine : public oboe::AudioStreamDataCallback{
2630
void setSoundsVolume(const std::vector<std::pair<std::string, double>>&);
2731
LoadSoundResult loadSound(int fd, int offset, int length);
2832
void unloadSounds(const std::optional<std::vector<std::string>>&);
33+
StreamState getStreamState();
2934

3035
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override;
3136

@@ -35,7 +40,7 @@ class AudioEngine : public oboe::AudioStreamDataCallback{
3540
int32_t mDesiredSampleRate{};
3641
int mDesiredChannelCount{};
3742

38-
oboe::Usage getUsageFromInt(int usage);
43+
static oboe::Usage getUsageFromInt(int usage);
3944
};
4045

4146
#endif //AUDIOPLAYBACK_AUDIOENGINE_H

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

+16-10
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ extern "C" {
8585
JNIEXPORT jobject JNICALL
8686
Java_com_audioplayback_AudioPlaybackModule_setupAudioStreamNative(
8787
JNIEnv *env,
88-
jobject thiz,
88+
jobject,
8989
jdouble sample_rate,
9090
jdouble channel_count,
9191
jint usage) {
@@ -105,7 +105,7 @@ Java_com_audioplayback_AudioPlaybackModule_setupAudioStreamNative(
105105
}
106106

107107
JNIEXPORT jobject JNICALL
108-
Java_com_audioplayback_AudioPlaybackModule_openAudioStreamNative(JNIEnv *env, jobject thiz) {
108+
Java_com_audioplayback_AudioPlaybackModule_openAudioStreamNative(JNIEnv *env, jobject) {
109109
auto result = audioEngine->openAudioStream();
110110

111111
jclass structClass = env->FindClass("com/audioplayback/models/OpenAudioStreamResult");
@@ -122,7 +122,7 @@ Java_com_audioplayback_AudioPlaybackModule_openAudioStreamNative(JNIEnv *env, jo
122122
}
123123

124124
JNIEXPORT jobject JNICALL
125-
Java_com_audioplayback_AudioPlaybackModule_pauseAudioStreamNative(JNIEnv *env, jobject thiz) {
125+
Java_com_audioplayback_AudioPlaybackModule_pauseAudioStreamNative(JNIEnv *env, jobject ) {
126126
auto result = audioEngine->pauseAudioStream();
127127

128128
jclass structClass = env->FindClass("com/audioplayback/models/PauseAudioStreamResult");
@@ -140,7 +140,7 @@ Java_com_audioplayback_AudioPlaybackModule_pauseAudioStreamNative(JNIEnv *env, j
140140

141141

142142
JNIEXPORT jobject JNICALL
143-
Java_com_audioplayback_AudioPlaybackModule_closeAudioStreamNative(JNIEnv *env, jobject thiz) {
143+
Java_com_audioplayback_AudioPlaybackModule_closeAudioStreamNative(JNIEnv *env, jobject ) {
144144
auto result = audioEngine->closeAudioStream();
145145

146146
jclass structClass = env->FindClass("com/audioplayback/models/CloseAudioStreamResult");
@@ -157,7 +157,7 @@ Java_com_audioplayback_AudioPlaybackModule_closeAudioStreamNative(JNIEnv *env, j
157157
}
158158

159159
JNIEXPORT void JNICALL
160-
Java_com_audioplayback_AudioPlaybackModule_unloadSoundsNative(JNIEnv *env, jobject thiz,
160+
Java_com_audioplayback_AudioPlaybackModule_unloadSoundsNative(JNIEnv *env, jobject ,
161161
jobjectArray ids) {
162162
if(ids == nullptr) {
163163
audioEngine->unloadSounds(std::nullopt);
@@ -168,7 +168,7 @@ Java_com_audioplayback_AudioPlaybackModule_unloadSoundsNative(JNIEnv *env, jobje
168168

169169

170170
JNIEXPORT jobject JNICALL
171-
Java_com_audioplayback_AudioPlaybackModule_loadSoundNative(JNIEnv *env, jobject instance, jint fd, jint fileLength, jint fileOffset) {
171+
Java_com_audioplayback_AudioPlaybackModule_loadSoundNative(JNIEnv *env, jobject , jint fd, jint fileLength, jint fileOffset) {
172172
auto result = audioEngine->loadSound(fd, fileOffset, fileLength);
173173

174174
// Once done, close the file descriptor
@@ -195,27 +195,33 @@ Java_com_audioplayback_AudioPlaybackModule_loadSoundNative(JNIEnv *env, jobject
195195

196196

197197
JNIEXPORT void JNICALL
198-
Java_com_audioplayback_AudioPlaybackModule_playSoundsNative(JNIEnv *env, jobject thiz, jobjectArray ids,
198+
Java_com_audioplayback_AudioPlaybackModule_playSoundsNative(JNIEnv *env, jobject , jobjectArray ids,
199199
jbooleanArray values) {
200200
audioEngine->playSounds(zipStringBooleanArrays(env, ids, values));
201201
}
202202

203203
JNIEXPORT void JNICALL
204-
Java_com_audioplayback_AudioPlaybackModule_loopSoundsNative(JNIEnv *env, jobject thiz, jobjectArray ids,
204+
Java_com_audioplayback_AudioPlaybackModule_loopSoundsNative(JNIEnv *env, jobject , jobjectArray ids,
205205
jbooleanArray values) {
206206
audioEngine->loopSounds(zipStringBooleanArrays(env, ids, values));
207207
}
208208

209209
JNIEXPORT void JNICALL
210-
Java_com_audioplayback_AudioPlaybackModule_seekSoundsToNative(JNIEnv *env, jobject thiz, jobjectArray ids,
210+
Java_com_audioplayback_AudioPlaybackModule_seekSoundsToNative(JNIEnv *env, jobject , jobjectArray ids,
211211
jdoubleArray values) {
212212
audioEngine->seekSoundsTo(zipStringDoubleArrays(env, ids, values));
213213
}
214214

215215
JNIEXPORT void JNICALL
216-
Java_com_audioplayback_AudioPlaybackModule_setSoundsVolumeNative(JNIEnv *env, jobject thiz,
216+
Java_com_audioplayback_AudioPlaybackModule_setSoundsVolumeNative(JNIEnv *env, jobject ,
217217
jobjectArray ids,
218218
jdoubleArray values) {
219219
audioEngine->setSoundsVolume(zipStringDoubleArrays(env, ids, values));
220220
}
221221
}
222+
223+
extern "C"
224+
JNIEXPORT jint JNICALL
225+
Java_com_audioplayback_AudioPlaybackModule_getStreamStateNative(JNIEnv *, jobject ) {
226+
return static_cast<int>(audioEngine->getStreamState());
227+
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ class AudioPlaybackModule internal constructor(context: ReactApplicationContext)
128128
}
129129
}
130130

131+
@ReactMethod(isBlockingSynchronousMethod = true)
132+
override fun getStreamState(): Double {
133+
return getStreamStateNative().toDouble()
134+
}
135+
131136
private fun readableArrayToStringBooleanArray(arg: ReadableArray): Pair<Array<String>, BooleanArray> {
132137
val size = arg.size()
133138
// Arrays to hold the results
@@ -200,6 +205,7 @@ class AudioPlaybackModule internal constructor(context: ReactApplicationContext)
200205
private external fun setSoundsVolumeNative(ids: Array<String>, values: DoubleArray)
201206
private external fun loadSoundNative(fd: Int, fileLength: Int, fileOffset: Int): LoadSoundResult
202207
private external fun unloadSoundsNative(ids: Array<String>?)
208+
private external fun getStreamStateNative(): Int
203209

204210
// Example method
205211
// See https://reactnative.dev/docs/native-modules-android

example/ios/Podfile.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -1854,4 +1854,4 @@ SPEC CHECKSUMS:
18541854

18551855
PODFILE CHECKSUM: 0de3c0d5ec96ef8679c10cf5c8e600381b2b0ac8
18561856

1857-
COCOAPODS: 1.15.2
1857+
COCOAPODS: 1.14.3

example/src/components/StreamControl.tsx

+21
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import {
22
AndroidAudioStreamUsage,
33
AudioManager,
4+
StreamState,
45
} from 'react-native-audio-playback';
56

67
import { Button } from './Button';
78
import { Section } from './Section';
9+
import { Alert } from 'react-native';
810

911
interface StreamControlProps {
1012
onLoadSounds: () => void;
@@ -31,13 +33,32 @@ export function StreamControl({ onLoadSounds }: StreamControlProps) {
3133
AudioManager.shared.closeAudioStream();
3234
}
3335

36+
function onGetStreamState() {
37+
const streamState = AudioManager.shared.getStreamState();
38+
const streamStateString = (() => {
39+
switch (streamState) {
40+
case StreamState.closed:
41+
return 'closed';
42+
case StreamState.initialized:
43+
return 'initialized';
44+
case StreamState.open:
45+
return 'open';
46+
case StreamState.paused:
47+
return 'paused';
48+
}
49+
})();
50+
51+
Alert.alert('Stream State', streamStateString);
52+
}
53+
3454
return (
3555
<Section title="Audio Manager">
3656
<Button title="Setup Stream" onPress={onSetupStream} />
3757
<Button title="Open Stream" onPress={onOpenStream} />
3858
<Button title="Pause Stream" onPress={onPauseStream} />
3959
<Button title="Close Stream" onPress={onCloseStream} />
4060
<Button title="Load Sounds" onPress={onLoadSounds} />
61+
<Button title="Get Stream State" onPress={onGetStreamState} />
4162
</Section>
4263
);
4364
}

ios/AudioEngine.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import Foundation
1010
import AVFoundation
1111
import AudioToolbox
1212

13-
enum AudioStreamState {
14-
case initialized, opened, closed, paused
13+
enum AudioStreamState: Int {
14+
case closed, initialized, opened, paused
1515
}
1616

1717
struct InterruptionState {
@@ -97,6 +97,10 @@ class AudioEngine {
9797
}
9898
}
9999

100+
public func getStreamState() -> AudioStreamState {
101+
return audioStreamState
102+
}
103+
100104
public func setupAudioStream(
101105
sampleRate: Double,
102106
channelCount: Int,

ios/AudioPlayback.mm

+5
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ - (instancetype) init {
7979
[moduleImpl setSoundsVolumeWithArg:arg];
8080
}
8181

82+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getStreamState) {
83+
return @([moduleImpl getAudioStreamState]);
84+
}
85+
86+
8287
// Don't compile this code when we build for the old architecture.
8388
#ifdef RCT_NEW_ARCH_ENABLED
8489
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:

ios/AudioPlaybackImpl.swift

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ 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 getAudioStreamState() -> Double {
10+
return Double(audioEngine.getStreamState().rawValue)
11+
}
12+
913
@objc public func setupAudioStream(
1014
sampleRate: Double,
1115
channelCount: Double,

src/NativeAudioPlayback.ts

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface Spec extends TurboModule {
2323
loadSound: (
2424
uri: string
2525
) => Promise<{ id: string | null; error: string | null }>;
26+
getStreamState: () => number;
2627
}
2728

2829
export default TurboModuleRegistry.getEnforcing<Spec>('AudioPlayback');

src/index.tsx

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

src/models/AudioManager.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
closeAudioStream,
3+
getStreamState,
34
loadSound,
45
loopSounds,
56
openAudioStream,
@@ -10,7 +11,11 @@ import {
1011
setupAudioStream,
1112
} from '../module';
1213

13-
import { AndroidAudioStreamUsage, IosAudioSessionCategory } from '../types';
14+
import {
15+
AndroidAudioStreamUsage,
16+
IosAudioSessionCategory,
17+
StreamState,
18+
} from '../types';
1419
import { Player } from './Player';
1520

1621
export class AudioManager {
@@ -79,4 +84,8 @@ export class AudioManager {
7984
public setSoundsVolume(args: ReadonlyArray<[Player, number]>): void {
8085
setSoundsVolume(args.map(([player, volume]) => [player.id, volume]));
8186
}
87+
88+
public getStreamState(): StreamState {
89+
return getStreamState();
90+
}
8291
}

src/module.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { Image, NativeModules, Platform } from 'react-native';
22

33
import type { Spec } from './NativeAudioPlayback';
4-
import type { AndroidAudioStreamUsage, IosAudioSessionCategory } from './types';
4+
import {
5+
StreamState,
6+
type AndroidAudioStreamUsage,
7+
type IosAudioSessionCategory,
8+
} from './types';
59

610
const LINKING_ERROR =
711
`The package 'react-native-audio-playback' doesn't seem to be linked. Make sure: \n\n` +
@@ -112,3 +116,21 @@ export async function loadSound(requiredAsset: number): Promise<string> {
112116
export function unloadSound(playerId: string) {
113117
AudioPlayback.unloadSound(playerId);
114118
}
119+
120+
export function getStreamState(): StreamState {
121+
const streamStateRaw = AudioPlayback.getStreamState();
122+
switch (streamStateRaw) {
123+
case 0:
124+
return StreamState.closed;
125+
case 1:
126+
return StreamState.initialized;
127+
case 2:
128+
return StreamState.open;
129+
case 3:
130+
return StreamState.paused;
131+
default:
132+
throw new Error(
133+
'Unknown stream state. Please create an issue with a reproducible'
134+
);
135+
}
136+
}

src/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,10 @@ export enum AndroidAudioStreamUsage {
2121
Game,
2222
Assistant,
2323
}
24+
25+
export enum StreamState {
26+
closed,
27+
initialized,
28+
open,
29+
paused,
30+
}

0 commit comments

Comments
 (0)