0

I have the following for loop.

for (song in songArray){
    playSong(song)
}

and playSong as below:

fun playSong(song){
mediaPlayer.create(context, R.raw.song)
mediaPlayer.start

mediaPlayer?.setOnCompletionListener {
        mediaPlayer!!.release()
        mediaPlayer = null
        }
}

The for loop does not wait for the entire playSong function to complete, and just immediately starts the next song. I want the the listener to be heard and the song to complete before iterating to the next song. If you could give me some guidance on this, I would appreciate it.

1
  • 2
    The for loop does wait for the function playSong to finish. Your problem is that playSong finishes immediately, not after the song finished playing, and the lambda you pass to setOnCompletionListener is only called later (after playSong has returned). This is called asynchronous execution if you want to look it up. If you want to reason sequentially about these things you could make use of coroutines. Otherwise, you can use one of the options from the current answers. Commented Sep 9, 2022 at 19:54

2 Answers 2

3

Here's how it could be done with coroutines. First, you need to create a suspend function version of playing media and waiting for it to finish. Since a completion listener is not a one-shot callback, I think it is more appropriate to use callbackFlow instead of suspendCoroutine.

suspend fun MediaPlayer.startAndAwait() { 
    callbackFlow { 
        setOnCompletionListener {
            trySendBlocking(Unit)
        }
        awaitClose { setOnCompletionListener(null) }
    }.first()
    start()
}

Then you can use this function in a coroutine, so you can loop sequentially:

suspend fun playSong(song: Int){
    MediaPlayer.create(context, song).apply {
        mediaPlayer = this // so you can cancel playback from elsewhere
        startAndAwait()
        release()
        mediaPlayer = null
    }
}

//In a coroutine:

for (song in songArray){
    playSong(song)
}
Sign up to request clarification or add additional context in comments.

3 Comments

Why a callback flow instead of suspendCancellableCoroutine?
Actually, the question was rather, why is on-completion-listener not a one-shot callback? And why does it make sense in this case to use a callbackFlow instead of suspend[Cancellable]Coroutine? It seems that using callbackFlow+first suffers from the same concurrency problem anyway, you're assigning a new completion listener just for one event and then removing it, which breaks if 2 concurrent calls to startAndAwait happen on the same MediaPlayer. So I don't quite see why suspendCoroutine wouldn't be better here
True, I think it’s a bit fragile either way. The problem is not just that it’s not a one-shot callback (the listener can be called multiple times if media is played multiple times), but you can only set one listener at a time so other functions could overwrite your listener. I think I just picked callbackFlow because it felt so wrong to use suspendCoroutine with a listener, but callbackFlow doesn’t solve the problem either so maybe that just makes it slightly more verbose.
2

Instead of using for loop play songs from array when the previous one finishes

fun playSongArray(songArray: IntArray) {
    var i = 0
    mediaPlayer.create(context, R.raw.songArray[0])
    mediaPlayer.start
    mediaPlayer?.setOnCompletionListener {
        if (i < songArray.size) {
            mediaPlayer.create(context, R.raw.songArray[1])
            mediaPlayer.start
            i++
        } else {
            mediaPlayer!!.release()
            mediaPlayer = null
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.