react-native-audio-api 0.13.0-nightly-79cb732-20260630 → 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 (144) 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 -1
  9. package/common/cpp/audioapi/HostObjects/utils/AudioFileUtilsHostObject.cpp +55 -47
  10. package/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +60 -6
  11. package/common/cpp/audioapi/core/BaseAudioContext.cpp +1 -1
  12. package/common/cpp/audioapi/core/sources/AudioFileSourceNode.cpp +7 -2
  13. package/common/cpp/audioapi/core/utils/AudioDecoding.cpp +87 -1
  14. package/common/cpp/audioapi/core/utils/{AudioDecoding.hpp → AudioDecoding.h} +21 -12
  15. package/common/cpp/audioapi/core/utils/decoding/SeekDecoderDaemon.cpp +4 -4
  16. package/common/cpp/audioapi/core/utils/decoding/SeekDecoderDaemon.h +3 -0
  17. package/common/cpp/audioapi/libs/decoding/IncrementalAudioDecoder.h +11 -0
  18. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.cpp +72 -48
  19. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.h +10 -1
  20. package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.cpp +7 -0
  21. package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.h +5 -0
  22. package/common/cpp/audioapi/types/NodeOptions.h +3 -0
  23. package/common/cpp/audioapi/utils/TaskOffloader.hpp +43 -11
  24. package/ios/audioapi/ios/AudioAPIModule.mm +9 -0
  25. package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +6 -3
  26. package/lib/commonjs/Audio/Audio.js +2 -0
  27. package/lib/commonjs/Audio/Audio.js.map +1 -1
  28. package/lib/commonjs/Audio/useAudioSourceLoader.js +15 -11
  29. package/lib/commonjs/Audio/useAudioSourceLoader.js.map +1 -1
  30. package/lib/commonjs/Audio/utils.js +4 -1
  31. package/lib/commonjs/Audio/utils.js.map +1 -1
  32. package/lib/commonjs/api.js +15 -0
  33. package/lib/commonjs/api.js.map +1 -1
  34. package/lib/commonjs/api.web.js +13 -0
  35. package/lib/commonjs/api.web.js.map +1 -1
  36. package/lib/commonjs/core/AudioDecoder.js +1 -11
  37. package/lib/commonjs/core/AudioDecoder.js.map +1 -1
  38. package/lib/commonjs/core/AudioFileUtils.js +32 -11
  39. package/lib/commonjs/core/AudioFileUtils.js.map +1 -1
  40. package/lib/commonjs/mock/index.js +22 -1
  41. package/lib/commonjs/mock/index.js.map +1 -1
  42. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
  43. package/lib/commonjs/specs/NativeAudioAPIModule.web.js +1 -0
  44. package/lib/commonjs/specs/NativeAudioAPIModule.web.js.map +1 -1
  45. package/lib/commonjs/types.js.map +1 -1
  46. package/lib/commonjs/utils/flags.js +18 -0
  47. package/lib/commonjs/utils/flags.js.map +1 -0
  48. package/lib/commonjs/utils/index.js +24 -0
  49. package/lib/commonjs/utils/index.js.map +1 -1
  50. package/lib/commonjs/utils/paths.js +9 -0
  51. package/lib/commonjs/utils/paths.js.map +1 -1
  52. package/lib/commonjs/utils/remoteHttpSource.js +80 -0
  53. package/lib/commonjs/utils/remoteHttpSource.js.map +1 -0
  54. package/lib/commonjs/web-core/AudioDecoder.web.js +57 -0
  55. package/lib/commonjs/web-core/AudioDecoder.web.js.map +1 -1
  56. package/lib/module/Audio/Audio.js +2 -0
  57. package/lib/module/Audio/Audio.js.map +1 -1
  58. package/lib/module/Audio/useAudioSourceLoader.js +16 -12
  59. package/lib/module/Audio/useAudioSourceLoader.js.map +1 -1
  60. package/lib/module/Audio/utils.js +4 -1
  61. package/lib/module/Audio/utils.js.map +1 -1
  62. package/lib/module/api.js +2 -1
  63. package/lib/module/api.js.map +1 -1
  64. package/lib/module/api.web.js +4 -1
  65. package/lib/module/api.web.js.map +1 -1
  66. package/lib/module/core/AudioDecoder.js +2 -12
  67. package/lib/module/core/AudioDecoder.js.map +1 -1
  68. package/lib/module/core/AudioFileUtils.js +31 -11
  69. package/lib/module/core/AudioFileUtils.js.map +1 -1
  70. package/lib/module/mock/index.js +20 -1
  71. package/lib/module/mock/index.js.map +1 -1
  72. package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
  73. package/lib/module/specs/NativeAudioAPIModule.web.js +1 -0
  74. package/lib/module/specs/NativeAudioAPIModule.web.js.map +1 -1
  75. package/lib/module/types.js.map +1 -1
  76. package/lib/module/utils/flags.js +15 -0
  77. package/lib/module/utils/flags.js.map +1 -0
  78. package/lib/module/utils/index.js +17 -0
  79. package/lib/module/utils/index.js.map +1 -1
  80. package/lib/module/utils/paths.js +8 -0
  81. package/lib/module/utils/paths.js.map +1 -1
  82. package/lib/module/utils/remoteHttpSource.js +75 -0
  83. package/lib/module/utils/remoteHttpSource.js.map +1 -0
  84. package/lib/module/web-core/AudioDecoder.web.js +56 -0
  85. package/lib/module/web-core/AudioDecoder.web.js.map +1 -1
  86. package/lib/typescript/Audio/Audio.d.ts.map +1 -1
  87. package/lib/typescript/Audio/types.d.ts +5 -0
  88. package/lib/typescript/Audio/types.d.ts.map +1 -1
  89. package/lib/typescript/Audio/useAudioSourceLoader.d.ts +2 -1
  90. package/lib/typescript/Audio/useAudioSourceLoader.d.ts.map +1 -1
  91. package/lib/typescript/Audio/utils.d.ts.map +1 -1
  92. package/lib/typescript/api.d.ts +2 -1
  93. package/lib/typescript/api.d.ts.map +1 -1
  94. package/lib/typescript/api.web.d.ts +2 -1
  95. package/lib/typescript/api.web.d.ts.map +1 -1
  96. package/lib/typescript/core/AudioDecoder.d.ts.map +1 -1
  97. package/lib/typescript/core/AudioFileUtils.d.ts +3 -3
  98. package/lib/typescript/core/AudioFileUtils.d.ts.map +1 -1
  99. package/lib/typescript/jsi-interfaces.d.ts +1 -1
  100. package/lib/typescript/jsi-interfaces.d.ts.map +1 -1
  101. package/lib/typescript/mock/index.d.ts +7 -2
  102. package/lib/typescript/mock/index.d.ts.map +1 -1
  103. package/lib/typescript/specs/NativeAudioAPIModule.d.ts +1 -0
  104. package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
  105. package/lib/typescript/specs/NativeAudioAPIModule.web.d.ts +1 -0
  106. package/lib/typescript/specs/NativeAudioAPIModule.web.d.ts.map +1 -1
  107. package/lib/typescript/types.d.ts +2 -0
  108. package/lib/typescript/types.d.ts.map +1 -1
  109. package/lib/typescript/utils/flags.d.ts +9 -0
  110. package/lib/typescript/utils/flags.d.ts.map +1 -0
  111. package/lib/typescript/utils/index.d.ts +2 -0
  112. package/lib/typescript/utils/index.d.ts.map +1 -1
  113. package/lib/typescript/utils/paths.d.ts +1 -0
  114. package/lib/typescript/utils/paths.d.ts.map +1 -1
  115. package/lib/typescript/utils/remoteHttpSource.d.ts +7 -0
  116. package/lib/typescript/utils/remoteHttpSource.d.ts.map +1 -0
  117. package/lib/typescript/web-core/AudioDecoder.web.d.ts +4 -1
  118. package/lib/typescript/web-core/AudioDecoder.web.d.ts.map +1 -1
  119. package/package.json +1 -1
  120. package/src/Audio/Audio.tsx +2 -0
  121. package/src/Audio/types.ts +5 -0
  122. package/src/Audio/useAudioSourceLoader.ts +25 -15
  123. package/src/Audio/utils.ts +4 -0
  124. package/src/api.ts +2 -1
  125. package/src/api.web.ts +5 -0
  126. package/src/core/AudioDecoder.ts +2 -14
  127. package/src/core/AudioFileUtils.ts +66 -16
  128. package/src/jsi-interfaces.ts +3 -2
  129. package/src/mock/index.ts +43 -0
  130. package/src/specs/NativeAudioAPIModule.ts +1 -0
  131. package/src/specs/NativeAudioAPIModule.web.ts +2 -0
  132. package/src/types.ts +2 -0
  133. package/src/utils/flags.ts +12 -0
  134. package/src/utils/index.ts +24 -0
  135. package/src/utils/paths.ts +11 -0
  136. package/src/utils/remoteHttpSource.ts +87 -0
  137. package/src/web-core/AudioDecoder.web.ts +86 -1
  138. package/lib/commonjs/utils/metadataPrefetching.js +0 -67
  139. package/lib/commonjs/utils/metadataPrefetching.js.map +0 -1
  140. package/lib/module/utils/metadataPrefetching.js +0 -61
  141. package/lib/module/utils/metadataPrefetching.js.map +0 -1
  142. package/lib/typescript/utils/metadataPrefetching.d.ts +0 -16
  143. package/lib/typescript/utils/metadataPrefetching.d.ts.map +0 -1
  144. 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>
