A Kotlin Multiplatform library to rapidly integrate audio across all your Kotlin Multiplatform apps. Currently, this library only ingests URLs and local paths. Composable Resources are also possible, but may be finicky.
| Format | Android | iOS | javascript / wasm | JVM* | File / Container Types |
|---|---|---|---|---|---|
| AAC LC | ✅ | ✅ | ❌ | ❓ | 3GPP (.3gp) MPEG-4 (.mp4, .m4a) ADTS raw AAC (.aac, decode in Android 3.1+, encode in Android 4.0+, ADIF not supported) MPEG-TS (.ts, not seekable, Android 3.0+) |
| AMR-NB | ✅ | ❌ | ❌ | ❓ | 3GPP (.3gp) AMR (.amr) |
| FLAC | ✅ | ❌ | ❌ | ❓ | FLAC (.flac) MPEG-4 (.mp4, .m4a, Android 10+) |
| MIDI | ✅ | ❌ | ❌ | ❓ | Type 0 and 1 (.mid, .xmf, .mxmf) RTTTL/RTX (.rtttl, .rtx) OTA (.ota) iMelody (.imy) |
| MP3 | ✅ | ✅ | ✅ | ✅ | MP3 (.mp3) MPEG-4 (.mp4, .m4a, Android 10+) Matroska (.mkv, Android 10+) |
| Opus | ✅ | ❌ | ❓ | ❓ | Ogg (.ogg) Matroska (.mkv) |
| PCM/WAVE | ✅ | ❌ | ✅ | ❓ | WAVE (.wav) |
| Vorbis | ✅ | ❌ | ❓ | ❓ | Ogg (.ogg) Matroska (.mkv, Android 4.0+) MPEG-4 (.mp4, .m4a, Android 10+) |
- NOTE: JVM file formats are dependent on the underlying operating system the app is run on.
You'll need to add your maven dependency list
# in your 'libs.versions.toml' file
[versions]
kotlin = "+" # gets the latest version
lexilabs-basic = "+" # gets the latest version
[libraries]
lexilabs-basic-sound = { module = "app.lexilabs.basic:basic-sound", version.ref = "lexilabs-basic" }then include the library in your gradle build
// in your 'shared/build.gradle.kts' file
sourceSets {
commonMain.dependencies {
implementation(libs.lexilabs.basic.sound)
}
}You can initialize an Audio object with a URL
val resource = "https://dare.wisc.edu/wp-content/uploads/sites/1051/2008/11/MS072.mp3"
val audio = Audio(resource, true) // AutoPlay is marked "true"You can play the audio separately from initializing the Audio object.
val resource = "https://dare.wisc.edu/wp-content/uploads/sites/1051/2008/11/MS072.mp3"
val audio = Audio(resource) // loads the audio file
DoSomethingElse()
audio.play() // plays the audio immediately upon executionYou can also pause and stop the audio:
/** PAUSING **/
audio.pause() // remembers where it paused
audio.play() // and resumes once executed again
/** STOPPING **/
audio.stop() // resets to the beginning of the file
audio.play() // and replays it again upon executionYou should release your audio when done to preserve memory:
audio.release() // converts the audio instance to nullThere are lots of options to load larger files asynchronously:
// Create empty Audio instance
val audio: Audio = Audio()
audio.resource = "https://dare.wisc.edu/wp-content/uploads/sites/1051/2008/11/MS072.mp3"
// Begin collecting the state of audio
val audioState by audioPlayer.audioState.collectAsState()
// Begin loading the audio async
lifecycleScope.launch {
withContext(Dispatchers.IO) {
audio.load()
}
}
DoSomethingElse() // do other stuff in the meantime
Button(
onClick = {
when (audioState) {
is AudioState.NONE -> audioPlayer.load()
is AudioState.READY -> audioPlayer.play()
is AudioState.ERROR -> println((audioState as AudioState.ERROR).message)
is AudioState.PAUSED -> audioPlayer.play()
is AudioState.PLAYING -> audioPlayer.pause()
else -> {
/** DO NOTHING **/
}
}
}
) {
when (audioState) {
is AudioState.ERROR -> Text("Error")
is AudioState.LOADING -> Text("Loading")
is AudioState.NONE -> Text("None")
is AudioState.READY -> Text("Ready")
is AudioState.PAUSED -> Text("Paused")
is AudioState.PLAYING -> Text("Playing")
else -> { Text("Error") }
}
}If you need to load a Compose Resource, you need to use a constructor that includes Context.
Make sure you safely pass your Context without memory leaks..
val resource = Res.getUri("files/ringtone.wav")
// You can pass your Context
val audio = Audio(context, resource) // loads the audio file
audio.play() // plays the audio immediately upon executionSoundBoard allows you to load audio to memory to play multiple times later without reloading -- sort of like an actual soundboard. The primary steps include:
- Create a SoundBoard instance
- Load SoundBytes onto the SoundBoard
- PowerUp the SoundBoard
- Play Sounds via the mixer
If you need help creating a
Contextfor the Android implementation, you're welcome to steal my method.
// commonMain
/* Create a SoundBoard Instance */
val soundBoard = SoundBoard(context)
/* Create a SoundByte */
val click = SoundByte(
name = "click",
localPath = Res.getUri("files/click.mp3")
)
/* Load the SoundByte onto the SoundBoard */
soundBoard.load(click)
/* PowerUp the SoundBoard */
soundBoard.powerUp()
/* Play sounds via the mixer */
soundBoard.mixer.play("click") // Use a String
soundBoard.mixer.play(click) // Use the original SoundByte value
/* Repeat as much as you like */
/* When you're done, PowerDown the SoundBoard to release resources */
soundBoard.powerDown()AudioByte allows you to load audio to memory to play multiple times later without reloading -- sort of like a soundboard. You could make a callable class that is passed throughout the app so the sounds could be access in any context. If you need help creating a platformContext, you're welcome to steal my method.
// Your custom class built in commonMain
class AudioByte(platformContext: Any) {
//
private val audioByte: AudioByte = AudioByte()
private val click: Any = audioByte.load(platformContext, Res.getUri("files/click.mp3"))
private val fanfare: Any = audioByte.load(platformContext, Res.getUri("files/fanfare.mp3"))
fun click() = audioByte.play(click)
fun fanfare() = audioByte.play(solveId)
fun release() = audioByte.release()
}
// create your class later
val audioByte = AudioByte(myPlatformContext)
// generate the sound whenever you like after
audioByte.click()
// remember to release when you won't need the audio board anymore.
// If you use the sound everywhere, you won't need to do this
audioByte.release()