Kotlin for loop does not wait for function to finish - android

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.

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)
}

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
}
}
}

Related

Creating OnCompletionListener in Compose

I am a Java/Andriod programmer new to Kotlin and Jetpack Compose. I am creating a simple sound-board app with three buttons that will play a unique sound when pressed. All is going well, but I am struggling with creating the OnCompletionListener for the Mediaplayer instances (so I can release resources and change the button on the UI)
Within my button Composable I create the instance of the Mediaplayer
`val mediaPlayer:MediaPlayer by remember {
mutableStateOf(MediaPlayer.create(context,soundID))
}`
which works great in the OnClick of the Image composable:
Image (
painter = painterResource(id = (imageID)),
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = Modifier
.size(250.dp)
.aspectRatio(16f / 9f)
.clip(RoundedCornerShape(cornerDiameter.dp))
.border(
BorderStroke(4.dp, Color.Yellow),
RoundedCornerShape(cornerDiameter.dp)
)
.clickable(
enabled = true,
onClick = {
if (isPlaying) {
println("STOPPING player")
mediaPlayer.pause()
isPlaying = false
} else {
println("starting player")
mediaPlayer.start()
isPlaying = true
}
}
)
)
When the respective audio is done playing I want to call a routine to clean up and update the UI. When I create the onCompletionLIstener I send it the instance of the MediaPlayer:
val onCompletionListener =
MediaPlayer.OnCompletionListener(trackDone(mediaPlayer))
mediaPlayer.setOnCompletionListener(onCompletionListener)
which expects the function trackDone to be (MediaPlayer!) → Unit, which it auto creates:
fun trackDone(mediaPlayer: MediaPlayer): (MediaPlayer) -> Unit {
if(mediaPlayer != null)
{
mediaPlayer!!.stop()
mediaPlayer!!.release()
}
}
However, I now get an error for trackDone saying "A 'return' expression required in a function with a block body ('{...}')". But I can't figure out what type of return I can provide to satisfy this. Returning mediaPlayer does not work.
Any help is appreciated. I hope I have given enough information.
The compiler expects trackDone to return a lambda that accepts a MediaPlayer and returns nothing. I'm not sure what you want to accomplish with this function signature but the code below will fix the compile error.
fun trackDone(mediaPlayer: MediaPlayer): (MediaPlayer) -> Unit {
if(mediaPlayer != null)
{
mediaPlayer!!.stop()
mediaPlayer!!.release()
}
return {}
}
I assume this is not exactly your intention and instead you want some callback from this function.
First, please clean it up, you don't need to check for nullability and you don't need to shout !! in your code, because the function already expects a non-nullable MediaPlayer argument.
Next, simply add an additional function type parameter that will be used as a callback inside trackDone.
Putting them all together, your trackDone function should look like this
fun trackDone(mediaPlayer: MediaPlayer, onComplete : (MediaPlayer) -> Unit) {
// remove unnecessary nullability checking
mediaPlayer.stop()
mediaPlayer.release()
// on complete callback
onComplete(mediaPlayer)
}
or if you don't want to return the MediaPlayer instance then this,
fun trackDone(mediaPlayer: MediaPlayer, onComplete : () -> Unit) {
// remove unnecessary nullability checking
mediaPlayer.stop()
mediaPlayer.release()
// on complete callback
onComplete()
}
and this is how it would be used 'ideally'.
fun someFunction() {
val mediaPlayer: MediaPlayer
...
...
...
trackDone(mediaPlayer) { stoppedMediaPlayer ->
// DONE
}
// or
trackDone(mediaPlayer) {
// DONE
}
}
Now I have no idea if this would "work" for a "completeListener" callback like you want to achieve, but the "fix" will surely compile.

Timer based on Handler stops, Android, Kotlin

