react-native-audio-api
Version:
react-native-audio-api provides system for controlling audio in React Native environment compatible with Web Audio API specification
88 lines (86 loc) • 4.05 kB
JavaScript
;
import { Image, Platform } from 'react-native';
import { NativeAudioAPIModule } from "../specs/index.js";
import { AudioApiError } from "../errors/index.js";
import { isBase64Source, isDataBlobString, isRemoteSource, resolveLocalFilePath } from "../utils/paths.js";
import { base64ToArrayBuffer } from "../utils/index.js";
import AudioBuffer from "./AudioBuffer.js";
class AudioDecoder {
static instance = null;
constructor() {
this.decoder = globalThis.createAudioDecoder();
}
async decodeAudioDataImplementation(input, sampleRate, fetchOptions) {
const rate = sampleRate ?? 0;
if (input instanceof ArrayBuffer) {
return this.decodeFromArrayBuffer(input, rate);
}
const stringSource = this.resolveStringSource(input);
this.assertSupportedStringSource(stringSource);
if (isRemoteSource(stringSource)) {
return this.decodeFromRemoteUrl(stringSource, rate, fetchOptions);
}
return this.decodeFromLocalFile(stringSource, rate);
}
async decodeFromArrayBuffer(arrayBuffer, sampleRate) {
const buffer = await this.decoder.decodeWithMemoryBlock(
// @ts-ignore internal function
new Uint8Array(arrayBuffer), sampleRate);
return new AudioBuffer(buffer);
}
resolveStringSource(input) {
return typeof input === 'number' ? Image.resolveAssetSource(input).uri : input;
}
assertSupportedStringSource(source) {
if (typeof source !== 'string') {
throw new TypeError('Input must be a module, uri or ArrayBuffer');
}
if (isBase64Source(source)) {
throw new AudioApiError('Base64 source decoding is not currently supported, to decode raw PCM base64 strings use decodePCMInBase64 method.');
}
if (isDataBlobString(source)) {
throw new AudioApiError('Data Blob string decoding is not currently supported.');
}
}
async decodeFromRemoteUrl(url, sampleRate, fetchOptions) {
const arrayBuffer = await fetch(url, fetchOptions).then(res => res.arrayBuffer());
return this.decodeFromArrayBuffer(arrayBuffer, sampleRate);
}
async decodeFromLocalFile(stringSource, sampleRate) {
const useAndroidBundledAssetReader = Platform.OS === 'android' && !stringSource.startsWith('file://') && !__DEV__;
// special workaround, because android bundled assets are passed as f.e. asset_example_file
// which has to be handled on native side and extension is not passed with it so decode using memory
if (useAndroidBundledAssetReader) {
const base64Payload = await NativeAudioAPIModule.readAndroidReleaseAssetBytesAsBase64(stringSource);
const arrayBuffer = base64ToArrayBuffer(base64Payload);
return this.decodeFromArrayBuffer(arrayBuffer, sampleRate);
}
const filePath = resolveLocalFilePath(stringSource);
const buffer = await this.decoder.decodeWithFilePath(filePath, sampleRate);
return new AudioBuffer(buffer);
}
static getInstance() {
if (!AudioDecoder.instance) {
AudioDecoder.instance = new AudioDecoder();
}
return AudioDecoder.instance;
}
async decodeAudioDataInstance(input, sampleRate, fetchOptions) {
const audioBuffer = await this.decodeAudioDataImplementation(input, sampleRate, fetchOptions);
if (!audioBuffer) {
throw new AudioApiError('Failed to decode audio data.');
}
return audioBuffer;
}
async decodePCMInBase64Instance(base64String, inputSampleRate, inputChannelCount, interleaved) {
const buffer = await this.decoder.decodeWithPCMInBase64(base64String, inputSampleRate, inputChannelCount, interleaved);
return new AudioBuffer(buffer);
}
}
export async function decodeAudioData(input, sampleRate, fetchOptions) {
return AudioDecoder.getInstance().decodeAudioDataInstance(input, sampleRate, fetchOptions);
}
export async function decodePCMInBase64(base64String, inputSampleRate, inputChannelCount, isInterleaved = true) {
return AudioDecoder.getInstance().decodePCMInBase64Instance(base64String, inputSampleRate, inputChannelCount, isInterleaved);
}
//# sourceMappingURL=AudioDecoder.js.map