Skip to content

Commit

Permalink
feat: decode audio data source with promise (#220)
Browse files Browse the repository at this point in the history
* feat: decode audio data source with promise

* fix: linter
  • Loading branch information
michalsek authored Dec 4, 2024
1 parent f20410e commit 3bbc3ce
Show file tree
Hide file tree
Showing 17 changed files with 184 additions and 50 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,4 @@ react-native-audio-api*.tgz
# Android
.kotlin

.env
33 changes: 19 additions & 14 deletions apps/common-app/src/examples/AudioFile/AudioFile.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import React from 'react';
import React, { useCallback } from 'react';
import { useState, useRef, useEffect, FC } from 'react';
import { Container, Button } from '../../components';

import {
AudioBuffer,
AudioContext,
AudioBufferSourceNode,
AudioBuffer,
} from 'react-native-audio-api';
import { ActivityIndicator } from 'react-native';

const assetUrl =
'https://audio-ssl.itunes.apple.com/apple-assets-us-std-000001/AudioPreview18/v4/9c/db/54/9cdb54b3-5c52-3063-b1ad-abe42955edb5/mzaf_520282131402737225.plus.aac.p.m4a';

const AudioFile: FC = () => {
const [isPlaying, setIsPlaying] = useState(false);
Expand All @@ -22,28 +26,28 @@ const AudioFile: FC = () => {

audioBufferSourceNodeRef.current =
audioContextRef.current.createBufferSource();
audioBufferSourceNodeRef.current.buffer;

audioBufferSourceNodeRef.current.connect(
audioContextRef.current.destination
);
};

const fetchAudioBuffer = async () => {
const fetchAudioBuffer = useCallback(async () => {
if (!audioContextRef.current) {
audioContextRef.current = new AudioContext();
}

setAudioBuffer(
// audioContextRef.current.decodeAudioDataSource(
// '/Users/maciejmakowski/projects/react-native-audio-api/apps/common-app/src/examples/AudioFile/runaway_kanye_west.mp3'
// )
audioContextRef.current.decodeAudioDataSource(
'https://audio-ssl.itunes.apple.com/apple-assets-us-std-000001/AudioPreview18/v4/9c/db/54/9cdb54b3-5c52-3063-b1ad-abe42955edb5/mzaf_520282131402737225.plus.aac.p.m4a'
)
);
};
const buffer =
await audioContextRef.current.decodeAudioDataSource(assetUrl);

setAudioBuffer(buffer);
}, []);

const handlePress = () => {
if (!audioBuffer) {
return;
}

if (isPlaying) {
audioBufferSourceNodeRef.current?.stop();
} else {
Expand All @@ -65,11 +69,12 @@ const AudioFile: FC = () => {
return () => {
audioContextRef.current?.close();
};
}, []);
}, [fetchAudioBuffer]);

return (
<Container centered>
<Button title={isPlaying ? 'Stop' : 'Play'} onPress={handlePress} />
{!audioBuffer && <ActivityIndicator color="#FFFFFF" />}
</Container>
);
};
Expand Down
4 changes: 2 additions & 2 deletions apps/fabric-example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2063,8 +2063,8 @@ SPEC CHECKSUMS:
RNReanimated: 77242c6d67416988a2fd9f5cf574bb3e60016362
RNScreens: e389d6a6a66a4f0d3662924ecae803073ccce8ec
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: f8ec45ce98bba1bc93dd28f2ee37215180e6d2b6
Yoga: 1d66db49f38fd9e576a1d7c3b081e46ab4c28b9e

PODFILE CHECKSUM: 75ad38075e71875257a2590065853ea6a608b897

COCOAPODS: 1.15.2
COCOAPODS: 1.16.2
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ namespace audioapi {
using namespace facebook;

AudioAPIInstallerHostObject::AudioAPIInstallerHostObject(
const std::shared_ptr<AudioAPIInstallerWrapper> &wrapper)
: wrapper_(wrapper) {}
const std::shared_ptr<AudioAPIInstallerWrapper> &wrapper, jsi::Runtime* runtime, const std::shared_ptr<facebook::react::CallInvoker> &jsInvoker)
: wrapper_(wrapper) {
promiseVendor_ = std::make_shared<JsiPromise::PromiseVendor>(runtime, jsInvoker);
}

