Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
45ee712
refactor: move AudioFormat to separate file
vltkv Sep 4, 2025
7af9407
refactor: extract AudioDecoder into separate module
vltkv Sep 9, 2025
70195e5
refactor: simplify utility functions
vltkv Sep 11, 2025
ecb5363
refactor: one decode method with overloads
vltkv Sep 16, 2025
e798179
Merge remote-tracking branch 'origin/main' into audio-decoding-module
vltkv Sep 16, 2025
0796dd6
refactor: revert previous formatting
vltkv Sep 18, 2025
aac1a99
fix: audioDecoder interface
vltkv Sep 19, 2025
2e97d04
refactor: remove audiodecoder property from audioContexts cpp files
vltkv Sep 19, 2025
f01b4ba
refactor: move AudioDecoder to utils folder
vltkv Sep 19, 2025
ce2052f
feat: add AudioStretcher draft
vltkv Sep 22, 2025
47719c1
Merge branch 'main' into audio-decoding-module
vltkv Sep 23, 2025
663d301
refactor: add audio stretcher module
vltkv Sep 25, 2025
cb27e43
refactor: increase stretchedBuffer size
vltkv Sep 25, 2025
5a06e63
docs: update desc in BaseAudioContext
vltkv Sep 25, 2025
07477f8
docs: add section about AudioDecoder
vltkv Sep 25, 2025
38057a2
refactor: audioplayer
vltkv Sep 25, 2025
0ed5964
Merge branch 'main' into audio-decoding-module
vltkv Sep 25, 2025
089abf8
fix: small fixes
vltkv Sep 25, 2025
faa134a
fix: tests
vltkv Sep 25, 2025
9fc6690
fix: test
vltkv Sep 25, 2025
dc59754
refactor: rename funtion decode
vltkv Sep 26, 2025
88d1eff
feat: add removing the file:// prefix
vltkv Sep 26, 2025
8edf837
refactor: deleted unnecessary comments
vltkv Sep 26, 2025
10b630f
fix: audiostretcher
vltkv Sep 26, 2025
3a49f72
Merge branch 'audio-decoding-module' into audio-stretcher-module
vltkv Sep 26, 2025
529c4bb
fix: hostobjects fixes
vltkv Sep 26, 2025
ea2cb2c
feat: decoder can get channelCount and sampleRate from data
vltkv Sep 30, 2025
50af869
refactor: audio decoder
vltkv Sep 30, 2025
97dcd40
fix: replace implicit conversions with explici static_cast
vltkv Sep 30, 2025
2d413e4
feat: add ffmpeg decoding
vltkv Sep 30, 2025
3c50322
refactor: ffmpeg decoding
vltkv Sep 30, 2025
75469db
fix: add ffmpeg decoding to android
vltkv Sep 30, 2025
59f2ad2
Merge branch 'main' into audio-decoding-module
vltkv Sep 30, 2025
80047c6
docs: update decoding description
vltkv Oct 1, 2025
c318a15
Merge remote-tracking branch 'refs/remotes/origin/audio-decoding-modu…
vltkv Oct 1, 2025
a93b309
docs: add desc in base-audio-context
vltkv Oct 1, 2025
e13e2ae
Merge branch 'audio-decoding-module' into audio-stretcher-module
vltkv Oct 1, 2025
feae46a
docs: add time-stretching desc
vltkv Oct 1, 2025
fc531c5
feat: audio stretcher
vltkv Oct 1, 2025
4383078
fix: remove unnecessary sampleRate field
vltkv Oct 1, 2025
433766b
fix: apply suggestions
vltkv Oct 2, 2025
2858391
fix: nitpicks
vltkv Oct 3, 2025
fb8bf2a
Merge branch 'main' into audio-decoding-module
vltkv Oct 3, 2025
ca92408
refactor: use createAsyncPromise
vltkv Oct 3, 2025
88151fd
Merge branch 'audio-decoding-module' into audio-stretcher-module
vltkv Oct 3, 2025
8072594
refactor: use createAsyncPromise
vltkv Oct 3, 2025
a07bbfa
Merge branch 'main' into audio-stretcher-module
vltkv Oct 6, 2025
fb75118
fix: small fixes
vltkv Oct 6, 2025
967313a
fix: revert to createPromise in host objects
vltkv Oct 7, 2025
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
16 changes: 9 additions & 7 deletions apps/common-app/src/examples/PlaybackSpeed/PlaybackSpeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React, {
} from 'react';
import { View, StyleSheet } from 'react-native';
import { Container, Button, Spacer, Slider, Select } from '../../components';
import { AudioContext } from 'react-native-audio-api';
import { AudioContext, changePlaybackSpeed } from 'react-native-audio-api';
import type { AudioBufferSourceNode } from 'react-native-audio-api';
import {
PCM_DATA,
Expand Down Expand Up @@ -49,12 +49,14 @@ const PlaybackSpeed: FC = () => {
setIsLoading(true);

try {
const buffer = await audioContext.decodePCMInBase64(
PCM_DATA,
48000,
1,
true
);
const buffer = await audioContext
.decodePCMInBase64(PCM_DATA, 48000, 1, true)
.then((audioBuffer) =>
changePlaybackSpeed(
audioBuffer,
audioSettings.PSOLA ? playbackSpeed : 1
)
);

const source = audioContext.createBufferSource({
pitchCorrection: audioSettings.PSOLA
Expand Down
2 changes: 1 addition & 1 deletion packages/audiodocs/docs/sources/audio-buffer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ specific sample rate which is the quantity of frames that will play in one secon

![](/img/audioBuffer.png)

It can be created from audio file using [`decodeAudioData`](/docs/utils/audio-decoder#decodeaudiodata) or from raw data using `constructor`.
It can be created from audio file using [`decodeAudioData`](/docs/utils/decoding#decodeaudiodata) or from raw data using `constructor`.
Once you have data in `AudioBuffer`, audio can be played by passing it to [`AudioBufferSourceNode`](audio-buffer-source-node).

## Constructor
Expand Down
4 changes: 2 additions & 2 deletions packages/audiodocs/docs/utils/decoding.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { Optional, MobileOnly } from '@site/src/components/Badges';

# Decoding

You can decode audio data independently, without creating an AudioContext, using the exported functions [`decodeAudioData`](/docs/utils/audio-decoder#decodeaudiodata) and
[`decodePCMInBase64`](/docs/utils/audio-decoder#decodepcminbase64).
You can decode audio data independently, without creating an AudioContext, using the exported functions [`decodeAudioData`](/docs/utils/decoding#decodeaudiodata) and
[`decodePCMInBase64`](/docs/utils/decoding#decodepcminbase64).

If you already have an audio context, you can decode audio data directly using its [`decodeAudioData`](/docs/core/base-audio-context#decodeaudiodata) function;
the decoded audio will then be automatically resampled to match the context's `sampleRate`.
Expand Down
39 changes: 39 additions & 0 deletions packages/audiodocs/docs/utils/time-stretching.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
sidebar_position: 2
---

import { Optional, MobileOnly } from '@site/src/components/Badges';

# Time stretching

You can change the playback speed of an audio buffer independently, without creating an AudioContext, using the exported function [`changePlaybackSpeed`](/docs/utils/decoding#decodeaudiodata).

### `changePlaybackSpeed`

Changes the playback speed of an audio buffer.

| Parameter | Type | Description |
| :----: | :----: | :-------- |
| `input` | `AudioBuffer` | The audio buffer whose playback speed you want to change. |
| `playbackSpeed` | `number` | The factor by which to change the playback speed. Values between [1.0, 2.0] speed up playback, values between [0.5, 1.0] slow it down. |


#### Returns `Promise<AudioBuffer>`.

<details>
<summary>Example usage</summary>
```tsx
const url = ... // url to an audio
const sampleRate = 48000

const buffer = await fetch(url)
.then((response) => response.arrayBuffer())
.then((arrayBuffer) => decodeAudioData(arrayBuffer, sampleRate))
.then((audioBuffer) => changePlaybackSpeed(audioBuffer, 1.25))
.catch((error) => {
console.error('Error decoding audio data source:', error);
return null;
});
```
</details>

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <audioapi/HostObjects/OfflineAudioContextHostObject.h>
#include <audioapi/HostObjects/inputs/AudioRecorderHostObject.h>
#include <audioapi/HostObjects/utils/AudioDecoderHostObject.h>
#include <audioapi/HostObjects/utils/AudioStretcherHostObject.h>
#include <audioapi/core/AudioContext.h>
#include <audioapi/core/OfflineAudioContext.h>
#include <audioapi/core/inputs/AudioRecorder.h>
Expand Down Expand Up @@ -36,6 +37,8 @@ class AudioAPIModuleInstaller {
jsiRuntime, jsCallInvoker, audioEventHandlerRegistry, uiRuntime);
auto createAudioDecoder =
getCreateAudioDecoderFunction(jsiRuntime, jsCallInvoker);
auto createAudioStretcher =
getCreateAudioStretcherFunction(jsiRuntime, jsCallInvoker);

jsiRuntime->global().setProperty(
*jsiRuntime, "createAudioContext", createAudioContext);
Expand All @@ -45,6 +48,8 @@ class AudioAPIModuleInstaller {
*jsiRuntime, "createOfflineAudioContext", createOfflineAudioContext);
jsiRuntime->global().setProperty(
*jsiRuntime, "createAudioDecoder", createAudioDecoder);
jsiRuntime->global().setProperty(
*jsiRuntime, "createAudioStretcher", createAudioStretcher);

auto audioEventHandlerRegistryHostObject =
std::make_shared<AudioEventHandlerRegistryHostObject>(
Expand Down Expand Up @@ -190,6 +195,26 @@ class AudioAPIModuleInstaller {
runtime, audioDecoderHostObject);
});
}

static jsi::Function getCreateAudioStretcherFunction(
jsi::Runtime *jsiRuntime,
const std::shared_ptr<react::CallInvoker> &jsCallInvoker) {
return jsi::Function::createFromHostFunction(
*jsiRuntime,
jsi::PropNameID::forAscii(*jsiRuntime, "createAudioStretcher"),
0,
[jsCallInvoker](
jsi::Runtime &runtime,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count) -> jsi::Value {
auto audioStretcherHostObject =
std::make_shared<AudioStretcherHostObject>(
&runtime, jsCallInvoker);
return jsi::Object::createFromHostObject(
runtime, audioStretcherHostObject);
});
}
};

} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -32,49 +32,63 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithMemoryBlock) {

auto sampleRate = args[1].getNumber();

return promiseVendor_->createAsyncPromise(
[data, size, sampleRate](
jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
auto result =
AudioDecoder::decodeWithMemoryBlock(data, size, sampleRate);

if (!result) {
return std::string("Failed to decode audio data.");
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

auto jsiObject =
jsi::Object::createFromHostObject(runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
auto promise = promiseVendor_->createPromise(
[data, size, sampleRate](std::shared_ptr<Promise> promise) {
std::thread([data, size, sampleRate, promise = std::move(promise)]() {
auto result =
AudioDecoder::decodeWithMemoryBlock(data, size, sampleRate);

if (!result) {
promise->reject("Failed to decode audio data.");
return;
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

promise->resolve([audioBufferHostObject = std::move(
audioBufferHostObject)](jsi::Runtime &runtime) {
auto jsiObject = jsi::Object::createFromHostObject(
runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
});
}).detach();
});
return promise;
}

JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithFilePath) {
auto sourcePath = args[0].getString(runtime).utf8(runtime);
auto sampleRate = args[1].getNumber();

return promiseVendor_->createAsyncPromise(
[sourcePath, sampleRate](
jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
auto result = AudioDecoder::decodeWithFilePath(sourcePath, sampleRate);

if (!result) {
return std::string("Failed to decode audio data source.");
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

auto jsiObject =
jsi::Object::createFromHostObject(runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
auto promise = promiseVendor_->createPromise(
[sourcePath, sampleRate](std::shared_ptr<Promise> promise) {
std::thread([sourcePath, sampleRate, promise = std::move(promise)]() {
auto result =
AudioDecoder::decodeWithFilePath(sourcePath, sampleRate);

if (!result) {
promise->reject("Failed to decode audio data source.");
return;
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

promise->resolve([audioBufferHostObject = std::move(
audioBufferHostObject)](jsi::Runtime &runtime) {
auto jsiObject = jsi::Object::createFromHostObject(
runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
});
}).detach();
});

return promise;
}

JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithPCMInBase64) {
Expand All @@ -83,25 +97,37 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithPCMInBase64) {
auto inputChannelCount = args[2].getNumber();
auto interleaved = args[3].getBool();

return promiseVendor_->createAsyncPromise(
auto promise = promiseVendor_->createPromise(
[b64, inputSampleRate, inputChannelCount, interleaved](
jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
auto result = AudioDecoder::decodeWithPCMInBase64(
b64, inputSampleRate, inputChannelCount, interleaved);

if (!result) {
return std::string("Failed to decode audio data source.");
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

auto jsiObject =
jsi::Object::createFromHostObject(runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
std::shared_ptr<Promise> promise) {
std::thread([b64,
inputSampleRate,
inputChannelCount,
interleaved,
promise = std::move(promise)]() {
auto result = AudioDecoder::decodeWithPCMInBase64(
b64, inputSampleRate, inputChannelCount, interleaved);

if (!result) {
promise->reject("Failed to decode audio data source.");
return;
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

promise->resolve([audioBufferHostObject = std::move(
audioBufferHostObject)](jsi::Runtime &runtime) {
auto jsiObject = jsi::Object::createFromHostObject(
runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
});
}).detach();
});

return promise;
}

} // namespace audioapi
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#pragma once

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

#include <jsi/jsi.h>
#include <memory>
#include <string>
#include <thread>
#include <utility>

namespace audioapi {

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

JSI_HOST_FUNCTION_IMPL(AudioStretcherHostObject, changePlaybackSpeed) {
auto audioBuffer =
args[0].getObject(runtime).asHostObject<AudioBufferHostObject>(runtime);
auto playbackSpeed = static_cast<float>(args[1].asNumber());

auto promise = promiseVendor_->createPromise(
[audioBuffer, playbackSpeed](std::shared_ptr<Promise> promise) {
std::thread([audioBuffer,
playbackSpeed,
promise = std::move(promise)]() {
auto result = AudioStretcher::changePlaybackSpeed(
*audioBuffer->audioBuffer_, playbackSpeed);

if (!result) {
promise->reject("Failed to change audio playback speed.");
return;
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

promise->resolve([audioBufferHostObject = std::move(
audioBufferHostObject)](jsi::Runtime &runtime) {
auto jsiObject = jsi::Object::createFromHostObject(
runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
});
}).detach();
});
return promise;
}

} // namespace audioapi
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

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

#include <jsi/jsi.h>
#include <memory>
#include <string>
#include <thread>
#include <utility>

namespace audioapi {
using namespace facebook;

class AudioStretcherHostObject : public JsiHostObject {
public:
explicit AudioStretcherHostObject(
jsi::Runtime *runtime,
const std::shared_ptr<react::CallInvoker> &callInvoker);
JSI_HOST_FUNCTION_DECL(changePlaybackSpeed);

private:
std::shared_ptr<PromiseVendor> promiseVendor_;
};
} // namespace audioapi
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#pragma once

#include <audioapi/core/types/AudioFormat.h>
#include <audioapi/libs/audio-stretch/stretch.h>
#include <audioapi/libs/miniaudio/miniaudio.h>
#include <algorithm>
#include <cstring>
Expand Down
Loading