react-native-audio-api 0.6.0-rc.1 → 0.6.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -22
- package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +73 -0
- package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.h +37 -0
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +6 -10
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.h +2 -3
- package/android/src/main/java/com/swmansion/audioapi/AudioManagerModule.kt +10 -8
- package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +7 -7
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionEventEmitter.kt +4 -0
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +31 -13
- package/android/src/main/java/com/swmansion/audioapi/system/VolumeChangeListener.kt +27 -0
- package/common/cpp/audioapi/AudioAPIModuleInstaller.h +29 -5
- package/common/cpp/audioapi/HostObjects/AnalyserNodeHostObject.h +1 -0
- package/common/cpp/audioapi/HostObjects/AudioRecorderHostObject.h +149 -0
- package/common/cpp/audioapi/core/AudioContext.cpp +4 -3
- package/common/cpp/audioapi/core/BaseAudioContext.cpp +6 -6
- package/common/cpp/audioapi/core/inputs/AudioRecorder.h +38 -0
- package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h +5 -0
- package/common/cpp/audioapi/core/sources/OscillatorNode.cpp +1 -1
- package/common/cpp/audioapi/core/utils/AudioNodeManager.cpp +45 -11
- package/common/cpp/audioapi/core/utils/AudioNodeManager.h +6 -2
- package/ios/audioapi/ios/AudioManagerModule.mm +9 -10
- package/ios/audioapi/ios/core/IOSAudioPlayer.h +11 -12
- package/ios/audioapi/ios/core/IOSAudioPlayer.mm +22 -16
- package/ios/audioapi/ios/core/IOSAudioRecorder.h +36 -0
- package/ios/audioapi/ios/core/IOSAudioRecorder.mm +62 -0
- package/ios/audioapi/ios/core/{AudioPlayer.h → NativeAudioPlayer.h} +1 -8
- package/ios/audioapi/ios/core/{AudioPlayer.m → NativeAudioPlayer.m} +4 -33
- package/ios/audioapi/ios/core/NativeAudioRecorder.h +25 -0
- package/ios/audioapi/ios/core/NativeAudioRecorder.m +47 -0
- package/ios/audioapi/ios/system/AudioEngine.h +7 -1
- package/ios/audioapi/ios/system/AudioEngine.mm +64 -20
- package/ios/audioapi/ios/system/AudioSessionManager.h +3 -1
- package/ios/audioapi/ios/system/AudioSessionManager.mm +37 -25
- package/ios/audioapi/ios/system/NotificationManager.h +7 -1
- package/ios/audioapi/ios/system/NotificationManager.mm +30 -0
- package/lib/commonjs/api.js +15 -1
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/core/AudioRecorder.js +51 -0
- package/lib/commonjs/core/AudioRecorder.js.map +1 -0
- package/lib/commonjs/errors/NotSupportedError.js.map +1 -1
- package/lib/commonjs/hooks/useSytemVolume.js +24 -0
- package/lib/commonjs/hooks/useSytemVolume.js.map +1 -0
- package/lib/commonjs/plugin/withAudioAPI.js +1 -1
- package/lib/commonjs/plugin/withAudioAPI.js.map +1 -1
- package/lib/commonjs/specs/NativeAudioManagerModule.js +5 -2
- package/lib/commonjs/specs/NativeAudioManagerModule.js.map +1 -1
- package/lib/commonjs/system/AudioManager.js +23 -50
- package/lib/commonjs/system/AudioManager.js.map +1 -1
- package/lib/module/api.js +3 -1
- package/lib/module/api.js.map +1 -1
- package/lib/module/core/AudioRecorder.js +45 -0
- package/lib/module/core/AudioRecorder.js.map +1 -0
- package/lib/module/errors/NotSupportedError.js.map +1 -1
- package/lib/module/hooks/useSytemVolume.js +19 -0
- package/lib/module/hooks/useSytemVolume.js.map +1 -0
- package/lib/module/plugin/withAudioAPI.js +1 -1
- package/lib/module/plugin/withAudioAPI.js.map +1 -1
- package/lib/module/specs/NativeAudioManagerModule.js +5 -2
- package/lib/module/specs/NativeAudioManagerModule.js.map +1 -1
- package/lib/module/system/AudioManager.js +23 -50
- package/lib/module/system/AudioManager.js.map +1 -1
- package/lib/typescript/api.d.ts +5 -1
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/core/AudioRecorder.d.ts +22 -0
- package/lib/typescript/core/AudioRecorder.d.ts.map +1 -0
- package/lib/typescript/errors/NotSupportedError.d.ts.map +1 -1
- package/lib/typescript/hooks/useSytemVolume.d.ts +2 -0
- package/lib/typescript/hooks/useSytemVolume.d.ts.map +1 -0
- package/lib/typescript/interfaces.d.ts +11 -5
- package/lib/typescript/interfaces.d.ts.map +1 -1
- package/lib/typescript/plugin/withAudioAPI.d.ts.map +1 -1
- package/lib/typescript/specs/NativeAudioManagerModule.d.ts +2 -1
- package/lib/typescript/specs/NativeAudioManagerModule.d.ts.map +1 -1
- package/lib/typescript/system/AudioManager.d.ts +4 -3
- package/lib/typescript/system/AudioManager.d.ts.map +1 -1
- package/lib/typescript/system/types.d.ts +26 -7
- package/lib/typescript/system/types.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +5 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/api.ts +13 -2
- package/src/core/AudioRecorder.ts +81 -0
- package/src/hooks/useSytemVolume.ts +19 -0
- package/src/interfaces.ts +25 -11
- package/src/plugin/withAudioAPI.ts +2 -1
- package/src/specs/NativeAudioManagerModule.ts +5 -8
- package/src/system/AudioManager.ts +30 -107
- package/src/system/types.ts +31 -21
- package/src/types.ts +13 -0
- /package/src/errors/{NotSupportedError.tsx → NotSupportedError.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,43 +1,56 @@
|
|
|
1
1
|
<img src="./assets/react-native-audio-api-gh-cover.png?v0.0.1" alt="React Native Audio API" width="100%">
|
|
2
2
|
|
|
3
|
-
### React Native
|
|
3
|
+
### High-performance audio engine for React Native based on web audio api specification
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/react-native-audio-api)
|
|
6
|
+
[](https://www.npmjs.com/package/react-native-audio-api?activeTab=versions)
|
|
7
|
+
[](https://github.com/software-mansion/react-native-audio-api/actions)
|
|
4
8
|
|
|
5
9
|
`react-native-audio-api` provides system for controlling audio in React Native environment compatible with Web Audio API specification,
|
|
6
10
|
allowing developers to generate and modify audio in exact same way it is possible in browsers.
|
|
7
11
|
|
|
8
12
|
## Installation
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
check out the [Getting Started](https://docs.swmansion.com/react-native-audio-api/fundamentals/getting-started) section of our documentation for detailed instructions!
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
# using npm
|
|
14
|
-
npm install react-native-audio-api
|
|
16
|
+
## Roadmap
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
- <sub>[](https://github.com/software-mansion/react-native-audio-api/releases/tag/0.1.0)</sub> **Sound synthesis and system audio** 🐣 <br />
|
|
19
|
+
Access to devices audio engines and threads, basic nodes for sound synthesis, simple effects and audio graph implementation
|
|
20
|
+
<br />
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
- <sub>[](https://github.com/software-mansion/react-native-audio-api/releases/tag/0.2.0)</sub> **Graph Processing** 🛎️ <br />
|
|
23
|
+
Support for multi-channel audio processing, audio-graph route optimizations, improved react-native layer for managing audio nodes
|
|
24
|
+
<br />
|
|
25
|
+
- <sub>[](https://github.com/software-mansion/react-native-audio-api/releases/tag/0.3.2)</sub> **Audio Files** 🎸 <br />
|
|
26
|
+
Support for local and remote audio file resources: MP3, WAV, FLAC.
|
|
27
|
+
<br />
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
- <sub>[](https://github.com/software-mansion/react-native-audio-api/releases/tag/0.4.0)</sub> **Analyser Node** 🌊 <br />
|
|
30
|
+
Ability to draw, animate or simply debug audio data in time or frequency domain.
|
|
31
|
+
<br />
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# Build native Android project
|
|
28
|
-
npx expo run:android
|
|
29
|
-
```
|
|
33
|
+
- <sub>[](https://github.com/software-mansion/react-native-audio-api/releases/tag/0.5.0)</sub> **Pitch correction, extending the web** 💥 <br />
|
|
34
|
+
Ability to modify playback speed without affecting pitch of the sound
|
|
35
|
+
<br />
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
- <sub></sub> **System configuration** 🛠️ <br />
|
|
38
|
+
Full control of system audio settings, remote controls, lock screen integration and most importantly configurable background modes
|
|
39
|
+
<br />
|
|
32
40
|
|
|
33
|
-
|
|
41
|
+
- **Microphone support** 🎙️ <br />
|
|
42
|
+
Grab audio data from device microphone or connected device, connect it to the audio graph or stream through the internet
|
|
43
|
+
<br />
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
- **Connect audio param** 🤞 <br />
|
|
46
|
+
Ability to connect Audio nodes to audio params, which will allow for powerful and efficient modulation of audio parameters, creating effects like tremolo, vibrato or complex envelope followers.
|
|
47
|
+
<br />
|
|
37
48
|
|
|
38
|
-
|
|
49
|
+
- **JS Audio Worklets** 🐎 <br />
|
|
50
|
+
Ability to run JS functions connected to the audio graph running on audio thread allowing for full customization of what happens to the audio signal.
|
|
51
|
+
<br />
|
|
39
52
|
|
|
40
|
-
## Coverage
|
|
53
|
+
## Web Audio API Specification Coverage
|
|
41
54
|
|
|
42
55
|
Our current coverage of Web Audio API specification can be found here: [Web Audio API coverage](https://software-mansion.github.io/react-native-audio-api/other/web-audio-api-coverage).
|
|
43
56
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#include <audioapi/android/core/AndroidAudioRecorder.h>
|
|
2
|
+
#include <audioapi/core/Constants.h>
|
|
3
|
+
#include <audioapi/utils/AudioArray.h>
|
|
4
|
+
#include <audioapi/utils/AudioBus.h>
|
|
5
|
+
|
|
6
|
+
namespace audioapi {
|
|
7
|
+
|
|
8
|
+
AndroidAudioRecorder::AndroidAudioRecorder(
|
|
9
|
+
float sampleRate,
|
|
10
|
+
int bufferLength,
|
|
11
|
+
const std::function<void(void)> &onError,
|
|
12
|
+
const std::function<void(void)> &onStatusChange,
|
|
13
|
+
const std::function<void(std::shared_ptr<AudioBus>, int, double)>
|
|
14
|
+
&onAudioReady)
|
|
15
|
+
: AudioRecorder(
|
|
16
|
+
sampleRate,
|
|
17
|
+
bufferLength,
|
|
18
|
+
onError,
|
|
19
|
+
onStatusChange,
|
|
20
|
+
onAudioReady) {
|
|
21
|
+
AudioStreamBuilder builder;
|
|
22
|
+
builder.setSharingMode(SharingMode::Exclusive)
|
|
23
|
+
->setDirection(Direction::Input)
|
|
24
|
+
->setFormat(AudioFormat::Float)
|
|
25
|
+
->setFormatConversionAllowed(true)
|
|
26
|
+
->setPerformanceMode(PerformanceMode::None)
|
|
27
|
+
->setChannelCount(1)
|
|
28
|
+
->setSampleRateConversionQuality(SampleRateConversionQuality::Medium)
|
|
29
|
+
->setDataCallback(this)
|
|
30
|
+
->setSampleRate(static_cast<int>(sampleRate))
|
|
31
|
+
->setFramesPerDataCallback(bufferLength)
|
|
32
|
+
->openStream(mStream_);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
AndroidAudioRecorder::~AndroidAudioRecorder() {
|
|
36
|
+
AudioRecorder::~AudioRecorder();
|
|
37
|
+
|
|
38
|
+
if (mStream_) {
|
|
39
|
+
mStream_->requestStop();
|
|
40
|
+
mStream_->close();
|
|
41
|
+
mStream_.reset();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void AndroidAudioRecorder::start() {
|
|
46
|
+
if (mStream_) {
|
|
47
|
+
mStream_->requestStart();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
void AndroidAudioRecorder::stop() {
|
|
52
|
+
if (mStream_) {
|
|
53
|
+
mStream_->requestStop();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
DataCallbackResult AndroidAudioRecorder::onAudioReady(
|
|
58
|
+
oboe::AudioStream *oboeStream,
|
|
59
|
+
void *audioData,
|
|
60
|
+
int32_t numFrames) {
|
|
61
|
+
auto buffer = static_cast<float *>(audioData);
|
|
62
|
+
|
|
63
|
+
auto bus = std::make_shared<AudioBus>(bufferLength_, 1, sampleRate_);
|
|
64
|
+
memcpy(bus->getChannel(0)->getData(), buffer, numFrames * sizeof(float));
|
|
65
|
+
auto when = static_cast<double>(
|
|
66
|
+
oboeStream->getTimestamp(CLOCK_MONOTONIC).value().timestamp);
|
|
67
|
+
|
|
68
|
+
onAudioReady_(bus, numFrames, when);
|
|
69
|
+
|
|
70
|
+
return DataCallbackResult::Continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
} // namespace audioapi
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <audioapi/core/inputs/AudioRecorder.h>
|
|
4
|
+
|
|
5
|
+
#include <oboe/Oboe.h>
|
|
6
|
+
#include <functional>
|
|
7
|
+
#include <memory>
|
|
8
|
+
|
|
9
|
+
namespace audioapi {
|
|
10
|
+
|
|
11
|
+
using namespace oboe;
|
|
12
|
+
|
|
13
|
+
class AudioBus;
|
|
14
|
+
|
|
15
|
+
class AndroidAudioRecorder : public AudioStreamDataCallback, public AudioRecorder {
|
|
16
|
+
public:
|
|
17
|
+
AndroidAudioRecorder(float sampleRate,
|
|
18
|
+
int bufferLength,
|
|
19
|
+
const std::function<void(void)> &onError,
|
|
20
|
+
const std::function<void(void)> &onStatusChange,
|
|
21
|
+
const std::function<void(std::shared_ptr<AudioBus>, int, double)> &onAudioReady);
|
|
22
|
+
|
|
23
|
+
~AndroidAudioRecorder() override;
|
|
24
|
+
|
|
25
|
+
void start() override;
|
|
26
|
+
void stop() override;
|
|
27
|
+
|
|
28
|
+
DataCallbackResult onAudioReady(
|
|
29
|
+
AudioStream *oboeStream,
|
|
30
|
+
void *audioData,
|
|
31
|
+
int32_t numFrames) override;
|
|
32
|
+
|
|
33
|
+
private:
|
|
34
|
+
std::shared_ptr<AudioStream> mStream_;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
} // namespace audioapi
|
|
@@ -27,11 +27,13 @@ AudioPlayer::AudioPlayer(
|
|
|
27
27
|
isInitialized_ = true;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
void AudioPlayer::start() {
|
|
31
|
+
if (mStream_) {
|
|
32
|
+
mStream_->requestStart();
|
|
33
|
+
}
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
void AudioPlayer::
|
|
36
|
+
void AudioPlayer::resume() {
|
|
35
37
|
if (mStream_) {
|
|
36
38
|
mStream_->requestStart();
|
|
37
39
|
}
|
|
@@ -47,13 +49,7 @@ void AudioPlayer::stop() {
|
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
void AudioPlayer::
|
|
51
|
-
if (mStream_) {
|
|
52
|
-
mStream_->requestStart();
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
void AudioPlayer::suspend() {
|
|
52
|
+
void AudioPlayer::pause() {
|
|
57
53
|
if (mStream_) {
|
|
58
54
|
mStream_->requestPause();
|
|
59
55
|
}
|
|
@@ -18,11 +18,10 @@ class AudioPlayer : public AudioStreamDataCallback {
|
|
|
18
18
|
const std::function<void(std::shared_ptr<AudioBus>, int)> &renderAudio,
|
|
19
19
|
float sampleRate);
|
|
20
20
|
|
|
21
|
-
[[nodiscard]] float getSampleRate() const;
|
|
22
21
|
void start();
|
|
23
|
-
void stop();
|
|
24
22
|
void resume();
|
|
25
|
-
void
|
|
23
|
+
void stop();
|
|
24
|
+
void pause();
|
|
26
25
|
|
|
27
26
|
DataCallbackResult onAudioReady(
|
|
28
27
|
AudioStream *oboeStream,
|
|
@@ -14,24 +14,24 @@ class AudioManagerModule(
|
|
|
14
14
|
const val NAME = "AudioManagerModule"
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
private val mediaSessionManager: MediaSessionManager = MediaSessionManager(reactContext)
|
|
18
|
-
|
|
19
17
|
init {
|
|
20
18
|
try {
|
|
21
19
|
System.loadLibrary("react-native-audio-api")
|
|
22
20
|
} catch (exception: UnsatisfiedLinkError) {
|
|
23
21
|
throw RuntimeException("Could not load native module AudioAPIModule", exception)
|
|
24
22
|
}
|
|
23
|
+
|
|
24
|
+
MediaSessionManager.initialize(reactContext)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
28
28
|
fun setLockScreenInfo(info: ReadableMap?) {
|
|
29
|
-
|
|
29
|
+
MediaSessionManager.setLockScreenInfo(info)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
33
33
|
fun resetLockScreenInfo() {
|
|
34
|
-
|
|
34
|
+
MediaSessionManager.resetLockScreenInfo()
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
@@ -39,7 +39,7 @@ class AudioManagerModule(
|
|
|
39
39
|
name: String,
|
|
40
40
|
enabled: Boolean,
|
|
41
41
|
) {
|
|
42
|
-
|
|
42
|
+
MediaSessionManager.enableRemoteCommand(name, enabled)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
@@ -47,16 +47,18 @@ class AudioManagerModule(
|
|
|
47
47
|
category: String?,
|
|
48
48
|
mode: String?,
|
|
49
49
|
options: ReadableArray?,
|
|
50
|
-
active: Boolean,
|
|
51
50
|
) {
|
|
52
51
|
// Nothing to do here
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
56
|
-
fun getDevicePreferredSampleRate(): Double =
|
|
55
|
+
fun getDevicePreferredSampleRate(): Double = MediaSessionManager.getDevicePreferredSampleRate()
|
|
56
|
+
|
|
57
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
58
|
+
fun observeAudioInterruptions(enable: Boolean) = MediaSessionManager.observeAudioInterruptions(enable)
|
|
57
59
|
|
|
58
60
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
59
|
-
fun
|
|
61
|
+
fun observeVolumeChanges(enable: Boolean) = MediaSessionManager.observeVolumeChanges(enable)
|
|
60
62
|
|
|
61
63
|
override fun getName(): String = NAME
|
|
62
64
|
}
|
|
@@ -207,7 +207,7 @@ class MediaNotificationManager(
|
|
|
207
207
|
return NotificationCompat.Action(icon, title, i)
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
class NotificationService : Service() {
|
|
211
211
|
private val binder = LocalBinder()
|
|
212
212
|
private var notification: Notification? = null
|
|
213
213
|
|
|
@@ -231,9 +231,9 @@ class MediaNotificationManager(
|
|
|
231
231
|
val intent = Intent(this, NotificationService::class.java)
|
|
232
232
|
ContextCompat.startForegroundService(this, intent)
|
|
233
233
|
notification =
|
|
234
|
-
|
|
235
|
-
.prepareNotification(NotificationCompat.Builder(this, channelId), false)
|
|
236
|
-
startForeground(notificationId, notification)
|
|
234
|
+
MediaSessionManager.mediaNotificationManager
|
|
235
|
+
.prepareNotification(NotificationCompat.Builder(this, MediaSessionManager.channelId), false)
|
|
236
|
+
startForeground(MediaSessionManager.notificationId, notification)
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
239
|
|
|
@@ -241,9 +241,9 @@ class MediaNotificationManager(
|
|
|
241
241
|
super.onCreate()
|
|
242
242
|
try {
|
|
243
243
|
notification =
|
|
244
|
-
|
|
245
|
-
.prepareNotification(NotificationCompat.Builder(this, channelId), false)
|
|
246
|
-
startForeground(notificationId, notification)
|
|
244
|
+
MediaSessionManager.mediaNotificationManager
|
|
245
|
+
.prepareNotification(NotificationCompat.Builder(this, MediaSessionManager.channelId), false)
|
|
246
|
+
startForeground(MediaSessionManager.notificationId, notification)
|
|
247
247
|
} catch (ex: Exception) {
|
|
248
248
|
Log.w("AudioManagerModule", "Error starting service: ${ex.message}")
|
|
249
249
|
}
|
|
@@ -73,6 +73,10 @@ class MediaSessionEventEmitter(
|
|
|
73
73
|
sendEvent("onInterruption", values)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
fun onVolumeChange(values: Map<String, Number>) {
|
|
77
|
+
sendEvent("onVolumeChange", values)
|
|
78
|
+
}
|
|
79
|
+
|
|
76
80
|
private fun stopForegroundService() {
|
|
77
81
|
NotificationManagerCompat.from(reactContext).cancel(notificationId)
|
|
78
82
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
@@ -18,21 +18,19 @@ import androidx.core.content.ContextCompat
|
|
|
18
18
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
19
19
|
import com.facebook.react.bridge.ReadableMap
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
) {
|
|
21
|
+
object MediaSessionManager {
|
|
22
|
+
lateinit var reactContext: ReactApplicationContext
|
|
24
23
|
val notificationId = 100
|
|
25
24
|
val channelId = "react-native-audio-api"
|
|
26
25
|
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
private
|
|
35
|
-
MediaReceiver(reactContext, this)
|
|
26
|
+
private lateinit var audioManager: AudioManager
|
|
27
|
+
lateinit var mediaSession: MediaSessionCompat
|
|
28
|
+
lateinit var mediaNotificationManager: MediaNotificationManager
|
|
29
|
+
private lateinit var lockScreenManager: LockScreenManager
|
|
30
|
+
lateinit var eventEmitter: MediaSessionEventEmitter
|
|
31
|
+
private lateinit var audioFocusListener: AudioFocusListener
|
|
32
|
+
private lateinit var volumeChangeListener: VolumeChangeListener
|
|
33
|
+
private lateinit var mediaReceiver: MediaReceiver
|
|
36
34
|
|
|
37
35
|
private val connection =
|
|
38
36
|
object : ServiceConnection {
|
|
@@ -60,13 +58,19 @@ class MediaSessionManager(
|
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
fun initialize(reactContext: ReactApplicationContext) {
|
|
62
|
+
this.reactContext = reactContext
|
|
63
|
+
this.audioManager = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
64
|
+
this.mediaSession = MediaSessionCompat(reactContext, "MediaSessionManager")
|
|
65
|
+
|
|
64
66
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
65
67
|
createChannel()
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
this.mediaNotificationManager = MediaNotificationManager(reactContext, notificationId, channelId)
|
|
69
71
|
this.lockScreenManager = LockScreenManager(reactContext, mediaSession, mediaNotificationManager, channelId)
|
|
72
|
+
this.eventEmitter = MediaSessionEventEmitter(reactContext, notificationId)
|
|
73
|
+
this.mediaReceiver = MediaReceiver(reactContext, this)
|
|
70
74
|
this.mediaSession.setCallback(MediaSessionCallback(eventEmitter, lockScreenManager))
|
|
71
75
|
|
|
72
76
|
val filter = IntentFilter()
|
|
@@ -87,6 +91,7 @@ class MediaSessionManager(
|
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
this.audioFocusListener = AudioFocusListener(audioManager, eventEmitter, lockScreenManager)
|
|
94
|
+
this.volumeChangeListener = VolumeChangeListener(audioManager, eventEmitter)
|
|
90
95
|
|
|
91
96
|
val myIntent = Intent(reactContext, MediaNotificationManager.NotificationService::class.java)
|
|
92
97
|
|
|
@@ -129,6 +134,19 @@ class MediaSessionManager(
|
|
|
129
134
|
}
|
|
130
135
|
}
|
|
131
136
|
|
|
137
|
+
fun observeVolumeChanges(observe: Boolean) {
|
|
138
|
+
if (observe) {
|
|
139
|
+
ContextCompat.registerReceiver(
|
|
140
|
+
reactContext,
|
|
141
|
+
volumeChangeListener,
|
|
142
|
+
volumeChangeListener.getIntentFilter(),
|
|
143
|
+
ContextCompat.RECEIVER_NOT_EXPORTED,
|
|
144
|
+
)
|
|
145
|
+
} else {
|
|
146
|
+
reactContext.unregisterReceiver(volumeChangeListener)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
132
150
|
@RequiresApi(Build.VERSION_CODES.O)
|
|
133
151
|
private fun createChannel() {
|
|
134
152
|
val notificationManager =
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.swmansion.audioapi.system
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.content.IntentFilter
|
|
7
|
+
import android.media.AudioManager
|
|
8
|
+
|
|
9
|
+
class VolumeChangeListener(
|
|
10
|
+
private val audioManager: AudioManager,
|
|
11
|
+
private val eventEmitter: MediaSessionEventEmitter,
|
|
12
|
+
) : BroadcastReceiver() {
|
|
13
|
+
override fun onReceive(
|
|
14
|
+
context: Context?,
|
|
15
|
+
intent: Intent?,
|
|
16
|
+
) {
|
|
17
|
+
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toDouble()
|
|
18
|
+
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toDouble()
|
|
19
|
+
eventEmitter.onVolumeChange(mapOf("value" to currentVolume / maxVolume))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fun getIntentFilter(): IntentFilter {
|
|
23
|
+
val intentFilter = IntentFilter()
|
|
24
|
+
intentFilter.addAction("android.media.VOLUME_CHANGED_ACTION")
|
|
25
|
+
return intentFilter
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
#include <audioapi/jsi/JsiPromise.h>
|
|
4
4
|
#include <audioapi/core/AudioContext.h>
|
|
5
5
|
#include <audioapi/core/OfflineAudioContext.h>
|
|
6
|
+
#include <audioapi/core/inputs/AudioRecorder.h>
|
|
6
7
|
#include <audioapi/HostObjects/AudioContextHostObject.h>
|
|
7
8
|
#include <audioapi/HostObjects/OfflineAudioContextHostObject.h>
|
|
9
|
+
#include <audioapi/HostObjects/AudioRecorderHostObject.h>
|
|
8
10
|
|
|
9
11
|
#include <memory>
|
|
10
12
|
|
|
@@ -16,11 +18,12 @@ class AudioAPIModuleInstaller {
|
|
|
16
18
|
public:
|
|
17
19
|
static void injectJSIBindings(jsi::Runtime *jsiRuntime, const std::shared_ptr<react::CallInvoker> &jsCallInvoker) {
|
|
18
20
|
auto createAudioContext = getCreateAudioContextFunction(jsiRuntime, jsCallInvoker);
|
|
21
|
+
auto createAudioRecorder = getCreateAudioRecorderFunction(jsiRuntime, jsCallInvoker);
|
|
19
22
|
auto createOfflineAudioContext = getCreateOfflineAudioContextFunction(jsiRuntime, jsCallInvoker);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
jsiRuntime->global().setProperty(
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
jsiRuntime->global().setProperty(*jsiRuntime, "createAudioContext", createAudioContext);
|
|
25
|
+
jsiRuntime->global().setProperty(*jsiRuntime, "createAudioRecorder", createAudioRecorder);
|
|
26
|
+
jsiRuntime->global().setProperty(*jsiRuntime, "createOfflineAudioContext", createOfflineAudioContext);
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
private:
|
|
@@ -60,7 +63,7 @@ class AudioAPIModuleInstaller {
|
|
|
60
63
|
auto length = static_cast<size_t>(args[1].getNumber());
|
|
61
64
|
auto sampleRate = static_cast<float>(args[2].getNumber());
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
auto offlineAudioContext = std::make_shared<OfflineAudioContext>(numberOfChannels, length, sampleRate);
|
|
64
67
|
auto audioContextHostObject = std::make_shared<OfflineAudioContextHostObject>(
|
|
65
68
|
offlineAudioContext, jsiRuntime, jsCallInvoker);
|
|
66
69
|
|
|
@@ -68,6 +71,27 @@ class AudioAPIModuleInstaller {
|
|
|
68
71
|
runtime, audioContextHostObject);
|
|
69
72
|
});
|
|
70
73
|
}
|
|
74
|
+
|
|
75
|
+
static jsi::Function getCreateAudioRecorderFunction(jsi::Runtime *jsiRuntime, const std::shared_ptr<react::CallInvoker> &jsCallInvoker) {
|
|
76
|
+
return jsi::Function::createFromHostFunction(
|
|
77
|
+
*jsiRuntime,
|
|
78
|
+
jsi::PropNameID::forAscii(*jsiRuntime, "createAudioRecorder"),
|
|
79
|
+
0,
|
|
80
|
+
[jsCallInvoker](
|
|
81
|
+
jsi::Runtime &runtime,
|
|
82
|
+
const jsi::Value &thisValue,
|
|
83
|
+
const jsi::Value *args,
|
|
84
|
+
size_t count) -> jsi::Value {
|
|
85
|
+
auto options = args[0].getObject(runtime);
|
|
86
|
+
|
|
87
|
+
auto sampleRate = static_cast<float>(options.getProperty(runtime, "sampleRate").getNumber());
|
|
88
|
+
auto bufferLength = static_cast<int>(options.getProperty(runtime, "bufferLengthInSamples").getNumber());
|
|
89
|
+
|
|
90
|
+
auto audioRecorderHostObject = std::make_shared<AudioRecorderHostObject>(&runtime, jsCallInvoker, sampleRate, bufferLength);
|
|
91
|
+
|
|
92
|
+
return jsi::Object::createFromHostObject(runtime, audioRecorderHostObject);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
71
95
|
};
|
|
72
96
|
|
|
73
97
|
} // namespace audioapi
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <jsi/jsi.h>
|
|
4
|
+
|
|
5
|
+
#include <audioapi/core/sources/AudioBuffer.h>
|
|
6
|
+
#include <audioapi/HostObjects/AudioBufferHostObject.h>
|
|
7
|
+
#include <audioapi/core/inputs/AudioRecorder.h>
|
|
8
|
+
|
|
9
|
+
#ifdef ANDROID
|
|
10
|
+
#include <audioapi/android/core/AndroidAudioRecorder.h>
|
|
11
|
+
#else
|
|
12
|
+
#include <audioapi/ios/core/IOSAudioRecorder.h>
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
#include <memory>
|
|
16
|
+
#include <utility>
|
|
17
|
+
#include <vector>
|
|
18
|
+
#include <cstdio>
|
|
19
|
+
|
|
20
|
+
namespace audioapi {
|
|
21
|
+
using namespace facebook;
|
|
22
|
+
|
|
23
|
+
class AudioRecorderHostObject : public JsiHostObject {
|
|
24
|
+
public:
|
|
25
|
+
explicit AudioRecorderHostObject(
|
|
26
|
+
jsi::Runtime *runtime,
|
|
27
|
+
const std::shared_ptr<react::CallInvoker> &callInvoker,
|
|
28
|
+
float sampleRate,
|
|
29
|
+
int bufferLength)
|
|
30
|
+
: callInvoker_(callInvoker) {
|
|
31
|
+
promiseVendor_ = std::make_shared<PromiseVendor>(runtime, callInvoker);
|
|
32
|
+
|
|
33
|
+
#ifdef ANDROID
|
|
34
|
+
audioRecorder_ = std::make_shared<AndroidAudioRecorder>(
|
|
35
|
+
sampleRate,
|
|
36
|
+
bufferLength,
|
|
37
|
+
this->getOnError(),
|
|
38
|
+
this->getOnStatusChange(),
|
|
39
|
+
this->getOnAudioReady()
|
|
40
|
+
);
|
|
41
|
+
#else
|
|
42
|
+
audioRecorder_ = std::make_shared<IOSAudioRecorder>(
|
|
43
|
+
sampleRate,
|
|
44
|
+
bufferLength,
|
|
45
|
+
this->getOnError(),
|
|
46
|
+
this->getOnStatusChange(),
|
|
47
|
+
this->getOnAudioReady()
|
|
48
|
+
);
|
|
49
|
+
#endif
|
|
50
|
+
|
|
51
|
+
addFunctions(
|
|
52
|
+
JSI_EXPORT_FUNCTION(AudioRecorderHostObject, start),
|
|
53
|
+
JSI_EXPORT_FUNCTION(AudioRecorderHostObject, stop),
|
|
54
|
+
JSI_EXPORT_FUNCTION(AudioRecorderHostObject, onAudioReady),
|
|
55
|
+
JSI_EXPORT_FUNCTION(AudioRecorderHostObject, onError),
|
|
56
|
+
JSI_EXPORT_FUNCTION(AudioRecorderHostObject, onStatusChange));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
~AudioRecorderHostObject() override {
|
|
60
|
+
errorCallback_ = nullptr;
|
|
61
|
+
audioReadyCallback_ = nullptr;
|
|
62
|
+
statusChangeCallback_ = nullptr;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
JSI_HOST_FUNCTION(start) {
|
|
66
|
+
audioRecorder_->start();
|
|
67
|
+
|
|
68
|
+
return jsi::Value::undefined();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
JSI_HOST_FUNCTION(stop) {
|
|
72
|
+
audioRecorder_->stop();
|
|
73
|
+
|
|
74
|
+
return jsi::Value::undefined();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
JSI_HOST_FUNCTION(onAudioReady) {
|
|
78
|
+
audioReadyCallback_ = std::make_unique<jsi::Function>(args[0].getObject(runtime).getFunction(runtime));
|
|
79
|
+
|
|
80
|
+
return jsi::Value::undefined();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
JSI_HOST_FUNCTION(onError) {
|
|
84
|
+
errorCallback_ = std::make_unique<jsi::Function>(args[0].getObject(runtime).getFunction(runtime));
|
|
85
|
+
|
|
86
|
+
return jsi::Value::undefined();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
JSI_HOST_FUNCTION(onStatusChange) {
|
|
90
|
+
statusChangeCallback_ = std::make_unique<jsi::Function>(args[0].getObject(runtime).getFunction(runtime));
|
|
91
|
+
|
|
92
|
+
return jsi::Value::undefined();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
protected:
|
|
96
|
+
std::shared_ptr<AudioRecorder> audioRecorder_;
|
|
97
|
+
std::shared_ptr<PromiseVendor> promiseVendor_;
|
|
98
|
+
std::shared_ptr<react::CallInvoker> callInvoker_;
|
|
99
|
+
|
|
100
|
+
std::unique_ptr<jsi::Function> errorCallback_;
|
|
101
|
+
std::unique_ptr<jsi::Function> audioReadyCallback_;
|
|
102
|
+
std::unique_ptr<jsi::Function> statusChangeCallback_;
|
|
103
|
+
|
|
104
|
+
std::function<void(std::shared_ptr<AudioBus>, int, double)> getOnAudioReady() {
|
|
105
|
+
return [this](const std::shared_ptr<AudioBus> &bus, int numFrames, double when) {
|
|
106
|
+
if (audioReadyCallback_ == nullptr) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
callInvoker_->invokeAsync([this, bus = bus, numFrames, when](jsi::Runtime &runtime) {
|
|
111
|
+
auto buffer = std::make_shared<AudioBuffer>(bus);
|
|
112
|
+
auto bufferHostObject = std::make_shared<AudioBufferHostObject>(buffer);
|
|
113
|
+
|
|
114
|
+
audioReadyCallback_->call(
|
|
115
|
+
runtime,
|
|
116
|
+
jsi::Object::createFromHostObject(runtime, bufferHostObject),
|
|
117
|
+
jsi::Value(numFrames),
|
|
118
|
+
jsi::Value(when)
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
std::function<void(void)> getOnError() {
|
|
125
|
+
return [this]() {
|
|
126
|
+
if (errorCallback_ == nullptr) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
callInvoker_->invokeAsync([this](jsi::Runtime &runtime) {
|
|
131
|
+
errorCallback_->call(runtime);
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
std::function<void(void)> getOnStatusChange() {
|
|
137
|
+
return [this]() {
|
|
138
|
+
if (statusChangeCallback_ == nullptr) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
callInvoker_->invokeAsync([this](jsi::Runtime &runtime) {
|
|
143
|
+
statusChangeCallback_->call(runtime);
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
} // namespace audioapi
|