Skip to content

Commit

Permalink
feat: expose stream state (#29)
Browse files Browse the repository at this point in the history
* feat: expose the state of the audio stream on iOS

* feat: expose the state of the audio stream on Android

* feat: add docs
  • Loading branch information
itsramiel authored Feb 23, 2025
1 parent 7eff1a9 commit 97a3f6d
Show file tree
Hide file tree
Showing 15 changed files with 137 additions and 18 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ AudioManager.shared.<some-method>
- `playSounds(args: ReadonlyArray<[Player, boolean]>): void` Plays/pauses multiple sounds
- `loopSounds(args: ReadonlyArray<[Player, boolean]>): void` Loops/unloops multiple sounds
- `seekSoundsTo(args: ReadonlyArray<[Player, number]>): void` Seeks multiple sounds
- `public setSoundsVolume(args: ReadonlyArray<[Player, number]>): void` Sets the volume of multiple sounds, volume should be a number between 0 and 1.
- `setSoundsVolume(args: ReadonlyArray<[Player, number]>): void` Sets the volume of multiple sounds, volume should be a number between 0 and 1.
- `getStreamState(): StreamState` Returns the current state of the stream.

### Player

Expand Down
24 changes: 24 additions & 0 deletions android/src/main/cpp/AudioEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,27 @@ oboe::Usage AudioEngine::getUsageFromInt(int usage) {
default: return oboe::Usage::Media;
}
}

StreamState AudioEngine::getStreamState() {
if(!mAudioStream) {
return StreamState::closed;
}

oboe::StreamState streamState = {mAudioStream->getState()};
switch (streamState) {
case oboe::StreamState::Closing:
case oboe::StreamState::Closed:
case oboe::StreamState::Disconnected:
case oboe::StreamState::Unknown:
case oboe::StreamState::Uninitialized: return StreamState::closed;
case oboe::StreamState::Open: return StreamState::initialized;
case oboe::StreamState::Starting:
case oboe::StreamState::Started: return StreamState::open;
case oboe::StreamState::Flushing:
case oboe::StreamState::Flushed:
case oboe::StreamState::Stopping:
case oboe::StreamState::Stopped:
case oboe::StreamState::Pausing:
case oboe::StreamState::Paused: return StreamState::paused;
}
}
7 changes: 6 additions & 1 deletion android/src/main/cpp/AudioEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#include "AudioConstants.h"
#include <android/asset_manager.h>

enum class StreamState {
closed, initialized, open, paused
};

