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
4 changes: 2 additions & 2 deletions apps/common-app/src/examples/PlaybackSpeed/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export const PCM_DATA = PCM_DATA1 + PCM_DATA2 + PCM_DATA3 + PCM_DATA4;
export const labelWidth = 80;

export const PLAYBACK_SPEED_CONFIG = {
min: 0.25,
max: 3.0,
min: 0.5,
max: 2.0,
step: 0.25,
default: 1,
} as const;
Expand Down
2 changes: 1 addition & 1 deletion apps/fabric-example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3081,7 +3081,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: c91900fa724baee992f01c05eeb4c9e01a807f78
ReactCodegen: 8125d6ee06ea06f48f156cbddec5c2ca576d62e6
ReactCommon: 116d6ee71679243698620d8cd9a9042541e44aa6
RNAudioAPI: f483edfc07b2343174d27a99e9e40d8b89ca33d1
RNAudioAPI: 33e9940fd9e8b3694107f461a45fe23eb40c652b
RNGestureHandler: 3a73f098d74712952870e948b3d9cf7b6cae9961
RNReanimated: a035264789d1f64cb5adba7085d6aac6e9ec70a7
RNScreens: 6ced6ae8a526512a6eef6e28c2286e1fc2d378c3
Expand Down
14 changes: 14 additions & 0 deletions packages/audiodocs/docs/sources/audio-buffer-source-node.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ Schedules `AudioBufferSourceNode` to start playback of audio data contained in t

#### Returns `undefined`.

## Events

### `onLoopEnded`

Sets (or remove) callback that will be fired when buffer source node reached the end of the loop and is looping back to `loopStart`.
You can remove callback by passing `null`.


```ts
audioBufferSourceNode.onLoopEnded = () => { //setting callback
console.log("loop ended");
};
```

## Remarks

#### `buffer`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ AudioBufferSourceNodeHostObject::AudioBufferSourceNodeHostObject(
JSI_EXPORT_PROPERTY_SETTER(AudioBufferSourceNodeHostObject, loop),
JSI_EXPORT_PROPERTY_SETTER(AudioBufferSourceNodeHostObject, loopSkip),
JSI_EXPORT_PROPERTY_SETTER(AudioBufferSourceNodeHostObject, loopStart),
JSI_EXPORT_PROPERTY_SETTER(AudioBufferSourceNodeHostObject, loopEnd));
JSI_EXPORT_PROPERTY_SETTER(AudioBufferSourceNodeHostObject, loopEnd),
JSI_EXPORT_PROPERTY_SETTER(AudioBufferSourceNodeHostObject, onLoopEnded));

// start method is overridden in this class
functions_->erase("start");
Expand All @@ -29,6 +30,16 @@ AudioBufferSourceNodeHostObject::AudioBufferSourceNodeHostObject(
JSI_EXPORT_FUNCTION(AudioBufferSourceNodeHostObject, setBuffer));
}

AudioBufferSourceNodeHostObject::~AudioBufferSourceNodeHostObject() {
auto audioBufferSourceNode =
std::static_pointer_cast<AudioBufferSourceNode>(node_);

// When JSI object is garbage collected (together with the eventual callback),
// underlying source node might still be active and try to call the
// non-existing callback.
audioBufferSourceNode->clearOnLoopEndedCallback();
}

JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loop) {
auto audioBufferSourceNode =
std::static_pointer_cast<AudioBufferSourceNode>(node_);
Expand Down Expand Up @@ -94,6 +105,14 @@ JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loopEnd) {
audioBufferSourceNode->setLoopEnd(value.getNumber());
}

JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, onLoopEnded) {
auto audioBufferSourceNode =
std::static_pointer_cast<AudioBufferSourceNode>(node_);

audioBufferSourceNode->setOnLoopEndedCallbackId(
std::stoull(value.getString(runtime).utf8(runtime)));
}

