react-native-audio-api
Version:
react-native-audio-api provides system for controlling audio in React Native environment compatible with Web Audio API specification
162 lines (133 loc) • 4.26 kB
text/typescript
import { InvalidStateError } from '../../errors';
import AudioBuffer from '../AudioBuffer.web';
import AudioNode from '../AudioNode.web';
import AudioParam from '../AudioParam.web';
import BaseAudioContext from '../BaseAudioContext.web';
import { AudioBufferSourceNodeBackend } from '../types.web';
export default class AudioBufferSourceNodeStandard implements AudioBufferSourceNodeBackend {
private node: globalThis.AudioBufferSourceNode;
private hasBeenStarted: boolean = false;
private _loopSkip: boolean = false;
private _onLoopEnded: ((event: object) => void) | undefined = undefined;
readonly playbackRate: AudioParam;
readonly detune: AudioParam;
public constructor(
context: BaseAudioContext,
options?: AudioBufferSourceOptions
) {
const { buffer, ...rest } = options ?? {};
this.node = new globalThis.AudioBufferSourceNode(context.context, {
...rest,
...(buffer ? { buffer: (buffer as AudioBuffer).buffer } : {}),
});
this.detune = new AudioParam(this.node.detune, context);
this.playbackRate = new AudioParam(this.node.playbackRate, context);
}
public start(when: number = 0, offset?: number, duration?: number): void {
if (when && when < 0) {
throw new RangeError(
`when must be a finite non-negative number: ${when}`
);
}
if (offset && offset < 0) {
throw new RangeError(
`offset must be a finite non-negative number: ${offset}`
);
}
if (duration && duration < 0) {
throw new RangeError(
`duration must be a finite non-negative number: ${duration}`
);
}
if (this.hasBeenStarted) {
throw new InvalidStateError('Cannot call start more than once');
}
this.hasBeenStarted = true;
this.node.start(when, offset, duration);
}
public stop(when?: number): void {
if (when !== undefined && when < 0) {
throw new RangeError(
`when must be a finite non-negative number: ${when}`
);
}
if (!this.hasBeenStarted) {
throw new InvalidStateError(
'Cannot call stop without calling start first'
);
}
this.node.stop(when);
}
public connect(destination: AudioNode | AudioParam): AudioNode | AudioParam {
if (destination instanceof AudioParam) {
this.node.connect(destination.param);
} else {
this.node.connect(destination.node);
}
return destination;
}
public disconnect(destination?: AudioNode | AudioParam): void {
if (destination === undefined) {
this.node.disconnect();
return;
}
if (destination instanceof AudioParam) {
this.node.disconnect(destination.param);
return;
}
this.node.disconnect(destination.node);
}
public get buffer(): AudioBuffer | null {
const buffer = this.node.buffer;
if (!buffer) {
return null;
}
return new AudioBuffer(buffer);
}
public set buffer(buffer: AudioBuffer | null) {
if (!buffer) {
this.node.buffer = null;
return;
}
this.node.buffer = buffer.buffer;
}
public get loop(): boolean {
return this.node.loop;
}
public set loop(value: boolean) {
this.node.loop = value;
}
public get loopStart(): number {
return this.node.loopStart;
}
public set loopStart(value: number) {
this.node.loopStart = value;
}
public get loopEnd(): number {
return this.node.loopEnd;
}
public set loopEnd(value: number) {
this.node.loopEnd = value;
}
public get loopSkip(): boolean {
return this._loopSkip;
}
public set loopSkip(value: boolean) {
this._loopSkip = value;
}
public get onEnded(): ((event: Event) => void) | null {
return this.node.onended as ((event: Event) => void) | null;
}
public set onEnded(callback: ((event: Event) => void) | null) {
this.node.onended = callback as
| ((this: AudioScheduledSourceNode, ev: Event) => unknown)
| null;
}
public get onLoopEnded(): ((event: object) => void) | undefined {
return this._onLoopEnded;
}
// The browser Web Audio API has no per-loop event; callback is stored but never fired.
public set onLoopEnded(callback: ((event: object) => void) | null) {
this._onLoopEnded = callback ?? undefined;
}
}