react-native-audio-api 0.6.0-rc.1 → 0.6.0-rc.3

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.
Files changed (90) hide show
  1. package/README.md +35 -22
  2. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +73 -0
  3. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.h +37 -0
  4. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +6 -10
  5. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.h +2 -3
  6. package/android/src/main/java/com/swmansion/audioapi/AudioManagerModule.kt +10 -8
  7. package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +7 -7
  8. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionEventEmitter.kt +4 -0
  9. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +31 -13
  10. package/android/src/main/java/com/swmansion/audioapi/system/VolumeChangeListener.kt +27 -0
  11. package/common/cpp/audioapi/AudioAPIModuleInstaller.h +29 -5
  12. package/common/cpp/audioapi/HostObjects/AnalyserNodeHostObject.h +1 -0
  13. package/common/cpp/audioapi/HostObjects/AudioRecorderHostObject.h +149 -0
  14. package/common/cpp/audioapi/core/AudioContext.cpp +4 -3
  15. package/common/cpp/audioapi/core/BaseAudioContext.cpp +6 -6
  16. package/common/cpp/audioapi/core/inputs/AudioRecorder.h +38 -0
  17. package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h +5 -0
  18. package/common/cpp/audioapi/core/sources/OscillatorNode.cpp +1 -1
  19. package/common/cpp/audioapi/core/utils/AudioNodeManager.cpp +45 -11
  20. package/common/cpp/audioapi/core/utils/AudioNodeManager.h +6 -2
  21. package/ios/audioapi/ios/AudioManagerModule.mm +9 -10
  22. package/ios/audioapi/ios/core/IOSAudioPlayer.h +11 -12
  23. package/ios/audioapi/ios/core/IOSAudioPlayer.mm +22 -16
  24. package/ios/audioapi/ios/core/IOSAudioRecorder.h +36 -0
  25. package/ios/audioapi/ios/core/IOSAudioRecorder.mm +64 -0
  26. package/ios/audioapi/ios/core/{AudioPlayer.h → NativeAudioPlayer.h} +1 -8
  27. package/ios/audioapi/ios/core/{AudioPlayer.m → NativeAudioPlayer.m} +4 -33
  28. package/ios/audioapi/ios/core/NativeAudioRecorder.h +31 -0
  29. package/ios/audioapi/ios/core/NativeAudioRecorder.m +107 -0
  30. package/ios/audioapi/ios/system/AudioEngine.h +7 -1
  31. package/ios/audioapi/ios/system/AudioEngine.mm +64 -20
  32. package/ios/audioapi/ios/system/AudioSessionManager.h +3 -1
  33. package/ios/audioapi/ios/system/AudioSessionManager.mm +37 -25
  34. package/ios/audioapi/ios/system/NotificationManager.h +7 -1
  35. package/ios/audioapi/ios/system/NotificationManager.mm +30 -0
  36. package/lib/commonjs/api.js +15 -1
  37. package/lib/commonjs/api.js.map +1 -1
  38. package/lib/commonjs/core/AudioRecorder.js +51 -0
  39. package/lib/commonjs/core/AudioRecorder.js.map +1 -0
  40. package/lib/commonjs/errors/NotSupportedError.js.map +1 -1
  41. package/lib/commonjs/hooks/useSytemVolume.js +24 -0
  42. package/lib/commonjs/hooks/useSytemVolume.js.map +1 -0
  43. package/lib/commonjs/plugin/withAudioAPI.js +1 -1
  44. package/lib/commonjs/plugin/withAudioAPI.js.map +1 -1
  45. package/lib/commonjs/specs/NativeAudioManagerModule.js +5 -2
  46. package/lib/commonjs/specs/NativeAudioManagerModule.js.map +1 -1
  47. package/lib/commonjs/system/AudioManager.js +23 -50
  48. package/lib/commonjs/system/AudioManager.js.map +1 -1
  49. package/lib/module/api.js +3 -1
  50. package/lib/module/api.js.map +1 -1
  51. package/lib/module/core/AudioRecorder.js +45 -0
  52. package/lib/module/core/AudioRecorder.js.map +1 -0
  53. package/lib/module/errors/NotSupportedError.js.map +1 -1
  54. package/lib/module/hooks/useSytemVolume.js +19 -0
  55. package/lib/module/hooks/useSytemVolume.js.map +1 -0
  56. package/lib/module/plugin/withAudioAPI.js +1 -1
  57. package/lib/module/plugin/withAudioAPI.js.map +1 -1
  58. package/lib/module/specs/NativeAudioManagerModule.js +5 -2
  59. package/lib/module/specs/NativeAudioManagerModule.js.map +1 -1
  60. package/lib/module/system/AudioManager.js +23 -50
  61. package/lib/module/system/AudioManager.js.map +1 -1
  62. package/lib/typescript/api.d.ts +5 -1
  63. package/lib/typescript/api.d.ts.map +1 -1
  64. package/lib/typescript/core/AudioRecorder.d.ts +22 -0
  65. package/lib/typescript/core/AudioRecorder.d.ts.map +1 -0
  66. package/lib/typescript/errors/NotSupportedError.d.ts.map +1 -1
  67. package/lib/typescript/hooks/useSytemVolume.d.ts +2 -0
  68. package/lib/typescript/hooks/useSytemVolume.d.ts.map +1 -0
  69. package/lib/typescript/interfaces.d.ts +11 -5
  70. package/lib/typescript/interfaces.d.ts.map +1 -1
  71. package/lib/typescript/plugin/withAudioAPI.d.ts.map +1 -1
  72. package/lib/typescript/specs/NativeAudioManagerModule.d.ts +2 -1
  73. package/lib/typescript/specs/NativeAudioManagerModule.d.ts.map +1 -1
  74. package/lib/typescript/system/AudioManager.d.ts +4 -3
  75. package/lib/typescript/system/AudioManager.d.ts.map +1 -1
  76. package/lib/typescript/system/types.d.ts +26 -7
  77. package/lib/typescript/system/types.d.ts.map +1 -1
  78. package/lib/typescript/types.d.ts +5 -0
  79. package/lib/typescript/types.d.ts.map +1 -1
  80. package/package.json +2 -1
  81. package/src/api.ts +13 -2
  82. package/src/core/AudioRecorder.ts +81 -0
  83. package/src/hooks/useSytemVolume.ts +19 -0
  84. package/src/interfaces.ts +25 -11
  85. package/src/plugin/withAudioAPI.ts +2 -1
  86. package/src/specs/NativeAudioManagerModule.ts +5 -8
  87. package/src/system/AudioManager.ts +30 -107
  88. package/src/system/types.ts +31 -21
  89. package/src/types.ts +13 -0
  90. /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 Audio API