I use Handler for creating a timer in a Widget.
I use the recommended constructor, i.e. passing a Looper to it.
private val updateHandler = Handler(Looper.getMainLooper())
#RequiresApi(Build.VERSION_CODES.Q)
private val runnable = Runnable {
updateDisplay()
}
#RequiresApi(Build.VERSION_CODES.Q)
private fun updateDisplay () {
updateHandler?.postDelayed(runnable, TIMER_MS)
// some other code
}
The TIMER MS is set to 3000 ms.
The timer runs fine for a while and execute the given code. However after a random time elapsed the timer stops working and no more execution of the given code happens.
Please advise what the problem could be ond how to fix it.
Alternatively, can I use some other timer? (The timer should go off every few second - this is the reason why I use Handler)
Thank you for any advice in advance
You could always try using a Coroutine for something like this:
class TimedRepeater(var delayMs: Long,
var worker: (() -> Unit)) {
private var timerJob: Job? = null
suspend fun start() {
if (timerJob != null) throw IllegalStateException()
timerJob = launch {
while(isActive) {
delay(delayMs)
worker()
}
}
}
suspend fun stop() {
if (timerJob == null) return
timerJob.cancelAndJoin()
timerJob = null
}
}
suspend fun myStuff() {
val timer = Timer(1000) {
// Do my work
}
timer.start()
// Some time later
timer.stop()
}
I haven't tested the above, but it should work well enough.
You can use CountDownTimer from Android framework to achieve the same. It internally uses Handler for timer
val timer = object: CountDownTimer(1000,1000){
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
}
}
timer.start()

Android Kotlin - ExoPlayer keeps playing when app goes to background

So far I only found questions about how to make ExoPlayer keep playing when app goes to background. Why the hell is that the case by me without coding this bs??
This is what I have so far and it's inside RecyclerView OnBingViewHolder:
val player = ExoPlayer.Builder(context).build()
val mediaItem: MediaItem = MediaItem.fromUri(fileUrl)
player.setMediaItem(mediaItem)
player.repeatMode = Player.REPEAT_MODE_ONE
holder.vidPlayer.player = player
player.prepare()
player.seekTo(100)
// player.play()
holder.vidPlayer.setTag(mpTag, player)
holder.vidPlayer.setTag(manuelPlayTag, false)
holder.vidPlayer.setTag(manuelPauseTag, false)
player.addListener(object : Player.Listener { // player listener
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
if (playWhenReady && playbackState == Player.STATE_READY) {
Log.d(tagg, "state: plays")
holder.vidPlayer.hideController()
} else if (playWhenReady) {
// might be idle (plays after prepare()),
// buffering (plays when data available)
// or ended (plays when seek away from end)
} else {
Log.d(tagg, "state: pause")
holder.vidPlayer.showController()
}
}
})
how I prevent the play when app goes to background?
When your app goes to the background the active Fragment/Activity's Lifecycle method onPause (and onStop) is called. In the onPause method you can cycle through your bound ViewHolders and stop the video player(s).
You can simply stop the ExoPlayer when the app goes to the background.
override fun onStop() {
super.onStop()
simpleExoPlayer.stop()
}
And in onStart just prepare() the ExoPlayer again:
override fun onStart() {
super.onStart()
simpleExoPlayer.prepare()
}
In order to automatically play the media, you need to set playWhenReady = true.
simpleExoPlayer.playWhenReady = true
By setting playWhenReady = true it will automcatically play the content and we don't need to explicitly call simpleExoPlayer.play().

How to put an audio before a stream connection in Android Studio (Kotlin)

