react-native-audio-api 0.12.0 → 0.12.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.
Files changed (110) hide show
  1. package/README.md +8 -3
  2. package/RNAudioAPI.podspec +0 -2
  3. package/android/src/main/cpp/audioapi/CMakeLists.txt +8 -2
  4. package/android/src/main/cpp/audioapi/android/AudioAPIModule.cpp +7 -29
  5. package/android/src/main/cpp/audioapi/android/JniEventPayloadParser.cpp +83 -0
  6. package/android/src/main/cpp/audioapi/android/JniEventPayloadParser.h +14 -0
  7. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +5 -4
  8. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +1 -0
  9. package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp +37 -21
  10. package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h +1 -1
  11. package/common/cpp/audioapi/AudioAPIModuleInstaller.h +21 -0
  12. package/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.cpp +26 -4
  13. package/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.h +1 -0
  14. package/common/cpp/audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.cpp +2 -2
  15. package/common/cpp/audioapi/HostObjects/utils/AudioDecoderHostObject.cpp +3 -3
  16. package/common/cpp/audioapi/HostObjects/utils/AudioFileUtilsHostObject.cpp +60 -0
  17. package/common/cpp/audioapi/HostObjects/utils/AudioFileUtilsHostObject.h +24 -0
  18. package/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +2 -2
  19. package/common/cpp/audioapi/core/OfflineAudioContext.cpp +0 -1
  20. package/common/cpp/audioapi/core/OfflineAudioContext.h +0 -1
  21. package/common/cpp/audioapi/core/effects/ConvolverNode.cpp +4 -10
  22. package/common/cpp/audioapi/core/effects/ConvolverNode.h +0 -4
  23. package/common/cpp/audioapi/core/effects/WorkletProcessingNode.cpp +1 -1
  24. package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp +13 -11
  25. package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.h +5 -9
  26. package/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.cpp +47 -139
  27. package/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h +11 -8
  28. package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp +29 -114
  29. package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h +6 -8
  30. package/common/cpp/audioapi/core/sources/AudioFileSourceNode.cpp +9 -11
  31. package/common/cpp/audioapi/core/sources/AudioFileSourceNode.h +1 -1
  32. package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +2 -2
  33. package/common/cpp/audioapi/core/types/AudioFormat.h +3 -1
  34. package/common/cpp/audioapi/core/utils/AudioDecoder.cpp +120 -91
  35. package/common/cpp/audioapi/core/utils/AudioDecoder.h +24 -101
  36. package/common/cpp/audioapi/core/utils/AudioFileConcatenator.cpp +862 -0
  37. package/common/cpp/audioapi/core/utils/AudioFileConcatenator.h +164 -0
  38. package/common/cpp/audioapi/core/utils/AudioFileWriter.cpp +2 -4
  39. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.cpp +7 -12
  40. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +2 -0
  41. package/common/cpp/audioapi/core/utils/buffer/BufferProcessingDirection.h +6 -0
  42. package/common/cpp/audioapi/core/utils/buffer/BufferProcessorBase.cpp +110 -0
  43. package/common/cpp/audioapi/core/utils/buffer/BufferProcessorBase.h +75 -0
  44. package/common/cpp/audioapi/core/utils/buffer/QueueBufferProcessor.cpp +129 -0
  45. package/common/cpp/audioapi/core/utils/buffer/QueueBufferProcessor.h +55 -0
  46. package/common/cpp/audioapi/core/utils/buffer/SingleBufferProcessor.cpp +95 -0
  47. package/common/cpp/audioapi/core/utils/buffer/SingleBufferProcessor.h +52 -0
  48. package/common/cpp/audioapi/core/utils/param/ParamControlQueue.cpp +12 -8
  49. package/common/cpp/audioapi/events/AudioEventHandlerRegistry.cpp +65 -157
  50. package/common/cpp/audioapi/events/AudioEventHandlerRegistry.h +52 -33
  51. package/common/cpp/audioapi/events/AudioEventPayload.h +87 -0
  52. package/common/cpp/audioapi/events/IAudioEventHandlerRegistry.h +12 -12
  53. package/common/cpp/audioapi/libs/decoding/IncrementalAudioDecoder.h +12 -10
  54. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.cpp +152 -78
  55. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.h +6 -6
  56. package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.cpp +34 -20
  57. package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.h +4 -4
  58. package/common/cpp/audioapi/utils/AudioArray.hpp +6 -1
  59. package/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp +1 -1
  60. package/common/cpp/audioapi/utils/TaskOffloader.hpp +3 -5
  61. package/ios/audioapi/ios/AudioAPIModule.h +2 -1
  62. package/ios/audioapi/ios/AudioAPIModule.mm +4 -25
  63. package/ios/audioapi/ios/core/IOSAudioPlayer.h +16 -2
  64. package/ios/audioapi/ios/core/IOSAudioPlayer.mm +90 -24
  65. package/ios/audioapi/ios/core/IOSAudioRecorder.mm +1 -1
  66. package/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm +64 -20
  67. package/ios/audioapi/ios/system/AudioSessionManager.mm +18 -7
  68. package/ios/audioapi/ios/system/SystemNotificationManager.mm +22 -22
  69. package/ios/audioapi/ios/system/notification/PlaybackNotification.mm +10 -13
  70. package/lib/commonjs/AudioAPIModule/AudioAPIModule.js +1 -1
  71. package/lib/commonjs/AudioAPIModule/AudioAPIModule.js.map +1 -1
  72. package/lib/commonjs/api.js +8 -0
  73. package/lib/commonjs/api.js.map +1 -1
  74. package/lib/commonjs/api.web.js +6 -1
  75. package/lib/commonjs/api.web.js.map +1 -1
  76. package/lib/commonjs/core/AudioFileUtils.js +35 -0
  77. package/lib/commonjs/core/AudioFileUtils.js.map +1 -0
  78. package/lib/commonjs/mock/index.js +15 -1
  79. package/lib/commonjs/mock/index.js.map +1 -1
  80. package/lib/module/AudioAPIModule/AudioAPIModule.js +1 -1
  81. package/lib/module/AudioAPIModule/AudioAPIModule.js.map +1 -1
  82. package/lib/module/api.js +1 -0
  83. package/lib/module/api.js.map +1 -1
  84. package/lib/module/api.web.js +4 -1
  85. package/lib/module/api.web.js.map +1 -1
  86. package/lib/module/core/AudioFileUtils.js +31 -0
  87. package/lib/module/core/AudioFileUtils.js.map +1 -0
  88. package/lib/module/mock/index.js +14 -1
  89. package/lib/module/mock/index.js.map +1 -1
  90. package/lib/typescript/AudioAPIModule/AudioAPIModule.d.ts.map +1 -1
  91. package/lib/typescript/api.d.ts +2 -0
  92. package/lib/typescript/api.d.ts.map +1 -1
  93. package/lib/typescript/api.web.d.ts +3 -1
  94. package/lib/typescript/api.web.d.ts.map +1 -1
  95. package/lib/typescript/core/AudioFileUtils.d.ts +2 -0
  96. package/lib/typescript/core/AudioFileUtils.d.ts.map +1 -0
  97. package/lib/typescript/interfaces.d.ts +3 -0
  98. package/lib/typescript/interfaces.d.ts.map +1 -1
  99. package/lib/typescript/mock/index.d.ts +3 -1
  100. package/lib/typescript/mock/index.d.ts.map +1 -1
  101. package/package.json +10 -4
  102. package/scripts/download-prebuilt-binaries.sh +34 -1
  103. package/scripts/validate-worklets-version.js +1 -1
  104. package/src/AudioAPIModule/AudioAPIModule.ts +1 -0
  105. package/src/AudioAPIModule/globals.d.ts +3 -0
  106. package/src/api.ts +2 -0
  107. package/src/api.web.ts +10 -2
  108. package/src/core/AudioFileUtils.ts +49 -0
  109. package/src/interfaces.ts +7 -0
  110. package/src/mock/index.ts +29 -0
