Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/fabric-example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2550,7 +2550,7 @@ SPEC CHECKSUMS:
ReactCodegen: d07ee3c8db75b43d1cbe479ae6affebf9925c733
ReactCommon: fe2a3af8975e63efa60f95fca8c34dc85deee360
ReactNativeDependencies: 4d5ce2683b6d74f7c686bf90a88c7d381295cf3c
RNAudioAPI: 764858df27270ed9a55803bb4c9c0ccb5bb14e9a
RNAudioAPI: 6668f71bdd9850005984acf39a3daef4935cec02
RNGestureHandler: 187c5c7936abf427bc4d22d6c3b1ac80ad1f63c0
RNReanimated: 64f4b3b33b48b19e0ba76a352571b52b1e931981
RNScreens: 01b065ded2dfe7987bcce770ff3a196be417ff41
Expand Down
21 changes: 19 additions & 2 deletions packages/audiodocs/docs/experimental/audio-tag.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Only **required** field is a `source`. Callbacks default to no-ops if omitted.
| `loop` | `boolean` | `false` | Loop playback. |
| `muted` | `boolean` | `false` | Muted state. |
| `volume` | `number` | `1` | Linear volume passed to the underlying source. |
| `preload` | `PreloadType` | `'auto'` | *Web support only* |
| `preload` | `PreloadType` | `'auto'` | Loading strategy for the source (`'none'`, `'metadata'`, `'auto'`). |
| `playbackRate` | `number` | `1` | *Web support only* |
| `preservesPitch` | `boolean` | `true` | *Web support only* |
| `onLoadStart` | `() => void` | no-op | Load started. |
Expand All @@ -72,6 +72,24 @@ Only **required** field is a `source`. Callbacks default to no-ops if omitted.
- `number` — Result of `require('./file.mp3')` (bundled asset).
- `AudioURISource` — `{ uri?: string; headers?: Record<string, string> }` for fetch with custom headers.

### `preload`

- `none` - Do not load on mount.
- `metadata` - Load enough to probe duration.
- `auto` (and empty string) - Load source immediately on mount (default behavior).

#### `metadata` format support (native)

Metadata preload without a full download is only supported for remote sources whose path ends with one of these extensions:

| Extension | Codec / container |
| :--- | :--- |
| `.opus` | Opus |
| `.mp4` | MP4 |
| `.m4a` | M4A |
| `.wav` | WAV |
| `.flac` | FLAC |

## Ref handle (`AudioTagHandle`) methods

