react-native-audio-api
Version:
react-native-audio-api provides system for controlling audio in React Native environment compatible with Web Audio API specification
146 lines (141 loc) • 5.85 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.decodeAudioData = decodeAudioData;
exports.decodePCMInBase64 = decodePCMInBase64;
exports.default = void 0;
exports.getAudioDuration = getAudioDuration;
var _errors = require("../errors");
var _utils = require("../utils");
var _OfflineAudioContext = _interopRequireDefault(require("./OfflineAudioContext.web"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const MAX_INT16_VALUE = 32768.0;
class AudioDecoder {
static instance = null;
// keep it a singleton pattern
// eslint-disable-next-line no-useless-constructor
constructor() {}
static getInstance() {
if (!AudioDecoder.instance) {
AudioDecoder.instance = new AudioDecoder();
}
return AudioDecoder.instance;
}
async decodeAudioDataImplementation(input, sampleRate, fetchOptions) {
const rate = sampleRate ?? 0;
if (input instanceof ArrayBuffer) {
return this.decodeFromArrayBuffer(input, rate);
}
if (typeof input !== 'string') {
throw TypeError('Input must be either an ArrayBuffer or a valid string URL path');
}
return this.decodeFromRemoteUrl(input, rate, fetchOptions);
}
async decodeFromArrayBuffer(arrayBuffer, sampleRate) {
const targetRate = sampleRate > 0 ? sampleRate : 44100;
const context = new _OfflineAudioContext.default(1, 1, targetRate);
return await context.decodeAudioData(arrayBuffer);
}
async decodeFromRemoteUrl(url, sampleRate, fetchOptions) {
const arrayBuffer = await fetch(url, fetchOptions).then(res => res.arrayBuffer());
return this.decodeFromArrayBuffer(arrayBuffer, sampleRate);
}
async decodeAudioDataInstance(input, sampleRate, fetchOptions) {
const audioBuffer = await this.decodeAudioDataImplementation(input, sampleRate, fetchOptions);
if (!audioBuffer) {
throw new _errors.AudioApiError('Failed to decode audio data.');
}
return audioBuffer;
}
async decodePCMInBase64Instance(base64String, inputSampleRate, inputChannelCount, interleaved) {
try {
const arrayBuffer = (0, _utils.base64ToArrayBuffer)(base64String);
// map as 16 bits
const int16samples = new Int16Array(arrayBuffer);
const totalSampleCount = int16samples.length;
const frameCount = totalSampleCount / inputChannelCount;
// get requested buffer to write into
const context = new _OfflineAudioContext.default(inputChannelCount, frameCount, inputSampleRate);
const buffer = context.createBuffer(inputChannelCount, frameCount, inputSampleRate);
// deinterleave
let outIndex;
for (let channel = 0; channel < inputChannelCount; channel++) {
const channelData = buffer.getChannelData(channel);
for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) {
outIndex = interleaved ? channel + frameIndex * inputChannelCount // Ch1, Ch2, Ch1, Ch2, ...
: frameIndex + channel * frameCount; // Ch1, Ch1, Ch1, ..., Ch2, Ch2, Ch2, ...
// normalize
channelData[frameIndex] = int16samples[outIndex] / MAX_INT16_VALUE;
}
}
return Promise.resolve(buffer);
} catch {
throw new _errors.AudioApiError('Failed to decode PCM data.');
}
}
loadDurationFromAudioElement(input) {
if (typeof globalThis.Audio !== 'function') {
return Promise.reject(new _errors.AudioApiError('getAudioDuration requires HTMLAudioElement support.'));
}
return new Promise((resolve, reject) => {
const audio = new globalThis.Audio();
let settled = false;
const cleanup = () => {
audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
audio.removeEventListener('error', handleError);
audio.src = '';
audio.load();
};
const settle = callback => {
if (settled) {
return;
}
settled = true;
cleanup();
callback();
};
const handleLoadedMetadata = () => {
const duration = audio.duration;
if (Number.isFinite(duration) && duration >= 0) {
settle(() => resolve(duration));
return;
}
settle(() => reject(new _errors.AudioApiError('Audio duration metadata is unavailable')));
};
const handleError = () => {
const errorMessage = audio.error?.message ? `: ${audio.error.message}` : '';
settle(() => reject(new _errors.AudioApiError(`Failed to load audio metadata${errorMessage}`)));
};
audio.preload = 'metadata';
audio.addEventListener('loadedmetadata', handleLoadedMetadata);
audio.addEventListener('error', handleError);
audio.src = input;
audio.load();
});
}
getAudioDurationInstance(input) {
if (input instanceof ArrayBuffer) {
const blob = new Blob([input]);
const objectUrl = URL.createObjectURL(blob);
return this.loadDurationFromAudioElement(objectUrl).finally(() => {
URL.revokeObjectURL(objectUrl);
});
}
if (typeof input !== 'string') {
return Promise.reject(new TypeError('Input must be a local file path, remote URL, or ArrayBuffer.'));
}
return this.loadDurationFromAudioElement(input);
}
}
exports.default = AudioDecoder;
async function decodeAudioData(input, sampleRate, fetchOptions) {
return AudioDecoder.getInstance().decodeAudioDataInstance(input, sampleRate, fetchOptions);
}
async function decodePCMInBase64(base64String, inputSampleRate, inputChannelCount, isInterleaved = true) {
return AudioDecoder.getInstance().decodePCMInBase64Instance(base64String, inputSampleRate, inputChannelCount, isInterleaved);
}
function getAudioDuration(input) {
return AudioDecoder.getInstance().getAudioDurationInstance(input);
}
//# sourceMappingURL=AudioDecoder.web.js.map