@@ -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>
@@ -9,8 +9,10 @@
9
9
 
10
10
  #include <algorithm>
11
11
  #include <cctype>
12
+ #include <cmath>
12
13
  #include <cstdint>
13
14
  #include <cstring>
15
+ #include <map>
14
16
  #include <memory>
15
17
  #include <string>
16
18
  #include <utility>
@@ -108,6 +110,90 @@ bool pathHasExtension(const std::string &path, const std::vector<std::string> &e
108
110
  extensions, [&pathLower](const std::string &ext) { return pathLower.ends_with(ext); });
109
111
  }
110
112
 
113
+ bool isHttpUrl(const std::string &path) {
114
+ return path.starts_with("http://") || path.starts_with("https://");
115
+ }
116
+
117
+ bool isValidDuration(float duration) {
118
+ return duration >= 0.0F && std::isfinite(duration);
119
+ }
120
+
121
+ AudioDurationResult probeDurationWithFilePath(const std::string &path) {
122
+ if (needsFFmpegByPath(path)) {
123
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
124
+ ffmpeg_decoder::FFmpegDecoder decoder;
125
+ const auto openResult = decoder.openFile(0, path);
126
+ if (openResult.is_err()) {
127
+ return Err("Failed to open file with FFmpeg decoder: " + openResult.unwrap_err());
128
+ }
129
+ auto result = resolveDurationFromDecoder(decoder);
130
+ decoder.close();
131
+ return result;
132
+ #else
133
+ return Err("FFmpeg is disabled, cannot inspect duration with file path");
134
+ #endif // RN_AUDIO_API_FFMPEG_DISABLED
135
+ }
136
+
137
+ miniaudio_decoder::MiniAudioDecoder decoder;
138
+ const auto openResult = decoder.openFile(0, path);
139
+ if (openResult.is_err()) {
140
+ return Err("Failed to open file with miniaudio decoder: " + openResult.unwrap_err());
141
+ }
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);
187
+ decoder.close();
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
195
+ }
196
+
111
197
  AudioBufferResult decodeWithFilePath(const std::string &path, float sampleRate) {
112
198
  const int sr = static_cast<int>(sampleRate);
113
199
 
@@ -5,15 +5,15 @@
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 {
15
14
 
16
15
  using AudioBufferResult = Result<std::shared_ptr<AudioBuffer>, std::string>;
16
+ using AudioDurationResult = Result<float, std::string>;
17
17
 
18
18
  [[nodiscard]] AudioBufferResult decodeWithFilePath(const std::string &path, float sampleRate);
19
19
  [[nodiscard]] AudioBufferResult
@@ -30,6 +30,18 @@ decodeWithMemoryBlock(const void *data, size_t size, float sampleRate);
30
30
  const std::string &path,
31
31
  const std::vector<std::string> &extensions);
32
32
 
33
+ [[nodiscard]] bool isHttpUrl(const std::string &path);
34
+ [[nodiscard]] bool isValidDuration(float duration);
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
+
33
45
  [[nodiscard]] inline bool needsFFmpeg(AudioFormat format) {
34
46
  return format == AudioFormat::MP4 || format == AudioFormat::M4A || format == AudioFormat::AAC;
35
47
  }
@@ -42,15 +54,12 @@ decodeWithMemoryBlock(const void *data, size_t size, float sampleRate);
42
54
  return static_cast<float>(static_cast<int16_t>((byte2 << CHAR_BIT) | byte1)) / INT16_MAX;
43
55
  }