JSI_HOST_FUNCTION_IMPL(AudioBufferSourceNodeHostObject, start) {
auto when = args[0].getNumber();
auto offset = args[1].getNumber();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class AudioBufferSourceNodeHostObject
explicit AudioBufferSourceNodeHostObject(
const std::shared_ptr<AudioBufferSourceNode> &node);

~AudioBufferSourceNodeHostObject() override;

JSI_PROPERTY_GETTER_DECL(loop);
JSI_PROPERTY_GETTER_DECL(loopSkip);
JSI_PROPERTY_GETTER_DECL(buffer);
Expand All @@ -26,6 +28,7 @@ class AudioBufferSourceNodeHostObject
JSI_PROPERTY_SETTER_DECL(loopSkip);
JSI_PROPERTY_SETTER_DECL(loopStart);
JSI_PROPERTY_SETTER_DECL(loopEnd);
JSI_PROPERTY_SETTER_DECL(onLoopEnded);

JSI_HOST_FUNCTION_DECL(start);
JSI_HOST_FUNCTION_DECL(setBuffer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,7 @@ AudioBufferBaseSourceNode::AudioBufferBaseSourceNode(
}

AudioBufferBaseSourceNode::~AudioBufferBaseSourceNode() {
if (onPositionChangedCallbackId_ != 0 &&
context_->audioEventHandlerRegistry_ != nullptr) {
context_->audioEventHandlerRegistry_->unregisterHandler(
"positionChanged", onPositionChangedCallbackId_);
onPositionChangedCallbackId_ = 0;
}
clearOnPositionChangedCallback();
}

std::shared_ptr<AudioParam> AudioBufferBaseSourceNode::getDetuneParam() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ AudioBufferSourceNode::~AudioBufferSourceNode() {

buffer_.reset();
alignedBus_.reset();

clearOnLoopEndedCallback();
}

bool AudioBufferSourceNode::getLoop() const {
Expand Down Expand Up @@ -123,6 +125,21 @@ void AudioBufferSourceNode::disable() {
alignedBus_.reset();
}

void AudioBufferSourceNode::clearOnLoopEndedCallback() {
if (onLoopEndedCallbackId_ == 0 || context_ == nullptr ||
context_->audioEventHandlerRegistry_ == nullptr) {
return;
}

context_->audioEventHandlerRegistry_->unregisterHandler(
"loopEnded", onLoopEndedCallbackId_);
onLoopEndedCallbackId_ = 0;
}

void AudioBufferSourceNode::setOnLoopEndedCallbackId(uint64_t callbackId) {
onLoopEndedCallbackId_ = callbackId;
}

void AudioBufferSourceNode::processNode(
const std::shared_ptr<AudioBus> &processingBus,
int framesToProcess) {
Expand Down Expand Up @@ -150,6 +167,15 @@ double AudioBufferSourceNode::getCurrentPosition() const {
static_cast<int>(vReadIndex_), buffer_->getSampleRate());
}

void AudioBufferSourceNode::sendOnLoopEndedEvent() {
auto onLoopEndedCallbackId =
onLoopEndedCallbackId_.load(std::memory_order_acquire);
if (onLoopEndedCallbackId != 0) {
context_->audioEventHandlerRegistry_->invokeHandlerWithEventBody(
"loopEnded", onLoopEndedCallbackId_, {});
}
}

/**
* Helper functions
*/
Expand Down Expand Up @@ -217,6 +243,8 @@ void AudioBufferSourceNode::processWithoutInterpolation(
playbackState_ = PlaybackState::STOP_SCHEDULED;
break;
}

sendOnLoopEndedEvent();
}
}

Expand Down Expand Up @@ -278,6 +306,8 @@ void AudioBufferSourceNode::processWithInterpolation(
playbackState_ = PlaybackState::STOP_SCHEDULED;
break;
}

sendOnLoopEndedEvent();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class AudioBufferSourceNode : public AudioBufferBaseSourceNode {
void start(double when, double offset, double duration = -1);
void disable() override;

void clearOnLoopEndedCallback();
void setOnLoopEndedCallbackId(uint64_t callbackId);

protected:
void processNode(const std::shared_ptr<AudioBus>& processingBus, int framesToProcess) override;
double getCurrentPosition() const override;
Expand All @@ -49,6 +52,9 @@ class AudioBufferSourceNode : public AudioBufferBaseSourceNode {
std::shared_ptr<AudioBuffer> buffer_;
std::shared_ptr<AudioBus> alignedBus_;

std::atomic<uint64_t> onLoopEndedCallbackId_ = 0; // 0 means no callback
void sendOnLoopEndedEvent();

void processWithoutInterpolation(
const std::shared_ptr<AudioBus>& processingBus,
size_t startOffset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@ AudioScheduledSourceNode::AudioScheduledSourceNode(BaseAudioContext *context)
}

AudioScheduledSourceNode::~AudioScheduledSourceNode() {
if (onEndedCallbackId_ != 0 &&
context_->audioEventHandlerRegistry_ != nullptr) {
context_->audioEventHandlerRegistry_->unregisterHandler(
"ended", onEndedCallbackId_);
onEndedCallbackId_ = 0;
}
clearOnEndedCallback();
}

void AudioScheduledSourceNode::start(double when) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ class AudioEventHandlerRegistry : public IAudioEventHandlerRegistry {
"volumeChange",
};

static constexpr std::array<std::string_view, 5> AUDIO_API_EVENT_NAMES = {
static constexpr std::array<std::string_view, 6> AUDIO_API_EVENT_NAMES = {
"ended",
"loopEnded",
"audioReady",
"positionChanged",
"audioError",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import AudioScheduledSourceNode from './AudioScheduledSourceNode';
export default class AudioBufferBaseSourceNode extends AudioScheduledSourceNode {
readonly playbackRate: AudioParam;
readonly detune: AudioParam;
private positionChangedSubscription?: AudioEventSubscription;
private positionChangedCallback?: (event: EventTypeWithValue) => void;
private onPositionChangedSubscription?: AudioEventSubscription;
private onPositionChangedCallback?: (event: EventTypeWithValue) => void;

constructor(context: BaseAudioContext, node: IAudioBufferBaseSourceNode) {
super(context, node);
Expand All @@ -21,27 +21,27 @@ export default class AudioBufferBaseSourceNode extends AudioScheduledSourceNode
public get onPositionChanged():
| ((event: EventTypeWithValue) => void)
| undefined {
return this.positionChangedCallback;
return this.onPositionChangedCallback;
}

public set onPositionChanged(
callback: ((event: EventTypeWithValue) => void) | null
) {
if (!callback) {
(this.node as IAudioBufferBaseSourceNode).onPositionChanged = '0';
this.positionChangedSubscription?.remove();
this.positionChangedSubscription = undefined;
this.positionChangedCallback = undefined;
this.onPositionChangedSubscription?.remove();
this.onPositionChangedSubscription = undefined;
this.onPositionChangedCallback = undefined;

return;
}

this.positionChangedCallback = callback;
this.positionChangedSubscription =
this.onPositionChangedCallback = callback;
this.onPositionChangedSubscription =
this.audioEventEmitter.addAudioEventListener('positionChanged', callback);

(this.node as IAudioBufferBaseSourceNode).onPositionChanged =
this.positionChangedSubscription.subscriptionId;
this.onPositionChangedSubscription.subscriptionId;
}

public get onPositionChangedInterval(): number {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,13 @@ export default class AudioBufferQueueSourceNode extends AudioBufferBaseSourceNod
(this.node as IAudioBufferQueueSourceNode).clearBuffers();
}

public override start(when: number = 0, offset?: number): void {
public override start(when: number = 0): void {
if (when < 0) {
throw new RangeError(
`when must be a finite non-negative number: ${when}`
);
}

if (offset) {
if (offset < 0) {
throw new RangeError(
`offset must be a finite non-negative number: ${offset}`
);
}
}

(this.node as IAudioBufferQueueSourceNode).start(when);
}

Expand Down
28 changes: 28 additions & 0 deletions packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import AudioBufferBaseSourceNode from './AudioBufferBaseSourceNode';
import AudioBuffer from './AudioBuffer';
import { InvalidStateError, RangeError } from '../errors';
import { EventEmptyType } from '../events/types';
import { AudioEventSubscription } from '../events';

export default class AudioBufferSourceNode extends AudioBufferBaseSourceNode {
private onLoopEndedSubscription?: AudioEventSubscription;
private onLoopEndedCallback?: (event: EventEmptyType) => void;

public get buffer(): AudioBuffer | null {
const buffer = (this.node as IAudioBufferSourceNode).buffer;
if (!buffer) {
Expand Down Expand Up @@ -90,4 +94,28 @@ export default class AudioBufferSourceNode extends AudioBufferBaseSourceNode {
) {
super.onEnded = callback;
}

public get onLoopEnded(): ((event: EventEmptyType) => void) | undefined {
return this.onLoopEndedCallback;
}

public set onLoopEnded(callback: ((event: EventEmptyType) => void) | null) {
if (!callback) {
(this.node as IAudioBufferSourceNode).onLoopEnded = '0';
this.onLoopEndedSubscription?.remove();
this.onLoopEndedSubscription = undefined;
this.onLoopEndedCallback = undefined;

return;
}

this.onLoopEndedCallback = callback;
this.onLoopEndedSubscription = this.audioEventEmitter.addAudioEventListener(
'loopEnded',
callback
);

(this.node as IAudioBufferSourceNode).onLoopEnded =
this.onLoopEndedSubscription.subscriptionId;
}
}
1 change: 1 addition & 0 deletions packages/react-native-audio-api/src/events/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface OnAudioReadyEventType {

interface AudioAPIEvents {
ended: OnEndedEventType;
loopEnded: EventEmptyType;
audioReady: OnAudioReadyEventType;
positionChanged: EventTypeWithValue;
audioError: EventEmptyType; // to change
Expand Down
3 changes: 3 additions & 0 deletions packages/react-native-audio-api/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ export interface IAudioBufferSourceNode extends IAudioBufferBaseSourceNode {

start: (when?: number, offset?: number, duration?: number) => void;
setBuffer: (audioBuffer: IAudioBuffer | null) => void;

// passing subscriptionId(uint_64 in cpp, string in js) to the cpp
onLoopEnded: string;
}

export interface IAudioBufferQueueSourceNode
Expand Down