class AudioEngine : public oboe::AudioStreamDataCallback{
public:
SetupAudioStreamResult setupAudioStream(double sampleRate, double channelCount, int usage);
Expand All @@ -26,6 +30,7 @@ class AudioEngine : public oboe::AudioStreamDataCallback{
void setSoundsVolume(const std::vector<std::pair<std::string, double>>&);
LoadSoundResult loadSound(int fd, int offset, int length);
void unloadSounds(const std::optional<std::vector<std::string>>&);
StreamState getStreamState();

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

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

oboe::Usage getUsageFromInt(int usage);
static oboe::Usage getUsageFromInt(int usage);
};

#endif //AUDIOPLAYBACK_AUDIOENGINE_H
26 changes: 16 additions & 10 deletions android/src/main/cpp/native-lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ extern "C" {
JNIEXPORT jobject JNICALL
Java_com_audioplayback_AudioPlaybackModule_setupAudioStreamNative(
JNIEnv *env,
jobject thiz,
jobject,
jdouble sample_rate,
jdouble channel_count,
jint usage) {
Expand All @@ -105,7 +105,7 @@ Java_com_audioplayback_AudioPlaybackModule_setupAudioStreamNative(
}

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

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

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

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


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

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

JNIEXPORT void JNICALL
Java_com_audioplayback_AudioPlaybackModule_unloadSoundsNative(JNIEnv *env, jobject thiz,
Java_com_audioplayback_AudioPlaybackModule_unloadSoundsNative(JNIEnv *env, jobject ,
jobjectArray ids) {
if(ids == nullptr) {
audioEngine->unloadSounds(std::nullopt);
Expand All @@ -168,7 +168,7 @@ Java_com_audioplayback_AudioPlaybackModule_unloadSoundsNative(JNIEnv *env, jobje


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

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


JNIEXPORT void JNICALL
Java_com_audioplayback_AudioPlaybackModule_playSoundsNative(JNIEnv *env, jobject thiz, jobjectArray ids,
Java_com_audioplayback_AudioPlaybackModule_playSoundsNative(JNIEnv *env, jobject , jobjectArray ids,
jbooleanArray values) {
audioEngine->playSounds(zipStringBooleanArrays(env, ids, values));
}

JNIEXPORT void JNICALL
Java_com_audioplayback_AudioPlaybackModule_loopSoundsNative(JNIEnv *env, jobject thiz, jobjectArray ids,
Java_com_audioplayback_AudioPlaybackModule_loopSoundsNative(JNIEnv *env, jobject , jobjectArray ids,
jbooleanArray values) {
audioEngine->loopSounds(zipStringBooleanArrays(env, ids, values));
}

JNIEXPORT void JNICALL
Java_com_audioplayback_AudioPlaybackModule_seekSoundsToNative(JNIEnv *env, jobject thiz, jobjectArray ids,
Java_com_audioplayback_AudioPlaybackModule_seekSoundsToNative(JNIEnv *env, jobject , jobjectArray ids,
jdoubleArray values) {
audioEngine->seekSoundsTo(zipStringDoubleArrays(env, ids, values));
}

JNIEXPORT void JNICALL
Java_com_audioplayback_AudioPlaybackModule_setSoundsVolumeNative(JNIEnv *env, jobject thiz,
Java_com_audioplayback_AudioPlaybackModule_setSoundsVolumeNative(JNIEnv *env, jobject ,
jobjectArray ids,
jdoubleArray values) {
audioEngine->setSoundsVolume(zipStringDoubleArrays(env, ids, values));
}
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_audioplayback_AudioPlaybackModule_getStreamStateNative(JNIEnv *, jobject ) {
return static_cast<int>(audioEngine->getStreamState());
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ class AudioPlaybackModule internal constructor(context: ReactApplicationContext)
}
}

@ReactMethod(isBlockingSynchronousMethod = true)
override fun getStreamState(): Double {
return getStreamStateNative().toDouble()
}

private fun readableArrayToStringBooleanArray(arg: ReadableArray): Pair<Array<String>, BooleanArray> {
val size = arg.size()
// Arrays to hold the results
Expand Down Expand Up @@ -200,6 +205,7 @@ class AudioPlaybackModule internal constructor(context: ReactApplicationContext)
private external fun setSoundsVolumeNative(ids: Array<String>, values: DoubleArray)
private external fun loadSoundNative(fd: Int, fileLength: Int, fileOffset: Int): LoadSoundResult
private external fun unloadSoundsNative(ids: Array<String>?)
private external fun getStreamStateNative(): Int

// Example method
// See https://reactnative.dev/docs/native-modules-android
Expand Down
2 changes: 1 addition & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1854,4 +1854,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 0de3c0d5ec96ef8679c10cf5c8e600381b2b0ac8

COCOAPODS: 1.15.2
COCOAPODS: 1.14.3
21 changes: 21 additions & 0 deletions example/src/components/StreamControl.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
AndroidAudioStreamUsage,
AudioManager,
StreamState,
} from 'react-native-audio-playback';

import { Button } from './Button';
import { Section } from './Section';
import { Alert } from 'react-native';

interface StreamControlProps {
onLoadSounds: () => void;
Expand All @@ -31,13 +33,32 @@ export function StreamControl({ onLoadSounds }: StreamControlProps) {
AudioManager.shared.closeAudioStream();
}

function onGetStreamState() {
const streamState = AudioManager.shared.getStreamState();
const streamStateString = (() => {
switch (streamState) {
case StreamState.closed:
return 'closed';
case StreamState.initialized:
return 'initialized';
case StreamState.open:
return 'open';
case StreamState.paused:
return 'paused';
}
})();

Alert.alert('Stream State', streamStateString);
}

return (
<Section title="Audio Manager">
<Button title="Setup Stream" onPress={onSetupStream} />
<Button title="Open Stream" onPress={onOpenStream} />
<Button title="Pause Stream" onPress={onPauseStream} />
<Button title="Close Stream" onPress={onCloseStream} />
<Button title="Load Sounds" onPress={onLoadSounds} />
<Button title="Get Stream State" onPress={onGetStreamState} />
</Section>
);
}
8 changes: 6 additions & 2 deletions ios/AudioEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Foundation
import AVFoundation
import AudioToolbox

enum AudioStreamState {
case initialized, opened, closed, paused
enum AudioStreamState: Int {
case closed, initialized, opened, paused
}

struct InterruptionState {
Expand Down Expand Up @@ -97,6 +97,10 @@ class AudioEngine {
}
}

public func getStreamState() -> AudioStreamState {
return audioStreamState
}

public func setupAudioStream(
sampleRate: Double,
channelCount: Int,
Expand Down
5 changes: 5 additions & 0 deletions ios/AudioPlayback.mm
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ - (instancetype) init {
[moduleImpl setSoundsVolumeWithArg:arg];
}

RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getStreamState) {
return @([moduleImpl getAudioStreamState]);
}


// Don't compile this code when we build for the old architecture.
#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
Expand Down
4 changes: 4 additions & 0 deletions ios/AudioPlaybackImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import AudioToolbox
private static let unknownError: String = "An unknown error occurred while loading the audio file. Please create an issue with a reproducible"
let audioEngine = AudioEngine()

@objc public func getAudioStreamState() -> Double {
return Double(audioEngine.getStreamState().rawValue)
}

@objc public func setupAudioStream(
sampleRate: Double,
channelCount: Double,
Expand Down
1 change: 1 addition & 0 deletions src/NativeAudioPlayback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface Spec extends TurboModule {
loadSound: (
uri: string
) => Promise<{ id: string | null; error: string | null }>;
getStreamState: () => number;
}

export default TurboModuleRegistry.getEnforcing<Spec>('AudioPlayback');
6 changes: 5 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export { AudioManager, Player } from './models';
export { IosAudioSessionCategory, AndroidAudioStreamUsage } from './types';
export {
IosAudioSessionCategory,
AndroidAudioStreamUsage,
StreamState,
} from './types';
11 changes: 10 additions & 1 deletion src/models/AudioManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
closeAudioStream,
getStreamState,
loadSound,
loopSounds,
openAudioStream,
Expand All @@ -10,7 +11,11 @@ import {
setupAudioStream,
} from '../module';

import { AndroidAudioStreamUsage, IosAudioSessionCategory } from '../types';
import {
AndroidAudioStreamUsage,
IosAudioSessionCategory,
StreamState,
} from '../types';
import { Player } from './Player';

export class AudioManager {
Expand Down Expand Up @@ -79,4 +84,8 @@ export class AudioManager {
public setSoundsVolume(args: ReadonlyArray<[Player, number]>): void {
setSoundsVolume(args.map(([player, volume]) => [player.id, volume]));
}

public getStreamState(): StreamState {
return getStreamState();
}
}
24 changes: 23 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Image, NativeModules, Platform } from 'react-native';

import type { Spec } from './NativeAudioPlayback';
import type { AndroidAudioStreamUsage, IosAudioSessionCategory } from './types';
import {
StreamState,
type AndroidAudioStreamUsage,
type IosAudioSessionCategory,
} from './types';

const LINKING_ERROR =
`The package 'react-native-audio-playback' doesn't seem to be linked. Make sure: \n\n` +
Expand Down Expand Up @@ -112,3 +116,21 @@ export async function loadSound(requiredAsset: number): Promise<string> {
export function unloadSound(playerId: string) {
AudioPlayback.unloadSound(playerId);
}

export function getStreamState(): StreamState {
const streamStateRaw = AudioPlayback.getStreamState();
switch (streamStateRaw) {
case 0:
return StreamState.closed;
case 1:
return StreamState.initialized;
case 2:
return StreamState.open;
case 3:
return StreamState.paused;
default:
throw new Error(
'Unknown stream state. Please create an issue with a reproducible'
);
}
}
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,10 @@ export enum AndroidAudioStreamUsage {
Game,
Assistant,
}

export enum StreamState {
closed,
initialized,
open,
paused,
}

0 comments on commit 97a3f6d

Please sign in to comment.