Skip to content
Merged
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
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,8 +23,7 @@
#include <audioapi/HostObjects/utils/JsEnumParser.h>
#include <audioapi/HostObjects/utils/NodeOptionsParser.h>
#include <audioapi/core/BaseAudioContext.h>
#include <audioapi/core/OfflineAudioContext.h>
#include <audioapi/core/utils/AudioDecoder.h>
#include <audioapi/core/utils/AudioDecoding.hpp>

#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.hpp>
#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,47 @@
#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.hpp>
#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 =
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 +84,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.hpp>
#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.hpp>
#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 @@ -88,7 +88,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.hpp>
#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,4 @@ AudioBufferResult decodeWithPCMInBase64(
return Ok(std::move(audioBuffer));
}

} // namespace audioapi::audiodecoder
} // namespace audioapi::audiodecoding
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
#pragma once

#include <audioapi/core/types/AudioFormat.h>
#include <audioapi/libs/decoding/IncrementalAudioDecoder.h>
#include <audioapi/utils/AudioBuffer.hpp>
#include <audioapi/utils/Result.hpp>
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <vector>

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

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

Expand Down Expand Up @@ -39,4 +42,15 @@ 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 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 @@ -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 Down Expand Up @@ -587,6 +587,6 @@ 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
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ ma_decoder_config makeDecoderConfig(const int outputSampleRate) {

} // namespace

MiniAudioDecoder::MiniAudioDecoder() = default;

MiniAudioDecoder::~MiniAudioDecoder() {
close();
}
Expand Down
Loading
Loading