std::vector<jsi::PropNameID> AudioAPIInstallerHostObject::getPropertyNames(
jsi::Runtime &runtime) {
Expand All @@ -32,7 +34,7 @@ jsi::Value AudioAPIInstallerHostObject::get(
size_t count) -> jsi::Value {
auto audioContext = wrapper_->createAudioContext();
auto audioContextHostObject =
AudioContextHostObject::createFromWrapper(audioContext);
AudioContextHostObject::createFromWrapper(audioContext, promiseVendor_);
return jsi::Object::createFromHostObject(
runtime, audioContextHostObject);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
#pragma once

#include <ReactCommon/CallInvoker.h>
#include <jsi/jsi.h>
#include <memory>
#include <utility>
#include <vector>


#include "AudioAPIInstallerWrapper.h"
#include "AudioContextHostObject.h"
#include "JsiPromise.h"

namespace audioapi {
using namespace facebook;
Expand All @@ -16,7 +19,7 @@ class AudioAPIInstallerWrapper;
class AudioAPIInstallerHostObject : public jsi::HostObject {
public:
explicit AudioAPIInstallerHostObject(
const std::shared_ptr<AudioAPIInstallerWrapper> &wrapper);
const std::shared_ptr<AudioAPIInstallerWrapper> &wrapper, jsi::Runtime* runtime, const std::shared_ptr<react::CallInvoker> &jsInvoker);

#ifdef ANDROID
static void createAndInstallFromWrapper(
Expand All @@ -41,5 +44,6 @@ class AudioAPIInstallerHostObject : public jsi::HostObject {

private:
std::shared_ptr<AudioAPIInstallerWrapper> wrapper_;
std::shared_ptr<JsiPromise::PromiseVendor> promiseVendor_;
};
} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ jsi::Value AudioBufferHostObject::get(
});
}

// `decodeAudioData` is a method that returns a promise to AudioBufferHostObject
// It seems that async/await checks for the presence of `then` method on the object
if (propName == "then") {
return jsi::Value::undefined();
}

throw std::runtime_error("Not yet implemented!");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ namespace audioapi {
using namespace facebook;

AudioContextHostObject::AudioContextHostObject(
const std::shared_ptr<AudioContextWrapper> &wrapper)
: BaseAudioContextHostObject(wrapper) {}
const std::shared_ptr<AudioContextWrapper> &wrapper, std::shared_ptr<JsiPromise::PromiseVendor> promiseVendor)
: BaseAudioContextHostObject(wrapper, promiseVendor) {}

std::vector<jsi::PropNameID> AudioContextHostObject::getPropertyNames(
jsi::Runtime &runtime) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <memory>
#include <vector>

#include"JsiPromise.h"
#include "AudioContextWrapper.h"
#include "BaseAudioContextHostObject.h"

Expand All @@ -13,7 +14,7 @@ using namespace facebook;
class AudioContextHostObject : public BaseAudioContextHostObject {
public:
explicit AudioContextHostObject(
const std::shared_ptr<AudioContextWrapper> &wrapper);
const std::shared_ptr<AudioContextWrapper> &wrapper, std::shared_ptr<JsiPromise::PromiseVendor> promiseVendor);

jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override;

Expand All @@ -25,8 +26,8 @@ class AudioContextHostObject : public BaseAudioContextHostObject {
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;

static std::shared_ptr<AudioContextHostObject> createFromWrapper(
const std::shared_ptr<AudioContextWrapper> &wrapper) {
return std::make_shared<AudioContextHostObject>(wrapper);
const std::shared_ptr<AudioContextWrapper> &wrapper, std::shared_ptr<JsiPromise::PromiseVendor> promiseVendor) {
return std::make_shared<AudioContextHostObject>(wrapper, promiseVendor);
}

private:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#include <thread>

#include "BaseAudioContextHostObject.h"

namespace audioapi {
using namespace facebook;

BaseAudioContextHostObject::BaseAudioContextHostObject(
const std::shared_ptr<BaseAudioContextWrapper> &wrapper)
: wrapper_(wrapper) {
const std::shared_ptr<BaseAudioContextWrapper> &wrapper, std::shared_ptr<JsiPromise::PromiseVendor> promiseVendor)
: wrapper_(wrapper), promiseVendor_(promiseVendor) {
auto destinationNodeWrapper = wrapper_->getDestination();
destination_ =
AudioDestinationNodeHostObject::createFromWrapper(destinationNodeWrapper);
Expand Down Expand Up @@ -203,22 +205,25 @@ jsi::Value BaseAudioContextHostObject::get(
}

if (propName == "decodeAudioDataSource") {
return jsi::Function::createFromHostFunction(
runtime,
propNameId,
1,
[this](
jsi::Runtime &runtime,
const jsi::Value &thisValue,
const jsi::Value *arguments,
size_t count) -> jsi::Value {
std::string source = arguments[0].getString(runtime).utf8(runtime);
auto buffer = wrapper_->decodeAudioDataSource(source);
auto audioBufferHostObject =
AudioBufferHostObject::createFromWrapper(buffer);
return jsi::Object::createFromHostObject(
runtime, audioBufferHostObject);
});
auto decode = [this](jsi::Runtime& runtime,
const jsi::Value&,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
auto sourcePath = arguments[0].getString(runtime).utf8(runtime);

auto promise = promiseVendor_->createPromise([this, &runtime, sourcePath](std::shared_ptr<JsiPromise::Promise> promise) {
std::thread([this, &runtime, sourcePath, promise = std::move(promise)]() {
auto results = wrapper_->decodeAudioDataSource(sourcePath);
auto audioBufferHostObject = AudioBufferHostObject::createFromWrapper(results);

promise->resolve(jsi::Object::createFromHostObject(runtime, audioBufferHostObject));
}).detach();
});

return promise;
};

return jsi::Function::createFromHostFunction(runtime, propNameId, 1, decode);
}

throw std::runtime_error("Not yet implemented!");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <utility>
#include <vector>

#include "JsiPromise.h"
#include "AudioBufferHostObject.h"
#include "AudioBufferSourceNodeHostObject.h"
#include "AudioDestinationNodeHostObject.h"
Expand All @@ -21,7 +22,7 @@ using namespace facebook;
class BaseAudioContextHostObject : public jsi::HostObject {
public:
explicit BaseAudioContextHostObject(
const std::shared_ptr<BaseAudioContextWrapper> &wrapper);
const std::shared_ptr<BaseAudioContextWrapper> &wrapper, std::shared_ptr<JsiPromise::PromiseVendor> promiseVendor);

jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override;

Expand All @@ -35,5 +36,6 @@ class BaseAudioContextHostObject : public jsi::HostObject {
protected:
std::shared_ptr<BaseAudioContextWrapper> wrapper_;
std::shared_ptr<AudioDestinationNodeHostObject> destination_;
std::shared_ptr<JsiPromise::PromiseVendor> promiseVendor_;
};
} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class AudioBus;
class AudioBuffer : public std::enable_shared_from_this<AudioBuffer> {
public:
explicit AudioBuffer(int numberOfChannels, int length, int sampleRate);
AudioBuffer(AudioBus *bus);
explicit AudioBuffer(AudioBus *bus);

[[nodiscard]] int getLength() const;
[[nodiscard]] int getSampleRate() const;
Expand Down
59 changes: 59 additions & 0 deletions packages/react-native-audio-api/common/cpp/utils/JsiPromise.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include "JsiPromise.h"

#include <jsi/jsi.h>
#include <ReactCommon/CallInvoker.h>
#include <functional>

namespace JsiPromise {

using namespace facebook;

jsi::Value PromiseVendor::createPromise(std::function<void(std::shared_ptr<Promise>)> func) {
if (_runtime == nullptr) {
throw new std::runtime_error("Runtime was null!");
}
auto& runtime = *_runtime;
auto callInvoker = _callInvoker;

// get Promise constructor
auto promiseCtor = runtime.global().getPropertyAsFunction(runtime, "Promise");

// create a "run" function (first Promise arg)
auto runPromise = jsi::Function::createFromHostFunction(runtime,
jsi::PropNameID::forUtf8(runtime, "runPromise"),
2,
[callInvoker, func](jsi::Runtime& runtime,
const jsi::Value& thisValue,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
auto resolveLocal = arguments[0].asObject(runtime).asFunction(runtime);
auto resolve = std::make_shared<jsi::Function>(std::move(resolveLocal));
auto rejectLocal = arguments[1].asObject(runtime).asFunction(runtime);
auto reject = std::make_shared<jsi::Function>(std::move(rejectLocal));

auto resolveWrapper = [resolve, &runtime, callInvoker](jsi::Value value) -> void {
auto valueShared = std::make_shared<jsi::Value>(std::move(value));
callInvoker->invokeAsync([resolve, &runtime, valueShared]() -> void {
resolve->call(runtime, *valueShared);
});
};

auto rejectWrapper = [reject, &runtime, callInvoker](const std::string& errorMessage) -> void {
auto error = jsi::JSError(runtime, errorMessage);
auto errorShared = std::make_shared<jsi::JSError>(error);
callInvoker->invokeAsync([reject, &runtime, errorShared]() -> void {
reject->call(runtime, errorShared->value());
});
};

auto promise = std::make_shared<Promise>(resolveWrapper, rejectWrapper);
func(promise);

return jsi::Value::undefined();
});

// return new Promise((resolve, reject) => ...)
return promiseCtor.callAsConstructor(runtime, runPromise);
}

} // namespace JsiPromise
42 changes: 42 additions & 0 deletions packages/react-native-audio-api/common/cpp/utils/JsiPromise.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

#include <ReactCommon/CallInvoker.h>
#include <jsi/jsi.h>
#include <utility>
#include <memory>
#include <string>

namespace JsiPromise {

using namespace facebook;

class Promise {
public:
Promise(std::function<void(jsi::Value)> resolve, std::function<void(const std::string&)> reject): _resolve(std::move(resolve)), _reject(std::move(reject)) {}

bool isResolved;
void resolve(jsi::Value&& value) {
_resolve(std::forward<jsi::Value>(value));
}
void reject(const std::string& errorMessage) {
_reject(errorMessage);
}

private:
std::function<void(jsi::Value)> _resolve;
std::function<void(const std::string&)> _reject;
};

class PromiseVendor {
public:
PromiseVendor(jsi::Runtime* runtime, std::shared_ptr<react::CallInvoker> callInvoker): _runtime(runtime), _callInvoker(callInvoker) {}

public:
jsi::Value createPromise(std::function<void(std::shared_ptr<Promise>)> func);

private:
jsi::Runtime* _runtime;
std::shared_ptr<react::CallInvoker> _callInvoker;
};

} // namespace JsiPromise
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,5 @@ BaseAudioContextWrapper::decodeAudioDataSource(const std::string &source) {
return std::make_shared<AudioBufferWrapper>(
context_->decodeAudioDataSource(source));
}

} // namespace audioapi
Loading

0 comments on commit 3bbc3ce

Please sign in to comment.