44
56
 
45
- template <typename D>
46
- requires std::is_base_of_v<decoding::IncrementalAudioDecoder, D>
47
- std::optional<double> probeDuration(const void *data, size_t size, int outputSampleRate) {
48
- D decoder;
49
- const auto openResult = decoder.openMemory(outputSampleRate, data, size);
50
- if (openResult.is_err()) {
51
- return std::nullopt;
52
- }
53
- return static_cast<double>(decoder.getDurationInSeconds());
54
- }
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 = {});
55
64
 
56
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(
@@ -10,6 +10,7 @@
10
10
  #include <audioapi/utils/SpscChannel.hpp>
11
11
  #include <array>
12
12
  #include <atomic>
13
+ #include <map>
13
14
  #include <memory>
14
15
  #include <optional>
15
16
  #include <string>
@@ -21,6 +22,8 @@ struct SeekDecoderDaemonOptions {
21
22
  bool requiresFFmpeg;
22
23
  // source
23
24
  std::string filePath;
25
+ std::string sourceUrl;
26
+ std::map<std::string, std::string> httpHeaders;
24
27
  std::vector<uint8_t> memoryData;
25
28
  // playback
26
29
  float contextSampleRate;
@@ -3,6 +3,7 @@
3
3
  #include <audioapi/utils/Macros.h>
4
4
  #include <audioapi/utils/Result.hpp>
5
5
  #include <cstddef>
6
+ #include <map>
6
7
  #include <string>
7
8
 
8
9
  namespace audioapi::decoding {
@@ -24,6 +25,16 @@ class IncrementalAudioDecoder {
24
25
  /// @return Ok(None) on success or Err(message) on failure.
25
26
  [[nodiscard]] virtual DecoderResult openFile(int outputSampleRate, const std::string &path) = 0;
26
27
 
28
+ /// @brief Opens a remote HTTP(S) URL for incremental decoding via FFmpeg.
29
+ /// @param outputSampleRate The output sample rate.
30
+ /// @param url The remote URL (http:// or https://).
31
+ /// @param headers Optional HTTP request headers (e.g. Authorization).
32
+ /// @return Ok(None) on success or Err(message) on failure.
33
+ [[nodiscard]] virtual DecoderResult openUrl(
34
+ int outputSampleRate,
35
+ const std::string &url,
36
+ const std::map<std::string, std::string> &headers = {}) = 0;
37
+
27
38
  /// @brief Opens a memory block for decoding.
28
39
  /// @param outputSampleRate The output sample rate.
29
40
  /// @param data The data to decode.