UNPKG

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
"use strict"; 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