react-native-audio-api 0.13.0-nightly-51a651d-20260701 → 0.13.0

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 (146) hide show
  1. package/android/build.gradle +1 -0
  2. package/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.cpp +2 -2
  3. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp +7 -3
  4. package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.cpp +3 -3
  5. package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +2 -0
  6. package/android/src/oldarch/NativeAudioAPIModuleSpec.java +4 -0
  7. package/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +1 -1
  8. package/common/cpp/audioapi/HostObjects/utils/AudioDecoderHostObject.cpp +1 -24
  9. package/common/cpp/audioapi/HostObjects/utils/AudioDecoderHostObject.h +0 -1
  10. package/common/cpp/audioapi/HostObjects/utils/AudioFileUtilsHostObject.cpp +55 -47
  11. package/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +60 -6
  12. package/common/cpp/audioapi/core/BaseAudioContext.cpp +1 -1
  13. package/common/cpp/audioapi/core/sources/AudioFileSourceNode.cpp +7 -2
  14. package/common/cpp/audioapi/core/utils/AudioDecoding.cpp +69 -25
  15. package/common/cpp/audioapi/core/utils/{AudioDecoding.hpp → AudioDecoding.h} +18 -13
  16. package/common/cpp/audioapi/core/utils/decoding/SeekDecoderDaemon.cpp +4 -4
  17. package/common/cpp/audioapi/core/utils/decoding/SeekDecoderDaemon.h +3 -0
  18. package/common/cpp/audioapi/libs/decoding/IncrementalAudioDecoder.h +11 -0
  19. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.cpp +72 -48
  20. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.h +10 -1
  21. package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.cpp +7 -0
  22. package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.h +5 -0
  23. package/common/cpp/audioapi/types/NodeOptions.h +3 -0
  24. package/common/cpp/audioapi/utils/TaskOffloader.hpp +43 -11
  25. package/ios/audioapi/ios/AudioAPIModule.mm +9 -0
  26. package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +6 -3
  27. package/lib/commonjs/Audio/Audio.js +2 -0
  28. package/lib/commonjs/Audio/Audio.js.map +1 -1
  29. package/lib/commonjs/Audio/useAudioSourceLoader.js +15 -11
  30. package/lib/commonjs/Audio/useAudioSourceLoader.js.map +1 -1
  31. package/lib/commonjs/Audio/utils.js +4 -1
  32. package/lib/commonjs/Audio/utils.js.map +1 -1
  33. package/lib/commonjs/api.js +10 -2
  34. package/lib/commonjs/api.js.map +1 -1
  35. package/lib/commonjs/api.web.js +6 -0
  36. package/lib/commonjs/api.web.js.map +1 -1
  37. package/lib/commonjs/core/AudioDecoder.js +1 -32
  38. package/lib/commonjs/core/AudioDecoder.js.map +1 -1
  39. package/lib/commonjs/core/AudioFileUtils.js +32 -11
  40. package/lib/commonjs/core/AudioFileUtils.js.map +1 -1
  41. package/lib/commonjs/mock/index.js +5 -5
  42. package/lib/commonjs/mock/index.js.map +1 -1
  43. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
  44. package/lib/commonjs/specs/NativeAudioAPIModule.web.js +1 -0
  45. package/lib/commonjs/specs/NativeAudioAPIModule.web.js.map +1 -1
  46. package/lib/commonjs/types.js.map +1 -1
  47. package/lib/commonjs/utils/flags.js +18 -0
  48. package/lib/commonjs/utils/flags.js.map +1 -0
  49. package/lib/commonjs/utils/index.js +24 -0
  50. package/lib/commonjs/utils/index.js.map +1 -1
  51. package/lib/commonjs/utils/paths.js +9 -0
  52. package/lib/commonjs/utils/paths.js.map +1 -1
  53. package/lib/commonjs/utils/remoteHttpSource.js +80 -0
  54. package/lib/commonjs/utils/remoteHttpSource.js.map +1 -0
  55. package/lib/commonjs/web-core/AudioDecoder.web.js +6 -2
  56. package/lib/commonjs/web-core/AudioDecoder.web.js.map +1 -1
  57. package/lib/module/Audio/Audio.js +2 -0
  58. package/lib/module/Audio/Audio.js.map +1 -1
  59. package/lib/module/Audio/useAudioSourceLoader.js +16 -12
  60. package/lib/module/Audio/useAudioSourceLoader.js.map +1 -1
  61. package/lib/module/Audio/utils.js +4 -1
  62. package/lib/module/Audio/utils.js.map +1 -1
  63. package/lib/module/api.js +3 -2
  64. package/lib/module/api.js.map +1 -1
  65. package/lib/module/api.web.js +3 -0
  66. package/lib/module/api.web.js.map +1 -1
  67. package/lib/module/core/AudioDecoder.js +2 -32
  68. package/lib/module/core/AudioDecoder.js.map +1 -1
  69. package/lib/module/core/AudioFileUtils.js +31 -11
  70. package/lib/module/core/AudioFileUtils.js.map +1 -1
  71. package/lib/module/mock/index.js +4 -5
  72. package/lib/module/mock/index.js.map +1 -1
  73. package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
  74. package/lib/module/specs/NativeAudioAPIModule.web.js +1 -0
  75. package/lib/module/specs/NativeAudioAPIModule.web.js.map +1 -1
  76. package/lib/module/types.js.map +1 -1
  77. package/lib/module/utils/flags.js +15 -0
  78. package/lib/module/utils/flags.js.map +1 -0
  79. package/lib/module/utils/index.js +17 -0
  80. package/lib/module/utils/index.js.map +1 -1
  81. package/lib/module/utils/paths.js +8 -0
  82. package/lib/module/utils/paths.js.map +1 -1
  83. package/lib/module/utils/remoteHttpSource.js +75 -0
  84. package/lib/module/utils/remoteHttpSource.js.map +1 -0
  85. package/lib/module/web-core/AudioDecoder.web.js +6 -2
  86. package/lib/module/web-core/AudioDecoder.web.js.map +1 -1
  87. package/lib/typescript/Audio/Audio.d.ts.map +1 -1
  88. package/lib/typescript/Audio/types.d.ts +5 -0
  89. package/lib/typescript/Audio/types.d.ts.map +1 -1
  90. package/lib/typescript/Audio/useAudioSourceLoader.d.ts +2 -1
  91. package/lib/typescript/Audio/useAudioSourceLoader.d.ts.map +1 -1
  92. package/lib/typescript/Audio/utils.d.ts.map +1 -1
  93. package/lib/typescript/api.d.ts +3 -2
  94. package/lib/typescript/api.d.ts.map +1 -1
  95. package/lib/typescript/api.web.d.ts +1 -0
  96. package/lib/typescript/api.web.d.ts.map +1 -1
  97. package/lib/typescript/core/AudioDecoder.d.ts +1 -2
  98. package/lib/typescript/core/AudioDecoder.d.ts.map +1 -1
  99. package/lib/typescript/core/AudioFileUtils.d.ts +3 -3
  100. package/lib/typescript/core/AudioFileUtils.d.ts.map +1 -1
  101. package/lib/typescript/jsi-interfaces.d.ts +1 -2
  102. package/lib/typescript/jsi-interfaces.d.ts.map +1 -1
  103. package/lib/typescript/mock/index.d.ts +3 -1
  104. package/lib/typescript/mock/index.d.ts.map +1 -1
  105. package/lib/typescript/specs/NativeAudioAPIModule.d.ts +1 -0
  106. package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
  107. package/lib/typescript/specs/NativeAudioAPIModule.web.d.ts +1 -0
  108. package/lib/typescript/specs/NativeAudioAPIModule.web.d.ts.map +1 -1
  109. package/lib/typescript/types.d.ts +2 -1
  110. package/lib/typescript/types.d.ts.map +1 -1
  111. package/lib/typescript/utils/flags.d.ts +9 -0
  112. package/lib/typescript/utils/flags.d.ts.map +1 -0
  113. package/lib/typescript/utils/index.d.ts +2 -0
  114. package/lib/typescript/utils/index.d.ts.map +1 -1
  115. package/lib/typescript/utils/paths.d.ts +1 -0
  116. package/lib/typescript/utils/paths.d.ts.map +1 -1
  117. package/lib/typescript/utils/remoteHttpSource.d.ts +7 -0
  118. package/lib/typescript/utils/remoteHttpSource.d.ts.map +1 -0
  119. package/lib/typescript/web-core/AudioDecoder.web.d.ts +1 -1
  120. package/lib/typescript/web-core/AudioDecoder.web.d.ts.map +1 -1
  121. package/package.json +1 -1
  122. package/src/Audio/Audio.tsx +2 -0
  123. package/src/Audio/types.ts +5 -0
  124. package/src/Audio/useAudioSourceLoader.ts +25 -15
  125. package/src/Audio/utils.ts +4 -0
  126. package/src/api.ts +3 -6
  127. package/src/api.web.ts +4 -0
  128. package/src/core/AudioDecoder.ts +3 -52
  129. package/src/core/AudioFileUtils.ts +66 -16
  130. package/src/jsi-interfaces.ts +3 -3
  131. package/src/mock/index.ts +5 -16
  132. package/src/specs/NativeAudioAPIModule.ts +1 -0
  133. package/src/specs/NativeAudioAPIModule.web.ts +2 -0
  134. package/src/types.ts +2 -1
  135. package/src/utils/flags.ts +12 -0
  136. package/src/utils/index.ts +24 -0
  137. package/src/utils/paths.ts +11 -0
  138. package/src/utils/remoteHttpSource.ts +87 -0
  139. package/src/web-core/AudioDecoder.web.ts +10 -7
  140. package/lib/commonjs/utils/metadataPrefetching.js +0 -67
  141. package/lib/commonjs/utils/metadataPrefetching.js.map +0 -1
  142. package/lib/module/utils/metadataPrefetching.js +0 -61
  143. package/lib/module/utils/metadataPrefetching.js.map +0 -1
  144. package/lib/typescript/utils/metadataPrefetching.d.ts +0 -16
  145. package/lib/typescript/utils/metadataPrefetching.d.ts.map +0 -1
  146. package/src/utils/metadataPrefetching.ts +0 -96