3
+ ### High-performance audio engine for React Native based on web audio api specification
4
+
5
+ [![NPM latest](https://img.shields.io/npm/v/react-native-audio-api/latest)](https://www.npmjs.com/package/react-native-audio-api)
6
+ [![NPM next](https://img.shields.io/npm/v/react-native-audio-api/next)](https://www.npmjs.com/package/react-native-audio-api?activeTab=versions)
7
+ [![github ci](https://img.shields.io/github/actions/workflow/status/software-mansion/react-native-audio-api/ci.yml)](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
- 1. Install `react-native-audio-api` library
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
- ```bash
13
- # using npm
14
- npm install react-native-audio-api
16
+ ## Roadmap
15
17
 
16
- # or using yarn
17
- yarn add react-native-audio-api
18
- ```
18
+ - <sub>[![Released in 0.1.0](https://img.shields.io/badge/Released_in-0.1.0-green)](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
- ## Usage with expo
22
+ - <sub>[![Released in 0.2.0](https://img.shields.io/badge/Released_in-0.2.0-green)](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>[![Released in 0.3.2](https://img.shields.io/badge/Released_in-0.3.2-green)](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
- `react-native-audio-api` contains native custom code and isn't part of the Expo Go application. In order to be available in expo managed builds, you have to use Expo development build. Simplest way on starting local expo dev builds, is to use:
29
+ - <sub>[![Released in 0.4.0](https://img.shields.io/badge/Released_in-0.4.0-green)](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
- ```bash
25
- # Build native iOS project
26
- npx expo run:ios
27
- # Build native Android project
28
- npx expo run:android
29
- ```
33
+ - <sub>[![Released in 0.5.0](https://img.shields.io/badge/Released_in-0.5.0-green)](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
- To learn more about expo development builds, please check out [Development Builds Documentation](https://docs.expo.dev/develop/development-builds/introduction/).
37
+ - <sub>![Coming in 0.6.0](https://img.shields.io/badge/Coming_in-0.6.0-blue)</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
- ## Documentation
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
- Check out our dedicated documentation page for info about this library, API reference and more:
36
- [https://software-mansion.github.io/react-native-audio-api/](https://software-mansion.github.io/react-native-audio-api/)
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
- You can also check out [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) for fully detailed description of the audio api.
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
- float AudioPlayer::getSampleRate() const {
31
- return sampleRate_;
30
+ void AudioPlayer::start() {
31
+ if (mStream_) {
32
+ mStream_->requestStart();
33
+ }
32
34
  }
33
35
 
34
- void AudioPlayer::start() {
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::resume() {
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 suspend();
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
- mediaSessionManager.setLockScreenInfo(info)
29
+ MediaSessionManager.setLockScreenInfo(info)
30
30
  }
31
31
 
32
32
  @ReactMethod(isBlockingSynchronousMethod = true)
33
33
  fun resetLockScreenInfo() {
34
- mediaSessionManager.resetLockScreenInfo()
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
- mediaSessionManager.enableRemoteCommand(name, enabled)
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 = mediaSessionManager.getDevicePreferredSampleRate()
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 observeAudioInterruptions(enable: Boolean) = mediaSessionManager.observeAudioInterruptions(enable)
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
- inner class NotificationService : Service() {
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
- MediaNotificationManager(reactContext, notificationId, channelId)
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
- MediaNotificationManager(reactContext, notificationId, channelId)
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
- class MediaSessionManager(
22
- val reactContext: ReactApplicationContext,
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 val audioManager: AudioManager = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
28
- val mediaSession: MediaSessionCompat = MediaSessionCompat(reactContext, "MediaSessionManager")
29
- val mediaNotificationManager: MediaNotificationManager
30
- private val lockScreenManager: LockScreenManager
31
- val eventEmitter: MediaSessionEventEmitter =
32
- MediaSessionEventEmitter(reactContext, notificationId)
33
- private val audioFocusListener: AudioFocusListener
34
- private val mediaReceiver: MediaReceiver =
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
- init {
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
- jsiRuntime->global().setProperty(
21
- *jsiRuntime, "createAudioContext", createAudioContext);
22
- jsiRuntime->global().setProperty(
23
- *jsiRuntime, "createOfflineAudioContext", createOfflineAudioContext);
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
- std::shared_ptr<OfflineAudioContext> offlineAudioContext = std::make_shared<OfflineAudioContext>(numberOfChannels, length, sampleRate);
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
@@ -145,4 +145,5 @@ class AnalyserNodeHostObject : public AudioNodeHostObject {
145
145
  analyserNode->setWindowType(value.getString(runtime).utf8(runtime));
146
146
  }
147
147
  };
148
+
148
149
  } // 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