audiokotlin-multiplatformcompose-desktop

How to play an MP3 file stored in resources folder in Kotlin Multiplatform with Jetpack Compose for Desktop?


I'm trying to play a sound in my Compose for Desktop project when pressing a button. My MP3 file is stored in the resources folder (./src/jvmMain/resources/beep.mp3).

I've been trying using the useResource function as follows (inside onClick parameter of a @Composable Button):

scope.launch {
 useResource("beep.mp3") {
                        val clip = AudioSystem.getClip()
                        val audioInputStream = AudioSystem.getAudioInputStream(
                            it
                        )
                        clip.open(audioInputStream)
                        clip.start()
                    }
}

but I get an error: Exception in thread "AWT-EventQueue-0" java.io.IOException: mark/reset not supported. I got the same error if I don't use the scope, which I define at the top level of my composable as:

val scope = rememberCoroutineScope()

Any help will be appreciated!


Solution

  • A library is needed to decode MP3 files. This is the key. You may add this maven dependency:

    implementation("com.googlecode.soundlibs:mp3spi:1.9.5.4")
    

    Using Java Sound APIs you originally used:

    import java.io.File
    import javax.sound.sampled.AudioFormat
    import javax.sound.sampled.AudioInputStream
    import javax.sound.sampled.AudioSystem
    import javax.sound.sampled.AudioSystem.getAudioInputStream
    import javax.sound.sampled.DataLine.Info
    import javax.sound.sampled.SourceDataLine
    
    class AudioPlayer {
        fun play(path: String) {
            val file = File(path)
            getAudioInputStream(file).use { `in` ->
                val outFormat = getOutFormat(`in`.format)
                val info = Info(SourceDataLine::class.java, outFormat)
                AudioSystem.getLine(info).use { line ->
                    (line as? SourceDataLine)?.let { l ->
                        l.open(outFormat)
                        l.start()
                        stream(getAudioInputStream(outFormat, `in`), l)
                        l.drain()
                        l.stop()
                    }
                }
            }
        }
    
        private fun getOutFormat(inFormat: AudioFormat): AudioFormat {
            val ch = inFormat.channels
            val rate = inFormat.sampleRate
            return AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, ch, ch * 2, rate, false)
        }
    
        private fun stream(`in`: AudioInputStream, line: SourceDataLine) {
            val buffer = ByteArray(65536)
            var n = 0
            while (n != -1) {
                line.write(buffer, 0, n)
                n = `in`.read(buffer, 0, buffer.size)
            }
        }
    }
    

    And call the play method from a non-UI thread / coroutine:

    AudioPlayer().play(fullPathToMP3File)
    

    Credit to original work by oldo: https://odoepner.wordpress.com/2013/07/19/play-mp3-or-ogg-using-javax-sound-sampled-mp3spi-vorbisspi/