Combining both solutions below, I wrote:
const startMusic = async () => {
let currentSong
let songPath
const songArray = [
{ path: require("../assets/sounds/Katsu.mp3"), song: mainTheme },
{ path: require("../assets/sounds/MainTheme2.mp3"), song: mainTheme2 },
{ path: require("../assets/sounds/MainTheme3.mp3"), song: mainTheme3 },
]
for (var i = 0; i < songArray.length; i++) {
currentSong = songArray[i].song
songPath = songArray[i].path
try {
await currentSong.loadAsync(songPath)
await currentSong.playAsync()
// setSoundObject(currentSong)
console.log("Music will start")
return new Promise(resolve => {
currentSong.setOnPlaybackStatusUpdate(playbackStatus => {
if (playbackStatus.didJustFinish) {
console.log("Just finished playing")
resolve()
}
})
})
} catch (error) {
console.log(`Error: ${error}`)
return
}
}
}
This actually plays the song, and the console logs occur on time ("Just finished playing" happens exactly when the song ends) I'm trying to figure out how to play the next song.. and how will it know when it has reached the final song?
return new Promise(resolve => {
currentSong.setOnPlaybackStatusUpdate(playbackStatus => {
if (playbackStatus.didJustFinish) {
console.log("Just finished playing")
resolve()
}
})
}).then(() => console.log("Next song?"))
Figured how where to put the .then
to get it to console log right after "Just finished playing" I'm just trying to see how to actually put the next song there
(then of course, telling it when to go back to the first song in the array)
Working on an assignment for a react native app using expo-av library for Sound files.
Right now, the app has a startMusic
function set in a Context file that is responsible for playing the app's background music. It only has one song for now:
const startMusic = async () => {
try {
await mainTheme.loadAsync(require("../assets/sounds/Katsu.mp3"))
await mainTheme.playAsync()
setSoundObject(mainTheme)
console.log("The first song is playing! Enjoy!")
} catch (error) {
console.log(`Couldnt load main theme: ${error}`)
return
}
}
It is used in the homescreen component's file like so:
const { startMusic } = useContext(MusicContext)
useEffect(() => {
startMusic()
}, [])
For the second song, I wrote another const in the MusicContext file:
const secondSong = async () => {
try {
await mainTheme2.loadAsync(require("../assets/sounds/MainTheme2.mp3"))
await mainTheme2.playAsync()
setSoundObject(mainTheme2)
console.log("Now playing the second track. Enjoy!")
} catch (error) {
console.log(`Could not play the second song: ${error}`)
return
}
}
Annnnnd… here is where my trouble lies. I know this wasn't gonna work but I wrote this in the component file to try to get the second song playing after the first song
useEffect(() => {
startMusic()
.then(secondSong())
}, [])
I know there's more to it than that but I'm having trouble.
Problem with your code is not just running one function after another (that would be as simple as startMusic().then(() => secondSong())
but still won't solve the problem), but the fact that your functions actually don't wait for a song to finish playing before resolving
You expect this line await mainTheme.playAsync()
to pause function execution until the song has finished, but what it in fact does according to docs https://docs.expo.io/versions/latest/sdk/av/ is exactly only starting the playback (without waiting for it to finish)
With that being said, you need to determine the moment your playback finishes, then create a Promise that will only resolve after the playback is finished so that your second song can only start after the first
In the simplest form without error handling and such, it can look like this
const startAndWaitForCompletion = async () => {
try {
await mainTheme.loadAsync(require('../assets/sounds/Katsu.mp3'))
await mainTheme.playAsync()
console.log('will start playing soon')
return new Promise((resolve) => {
mainTheme.setOnPlaybackStatusUpdate(playbackStatus => {
if (playbackStatus.didJustFinish) {
console.log('finished playing')
resolve()
}
}
})
} catch (error) {
console.log('error', error)
}
}
the trick is of course the .setOnPlaybackStatusUpdate
listener that will be called every so often with playback status, and by analyzing the status you can tell the song has finished playing. If you scroll to the bottom of the page I linked you will find other examples with status update
updated
const startAndWaitForCompletion = async (playbackObject, file) => {
try {
await playbackObject.loadAsync(file)
await playbackObject.playAsync()
console.log('will start playing soon')
return new Promise((resolve) => {
playbackObject.setOnPlaybackStatusUpdate(playbackStatus => {
if (playbackStatus.didJustFinish) {
console.log('finished playing')
resolve()
}
}
})
} catch (error) {
console.log('error', error)
}
}
////
const songs = [
{ path: require('../assets/sounds/Katsu.mp3'), song: mainTheme },
{ path: require('../assets/sounds/MainTheme2.mp3'), song: mainTheme2 },
{ path: require('../assets/sounds/MainTheme3.mp3'), song: mainTheme3 },
]
useEffect(() => {
(async () => {
for (let i = 0; i < songs.length; i++) {
await startAndWaitForCompletion(songs[i].song, songs[i].path)
}
})()
}, [])