Skip to content

Commit e8e51ac

Browse files
maciejmakowski2003Maciej Makowski
and
Maciej Makowski
authored
Refactor/system audio settings and engines (#480)
* refactor: android system functions improvement * ci: yarn format * feat: added option to init audioContext suspended * refactor: refactored audio player behaviors * ci: yarn format * refactor: small refactor of AudioContextOptions * fix: small nitpick * refactor: pausing engine only if all players are paused * ci: yarn format * fix: removed synchronized decorator from AudioAPIModule --------- Co-authored-by: Maciej Makowski <[email protected]>
1 parent 0a901f9 commit e8e51ac

File tree

24 files changed

+185
-58
lines changed

24 files changed

+185
-58
lines changed

apps/common-app/src/examples/AudioFile/AudioFile.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ import { Container, Button, Spacer, Slider } from '../../components';
1212
const URL =
1313
'https://software-mansion.github.io/react-native-audio-api/audio/voice/example-voice-01.mp3';
1414

15-
const LOOP_START = 0;
16-
const LOOP_END = 10;
17-
1815
const INITIAL_RATE = 1;
1916
const INITIAL_DETUNE = 0;
2017

@@ -54,18 +51,22 @@ const AudioFile: FC = () => {
5451
return;
5552
}
5653

57-
await audioContextRef.current.resume();
58-
5954
if (isPlaying) {
6055
bufferSourceRef.current?.stop(audioContextRef.current.currentTime);
6156
AudioManager.setLockScreenInfo({
6257
state: 'state_paused',
6358
});
59+
60+
setTimeout(async () => {
61+
await audioContextRef.current?.suspend();
62+
}, 5);
6463
} else {
6564
if (!audioBuffer) {
6665
fetchAudioBuffer();
6766
}
6867

68+
await audioContextRef.current.resume();
69+
6970
AudioManager.setLockScreenInfo({
7071
state: 'state_playing',
7172
});
@@ -76,12 +77,9 @@ const AudioFile: FC = () => {
7677
pitchCorrection: true,
7778
});
7879
bufferSourceRef.current.buffer = audioBuffer;
79-
bufferSourceRef.current.loop = true;
8080
bufferSourceRef.current.onended = (event) => {
8181
setOffset((_prev) => event.value || 0);
8282
};
83-
bufferSourceRef.current.loopStart = LOOP_START;
84-
bufferSourceRef.current.loopEnd = LOOP_END;
8583
bufferSourceRef.current.playbackRate.value = playbackRate;
8684
bufferSourceRef.current.detune.value = detune;
8785
bufferSourceRef.current.connect(audioContextRef.current.destination);
@@ -115,11 +113,9 @@ const AudioFile: FC = () => {
115113

116114
useEffect(() => {
117115
if (!audioContextRef.current) {
118-
audioContextRef.current = new AudioContext();
116+
audioContextRef.current = new AudioContext({ initSuspended: true });
119117
}
120118

121-
audioContextRef.current.suspend();
122-
123119
AudioManager.setLockScreenInfo({
124120
title: 'Audio file',
125121
artist: 'Software Mansion',
@@ -169,6 +165,7 @@ const AudioFile: FC = () => {
169165
remoteChangePlaybackPositionSubscription?.remove();
170166
interruptionSubscription?.remove();
171167
audioContextRef.current?.close();
168+
AudioManager.resetLockScreenInfo();
172169
};
173170
}, [fetchAudioBuffer]);
174171

packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ void AudioPlayer::start() {
3434
}
3535

3636
void AudioPlayer::stop() {
37+
if (mStream_) {
38+
mStream_->requestStop();
39+
}
40+
}
41+
42+
void AudioPlayer::resume() {
43+
if (mStream_) {
44+
mStream_->requestStart();
45+
}
46+
}
47+
48+
void AudioPlayer::suspend() {
3749
if (mStream_) {
3850
mStream_->requestPause();
3951
}
@@ -43,7 +55,6 @@ void AudioPlayer::cleanup() {
4355
isInitialized_ = false;
4456

4557
if (mStream_) {
46-
mStream_->requestStop();
4758
mStream_->close();
4859
mStream_.reset();
4960
}

packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class AudioPlayer : public AudioStreamDataCallback {
2020

2121
void start();
2222
void stop();
23+
void resume();
24+
void suspend();
2325
void cleanup();
2426

2527
DataCallbackResult onAudioReady(

packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ class LockScreenManager(
254254
return bitmap
255255
}
256256

257-
fun updatePlaybackState(playbackState: Int) {
257+
private fun updatePlaybackState(playbackState: Int) {
258258
isPlaying = playbackState == PlaybackStateCompat.STATE_PLAYING
259259

260260
pb.setState(playbackState, elapsedTime, speed)
@@ -289,6 +289,11 @@ class LockScreenManager(
289289
if (hasControl(PlaybackStateCompat.ACTION_REWIND)) {
290290
controlCount += 1
291291
}
292+
293+
if (hasControl(PlaybackStateCompat.ACTION_SEEK_TO)) {
294+
controlCount += 1
295+
}
296+
292297
val actions = IntArray(controlCount)
293298
for (i in actions.indices) {
294299
actions[i] = i

packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,19 @@ package com.swmansion.audioapi.system
33
import android.content.Intent
44
import android.os.Build
55
import android.support.v4.media.session.MediaSessionCompat
6-
import android.support.v4.media.session.PlaybackStateCompat
76
import androidx.core.app.NotificationManagerCompat
87
import com.swmansion.audioapi.AudioAPIModule
98
import java.lang.ref.WeakReference
109
import java.util.HashMap
1110

1211
class MediaSessionCallback(
1312
private val audioAPIModule: WeakReference<AudioAPIModule>,
14-
private val lockScreenManager: WeakReference<LockScreenManager>,
1513
) : MediaSessionCompat.Callback() {
1614
override fun onPlay() {
17-
lockScreenManager.get()?.updatePlaybackState(PlaybackStateCompat.STATE_PLAYING)
1815
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("remotePlay", mapOf())
1916
}
2017

2118
override fun onPause() {
22-
lockScreenManager.get()?.updatePlaybackState(PlaybackStateCompat.STATE_PAUSED)
2319
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("remotePause", mapOf())
2420
}
2521

packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ object MediaSessionManager {
8181
this.lockScreenManager = LockScreenManager(this.reactContext, WeakReference(this.mediaSession), WeakReference(mediaNotificationManager))
8282
this.mediaReceiver =
8383
MediaReceiver(this.reactContext, WeakReference(this.mediaSession), WeakReference(mediaNotificationManager), this.audioAPIModule)
84-
this.mediaSession.setCallback(MediaSessionCallback(this.audioAPIModule, WeakReference(this.lockScreenManager)))
84+
this.mediaSession.setCallback(MediaSessionCallback(this.audioAPIModule))
8585

8686
val filter = IntentFilter()
8787
filter.addAction(MediaNotificationManager.REMOVE_NOTIFICATION)

packages/react-native-audio-api/common/cpp/audioapi/AudioAPIModuleInstaller.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ class AudioAPIModuleInstaller {
4545
size_t count) -> jsi::Value {
4646
std::shared_ptr<AudioContext> audioContext;
4747
auto sampleRate = static_cast<float>(args[0].getNumber());
48-
audioContext = std::make_shared<AudioContext>(sampleRate, audioEventHandlerRegistry);
48+
auto initSuspended = args[1].getBool();
49+
audioContext = std::make_shared<AudioContext>(sampleRate, initSuspended, audioEventHandlerRegistry);
4950

5051
auto audioContextHostObject = std::make_shared<AudioContextHostObject>(
5152
audioContext, &runtime, jsCallInvoker);

packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioContextHostObject.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ class AudioContextHostObject : public BaseAudioContextHostObject {
4545
auto audioContext = std::static_pointer_cast<AudioContext>(context_);
4646
auto result = audioContext->resume();
4747

48+
if (!result) {
49+
promise->reject("Failed to resume audio context because it is already closed.");
50+
return;
51+
}
52+
4853
promise->resolve([result](jsi::Runtime &runtime) {
4954
return jsi::Value(result);
5055
});
@@ -60,6 +65,11 @@ class AudioContextHostObject : public BaseAudioContextHostObject {
6065
auto audioContext = std::static_pointer_cast<AudioContext>(context_);
6166
auto result = audioContext->suspend();
6267

68+
if (!result) {
69+
promise->reject("Failed to suspend audio context because it is already closed.");
70+
return;
71+
}
72+
6373
promise->resolve([result](jsi::Runtime &runtime) {
6474
return jsi::Value(result);
6575
});

packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.cpp

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace audioapi {
1313
AudioContext::AudioContext(
1414
float sampleRate,
15+
bool initSuspended,
1516
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry)
1617
: BaseAudioContext(audioEventHandlerRegistry) {
1718
#ifdef ANDROID
@@ -24,8 +25,16 @@ AudioContext::AudioContext(
2425
sampleRate_ = sampleRate;
2526
audioDecoder_ = std::make_shared<AudioDecoder>(sampleRate);
2627

27-
state_ = ContextState::RUNNING;
28+
if (initSuspended) {
29+
playerHasBeenStarted_ = false;
30+
state_ = ContextState::SUSPENDED;
31+
32+
return;
33+
}
34+
35+
playerHasBeenStarted_ = true;
2836
audioPlayer_->start();
37+
state_ = ContextState::RUNNING;
2938
}
3039

3140
AudioContext::~AudioContext() {
@@ -37,27 +46,43 @@ AudioContext::~AudioContext() {
3746
void AudioContext::close() {
3847
state_ = ContextState::CLOSED;
3948

49+
audioPlayer_->stop();
4050
audioPlayer_->cleanup();
4151
nodeManager_->cleanup();
4252
}
4353

4454
bool AudioContext::resume() {
45-
if (isClosed() || isRunning()) {
55+
if (isClosed()) {
4656
return false;
4757
}
4858

59+
if (isRunning()) {
60+
return true;
61+
}
62+
63+
if (!playerHasBeenStarted_) {
64+
playerHasBeenStarted_ = true;
65+
audioPlayer_->start();
66+
} else {
67+
audioPlayer_->resume();
68+
}
69+
4970
state_ = ContextState::RUNNING;
50-
audioPlayer_->start();
5171
return true;
5272
}
5373

5474
bool AudioContext::suspend() {
55-
if (isClosed() || isSuspended()) {
75+
if (isClosed()) {
5676
return false;
5777
}
5878

79+
if (isSuspended()) {
80+
return true;
81+
}
82+
83+
audioPlayer_->suspend();
84+
5985
state_ = ContextState::SUSPENDED;
60-
audioPlayer_->stop();
6186
return true;
6287
}
6388

packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class IOSAudioPlayer;
1414

1515
class AudioContext : public BaseAudioContext {
1616
public:
17-
explicit AudioContext(float sampleRate, const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry);
17+
explicit AudioContext(float sampleRate, bool initSuspended, const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry);
1818
~AudioContext() override;
1919

2020
void close();
@@ -27,6 +27,7 @@ class AudioContext : public BaseAudioContext {
2727
#else
2828
std::shared_ptr<IOSAudioPlayer> audioPlayer_;
2929
#endif
30+
bool playerHasBeenStarted_;
3031

3132
std::function<void(std::shared_ptr<AudioBus>, int)> renderAudio();
3233
};

packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ AudioParam::AudioParam(
1717
minValue_(minValue),
1818
maxValue_(maxValue),
1919
context_(context),
20-
audioBus_(std::make_shared<AudioBus>(
21-
RENDER_QUANTUM_SIZE,
22-
1,
23-
context->getSampleRate())) {
20+
audioBus_(
21+
std::make_shared<AudioBus>(
22+
RENDER_QUANTUM_SIZE,
23+
1,
24+
context->getSampleRate())) {
2425
startTime_ = 0;
2526
endTime_ = 0;
2627
startValue_ = value_;

packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.cpp

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,11 @@ void AudioBufferQueueSourceNode::processWithPitchCorrection(
152152

153153
playbackRateBus_->zero();
154154

155-
updatePlaybackInfo(processingBus, framesToProcess, startOffset, offsetLength);
155+
auto framesNeededToStretch =
156+
static_cast<int>(playbackRate * static_cast<float>(framesToProcess));
157+
158+
updatePlaybackInfo(
159+
playbackRateBus_, framesNeededToStretch, startOffset, offsetLength);
156160

157161
if (playbackRate == 0.0f || (!isPlaying() && !isStopScheduled())) {
158162
processingBus->zero();
@@ -162,15 +166,7 @@ void AudioBufferQueueSourceNode::processWithPitchCorrection(
162166
// Send position changed event
163167
sendOnPositionChangedEvent();
164168

165-
auto framesNeededToStretch =
166-
static_cast<int>(playbackRate * static_cast<float>(framesToProcess));
167-
auto stretchedStartOffset =
168-
static_cast<size_t>(static_cast<float>(startOffset) * playbackRate);
169-
auto stretchedOffsetLength =
170-
static_cast<size_t>(static_cast<float>(offsetLength) * playbackRate);
171-
172-
processWithoutInterpolation(
173-
playbackRateBus_, stretchedStartOffset, stretchedOffsetLength);
169+
processWithoutInterpolation(playbackRateBus_, startOffset, offsetLength);
174170

175171
stretch_->process(
176172
playbackRateBus_.get()[0],

packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ void AudioScheduledSourceNode::updatePlaybackInfo(
100100
assert(nonSilentFramesToProcess <= framesToProcess);
101101

102102
// stop will happen in the same render quantum
103-
if (stopFrame < lastFrame && stopFrame >= firstFrame) {
103+
if (stopFrame <= lastFrame && stopFrame >= firstFrame) {
104104
playbackState_ = PlaybackState::STOP_SCHEDULED;
105105
processingBus->zero(stopFrame - firstFrame, lastFrame - stopFrame);
106106
}

packages/react-native-audio-api/ios/audioapi/ios/AudioAPIModule.mm

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ - (void)invalidate
8585
return [self.audioSessionManager getDevicePreferredSampleRate];
8686
}
8787

88-
RCT_EXPORT_METHOD(setAudioSessionActivity : (BOOL)enabled resolve : (RCTPromiseResolveBlock)
89-
resolve reject : (RCTPromiseRejectBlock)reject)
88+
RCT_EXPORT_METHOD(
89+
setAudioSessionActivity : (BOOL)enabled resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)
90+
reject)
9091
{
9192
if ([self.audioSessionManager setActive:enabled]) {
9293
resolve(@"true");
@@ -126,15 +127,16 @@ - (void)invalidate
126127
[self.notificationManager observeVolumeChanges:(BOOL)enabled];
127128
}
128129

129-
RCT_EXPORT_METHOD(requestRecordingPermissions : (nonnull RCTPromiseResolveBlock)
130-
resolve reject : (nonnull RCTPromiseRejectBlock)reject)
130+
RCT_EXPORT_METHOD(
131+
requestRecordingPermissions : (nonnull RCTPromiseResolveBlock)resolve reject : (nonnull RCTPromiseRejectBlock)
132+
reject)
131133
{
132134
NSString *res = [self.audioSessionManager requestRecordingPermissions];
133135
resolve(res);
134136
}
135137

136-
RCT_EXPORT_METHOD(checkRecordingPermissions : (nonnull RCTPromiseResolveBlock)
137-
resolve reject : (nonnull RCTPromiseRejectBlock)reject)
138+
RCT_EXPORT_METHOD(
139+
checkRecordingPermissions : (nonnull RCTPromiseResolveBlock)resolve reject : (nonnull RCTPromiseRejectBlock)reject)
138140
{
139141
NSString *res = [self.audioSessionManager checkRecordingPermissions];
140142
resolve(res);
@@ -169,7 +171,7 @@ - (void)invokeHandlerWithEventName:(NSString *)eventName eventBody:(NSDictionary
169171
body[stdKey] = EventValue([value doubleValue]);
170172
} else if (strcmp(type, @encode(float)) == 0) {
171173
body[stdKey] = EventValue([value floatValue]);
172-
} else if (strcmp(type, @encode(BOOL)) == 0) {
174+
} else {
173175
body[stdKey] = EventValue([value boolValue]);
174176
}
175177
}

packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioPlayer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class IOSAudioPlayer {
2222

2323
void start();
2424
void stop();
25+
void resume();
26+
void suspend();
2527
void cleanup();
2628

2729
protected:

0 commit comments

Comments
 (0)