### `play`
Expand Down Expand Up @@ -123,7 +141,6 @@ type AudioComponentContextType = {
preload: PreloadType;
playbackRate: number;
preservesPitch: boolean;
audioContext: BaseAudioContext | null;
};
```
</details>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#include <audioapi/HostObjects/utils/JsEnumParser.h>
#include <audioapi/HostObjects/utils/NodeOptionsParser.h>
#include <audioapi/core/BaseAudioContext.h>
#include <audioapi/core/utils/AudioDecoder.h>
#include <audioapi/core/utils/AudioDecoding.h>

#include <memory>
#include <vector>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
#include <audioapi/HostObjects/utils/AudioDecoderHostObject.h>
#include <audioapi/core/utils/AudioDecoder.h>
#include <audioapi/core/utils/AudioDecoding.h>
#include <audioapi/jsi/JsiPromise.h>

#include <jsi/jsi.h>
Expand Down Expand Up @@ -28,7 +28,7 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithMemoryBlock) {
auto sampleRate = static_cast<float>(args[1].getNumber());

auto promise = promiseVendor_->createAsyncPromise([data, size, sampleRate]() -> PromiseResolver {
auto result = audiodecoder::decodeWithMemoryBlock(data, size, sampleRate);
auto result = audiodecoding::decodeWithMemoryBlock(data, size, sampleRate);

if (result.is_err()) {
return [result = std::move(result)](
Expand All @@ -54,7 +54,7 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithFilePath) {
auto sampleRate = static_cast<float>(args[1].getNumber());

auto promise = promiseVendor_->createAsyncPromise([sourcePath, sampleRate]() -> PromiseResolver {
auto result = audiodecoder::decodeWithFilePath(sourcePath, sampleRate);
auto result = audiodecoding::decodeWithFilePath(sourcePath, sampleRate);

if (result.is_err()) {
return [result = std::move(result)](
Expand Down Expand Up @@ -84,7 +84,7 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithPCMInBase64) {

auto promise = promiseVendor_->createAsyncPromise(
[b64, inputSampleRate, inputChannelCount, interleaved]() -> PromiseResolver {
auto result = audiodecoder::decodeWithPCMInBase64(
auto result = audiodecoding::decodeWithPCMInBase64(
b64, inputSampleRate, inputChannelCount, interleaved);

if (result.is_err()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#pragma once

#include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
#include <audioapi/core/utils/AudioDecoder.h>
#include <audioapi/jsi/JsiPromise.h>

#include <jsi/jsi.h>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
#include <audioapi/HostObjects/utils/AudioFileUtilsHostObject.h>
#include <audioapi/core/utils/AudioFileConcatenator.h>
#if !RN_AUDIO_API_FFMPEG_DISABLED
#include <audioapi/libs/ffmpeg/FFmpegDecoding.h>
#endif // RN_AUDIO_API_FFMPEG_DISABLED
#include <audioapi/core/utils/AudioDecoding.h>
#include <audioapi/jsi/JsiPromise.h>
#include <audioapi/libs/miniaudio/MiniAudioDecoding.h>

#include <jsi/jsi.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

namespace audioapi {

namespace {

std::optional<double> probeDurationWithDecoder(const uint8_t *data, size_t size, int sampleRate) {
auto duration = std::optional<double>();
duration =
audiodecoding::probeDuration<miniaudio_decoder::MiniAudioDecoder>(data, size, sampleRate);
Comment on lines +23 to +25
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto duration = std::optional<double>();
duration =
audiodecoding::probeDuration<miniaudio_decoder::MiniAudioDecoder>(data, size, sampleRate);
auto duration =
audiodecoding::probeDuration<miniaudio_decoder::MiniAudioDecoder>(data, size, sampleRate);

if (duration.has_value()) {
return duration;
}
#if !RN_AUDIO_API_FFMPEG_DISABLED
duration = audiodecoding::probeDuration<ffmpeg_decoder::FFmpegDecoder>(data, size, sampleRate);
return duration;
#else
Comment thread
mdydek marked this conversation as resolved.
return std::nullopt;
#endif // RN_AUDIO_API_FFMPEG_DISABLED
}

} // namespace

AudioFileUtilsHostObject::AudioFileUtilsHostObject(
jsi::Runtime *runtime,
const std::shared_ptr<react::CallInvoker> &callInvoker) {
promiseVendor_ = std::make_shared<PromiseVendor>(runtime, callInvoker);
addFunctions(JSI_EXPORT_FUNCTION(AudioFileUtilsHostObject, concatAudioFiles));
addFunctions(
JSI_EXPORT_FUNCTION(AudioFileUtilsHostObject, concatAudioFiles),
JSI_EXPORT_FUNCTION(AudioFileUtilsHostObject, probeDuration));
}

JSI_HOST_FUNCTION_IMPL(AudioFileUtilsHostObject, concatAudioFiles) {
Expand Down Expand Up @@ -57,4 +85,39 @@ JSI_HOST_FUNCTION_IMPL(AudioFileUtilsHostObject, concatAudioFiles) {
return promise;
}

JSI_HOST_FUNCTION_IMPL(AudioFileUtilsHostObject, probeDuration) {
if (count < 1 || !args[0].isObject()) {
throw jsi::JSError(runtime, "probeDuration expects an ArrayBuffer.");
}

auto arrayBufferObject = args[0].asObject(runtime);
if (!arrayBufferObject.isArrayBuffer(runtime)) {
throw jsi::JSError(runtime, "probeDuration expects an ArrayBuffer.");
}

auto arrayBuffer = arrayBufferObject.getArrayBuffer(runtime);
const auto *data = arrayBuffer.data(runtime);
const auto size = arrayBuffer.size(runtime);

const auto sampleRate =
count > 1 && args[1].isNumber() ? static_cast<int>(args[1].getNumber()) : 0;

auto promise = promiseVendor_->createAsyncPromise(
[bytes = std::vector<uint8_t>(data, data + size), sampleRate]() -> PromiseResolver {
auto duration = probeDurationWithDecoder(bytes.data(), bytes.size(), sampleRate);
if (!duration.has_value()) {
return [](jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
return jsi::Value::null();
};
}

return [duration = duration.value()](
jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
return jsi::Value(duration);
};
});

return promise;
}

} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class AudioFileUtilsHostObject : public JsiHostObject {
const std::shared_ptr<react::CallInvoker> &callInvoker);

JSI_HOST_FUNCTION_DECL(concatAudioFiles);
JSI_HOST_FUNCTION_DECL(probeDuration);

private:
std::shared_ptr<PromiseVendor> promiseVendor_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

#include <audioapi/HostObjects/effects/PeriodicWaveHostObject.h>
#include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
#include <audioapi/core/utils/AudioDecoder.h>
#include <audioapi/core/utils/AudioDecoding.h>
#include <audioapi/types/NodeOptions.h>

namespace audioapi::option_parser {
Expand Down Expand Up @@ -309,14 +309,14 @@ inline AudioFileSourceOptions parseAudioFileSourceOptions(
if (sourceValue.isString()) {
options.filePath = sourceValue.asString(runtime).utf8(runtime);
options.requiresFFmpeg =
audiodecoder::pathHasExtension(options.filePath, {".mp4", ".m4a", ".aac"});
audiodecoding::pathHasExtension(options.filePath, {".mp4", ".m4a", ".aac"});
} else if (sourceValue.isObject()) {
auto sourceObj = sourceValue.asObject(runtime);
if (sourceObj.isArrayBuffer(runtime)) {
auto arrayBuffer = sourceObj.getArrayBuffer(runtime);
auto *data = arrayBuffer.data(runtime);
auto size = arrayBuffer.size(runtime);
auto format = audiodecoder::detectAudioFormat(data, size);
auto format = audiodecoding::detectAudioFormat(data, size);
options.requiresFFmpeg =
format == AudioFormat::MP4 || format == AudioFormat::M4A || format == AudioFormat::AAC;
options.data = std::vector<uint8_t>(data, data + size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#include <audioapi/core/sources/StreamerNode.h>
#endif // RN_AUDIO_API_FFMPEG_DISABLED
#include <audioapi/core/sources/WorkletSourceNode.h>
#include <audioapi/core/utils/AudioDecoder.h>
#include <audioapi/core/utils/AudioDecoding.h>
#include <audioapi/core/utils/AudioGraphManager.h>
#include <audioapi/core/utils/worklets/SafeIncludes.h>
#include <audioapi/events/AudioEventHandlerRegistry.h>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ void AudioFileSourceNode::initDecoders(
decoding::DecoderResult openResult = Ok(None);
if (requiresFFmpeg_) {
#if !RN_AUDIO_API_FFMPEG_DISABLED
decoder_ = std::make_unique<ffmpegdecoder::FFmpegDecoder>();
decoder_ = std::make_unique<ffmpeg_decoder::FFmpegDecoder>();
#endif // RN_AUDIO_API_FFMPEG_DISABLED
} else {
decoder_ = std::make_unique<miniaudio_decoder::MiniAudioDecoder>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include <audioapi/core/utils/AudioDecoder.h>
#include <audioapi/core/utils/AudioDecoding.h>
#include <audioapi/libs/base64/base64.h>
#include <audioapi/libs/decoding/IncrementalAudioDecoder.h>
#include <audioapi/libs/miniaudio/MiniAudioDecoding.h>
Expand All @@ -16,7 +16,7 @@
#include <utility>
#include <vector>

namespace audioapi::audiodecoder {
namespace audioapi::audiodecoding {

// Drains an incremental decoder into an AudioBuffer. Total frame count is not
// known up front for some formats (e.g. Vorbis), so we read in fixed-size
Expand Down Expand Up @@ -113,7 +113,7 @@ AudioBufferResult decodeWithFilePath(const std::string &path, float sampleRate)

if (needsFFmpegByPath(path)) {
#if !RN_AUDIO_API_FFMPEG_DISABLED
ffmpegdecoder::FFmpegDecoder decoder;
ffmpeg_decoder::FFmpegDecoder decoder;
const auto openResult = decoder.openFile(sr, path);
if (openResult.is_err()) {
return Err("Failed to open file with FFmpeg decoder: " + openResult.unwrap_err());
Expand Down Expand Up @@ -142,7 +142,7 @@ AudioBufferResult decodeWithMemoryBlock(const void *data, size_t size, float sam

if (needsFFmpeg(format)) {
#if !RN_AUDIO_API_FFMPEG_DISABLED
ffmpegdecoder::FFmpegDecoder decoder;
ffmpeg_decoder::FFmpegDecoder decoder;
const auto openResult = decoder.openMemory(sr, data, size);
if (openResult.is_err()) {
return Err("Failed to open memory block with FFmpeg decoder: " + openResult.unwrap_err());
Expand Down Expand Up @@ -197,4 +197,17 @@ AudioBufferResult decodeWithPCMInBase64(
return Ok(std::move(audioBuffer));
}

} // namespace audioapi::audiodecoder
template <typename T>
concept Decoder = std::is_base_of_v<decoding::IncrementalAudioDecoder, T>;

template <Decoder D>
Comment on lines +200 to +203
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
template <typename T>
concept Decoder = std::is_base_of_v<decoding::IncrementalAudioDecoder, T>;
template <Decoder D>
template <typename D>
requires std::is_base_of_v<decoding::IncrementalAudioDecoder, D>

std::optional<double> probeDuration(const void *data, size_t size, int outputSampleRate) {
D decoder;
const auto openResult = decoder.openMemory(outputSampleRate, data, size);
if (openResult.is_err()) {
return std::nullopt;
}
return static_cast<double>(decoder.getDurationInSeconds());
}

} // namespace audioapi::audiodecoding
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
#include <audioapi/utils/Result.hpp>
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <vector>

namespace audioapi::audiodecoder {
namespace audioapi::audiodecoding {

using AudioBufferResult = Result<std::shared_ptr<AudioBuffer>, std::string>;

Expand Down Expand Up @@ -39,4 +40,8 @@ decodeWithMemoryBlock(const void *data, size_t size, float sampleRate);
return static_cast<float>(static_cast<int16_t>((byte2 << CHAR_BIT) | byte1)) / INT16_MAX;
}

} // namespace audioapi::audiodecoder
template <typename Decoder>
[[nodiscard]] std::optional<double>
probeDuration(const void *data, size_t size, int outputSampleRate);

} // namespace audioapi::audiodecoding
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ extern "C" {
#include <libavutil/rational.h>
}

namespace audioapi::ffmpegdecoder {
namespace audioapi::ffmpeg_decoder {

namespace {

Expand Down Expand Up @@ -555,11 +555,11 @@ std::shared_ptr<AudioBuffer> decodeWithMemoryBlock(const void *data, size_t size
return buildAudioBufferFromInterleaved(acc, dec.outputChannels(), dec.outputSampleRate());
}

} // namespace audioapi::ffmpegdecoder
} // namespace audioapi::ffmpeg_decoder

#else

namespace audioapi::ffmpegdecoder {
namespace audioapi::ffmpeg_decoder {
FFmpegDecoder::~FFmpegDecoder() = default;
void FFmpegDecoder::close() {}
decoding::DecoderResult FFmpegDecoder::openFile(int, const std::string &) {
Expand All @@ -580,13 +580,16 @@ decoding::DecoderResult FFmpegDecoder::seekToTime(double) {
size_t FFmpegDecoder::readPcmFrames(float *, size_t) {
return 0;
}
std::optional<double> FFmpegDecoder::probeDuration(const void *, size_t, int) {
return std::nullopt;
}
Comment on lines +583 to +585
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not defined in the header. What is this for?

std::shared_ptr<AudioBuffer> decodeWithFilePath(const std::string &, int) {
return nullptr;
}
std::shared_ptr<AudioBuffer> decodeWithMemoryBlock(const void *, size_t, int) {
return nullptr;
}

} // namespace audioapi::ffmpegdecoder
} // namespace audioapi::ffmpeg_decoder

#endif // !RN_AUDIO_API_FFMPEG_DISABLED
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <audioapi/libs/decoding/IncrementalAudioDecoder.h>
#include <audioapi/utils/AudioBuffer.hpp>
#include <audioapi/utils/Macros.h>
#include <cstddef>
#include <memory>
#include <string>
Expand All @@ -24,7 +25,7 @@ extern "C" {
#include <libswresample/swresample.h>
}

namespace audioapi::ffmpegdecoder {
namespace audioapi::ffmpeg_decoder {

/// Opaque IO state for openMemory (must outlive decode until close).
struct MemoryIOContext {
Expand All @@ -42,11 +43,8 @@ struct MemoryIOContext {
class FFmpegDecoder : public decoding::IncrementalAudioDecoder {
public:
FFmpegDecoder() = default;
FFmpegDecoder(const FFmpegDecoder &) = delete;
FFmpegDecoder &operator=(const FFmpegDecoder &) = delete;
FFmpegDecoder(FFmpegDecoder &&other) = delete;
FFmpegDecoder &operator=(FFmpegDecoder &&other) = delete;
~FFmpegDecoder() override;
DELETE_COPY_AND_MOVE(FFmpegDecoder);

[[nodiscard]] decoding::DecoderResult openFile(
int outputSampleRate,
Expand Down Expand Up @@ -101,4 +99,4 @@ class FFmpegDecoder : public decoding::IncrementalAudioDecoder {
std::shared_ptr<AudioBuffer> decodeWithMemoryBlock(const void *data, size_t size, int sample_rate);
std::shared_ptr<AudioBuffer> decodeWithFilePath(const std::string &path, int sample_rate);

} // namespace audioapi::ffmpegdecoder
} // namespace audioapi::ffmpeg_decoder
Loading
Loading