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

203 lines (202 loc) • 6.31 kB
"use strict"; import { useCallback, useEffect, useRef, useState } from 'react'; import { Platform } from 'react-native'; import { probeDuration } from "../core/AudioFileUtils.js"; import { NotSupportedError } from "../errors/index.js"; import { NativeAudioAPIModule } from "../specs/index.js"; import { base64ToArrayBuffer, isFfmpegEnabled } from "../utils/index.js"; import { isRemoteHttpUrl, loadRemoteHttpSource } from "../utils/remoteHttpSource.js"; import { AudioFileSourceNode } from "./AudioFileSourceNode.js"; import { getSourceHeaders } from "./utils.js"; export function useAudioSourceLoader({ context, path, source, preloadMode, forceDownload, loop, playbackRate, preservesPitch, autoPlay, effectiveVolumeRef, onLoadStart, onLoad, onError, onEnded, onAutoPlay }) { const [ready, setReady] = useState(false); const [duration, setDuration] = useState(0); const fileSourceRef = useRef(null); const sourceRef = useRef(null); const isFetchingCancelled = useRef(false); const fullDataFetched = useRef(false); const onLoadStartRef = useRef(onLoadStart); onLoadStartRef.current = onLoadStart; const onLoadRef = useRef(onLoad); onLoadRef.current = onLoad; const onErrorRef = useRef(onError); onErrorRef.current = onError; const onEndedRef = useRef(onEnded); onEndedRef.current = onEnded; const onAutoPlayRef = useRef(onAutoPlay); onAutoPlayRef.current = onAutoPlay; const playbackRateRef = useRef(playbackRate); playbackRateRef.current = playbackRate; const preservesPitchRef = useRef(preservesPitch); preservesPitchRef.current = preservesPitch; const disposeSource = useCallback(() => { fileSourceRef.current?.stopPositionTracking(); fileSourceRef.current?.dispose(); sourceRef.current = null; fullDataFetched.current = false; }, []); const spawnFileSource = useCallback(() => { const nextSource = sourceRef.current; if (!context || !nextSource) { return false; } fileSourceRef.current?.dispose(); const initialVolume = effectiveVolumeRef.current; const initialPlaybackRate = playbackRateRef.current; const initialPreservesPitch = preservesPitchRef.current; const node = context.context.createFileSource({ source: nextSource, headers: getSourceHeaders(source), loop, volume: initialVolume, playbackRate: initialPlaybackRate, preservesPitch: initialPreservesPitch }); if (!node) { onErrorRef.current(new NotSupportedError('This file format requires FFmpeg build')); return false; } const fileSource = new AudioFileSourceNode(context, node); const { duration: nextDuration } = fileSource.attach({ loop, onEnded: () => { onEndedRef.current(nextDuration); spawnFileSource(); } }); fileSource.setVolume(initialVolume); fileSource.setPlaybackRate(initialPlaybackRate); fileSource.setPreservesPitch(initialPreservesPitch); fileSourceRef.current = fileSource; setDuration(nextDuration); onLoadRef.current(); if (autoPlay) { onAutoPlayRef.current(); } return true; }, [autoPlay, context, effectiveVolumeRef, loop, source]); const loadPlaybackSource = useCallback(async () => { if (!path) { return false; } isFetchingCancelled.current = false; setReady(false); onLoadStartRef.current(); const headers = getSourceHeaders(source); try { if (isRemoteHttpUrl(path)) { sourceRef.current = await loadRemoteHttpSource(path, headers, forceDownload); } else if (Platform.OS === 'android' && !__DEV__ && !path.startsWith('file://')) { const base64Payload = await NativeAudioAPIModule.readAndroidReleaseAssetBytesAsBase64(path); sourceRef.current = base64ToArrayBuffer(base64Payload); } else if (path.startsWith('file://')) { sourceRef.current = path.replace('file://', ''); } else { sourceRef.current = path; } fullDataFetched.current = true; if (!isFetchingCancelled.current && spawnFileSource()) { setReady(true); return true; } return false; } catch (error) { if (!isFetchingCancelled.current) { onErrorRef.current(error); } setReady(false); return false; } }, [forceDownload, path, source, spawnFileSource]); const probeMetadataOnly = useCallback(async () => { if (!isRemoteHttpUrl(path)) { setReady(true); return; } isFetchingCancelled.current = false; setReady(false); onLoadStartRef.current(); const headers = getSourceHeaders(source); try { const probedDuration = await probeDuration(path, context?.sampleRate, headers); if (probedDuration && !isFetchingCancelled.current) { setDuration(probedDuration); } setReady(true); } catch (error) { if (!isFetchingCancelled.current) { onErrorRef.current(error); } setReady(false); } }, [context?.sampleRate, path, source]); const loadForPlayback = useCallback(async () => { if (fullDataFetched.current) { return fileSourceRef.current != null; } return loadPlaybackSource(); }, [loadPlaybackSource]); useEffect(() => { isFetchingCancelled.current = false; fullDataFetched.current = false; if (!path) { setReady(false); setDuration(0); disposeSource(); return () => { isFetchingCancelled.current = true; disposeSource(); }; } if (preloadMode === 'none') { setReady(true); return () => { isFetchingCancelled.current = true; disposeSource(); }; } if (preloadMode === 'metadata') { if (!isFfmpegEnabled()) { setReady(true); } else { probeMetadataOnly(); } return () => { isFetchingCancelled.current = true; disposeSource(); }; } loadPlaybackSource(); return () => { isFetchingCancelled.current = true; disposeSource(); }; }, [disposeSource, loadPlaybackSource, path, preloadMode, probeMetadataOnly]); return { fileSourceRef, ready, duration, setDuration, loadForPlayback, disposeSource }; } //# sourceMappingURL=useAudioSourceLoader.js.map