I would like to add background music that plays automatically, with only maybe a "pause/stop audio"
I've tried searching within multiple sources, but all of the tutorialsthat I've found include a "play sound" music, which won't be needed.
They don't really show it in the examples provided on the expo-av page, but the Audio.Sound.createAsync function allows for a lot of customization. You can set the volume, decide if it should loop, decide if it should play on load, and even subscribe playback status updates and set the intervals at which you get those updates. I went overboard and fleshed out a hook to return the status, position, and duration of the song as it playbacks; and if you need to subscribe to additional info the hook will allow you to subscribe to the playback update events:
import { useState, useEffect, useCallback, useMemo } from 'react';
import { Audio, usePermissions } from 'expo-av';
const msToSeconds = (num) => Math.round(num / 1000);
// these should work but I found that only isPlaying works
const STATUSES = ['isBuffering', 'isPlaying', 'didJustFinished', 'isLooping'];
const getStatus = (playbackEvent) => {
if (playbackEvent.durationMillis === playbackEvent.positionMillis)
return 'finished';
const status = STATUSES.find((status) => playbackEvent[status] === true);
if (status) return status === 'didJustFinished' ? 'finished' : status;
};
export default function useAudio({
uri,
shouldPlay = false,
onPlaybackStatusUpdate,
updateIntervals = 500,
startPosition = 0,
shouldLoop = false,
}) {
const [sound, setS] = useState(null);
const [position, setPosition] = useState(0);
const [status, setStatus] = useState('isLoading');
const [duration, setDuration] = useState(0);
const handlePlaybackStatusChange = useCallback(
(playbackEvent) => {
setStatus(getStatus(playbackEvent));
setPosition(msToSeconds(playbackEvent.positionMillis||0));
// because this function is recreated everytime onPlaybuckStatusChange
// is recreated, onPlaybackStatusUpdate may need to be wrap in an
// useCallback for better performance
onPlaybackStatusUpdate?.(playbackEvent);
},
[onPlaybackStatusUpdate]
);
// cleanup function
useEffect(() => {
return async () => {
if (sound) {
// await sound.pauseAsync();
await sound.unloadAsync();
}
};
}, [sound]);
// load song on uri changes
useEffect(() => {
const loadSound = async () => {
try {
const {sound,status} = await Audio.Sound.createAsync(
uri,
{
shouldPlay,
progressUpdateIntervalMillis: updateIntervals,
positionMillis: startPosition,
isLooping: shouldLoop,
volume:1
},
handlePlaybackStatusChange
);
setS(sound);
setDuration(msToSeconds(status.durationMillis));
} catch (err) {
console.log(err);
setStatus('error')
}
};
loadSound();
}, [
uri,
handlePlaybackStatusChange,
shouldLoop,
shouldPlay,
startPosition,
updateIntervals,
]);
return { position, status, duration,sound };
}
Using the hook (demo):
const playOnLoad = true;
const updateIntervals = 1000;
const startPosition = 1000 * 60 * 4.4;
export default function App() {
const { width } = useWindowDimensions();
const barWidth = width * 0.8;
const { position, duration, status } = useAudio({
uri: require('./song.mp3'),
shouldPlay: playOnLoad,
updateIntervals,
startPosition:0,
shouldLoop: true,
});
return (
<SafeAreaView style={styles.container}>
<View style={[styles.player, { width: barWidth }]}>
<Text>Sound status:{status}</Text>
<Progress.Bar progress={position/duration || 0} width={barWidth} color="green" />
<View style={styles.timeContainer}>
<Text style={styles.currentTime}>{formatSeconds(position)}</Text>
<Text style={styles.duration}>{formatSeconds(duration)}</Text>
</View>
</View>
</SafeAreaView>
);
}