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 (201 loc) • 6.2 kB
JavaScript
;
import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { View } from 'react-native';
import { AudioComponentContext } from "./AudioTagContext.js";
import { useStableAudioProps, resolveSourcePath } from "./utils.js";
import AudioControls from "./controls/AudioControls.js";
import { useAudioSourceLoader } from "./useAudioSourceLoader.js";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const Audio = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef((props, ref) => {
const {
children
} = props;
const {
autoPlay,
controls,
loop,
muted,
preload,
forceDownload,
source,
playbackRate,
preservesPitch,
volume,
context,
onLoadStart,
onLoad,
onError,
onPositionChange,
onEnded: onEndedCallback,
onPlay,
onPause,
onVolumeChange
} = useStableAudioProps(props);
const [volumeState, setVolumeState] = useState(null);
const [mutedState, setMutedState] = useState(null);
const [ready, setReady] = useState(false);
const [playbackState, setPlaybackState] = useState('idle');
const [currentTime, setCurrentTime] = useState(0);
const path = useMemo(() => resolveSourcePath(source), [source]);
const lastEffectiveVolumeRef = useRef(muted ? 0 : volume);
useEffect(() => {
return () => {
fileSourceRef.current?.disconnect();
fileSourceRef.current?.pause();
fileSourceRef.current?.dispose();
fileSourceRef.current = null;
};
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const effectiveMutedState = useMemo(() => {
return mutedState ?? muted;
}, [mutedState, muted]);
const effectiveVolumeState = useMemo(() => {
return effectiveMutedState ? 0 : volumeState ?? volume;
}, [effectiveMutedState, volumeState, volume]);
const effectiveVolumeRef = useRef(effectiveVolumeState);
effectiveVolumeRef.current = effectiveVolumeState;
const playRef = useRef(() => {});
const handleAutoPlay = useCallback(() => {
playRef.current();
}, []);
const handleEnded = useCallback(endedDuration => {
setPlaybackState('idle');
setCurrentTime(endedDuration);
onEndedCallback();
}, [onEndedCallback]);
const {
fileSourceRef,
ready: loaderReady,
duration,
loadForPlayback
} = useAudioSourceLoader({
context,
path,
source,
preloadMode: preload,
forceDownload,
loop,
playbackRate,
preservesPitch,
autoPlay,
effectiveVolumeRef,
onLoadStart,
onLoad,
onError,
onEnded: handleEnded,
onAutoPlay: handleAutoPlay
});
useEffect(() => {
setReady(loaderReady);
}, [loaderReady]);
useEffect(() => {
fileSourceRef.current?.setVolume(effectiveVolumeState);
}, [effectiveVolumeState, fileSourceRef]);
const play = useCallback(async () => {
if (!fileSourceRef.current) {
const loaded = await loadForPlayback();
if (!loaded) {
return;
}
}
if (!fileSourceRef.current) {
return;
}
fileSourceRef.current.play();
setPlaybackState('playing');
onPlay();
}, [fileSourceRef, loadForPlayback, onPlay]);
playRef.current = () => {
play();
};
const pause = useCallback(() => {
fileSourceRef.current?.pause();
setPlaybackState('paused');
onPause();
}, [onPause, fileSourceRef]);
const setPlaybackRate = useCallback(nextPlaybackRate => {
fileSourceRef.current?.setPlaybackRate(nextPlaybackRate);
}, [fileSourceRef]);
const seekToTime = useCallback(seconds => {
fileSourceRef.current?.seekToTime(seconds);
const nextTime = duration > 0 ? Math.max(0, Math.min(seconds, duration)) : Math.max(0, seconds);
setCurrentTime(nextTime);
onPositionChange(nextTime);
}, [duration, setCurrentTime, onPositionChange, fileSourceRef]);
useEffect(() => {
if (!path) {
setPlaybackState('idle');
setCurrentTime(0);
}
}, [path]);
useEffect(() => {
if (lastEffectiveVolumeRef.current !== effectiveVolumeState) {
lastEffectiveVolumeRef.current = effectiveVolumeState;
onVolumeChange(effectiveVolumeState);
}
}, [onVolumeChange, effectiveVolumeState]);
useEffect(() => {
fileSourceRef.current?.setLoop(loop);
}, [loop, fileSourceRef]);
useEffect(() => {
fileSourceRef.current?.setPlaybackRate(playbackRate);
}, [playbackRate, fileSourceRef]);
useEffect(() => {
fileSourceRef.current?.setPreservesPitch(preservesPitch);
}, [preservesPitch, fileSourceRef]);
useEffect(() => {
if (playbackState !== 'playing') {
return;
}
const fileSource = fileSourceRef.current;
fileSource?.startPositionTracking(seconds => {
setCurrentTime(seconds);
onPositionChange(seconds);
});
return () => {
fileSource?.stopPositionTracking();
};
}, [onPositionChange, playbackState, fileSourceRef]);
useImperativeHandle(ref, () => ({
play,
pause,
seekToTime,
setVolume: setVolumeState,
setMuted: setMutedState,
setPlaybackRate,
getFileSourceNode: () => fileSourceRef.current?.getFileSourceNode() ?? null
}), [pause, play, seekToTime, setMutedState, setVolumeState, setPlaybackRate, fileSourceRef]);
const ctxValue = useMemo(() => ({
play,
pause,
seekToTime,
setVolume: setVolumeState,
volume: effectiveVolumeState,
ready,
setMuted: setMutedState,
muted: effectiveMutedState,
playbackState,
currentTime,
duration,
autoPlay,
controls,
loop,
preload,
playbackRate,
preservesPitch,
sourcePath: path,
source
}), [play, pause, seekToTime, setVolumeState, effectiveVolumeState, ready, setMutedState, effectiveMutedState, playbackState, currentTime, duration, autoPlay, controls, loop, preload, playbackRate, preservesPitch, path, source]);
return /*#__PURE__*/_jsx(AudioComponentContext.Provider, {
value: ctxValue,
children: /*#__PURE__*/_jsxs(View, {
style: {
alignSelf: 'stretch',
width: '100%'
},
children: [controls && /*#__PURE__*/_jsx(AudioControls, {}), children]
})
});
}));
export default Audio;
//# sourceMappingURL=Audio.js.map