I am new to programming and I am making a basic app for radio in which an introduction audio sounds when you press a button, then a second audio should appear until the network connection of the online radio is established.
I have managed to make the intro audio sound complete when I click, then silence is generated until the online radio plays, but I don't know how to put a second audio that detects the charging status before the radio plays. This is my code:
fun MediaPlayerRadio(){
mediaPlayer = MediaPlayer.create(
this#MainActivity,
Uri.parse("https://radiolink.com")
)
mediaPlayer?.start()
}
........................................................................
fun MediaPlayerIntroSound(){
mediaPlayer = MediaPlayer.create(this, R.raw.SoundIntro)
mediaPlayer?.start()
}
........................................................................
fun click_Button_Radio(){
btn.setOnClickListener(){
if (btn.isSelected){
btn.isSelected = false
mediaPlayer?.stop()
}else{
btn.isSelected = !btn.isSelected
MediaPlayerIntroSound()
mediaPlayer!!.setOnCompletionListener(object : MediaPlayer.OnCompletionListener {
override fun onCompletion(mp: MediaPlayer?) {
MediaPlayerRadio()
}
})
}
}
}
I hope you can support me with this.
For this, you will have to use 2 media players, one for intro sound and another for actual sound.
While the time actual sound media player gets ready, you can play different audios on the intro sound media player.
To stop the intro audio player once the other player is ready, you can make use of a view model.
//Play Media player
mediaPlayer = MediaPlayer.create(this#MainActivity, R.raw.first_audio)
mediaPlayer.start()
// Add completion listener, so that we can change the audio
// once the first one is finished
mediaPlayer.setOnCompletionListener {
mediaPlayer.release()
mediaPlayer = MediaPlayer.create(this#MainActivity, R.raw.second_audio)
mediaPlayer.start()
}
// We are observing a MutableLiveData of Boolean type,
// once it is true we will stop the intro media player
// and start the radio player
viewModel.isPlayerReady.observe(this) { isReady ->
if(isReady) {
mediaPlayer.stop()
// Start radio media player
}
}
// This is how I am changing the value of MutableLiveData
demoBtn.setOnClickListener {
viewModel.isPlayerReady.value = true
}
I have tested the above approach and it works fine as expected.
Steps to link a view model to the activity:
Add the dependency: implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
Make a view model class and a mutable live variable of type boolean in it.
class MainViewModel: ViewModel() {
val isPlayerReady = MutableLiveData<Boolean>()
}
Link view model with the activity.
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
....
}

When will onCompletion be launched in MediaPlayer?

I try to use the following code to play music.
I know I need to release mediaPlayer as soon as possible if I don't use it again, so I place the release code in onCompletion.
1: Will onCompletion be launched after a music have finished play?
2: Will onCompletion be launched after I invoke mediaPlayer?.stop()?
3: Will onCompletion be launched if the Activity which invoke PlayHelper is destroied?
Code
class PlayHelper private constructor(): MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener {
private var mediaPlayer: MediaPlayer? = null
fun play(path: String){
mediaPlayer= MediaPlayer()
mediaPlayer?.setAudioAttributes(
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
)
mediaPlayer?.setDataSource(path)
mediaPlayer?.setOnPreparedListener(this#PlayHelper)
mediaPlayer?.prepareAsync()
}
fun pause(){
mediaPlayer?.pause()
}
fun stop(){
mediaPlayer?.stop()
}
/** Called when MediaPlayer is ready */
override fun onPrepared(mediaPlayer: MediaPlayer) {
mediaPlayer.start()
}
override fun onCompletion(mediaPlayer: MediaPlayer) {
if (mediaPlayer.isPlaying) {
mediaPlayer.stop();
mediaPlayer.release()
}
}
companion object {
// For Singleton instantiation
#Volatile private var instance: PlayHelper? = null
fun getInstance() = instance?: synchronized(this) {
instance?: PlayHelper().also { instance = it }
}
}
}
Regarding the official documents, onCompletion is called only when the end of a media source is reached during playback. So, in other cases, like calling mediaPlayer.stop(), it won't be called.
1: Will onCompletion be launched after a music have finished play?
It will.
2: Will onCompletion be launched after I invoke mediaPlayer?.stop()?
It will not.
3: Will onCompletion be launched if the Activity which invoke PlayHelper is destroied?
It will not. The PlayHelper will be removed from memory when the Activity is destroyed.
This link: onCompletionListener from the docs contains the following line:
Called when the end of a media source is reached during playback.

Categories

Resources