@@ -216,6 +216,7 @@ android {
216
216
  "**/librrc_root.so",
217
217
  "**/libjscexecutor.so",
218
218
  "**/libv8executor.so",
219
+ "**/libworklets.so",
219
220
  "**/libreanimated.so"
220
221
  ]
221
222
  }
@@ -63,8 +63,8 @@ void AndroidFileWriterBackend::cleanupPreallocatedInputPool() {
63
63
  }
64
64
 
65
65
  void AndroidFileWriterBackend::writeAudioData(void *data, int numFrames) {
66
- if (offloader_ == nullptr || freeSlots_ == nullptr || inputBufferBytesPerSlot_ == 0 ||
67
- inputBuffers_.empty()) {
66
+ if (!isFileOpen() || offloader_ == nullptr || freeSlots_ == nullptr ||
67
+ inputBufferBytesPerSlot_ == 0 || inputBuffers_.empty()) {
68
68
  return;
69
69
  }
70
70
 
@@ -108,7 +108,9 @@ CloseFileResult FFmpegAudioFileWriter::closeFile() {
108
108
  return CloseFileResult::Err("File is not open");
109
109
  }
110
110
 
111
- // Joins the worker before draining/finalizing the encoder below.
111
+ isFileOpen_.store(false, std::memory_order_release);
112
+
113
+ // Drains the worker queue before flushing/finalizing the encoder below.
112
114
  cleanupPreallocatedInputPool();
113
115
 
114
116
  result = processFifo(true);
@@ -466,10 +468,12 @@ CloseFileResult FFmpegAudioFileWriter::finalizeOutput() {
466
468
  avio_closep(&formatCtx_->pb);
467
469
  }
468
470
 
469
- double durationInSeconds = getCurrentDuration();
471
+ double durationInSeconds = 0.0;
472
+ if (encoderCtx_ != nullptr && encoderCtx_->sample_rate > 0) {
473
+ durationInSeconds = static_cast<double>(nextPts_) / encoderCtx_->sample_rate;
474
+ }
470
475
 
471
476
  filePath_ = "";
472
- isFileOpen_.store(false, std::memory_order_release);
473
477
 
474
478
  return CloseFileResult::Ok({fileSizeInMB, durationInSeconds});
475
479
  }
@@ -156,11 +156,11 @@ CloseFileResult MiniAudioFileWriter::closeFile() {
156
156
  return CloseFileResult ::Err("File is not open");
157
157
  }
158
158
 
159
- // Joins the worker before the encoder teardown below.
160
- cleanupPreallocatedInputPool();
161
-
162
159
  isFileOpen_.store(false, std::memory_order_release);
163
160
 
161
+ // Drains the worker queue before the encoder teardown below.
162
+ cleanupPreallocatedInputPool();
163
+
164
164
  if (encoder_ != nullptr) {
165
165
  ma_encoder_uninit(encoder_.get());
166
166
  encoder_.reset();
@@ -109,6 +109,8 @@ class AudioAPIModule(
109
109
 
110
110
  override fun getDevicePreferredSampleRate(): Double = MediaSessionManager.getDevicePreferredSampleRate()
111
111
 
112
+ override fun isFfmpegEnabled(): Boolean = !BuildConfig.RN_AUDIO_API_FFMPEG_DISABLED
113
+
112
114
  override fun setAudioSessionActivity(
113
115
  enabled: Boolean,
114
116
  promise: Promise?,
@@ -43,6 +43,10 @@ public abstract class NativeAudioAPIModuleSpec extends ReactContextBaseJavaModul
43
43
  @DoNotStrip
44
44
  public abstract double getDevicePreferredSampleRate();
45
45
 
46
+ @ReactMethod(isBlockingSynchronousMethod = true)
47
+ @DoNotStrip
48
+ public abstract boolean isFfmpegEnabled();
49
+
46
50
  @ReactMethod
47
51
  @DoNotStrip
48
52
  public abstract void setAudioSessionActivity(boolean enabled, Promise promise);
@@ -23,7 +23,7 @@
23
23
  #include <audioapi/HostObjects/utils/JsEnumParser.h>
24
24
  #include <audioapi/HostObjects/utils/NodeOptionsParser.h>
25
25
  #include <audioapi/core/BaseAudioContext.h>
26
- #include <audioapi/core/utils/AudioDecoding.hpp>
26
+ #include <audioapi/core/utils/AudioDecoding.h>
27
27
 
28
28
  #include <memory>
29
29
  #include <vector>
@@ -1,6 +1,6 @@
1
1
  #include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
2
2
  #include <audioapi/HostObjects/utils/AudioDecoderHostObject.h>
3
- #include <audioapi/core/utils/AudioDecoding.hpp>
3
+ #include <audioapi/core/utils/AudioDecoding.h>
4
4
  #include <audioapi/jsi/JsiPromise.h>
5
5
 
6
6
  #include <jsi/jsi.h>
@@ -16,7 +16,6 @@ AudioDecoderHostObject::AudioDecoderHostObject(
16
16
  addFunctions(
17
17
  JSI_EXPORT_FUNCTION(AudioDecoderHostObject, decodeWithPCMInBase64),
18
18
  JSI_EXPORT_FUNCTION(AudioDecoderHostObject, decodeWithFilePath),
19
- JSI_EXPORT_FUNCTION(AudioDecoderHostObject, getDurationWithFilePath),
20
19
  JSI_EXPORT_FUNCTION(AudioDecoderHostObject, decodeWithMemoryBlock));
21
20
  }
22
21
 
@@ -77,28 +76,6 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithFilePath) {
77
76
  return promise;
78
77
  }
79
78
 
80
- JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, getDurationWithFilePath) {
81
- auto sourcePath = args[0].getString(runtime).utf8(runtime);
82
-
83
- auto promise = promiseVendor_->createAsyncPromise([sourcePath]() -> PromiseResolver {
84
- auto result = audiodecoding::getDurationWithFilePath(sourcePath);
85
-
86
- if (result.is_err()) {
87
- return [result = std::move(result)](
88
- jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
89
- return result.unwrap_err();
90
- };
91
- }
92
-
93
- const auto duration = result.unwrap();
94
- return [duration](jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
95
- return jsi::Value(duration);
96
- };
97
- });
98
-
99
- return promise;
100
- }
101
-
102
79
  JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithPCMInBase64) {
103
80
  auto b64 = args[0].getString(runtime).utf8(runtime);
104
81
  auto inputSampleRate = static_cast<float>(args[1].getNumber());
@@ -16,7 +16,6 @@ class AudioDecoderHostObject : public JsiHostObject {
16
16
  const std::shared_ptr<react::CallInvoker> &callInvoker);
17
17
  JSI_HOST_FUNCTION_DECL(decodeWithMemoryBlock);
18
18
  JSI_HOST_FUNCTION_DECL(decodeWithFilePath);
19
- JSI_HOST_FUNCTION_DECL(getDurationWithFilePath);
20
19
  JSI_HOST_FUNCTION_DECL(decodeWithPCMInBase64);
21
20
 
22
21
  private:
@@ -1,40 +1,19 @@
1
1
  #include <audioapi/HostObjects/utils/AudioFileUtilsHostObject.h>
2
+ #include <audioapi/HostObjects/utils/NodeOptionsParser.h>
3
+ #include <audioapi/core/utils/AudioDecoding.h>
2
4
  #include <audioapi/core/utils/AudioFileConcatenator.h>
3
- #if !RN_AUDIO_API_FFMPEG_DISABLED
4
- #include <audioapi/libs/ffmpeg/FFmpegDecoding.h>
5
- #endif // RN_AUDIO_API_FFMPEG_DISABLED
6
- #include <audioapi/core/utils/AudioDecoding.hpp>
7
5
  #include <audioapi/jsi/JsiPromise.h>
8
- #include <audioapi/libs/miniaudio/MiniAudioDecoding.h>
9
6
 
10
7
  #include <jsi/jsi.h>
11
8
  #include <cstdint>
9
+ #include <map>
12
10
  #include <memory>
13
- #include <optional>
14
11
  #include <string>
15
12
  #include <utility>
16
13
  #include <vector>
17
14
 
18
15
  namespace audioapi {
19
16
 
20
- namespace {
21
-
22
- std::optional<double> probeDurationWithDecoder(const uint8_t *data, size_t size, int sampleRate) {
23
- auto duration =
24
- audiodecoding::probeDuration<miniaudio_decoder::MiniAudioDecoder>(data, size, sampleRate);
25
- if (duration.has_value()) {
26
- return duration;
27
- }
28
- #if !RN_AUDIO_API_FFMPEG_DISABLED
29
- duration = audiodecoding::probeDuration<ffmpeg_decoder::FFmpegDecoder>(data, size, sampleRate);
30
- return duration;
31
- #else
32
- return std::nullopt;
33
- #endif // RN_AUDIO_API_FFMPEG_DISABLED
34
- }
35
-
36
- } // namespace
37
-
38
17
  AudioFileUtilsHostObject::AudioFileUtilsHostObject(
39
18
  jsi::Runtime *runtime,
40
19
  const std::shared_ptr<react::CallInvoker> &callInvoker) {
@@ -85,38 +64,67 @@ JSI_HOST_FUNCTION_IMPL(AudioFileUtilsHostObject, concatAudioFiles) {
85
64
  }
86
65
 
87
66
  JSI_HOST_FUNCTION_IMPL(AudioFileUtilsHostObject, probeDuration) {
88
- if (count < 1 || !args[0].isObject()) {
89
- throw jsi::JSError(runtime, "probeDuration expects an ArrayBuffer.");
67
+ if (count < 1) {
68
+ throw jsi::JSError(runtime, "probeDuration expects an ArrayBuffer or URL/path string.");
90
69
  }
91
70
 
92
- auto arrayBufferObject = args[0].asObject(runtime);
93
- if (!arrayBufferObject.isArrayBuffer(runtime)) {
94
- throw jsi::JSError(runtime, "probeDuration expects an ArrayBuffer.");
71
+ const auto sampleRate =
72
+ count > 1 && args[1].isNumber() ? static_cast<int>(args[1].getNumber()) : 0;
73
+
74
+ if (args[0].isObject() && args[0].asObject(runtime).isArrayBuffer(runtime)) {
75
+ auto arrayBuffer = args[0].asObject(runtime).getArrayBuffer(runtime);
76
+ const auto *data = arrayBuffer.data(runtime);
77
+ const auto size = arrayBuffer.size(runtime);
78
+
79
+ auto promise = promiseVendor_->createAsyncPromise(
80
+ [bytes = std::vector<uint8_t>(data, data + size), sampleRate]() -> PromiseResolver {
81
+ auto result =
82
+ audiodecoding::probeDurationWithMemory(bytes.data(), bytes.size(), sampleRate);
83
+ if (result.is_err()) {
84
+ return [](jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
85
+ return jsi::Value::null();
86
+ };
87
+ }
88
+
89
+ return [duration = static_cast<double>(result.unwrap())](
90
+ jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
91
+ return jsi::Value(duration);
92
+ };
93
+ });
94
+
95
+ return promise;
95
96
  }
96
97
 
97
- auto arrayBuffer = arrayBufferObject.getArrayBuffer(runtime);
98
- const auto *data = arrayBuffer.data(runtime);
99
- const auto size = arrayBuffer.size(runtime);
98
+ if (args[0].isString()) {
99
+ const auto pathOrUrl = args[0].asString(runtime).utf8(runtime);
100
100
 
101
- const auto sampleRate =
102
- count > 1 && args[1].isNumber() ? static_cast<int>(args[1].getNumber()) : 0;
101
+ std::map<std::string, std::string> headers;
102
+ if (count > 2 && args[2].isObject()) {
103
+ headers = audioapi::option_parser::parseHttpHeaders(runtime, args[2].asObject(runtime));
104
+ }
103
105
 
104
- auto promise = promiseVendor_->createAsyncPromise(
105
- [bytes = std::vector<uint8_t>(data, data + size), sampleRate]() -> PromiseResolver {
106
- auto duration = probeDurationWithDecoder(bytes.data(), bytes.size(), sampleRate);
107
- if (!duration.has_value()) {
108
- return [](jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
109
- return jsi::Value::null();
106
+ auto promise = promiseVendor_->createAsyncPromise(
107
+ [pathOrUrl, sampleRate, headers = std::move(headers)]() -> PromiseResolver {
108
+ auto result = audiodecoding::isHttpUrl(pathOrUrl)
109
+ ? audiodecoding::probeDurationWithUrl(pathOrUrl, sampleRate, headers)
110
+ : audiodecoding::probeDurationWithFilePath(pathOrUrl);
111
+
112
+ if (result.is_err()) {
113
+ return [](jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
114
+ return jsi::Value::null();
115
+ };
116
+ }
117
+
118
+ return [duration = static_cast<double>(result.unwrap())](
119
+ jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
120
+ return jsi::Value(duration);
110
121
  };
111
- }
122
+ });
112
123
 
113
- return [duration = duration.value()](
114
- jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
115
- return jsi::Value(duration);
116
- };
117
- });
124
+ return promise;
125
+ }
118
126
 
119
- return promise;
127
+ throw jsi::JSError(runtime, "probeDuration expects an ArrayBuffer or URL/path string.");
120
128
  }
121
129
 
122
130
  } // namespace audioapi
@@ -6,15 +6,46 @@
6
6
  #include <cstddef>
7
7
  #include <cstdio>
8
8
  #include <limits>
9
+ #include <map>
9
10
  #include <memory>
11
+ #include <string>
10
12
  #include <vector>
11
13
 
12
14
  #include <audioapi/HostObjects/effects/PeriodicWaveHostObject.h>
13
15
  #include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
14
- #include <audioapi/core/utils/AudioDecoding.hpp>
16
+ #include <audioapi/core/utils/AudioDecoding.h>
15
17
  #include <audioapi/types/NodeOptions.h>
16
18
 
17
19
  namespace audioapi::option_parser {
20
+
21
+ inline std::map<std::string, std::string> parseHttpHeaders(
22
+ jsi::Runtime &runtime,
23
+ const jsi::Object &headersObject) {
24
+ std::map<std::string, std::string> headers;
25
+ const auto propertyNames = headersObject.getPropertyNames(runtime);
26
+ const auto count = propertyNames.size(runtime);
27
+ for (size_t i = 0; i < count; ++i) {
28
+ const auto keyValue = propertyNames.getValueAtIndex(runtime, i);
29
+ if (!keyValue.isString()) {
30
+ continue;
31
+ }
32
+ const auto key = keyValue.asString(runtime).utf8(runtime);
33
+ const auto value = headersObject.getProperty(runtime, key.c_str());
34
+ if (value.isString()) {
35
+ headers.emplace(key, value.asString(runtime).utf8(runtime));
36
+ }
37
+ }
38
+ return headers;
39
+ }
40
+
41
+ inline void mergeHttpHeaders(
42
+ std::map<std::string, std::string> &target,
43
+ const std::map<std::string, std::string> &incoming) {
44
+ for (const auto &[key, value] : incoming) {
45
+ target[key] = value;
46
+ }
47
+ }
48
+
18
49
  inline AudioNodeOptions parseAudioNodeOptions(
19
50
  jsi::Runtime &runtime,
20
51
  const jsi::Object &optionsObject) {
@@ -320,12 +351,29 @@ inline AudioFileSourceOptions parseAudioFileSourceOptions(
320
351
 
321
352
  auto sourceValue = optionsObject.getProperty(runtime, "source");
322
353
  if (sourceValue.isString()) {
323
- options.filePath = sourceValue.asString(runtime).utf8(runtime);
324
- options.requiresFFmpeg =
325
- audiodecoding::pathHasExtension(options.filePath, {".mp4", ".m4a", ".aac", ".m3u8"});
354
+ const auto path = sourceValue.asString(runtime).utf8(runtime);
355
+ if (audiodecoding::isHttpUrl(path)) {
356
+ options.sourceUrl = path;
357
+ options.requiresFFmpeg = true;
358
+ } else {
359
+ options.filePath = path;
360
+ options.requiresFFmpeg =
361
+ audiodecoding::pathHasExtension(path, {".mp4", ".m4a", ".aac", ".m3u8"});
362
+ }
326
363
  } else if (sourceValue.isObject()) {
327
364
  auto sourceObj = sourceValue.asObject(runtime);
328
- if (sourceObj.isArrayBuffer(runtime)) {
365
+ if (sourceObj.hasProperty(runtime, "uri")) {
366
+ const auto uriValue = sourceObj.getProperty(runtime, "uri");
367
+ if (uriValue.isString()) {
368
+ options.sourceUrl = uriValue.asString(runtime).utf8(runtime);
369
+ options.requiresFFmpeg = true;
370
+ }
371
+ const auto sourceHeadersValue = sourceObj.getProperty(runtime, "headers");
372
+ if (sourceHeadersValue.isObject()) {
373
+ mergeHttpHeaders(
374
+ options.httpHeaders, parseHttpHeaders(runtime, sourceHeadersValue.asObject(runtime)));
375
+ }
376
+ } else if (sourceObj.isArrayBuffer(runtime)) {
329
377
  auto arrayBuffer = sourceObj.getArrayBuffer(runtime);
330
378
  auto *data = arrayBuffer.data(runtime);
331
379
  auto size = arrayBuffer.size(runtime);
@@ -336,11 +384,17 @@ inline AudioFileSourceOptions parseAudioFileSourceOptions(
336
384
  }
337
385
  }
338
386
 
387
+ const auto headersValue = optionsObject.getProperty(runtime, "headers");
388
+ if (headersValue.isObject()) {
389
+ mergeHttpHeaders(
390
+ options.httpHeaders, parseHttpHeaders(runtime, headersValue.asObject(runtime)));
391
+ }
392
+
339
393
  #if RN_AUDIO_API_FFMPEG_DISABLED
340
394
  if (options.requiresFFmpeg) {
341
395
  throw jsi::JSError(
342
396
  runtime,
343
- "AudioFileSourceNode: source format (.mp4, .m4a, .aac, .m3u8) requires FFmpeg, "
397
+ "AudioFileSourceNode: remote URLs and formats (.mp4, .m4a, .aac, .m3u8) require FFmpeg, "
344
398
  "which is disabled in this build.");
345
399
  }
346
400
  #endif
@@ -21,7 +21,7 @@
21
21
  #include <audioapi/core/sources/StreamerNode.h>
22
22
  #endif // RN_AUDIO_API_FFMPEG_DISABLED
23
23
  #include <audioapi/core/sources/WorkletSourceNode.h>
24
- #include <audioapi/core/utils/AudioDecoding.hpp>
24
+ #include <audioapi/core/utils/AudioDecoding.h>
25
25
  #include <audioapi/core/utils/AudioGraphManager.h>
26
26
  #include <audioapi/core/utils/worklets/SafeIncludes.h>
27
27
  #include <audioapi/events/AudioEventHandlerRegistry.h>
@@ -43,9 +43,12 @@ AudioFileSourceNode::AudioFileSourceNode(
43
43
 
44
44
  const bool useFilePath = !options.filePath.empty();
45
45
  const bool useData = !options.data.empty();
46
+ const bool useUrl = !options.sourceUrl.empty();
46
47
 
47
- if (!useFilePath && !useData) {
48
- assert(false && "AudioFileSourceNode requires either a file path or memory data to initialize");
48
+ if (!useFilePath && !useData && !useUrl) {
49
+ assert(
50
+ false &&
51
+ "AudioFileSourceNode requires a file path, remote URL, or memory data to initialize");
49
52
  return;
50
53
  }
51
54
 
@@ -103,6 +106,8 @@ bool AudioFileSourceNode::initDecoder(
103
106
  SeekDecoderDaemonOptions daemonOptions{
104
107
  .requiresFFmpeg = options.requiresFFmpeg,
105
108
  .filePath = std::move(options.filePath),
109
+ .sourceUrl = std::move(options.sourceUrl),
110
+ .httpHeaders = std::move(options.httpHeaders),
106
111
  .memoryData = std::move(options.data),
107
112
  .contextSampleRate = context->getSampleRate(),
108
113
  .loop = options.loop};
@@ -1,4 +1,4 @@
1
- #include <audioapi/core/utils/AudioDecoding.hpp>
1
+ #include <audioapi/core/utils/AudioDecoding.h>
2
2
  #include <audioapi/libs/base64/base64.h>
3
3
  #include <audioapi/libs/decoding/IncrementalAudioDecoder.h>
4
4
  #include <audioapi/libs/miniaudio/MiniAudioDecoding.h>
@@ -12,6 +12,7 @@
12
12
  #include <cmath>
13
13
  #include <cstdint>
14
14
  #include <cstring>
15
+ #include <map>
15
16
  #include <memory>
16
17
  #include <string>
17
18
  #include <utility>
@@ -109,71 +110,114 @@ bool pathHasExtension(const std::string &path, const std::vector<std::string> &e
109
110
  extensions, [&pathLower](const std::string &ext) { return pathLower.ends_with(ext); });
110
111
  }
111
112
 
112
- bool isValidDuration(float duration) {
113
- return duration > 0.0F && std::isfinite(duration);
113
+ bool isHttpUrl(const std::string &path) {
114
+ return path.starts_with("http://") || path.starts_with("https://");
114
115
  }
115
116
 
116
- AudioDurationResult resolveDurationFromDecoder(decoding::IncrementalAudioDecoder &decoder) {
117
- const float duration = decoder.getDurationInSeconds();
118
- if (!isValidDuration(duration)) {
119
- return Err("Audio duration metadata is unavailable");
120
- }
121
-
122
- return Ok(duration);
117
+ bool isValidDuration(float duration) {
118
+ return duration >= 0.0F && std::isfinite(duration);
123
119
  }
124
120
 
125
- AudioBufferResult decodeWithFilePath(const std::string &path, float sampleRate) {
126
- const int sr = static_cast<int>(sampleRate);
127
-
121
+ AudioDurationResult probeDurationWithFilePath(const std::string &path) {
128
122
  if (needsFFmpegByPath(path)) {
129
123
  #if !RN_AUDIO_API_FFMPEG_DISABLED
130
124
  ffmpeg_decoder::FFmpegDecoder decoder;
131
- const auto openResult = decoder.openFile(sr, path);
125
+ const auto openResult = decoder.openFile(0, path);
132
126
  if (openResult.is_err()) {
133
127
  return Err("Failed to open file with FFmpeg decoder: " + openResult.unwrap_err());
134
128
  }
135
- auto result = decodeAll(decoder);
129
+ auto result = resolveDurationFromDecoder(decoder);
136
130
  decoder.close();
137
131
  return result;
138
132
  #else
139
- return Err("FFmpeg is disabled, cannot decode with file path");
133
+ return Err("FFmpeg is disabled, cannot inspect duration with file path");
140
134
  #endif // RN_AUDIO_API_FFMPEG_DISABLED
141
135
  }
142
136
 
143
137
  miniaudio_decoder::MiniAudioDecoder decoder;
144
- const auto openResult = decoder.openFile(sr, path);
138
+ const auto openResult = decoder.openFile(0, path);
145
139
  if (openResult.is_err()) {
146
140
  return Err("Failed to open file with miniaudio decoder: " + openResult.unwrap_err());
147
141
  }
148
- auto result = decodeAll(decoder);
142
+ auto result = resolveDurationFromDecoder(decoder);
143
+ decoder.close();
144
+ return result;
145
+ }
146
+
147
+ AudioDurationResult probeDurationWithMemory(const void *data, size_t size, int sampleRate) {
148
+ const int sr = sampleRate != 0 ? sampleRate : 0;
149
+
150
+ {
151
+ miniaudio_decoder::MiniAudioDecoder decoder;
152
+ const auto openResult = decoder.openMemory(sr, data, size);
153
+ if (openResult.is_ok()) {
154
+ auto result = resolveDurationFromDecoder(decoder);
155
+ decoder.close();
156
+ if (result.is_ok()) {
157
+ return result;
158
+ }
159
+ }
160
+ }
161
+
162
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
163
+ ffmpeg_decoder::FFmpegDecoder decoder;
164
+ const auto openResult = decoder.openMemory(sr, data, size);
165
+ if (openResult.is_err()) {
166
+ return Err("Failed to probe duration from memory: " + openResult.unwrap_err());
167
+ }
168
+ auto result = resolveDurationFromDecoder(decoder);
169
+ decoder.close();
170
+ return result;
171
+ #else
172
+ return Err("Failed to probe duration from memory");
173
+ #endif // RN_AUDIO_API_FFMPEG_DISABLED
174
+ }
175
+
176
+ AudioDurationResult probeDurationWithUrl(
177
+ const std::string &url,
178
+ int sampleRate,
179
+ const std::map<std::string, std::string> &headers) {
180
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
181
+ ffmpeg_decoder::FFmpegDecoder decoder;
182
+ const auto openResult = decoder.openUrl(sampleRate, url, headers);
183
+ if (openResult.is_err()) {
184
+ return Err("Failed to open URL with FFmpeg decoder: " + openResult.unwrap_err());
185
+ }
186
+ auto result = resolveDurationFromDecoder(decoder);
149
187
  decoder.close();
150
188
  return result;
189
+ #else
190
+ (void)url;
191
+ (void)sampleRate;
192
+ (void)headers;
193
+ return Err("FFmpeg is disabled, cannot probe duration from URL");
194
+ #endif // RN_AUDIO_API_FFMPEG_DISABLED
151
195
  }
152
196
 
153
- AudioDurationResult getDurationWithFilePath(const std::string &path) {
154
- constexpr int useDecoderNativeSampleRate = 0;
197
+ AudioBufferResult decodeWithFilePath(const std::string &path, float sampleRate) {
198
+ const int sr = static_cast<int>(sampleRate);
155
199
 
156
200
  if (needsFFmpegByPath(path)) {
157
201
  #if !RN_AUDIO_API_FFMPEG_DISABLED
158
202
  ffmpeg_decoder::FFmpegDecoder decoder;
159
- const auto openResult = decoder.openFile(useDecoderNativeSampleRate, path);
203
+ const auto openResult = decoder.openFile(sr, path);
160
204
  if (openResult.is_err()) {
161
205
  return Err("Failed to open file with FFmpeg decoder: " + openResult.unwrap_err());
162
206
  }
163
- auto result = resolveDurationFromDecoder(decoder);
207
+ auto result = decodeAll(decoder);
164
208
  decoder.close();
165
209
  return result;
166
210
  #else
167
- return Err("FFmpeg is disabled, cannot inspect duration with file path");
211
+ return Err("FFmpeg is disabled, cannot decode with file path");
168
212
  #endif // RN_AUDIO_API_FFMPEG_DISABLED
169
213
  }
170
214
 
171
215
  miniaudio_decoder::MiniAudioDecoder decoder;
172
- const auto openResult = decoder.openFile(useDecoderNativeSampleRate, path);
216
+ const auto openResult = decoder.openFile(sr, path);
173
217
  if (openResult.is_err()) {
174
218
  return Err("Failed to open file with miniaudio decoder: " + openResult.unwrap_err());
175
219
  }
176
- auto result = resolveDurationFromDecoder(decoder);
220
+ auto result = decodeAll(decoder);
177
221
  decoder.close();
178
222
  return result;
179
223
  }
@@ -5,10 +5,9 @@
5
5
  #include <audioapi/utils/AudioBuffer.hpp>
6
6
  #include <audioapi/utils/Result.hpp>
7
7
  #include <cstddef>
8
+ #include <map>
8
9
  #include <memory>
9
- #include <optional>
10
10
  #include <string>
11
- #include <type_traits>
12
11
  #include <vector>
13
12
 
14
13
  namespace audioapi::audiodecoding {
@@ -17,7 +16,6 @@ using AudioBufferResult = Result<std::shared_ptr<AudioBuffer>, std::string>;
17
16
  using AudioDurationResult = Result<float, std::string>;
18
17
 
19
18
  [[nodiscard]] AudioBufferResult decodeWithFilePath(const std::string &path, float sampleRate);
20
- [[nodiscard]] AudioDurationResult getDurationWithFilePath(const std::string &path);
21
19
  [[nodiscard]] AudioBufferResult
22
20
  decodeWithMemoryBlock(const void *data, size_t size, float sampleRate);
23
21
  [[nodiscard]] AudioBufferResult decodeWithPCMInBase64(
@@ -32,8 +30,18 @@ decodeWithMemoryBlock(const void *data, size_t size, float sampleRate);
32
30
  const std::string &path,
33
31
  const std::vector<std::string> &extensions);
34
32
 
33
+ [[nodiscard]] bool isHttpUrl(const std::string &path);
35
34
  [[nodiscard]] bool isValidDuration(float duration);
36
35
 
36
+ [[nodiscard]] inline AudioDurationResult resolveDurationFromDecoder(
37
+ decoding::IncrementalAudioDecoder &decoder) {
38
+ const float duration = decoder.getDurationInSeconds();
39
+ if (!isValidDuration(duration)) {
40
+ return Err("Audio duration metadata is unavailable");
41
+ }
42
+ return Ok(duration);
43
+ }
44
+
37
45
  [[nodiscard]] inline bool needsFFmpeg(AudioFormat format) {
38
46
  return format == AudioFormat::MP4 || format == AudioFormat::M4A || format == AudioFormat::AAC;
39
47
  }
@@ -46,15 +54,12 @@ decodeWithMemoryBlock(const void *data, size_t size, float sampleRate);
46
54
  return static_cast<float>(static_cast<int16_t>((byte2 << CHAR_BIT) | byte1)) / INT16_MAX;
47
55
  }
48
56
 
49
- template <typename D>
50
- requires std::is_base_of_v<decoding::IncrementalAudioDecoder, D>
51
- std::optional<double> probeDuration(const void *data, size_t size, int outputSampleRate) {
52
- D decoder;
53
- const auto openResult = decoder.openMemory(outputSampleRate, data, size);
54
- if (openResult.is_err()) {
55
- return std::nullopt;
56
- }
57
- return static_cast<double>(decoder.getDurationInSeconds());
58
- }
57
+ [[nodiscard]] AudioDurationResult probeDurationWithFilePath(const std::string &path);
58
+ [[nodiscard]] AudioDurationResult
59
+ probeDurationWithMemory(const void *data, size_t size, int sampleRate = 0);
60
+ [[nodiscard]] AudioDurationResult probeDurationWithUrl(
61
+ const std::string &url,
62
+ int sampleRate = 0,
63
+ const std::map<std::string, std::string> &headers = {});
59
64
 
60
65
  } // namespace audioapi::audiodecoding
@@ -18,7 +18,7 @@ SeekDecoderDaemon::SeekDecoderDaemon(
18
18
  commandReceiver_(std::move(commandReceiver)),
19
19
  frameSender_(std::move(frameSender)),
20
20
  frameReceiverForDrain_(std::move(frameReceiver)) {
21
- if (options.requiresFFmpeg) {
21
+ if (options.requiresFFmpeg || !options.sourceUrl.empty()) {
22
22
  #if !RN_AUDIO_API_FFMPEG_DISABLED
23
23
  decoder_ = std::make_unique<ffmpeg_decoder::FFmpegDecoder>();
24
24
  #endif
@@ -30,9 +30,9 @@ SeekDecoderDaemon::SeekDecoderDaemon(
30
30
 
31
31
  int contextSampleRate = static_cast<int>(options.contextSampleRate);
32
32
 
33
- // Check in AudioFileSourceNode constructor ensures that at least one of filePath or memoryData is provided,
34
- // so we can attempt to open the decoder with either without additional checks here.
35
- if (!options.filePath.empty()) {
33
+ if (!options.sourceUrl.empty()) {
34
+ openResult = decoder_->openUrl(contextSampleRate, options.sourceUrl, options.httpHeaders);
35
+ } else if (!options.filePath.empty()) {
36
36
  openResult = decoder_->openFile(contextSampleRate, options.filePath);
37
37
  } else {
38
38
  openResult = decoder_->openMemory(