@@ -39,7 +39,7 @@ struct MemoryIOContext {
39
39
  * 2) readPcmFrames repeatedly; 0 returned = end of stream
40
40
  * 3) close when done
41
41
  */
42
- class FFmpegDecoder : public decoding::IIncrementalAudioDecoder {
42
+ class FFmpegDecoder : public decoding::IncrementalAudioDecoder {
43
43
  public:
44
44
  FFmpegDecoder() = default;
45
45
  FFmpegDecoder(const FFmpegDecoder &) = delete;
@@ -48,11 +48,11 @@ class FFmpegDecoder : public decoding::IIncrementalAudioDecoder {
48
48
  FFmpegDecoder &operator=(FFmpegDecoder &&other) = delete;
49
49
  ~FFmpegDecoder() override;
50
50
 
51
- [[nodiscard]] bool openFile(
51
+ [[nodiscard]] decoding::DecoderResult openFile(
52
52
  int outputSampleRate,
53
53
  const std::string &path) override;
54
54
 
55
- [[nodiscard]] bool openMemory(
55
+ [[nodiscard]] decoding::DecoderResult openMemory(
56
56
  int outputSampleRate,
57
57
  const void *data,
58
58
  size_t size) override;
@@ -71,11 +71,11 @@ class FFmpegDecoder : public decoding::IIncrementalAudioDecoder {
71
71
 
72
72
  [[nodiscard]] float getCurrentPositionInSeconds() const override;
73
73
 
74
- [[nodiscard]] bool seekToTime(double seconds) override;
74
+ [[nodiscard]] decoding::DecoderResult seekToTime(double seconds) override;
75
75
 
76
76
  private:
77
- bool setupSwr();
78
- bool feedPipeline();
77
+ [[nodiscard]] decoding::DecoderResult setupSwr();
78
+ [[nodiscard]] decoding::DecoderResult feedPipeline();
79
79
  void appendFrameResampled(AVFrame *frame);
80
80
 
81
81
  AVFormatContext *fmt_ctx_ = nullptr;
@@ -7,20 +7,31 @@
7
7
  #include <algorithm>
8
8
  #include <cmath>
9
9
  #include <cstddef>
10
+ #include <string>
10
11
  #include <vector>
11
12
 
12
13
  namespace audioapi::miniaudio_decoder {
13
14
 
14
15
  namespace {
15
16
 
17
+ std::string parseMiniAudioError(ma_result result) {
18
+ const char *description = ma_result_description(result);
19
+ if (description == nullptr) {
20
+ return "Unknown MiniAudio error (" + std::to_string(result) + ")";
21
+ }
22
+ return std::string(description) + " (" + std::to_string(result) + ")";
23
+ }
24
+
16
25
  ma_decoder_config makeDecoderConfig(const int outputSampleRate) {
17
26
  const ma_uint32 outRate =
18
27
  outputSampleRate > 0 ? static_cast<ma_uint32>(outputSampleRate) : 0;
19
28
  ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 0, outRate);
29
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
20
30
  static ma_decoding_backend_vtable *customBackends[] = {
21
31
  ma_decoding_backend_libvorbis,
22
32
  ma_decoding_backend_libopus,
23
33
  };
34
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
24
35
  config.ppCustomBackendVTables = customBackends;
25
36
  config.customBackendCount =
26
37
  sizeof(customBackends) / sizeof(customBackends[0]);
@@ -79,12 +90,12 @@ float MiniAudioDecoder::getCurrentPositionInSeconds() const {
79
90
  static_cast<double>(outputSampleRate_));
80
91
  }
81
92
 
82
- bool MiniAudioDecoder::openFile(
93
+ decoding::DecoderResult MiniAudioDecoder::openFile(
83
94
  int outputSampleRate,
84
95
  const std::string &path) {
85
96
  close();
86
97
  if (path.empty()) {
87
- return false;
98
+ return Err("MiniAudioDecoder::openFile failed: path is empty");
88
99
  }
89
100
 
90
101
  ma_decoder_config config = makeDecoderConfig(outputSampleRate);
@@ -93,7 +104,7 @@ bool MiniAudioDecoder::openFile(
93
104
  ma_decoder_init_file(path.c_str(), &config, decoder_.get());
94
105
  if (result != MA_SUCCESS) {
95
106
  teardownDecoder();
96
- return false;
107
+ return Err("MiniAudioDecoder::openFile failed: " + parseMiniAudioError(result));
97
108
  }
98
109
 
99
110
  outputChannels_ = static_cast<int>(decoder_->outputChannels);
@@ -106,16 +117,16 @@ bool MiniAudioDecoder::openFile(
106
117
  totalLengthFrames_ = 0;
107
118
  }
108
119
  totalOutputFrames_ = 0;
109
- return true;
120
+ return Ok(None);
110
121
  }
111
122
 
112
- bool MiniAudioDecoder::openMemory(
123
+ decoding::DecoderResult MiniAudioDecoder::openMemory(
113
124
  int outputSampleRate,
114
125
  const void *data,
115
126
  size_t size) {
116
127
  close();
117
128
  if (data == nullptr || size == 0) {
118
- return false;
129
+ return Err("MiniAudioDecoder::openMemory failed: input data is empty");
119
130
  }
120
131
  memoryCopy_.assign(
121
132
  static_cast<const uint8_t *>(data),
@@ -130,7 +141,7 @@ bool MiniAudioDecoder::openMemory(
130
141
  decoder_.get());
131
142
  if (result != MA_SUCCESS) {
132
143
  teardownDecoder();
133
- return false;
144
+ return Err("MiniAudioDecoder::openMemory failed: " + parseMiniAudioError(result));
134
145
  }
135
146
 
136
147
  outputChannels_ = static_cast<int>(decoder_->outputChannels);
@@ -143,7 +154,7 @@ bool MiniAudioDecoder::openMemory(
143
154
  totalLengthFrames_ = 0;
144
155
  }
145
156
  totalOutputFrames_ = 0;
146
- return true;
157
+ return Ok(None);
147
158
  }
148
159
 
149
160
  size_t MiniAudioDecoder::readPcmFrames(float *outInterleaved, size_t frameCount) {
@@ -160,9 +171,9 @@ size_t MiniAudioDecoder::readPcmFrames(float *outInterleaved, size_t frameCount)
160
171
  return static_cast<size_t>(framesRead);
161
172
  }
162
173
 
163
- bool MiniAudioDecoder::seekToTime(double seconds) {
174
+ decoding::DecoderResult MiniAudioDecoder::seekToTime(double seconds) {
164
175
  if (!isOpen() || outputSampleRate_ <= 0) {
165
- return false;
176
+ return Err("MiniAudioDecoder::seekToTime failed: decoder is not open");
166
177
  }
167
178
  const float dur = getDurationInSeconds();
168
179
  if (dur > 0 && std::isfinite(dur)) {
@@ -170,17 +181,18 @@ bool MiniAudioDecoder::seekToTime(double seconds) {
170
181
  } else {
171
182
  seconds = std::max(0.0, seconds);
172
183
  if (!std::isfinite(seconds)) {
173
- return false;
184
+ return Err("MiniAudioDecoder::seekToTime failed: seconds is not finite");
174
185
  }
175
186
  }
176
187
 
177
188
  const ma_uint64 frame =
178
189
  static_cast<ma_uint64>(std::llround(seconds * static_cast<double>(outputSampleRate_)));
179
- if (ma_decoder_seek_to_pcm_frame(decoder_.get(), frame) != MA_SUCCESS) {
180
- return false;
190
+ const ma_result result = ma_decoder_seek_to_pcm_frame(decoder_.get(), frame);
191
+ if (result != MA_SUCCESS) {
192
+ return Err("MiniAudioDecoder::seekToTime failed: " + parseMiniAudioError(result));
181
193
  }
182
194
  totalOutputFrames_ = static_cast<size_t>(frame);
183
- return true;
195
+ return Ok(None);
184
196
  }
185
197
 
186
198
  namespace {
@@ -203,14 +215,15 @@ std::shared_ptr<AudioBuffer> buildAudioBufferFromInterleaved(
203
215
 
204
216
  std::shared_ptr<AudioBuffer> decodeWithFilePath(const std::string &path, int sample_rate) {
205
217
  MiniAudioDecoder dec;
206
- if (!dec.openFile(sample_rate, path)) {
218
+ const auto openResult = dec.openFile(sample_rate, path);
219
+ if (openResult.is_err()) {
207
220
  return nullptr;
208
221
  }
209
222
  const int ch = std::max(1, dec.outputChannels());
210
223
  std::vector<float> acc;
211
- std::vector<float> tmp(decoding::IIncrementalAudioDecoder::CHUNK_SIZE * static_cast<size_t>(ch));
224
+ std::vector<float> tmp(decoding::IncrementalAudioDecoder::CHUNK_SIZE * static_cast<size_t>(ch));
212
225
  while (true) {
213
- const size_t n = dec.readPcmFrames(tmp.data(), decoding::IIncrementalAudioDecoder::CHUNK_SIZE);
226
+ const size_t n = dec.readPcmFrames(tmp.data(), decoding::IncrementalAudioDecoder::CHUNK_SIZE);
214
227
  if (n == 0) {
215
228
  break;
216
229
  }
@@ -224,14 +237,15 @@ std::shared_ptr<AudioBuffer> decodeWithFilePath(const std::string &path, int sam
224
237
 
225
238
  std::shared_ptr<AudioBuffer> decodeWithMemoryBlock(const void *data, size_t size, int sample_rate) {
226
239
  MiniAudioDecoder dec;
227
- if (!dec.openMemory(sample_rate, data, size)) {
240
+ const auto openResult = dec.openMemory(sample_rate, data, size);
241
+ if (openResult.is_err()) {
228
242
  return nullptr;
229
243
  }
230
244
  const int ch = std::max(1, dec.outputChannels());
231
245
  std::vector<float> acc;
232
- std::vector<float> tmp(decoding::IIncrementalAudioDecoder::CHUNK_SIZE * static_cast<size_t>(ch));
246
+ std::vector<float> tmp(decoding::IncrementalAudioDecoder::CHUNK_SIZE * static_cast<size_t>(ch));
233
247
  while (true) {
234
- const size_t n = dec.readPcmFrames(tmp.data(), decoding::IIncrementalAudioDecoder::CHUNK_SIZE);
248
+ const size_t n = dec.readPcmFrames(tmp.data(), decoding::IncrementalAudioDecoder::CHUNK_SIZE);
235
249
  if (n == 0) {
236
250
  break;
237
251
  }
@@ -17,16 +17,16 @@ namespace audioapi::miniaudio_decoder {
17
17
  * MiniAudio-backed incremental decoder (Vorbis/Opus/WAV, etc. via ma_decoder + custom backends).
18
18
  * Same usage contract as ffmpegdecoder::FFmpegDecoder.
19
19
  */
20
- class MiniAudioDecoder : public decoding::IIncrementalAudioDecoder {
20
+ class MiniAudioDecoder : public decoding::IncrementalAudioDecoder {
21
21
  public:
22
22
  MiniAudioDecoder();
23
23
  ~MiniAudioDecoder() override;
24
24
  DELETE_COPY_AND_MOVE(MiniAudioDecoder);
25
25
 
26
- [[nodiscard]] bool openFile(
26
+ [[nodiscard]] decoding::DecoderResult openFile(
27
27
  int outputSampleRate,
28
28
  const std::string &path) override;
29
- [[nodiscard]] bool openMemory(
29
+ [[nodiscard]] decoding::DecoderResult openMemory(
30
30
  int outputSampleRate,
31
31
  const void *data,
32
32
  size_t size) override;
@@ -37,7 +37,7 @@ class MiniAudioDecoder : public decoding::IIncrementalAudioDecoder {
37
37
  [[nodiscard]] int outputSampleRate() const override;
38
38
  [[nodiscard]] float getDurationInSeconds() const override;
39
39
  [[nodiscard]] float getCurrentPositionInSeconds() const override;
40
- [[nodiscard]] bool seekToTime(double seconds) override;
40
+ [[nodiscard]] decoding::DecoderResult seekToTime(double seconds) override;
41
41
 
42
42
  private:
43
43
  void teardownDecoder();
@@ -195,13 +195,18 @@ class AlignedAudioArray {
195
195
 
196
196
  /// @brief Copies source array in reverse order into this array.
197
197
  /// @note Assumes source and this are in distinct, non-overlapping memory locations.
198
+ /// @note Precondition: length > 0. Reads source[sourceStart .. sourceStart-length+1].
198
199
  template <size_t OtherAlignment>
199
200
  void copyReverse(
200
201
  const AlignedAudioArray<OtherAlignment> &source,
201
202
  size_t sourceStart,
202
203
  size_t destinationStart,
203
204
  size_t length) {
204
- if (size_ - destinationStart < length || source.size_ - sourceStart < length) [[unlikely]] {
205
+ // Reverse read walks from sourceStart down to sourceStart-length+1, so the source
206
+ // precondition is sourceStart < source.size_ on the high end and sourceStart+1 >= length
207
+ // on the low end. Caller must also pass length > 0.
208
+ if (size_ - destinationStart < length || sourceStart + 1 < length ||
209
+ sourceStart >= source.size_) [[unlikely]] {
205
210
  throw std::out_of_range("Not enough space to copy to destination or from source.");
206
211
  }
207
212
 
@@ -23,7 +23,7 @@ using namespace channels::spsc;
23
23
  /// In this setup no locking happens and modifications can be seen by Audio thread.
24
24
  /// @note it is intended to be used for two threads one which schedules events and one which processes them
25
25
  /// @note it is not safe to be copied across two threads use std::shared_ptr if you need to share data
26
- template <typename T, int FunctionSize = 64>
26
+ template <typename T, int FunctionSize = 72>
27
27
  class CrossThreadEventScheduler {
28
28
  using EventType = FatFunction<FunctionSize, void(T &)>;
29
29
 
@@ -1,5 +1,6 @@
1
1
  #pragma once
2
2
 
3
+ #include <audioapi/utils/Macros.h>
3
4
  #include <audioapi/utils/SpscChannel.hpp>
4
5
  #include <cassert>
5
6
  #include <concepts>
@@ -16,7 +17,7 @@ namespace audioapi::task_offloader {
16
17
  template <std::default_initializable T, OverflowStrategy Strategy, WaitStrategy Wait>
17
18
  class TaskOffloader {
18
19
  public:
19
- template <typename Func>
20
+ template <std::invocable<T> Func>
20
21
  explicit TaskOffloader(size_t capacity, Func &&task) : shouldRun_(true) {
21
22
  auto [sender, receiver] = channels::spsc::channel<T, Strategy, Wait>(capacity);
22
23
  sender_ = std::move(sender);
@@ -33,10 +34,7 @@ class TaskOffloader {
33
34
  }
34
35
 
35
36
  // delete other functions
36
- TaskOffloader(const TaskOffloader &) = delete;
37
- TaskOffloader &operator=(const TaskOffloader &) = delete;
38
- TaskOffloader(TaskOffloader &&other) = delete;
39
- TaskOffloader &operator=(TaskOffloader &&other) = delete;
37
+ DELETE_COPY_AND_MOVE(TaskOffloader);
40
38
 
41
39
  ~TaskOffloader() {
42
40
  shouldRun_.store(false, std::memory_order_release);
@@ -8,6 +8,7 @@
8
8
 
9
9
  #import <React/RCTEventEmitter.h>
10
10
  #import <audioapi/events/AudioEvent.h>
11
+ #include <audioapi/events/AudioEventPayload.h>
11
12
 
12
13
  @class AudioEngine;
13
14
  @class SystemNotificationManager;
@@ -27,6 +28,6 @@
27
28
  @property (nonatomic, strong) NotificationRegistry *notificationRegistry;
28
29
 
29
30
  - (void)invokeHandlerWithEventName:(audioapi::AudioEvent)eventName
30
- eventBody:(NSDictionary *)eventBody;
31
+ payload:(audioapi::AudioEventPayload)payload;
31
32
 
32
33
  @end
@@ -318,34 +318,13 @@ RCT_EXPORT_METHOD(
318
318
  #endif // RCT_NEW_ARCH_ENABLED
319
319
 
320
320
  - (void)invokeHandlerWithEventName:(audioapi::AudioEvent)eventName
321
- eventBody:(NSDictionary *)eventBody
321
+ payload:(audioapi::AudioEventPayload)payload
322
322
  {
323
- std::unordered_map<std::string, EventValue> body = {};
324
-
325
- for (NSString *key in eventBody) {
326
- id value = eventBody[key];
327
- std::string stdKey = [key UTF8String];
328
-
329
- if ([value isKindOfClass:[NSString class]]) {
330
- std::string stdValue = [value UTF8String];
331
- body[stdKey] = EventValue(stdValue);
332
- } else if ([value isKindOfClass:[NSNumber class]]) {
333
- const char *type = [value objCType];
334
- if (strcmp(type, @encode(int)) == 0) {
335
- body[stdKey] = EventValue([value intValue]);
336
- } else if (strcmp(type, @encode(double)) == 0) {
337
- body[stdKey] = EventValue([value doubleValue]);
338
- } else if (strcmp(type, @encode(float)) == 0) {
339
- body[stdKey] = EventValue([value floatValue]);
340
- } else {
341
- body[stdKey] = EventValue([value boolValue]);
342
- }
343
- }
323
+ if (_eventHandler == nullptr) {
324
+ return;
344
325
  }
345
326
 
346
- if (_eventHandler != nullptr) {
347
- _eventHandler->invokeHandlerWithEventBody(eventName, body);
348
- }
327
+ _eventHandler->dispatchEvent(eventName, audioapi::kBroadcastListenerId, std::move(payload));
349
328
  }
350
329
 
351
330
  @end
@@ -4,11 +4,14 @@
4
4
  #import <NativeAudioPlayer.h>
5
5
  #else // when compiled as C++
6
6
  typedef struct objc_object NativeAudioPlayer;
7
+ typedef struct objc_object AudioBufferList;
7
8
  #endif // __OBJC__
8
9
 
9
10
  #include <audioapi/utils/AudioBuffer.hpp>
10
- #include <functional>
11
11
 
12
+ #include <atomic>
13
+ #include <cstddef>
14
+ #include <functional>
12
15
  namespace audioapi {
13
16
 
14
17
  class AudioContext;
@@ -29,12 +32,23 @@ class IOSAudioPlayer {
29
32
 
30
33
  bool isRunning() const;
31
34
 
32
- protected:
35
+ private:
36
+ void clearPendingSaved();
37
+ /// Audio-thread only. Always pulls the graph in steps of RENDER_QUANTUM_SIZE; if the system
38
+ /// buffer size is not a multiple of 128, the unused tail of the last quantum is kept (max 128
39
+ /// frames) and played at the start of the next callback.
40
+ void deliverOutputBuffers(AudioBufferList *outputData, int numFrames);
41
+
33
42
  std::shared_ptr<DSPAudioBuffer> audioBuffer_;
34
43
  NativeAudioPlayer *audioPlayer_;
35
44
  std::function<void(std::shared_ptr<DSPAudioBuffer>, int)> renderAudio_;
36
45
  int channelCount_;
37
46
  std::atomic<bool> isRunning_;
47
+ /// Set from main thread on start/resume; consumed on audio thread to drop stale pending audio.
48
+ std::atomic<bool> flushOverflowNextPull_{false};
49
+ /// Frames valid at the front of each `pendingSaved_[ch]` (0 … RENDER_QUANTUM_SIZE).
50
+ int pendingSavedCount_{0};
51
+ DSPAudioBuffer pendingSaved_;
38
52
  };
39
53
 
40
54
  } // namespace audioapi
@@ -1,10 +1,11 @@
1
1
  #import <AVFoundation/AVFoundation.h>
2
2
 
3
+ #include <algorithm>
4
+ #include <cstring>
5
+
3
6
  #include <audioapi/core/utils/Constants.h>
4
- #include <audioapi/dsp/VectorMath.h>
5
7
  #include <audioapi/ios/core/IOSAudioPlayer.h>
6
8
  #include <audioapi/ios/system/AudioEngine.h>
7
- #include <audioapi/utils/AudioArray.hpp>
8
9
  #include <audioapi/utils/AudioBuffer.hpp>
9
10
 
10
11
  namespace audioapi {
@@ -13,35 +14,20 @@ IOSAudioPlayer::IOSAudioPlayer(
13
14
  const std::function<void(std::shared_ptr<DSPAudioBuffer>, int)> &renderAudio,
14
15
  float sampleRate,
15
16
  int channelCount)
16
- : renderAudio_(renderAudio), channelCount_(channelCount), audioBuffer_(0), isRunning_(false)
17
+ : audioBuffer_(nullptr),
18
+ audioPlayer_(nullptr),
19
+ renderAudio_(renderAudio),
20
+ channelCount_(channelCount),
21
+ isRunning_(false),
22
+ pendingSaved_(RENDER_QUANTUM_SIZE, channelCount_, sampleRate)
17
23
  {
18
24
  RenderAudioBlock renderAudioBlock = ^(AudioBufferList *outputData, int numFrames) {
19
- int processedFrames = 0;
20
-
21
- while (processedFrames < numFrames) {
22
- int framesToProcess = std::min(numFrames - processedFrames, RENDER_QUANTUM_SIZE);
23
-
24
- if (isRunning_.load(std::memory_order_acquire)) {
25
- renderAudio_(audioBuffer_, framesToProcess);
26
- } else {
27
- audioBuffer_->zero();
28
- }
29
-
30
- for (size_t channel = 0; channel < channelCount_; channel += 1) {
31
- float *outputChannel = (float *)outputData->mBuffers[channel].mData;
32
-
33
- audioBuffer_->getChannel(channel)->copyTo(
34
- outputChannel, 0, processedFrames, framesToProcess);
35
- }
36
-
37
- processedFrames += framesToProcess;
38
- }
25
+ deliverOutputBuffers(outputData, numFrames);
39
26
  };
40
27
 
41
28
  audioPlayer_ = [[NativeAudioPlayer alloc] initWithRenderAudio:renderAudioBlock
42
29
  sampleRate:sampleRate
43
30
  channelCount:channelCount_];
44
-
45
31
  audioBuffer_ = std::make_shared<DSPAudioBuffer>(RENDER_QUANTUM_SIZE, channelCount_, sampleRate);
46
32
  }
47
33
 
@@ -50,6 +36,80 @@ IOSAudioPlayer::~IOSAudioPlayer()
50
36
  cleanup();
51
37
  }
52
38
 
39
+ void IOSAudioPlayer::clearPendingSaved()
40
+ {
41
+ pendingSavedCount_ = 0;
42
+ pendingSaved_.zero();
43
+ }
44
+
45
+ void IOSAudioPlayer::deliverOutputBuffers(AudioBufferList *outputData, int numFrames)
46
+ {
47
+ // If requested, clear any saved overflow before continuing normal rendering.
48
+ if (flushOverflowNextPull_.exchange(false, std::memory_order_acq_rel)) {
49
+ clearPendingSaved();
50
+ }
51
+
52
+ // if not running, set output to 0
53
+ if (!isRunning_.load(std::memory_order_acquire)) {
54
+ for (int channel = 0; channel < channelCount_; ++channel) {
55
+ auto *outputChannel = static_cast<float *>(outputData->mBuffers[channel].mData);
56
+ std::memset(outputChannel, 0, static_cast<size_t>(numFrames) * sizeof(float));
57
+ }
58
+ return;
59
+ }
60
+
61
+ int outPos = 0;
62
+ while (outPos < numFrames) {
63
+ const int need = numFrames - outPos;
64
+
65
+ if (pendingSavedCount_ > 0) {
66
+ const int fromPending = std::min(need, pendingSavedCount_);
67
+
68
+ // populate output with pendingSaved
69
+ for (int ch = 0; ch < channelCount_; ++ch) {
70
+ float *dst = static_cast<float *>(outputData->mBuffers[ch].mData) + outPos;
71
+ const float *src = pendingSaved_[ch].begin();
72
+ std::memcpy(dst, src, fromPending * sizeof(float));
73
+
74
+ // move the remaining samples to the beginning of the pendingSaved buffer
75
+ const int remain = pendingSavedCount_ - fromPending;
76
+ if (remain > 0) {
77
+ float *buf = pendingSaved_[ch].begin();
78
+ std::memmove(buf, buf + fromPending, remain * sizeof(float));
79
+ }
80
+ }
81
+
82
+ pendingSavedCount_ -= fromPending;
83
+ outPos += fromPending;
84
+ continue;
85
+ }
86
+
87
+ renderAudio_(audioBuffer_, RENDER_QUANTUM_SIZE);
88
+
89
+ // normal rendering - take RENDER_QUANTUM_SIZE frames from the graph and copy to output
90
+ const int stillNeed = numFrames - outPos;
91
+ if (stillNeed >= RENDER_QUANTUM_SIZE) {
92
+ for (int ch = 0; ch < channelCount_; ++ch) {
93
+ auto *src = (*audioBuffer_)[ch].begin();
94
+ float *dst = static_cast<float *>(outputData->mBuffers[ch].mData) + outPos;
95
+ std::memcpy(dst, src, RENDER_QUANTUM_SIZE * sizeof(float));
96
+ }
97
+ outPos += RENDER_QUANTUM_SIZE;
98
+ } else {
99
+ // when output will be sliced, copy the remaining frames to pendingSaved
100
+ const int tail = RENDER_QUANTUM_SIZE - stillNeed;
101
+ for (int ch = 0; ch < channelCount_; ++ch) {
102
+ auto *src = (*audioBuffer_)[ch].begin();
103
+ float *dst = static_cast<float *>(outputData->mBuffers[ch].mData) + outPos;
104
+ std::memcpy(dst, src, stillNeed * sizeof(float));
105
+ }
106
+ pendingSaved_.copy(*audioBuffer_, stillNeed, 0, tail);
107
+ pendingSavedCount_ = tail;
108
+ outPos += stillNeed;
109
+ }
110
+ }
111
+ }
112
+
53
113
  bool IOSAudioPlayer::start()
54
114
  {
55
115
  if (isRunning()) {
@@ -57,6 +117,9 @@ bool IOSAudioPlayer::start()
57
117
  }
58
118
 
59
119
  bool success = [audioPlayer_ start];
120
+ if (success) {
121
+ flushOverflowNextPull_.store(true, std::memory_order_release);
122
+ }
60
123
  isRunning_.store(success, std::memory_order_release);
61
124
  return success;
62
125
  }
@@ -74,6 +137,9 @@ bool IOSAudioPlayer::resume()
74
137
  }
75
138
 
76
139
  bool success = [audioPlayer_ resume];
140
+ if (success) {
141
+ flushOverflowNextPull_.store(true, std::memory_order_release);
142
+ }
77
143
  isRunning_.store(success, std::memory_order_release);
78
144
  return success;
79
145
  }
@@ -322,7 +322,7 @@ Result<std::tuple<std::vector<std::string>, double, double>, std::string> IOSAud
322
322
 
323
323
  if (hadCallback) {
324
324
  callbackOutputConfigured_.store(false, std::memory_order_release);
325
- dataCallback = std::move(dataCallback_);
325
+ dataCallback = dataCallback_;
326
326
  }
327
327
 
328
328
  if (hadConnection) {