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 = "2.1.0" # Updated Kotlin version required due to Composable Resources
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 execution
You 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 execution
You should release your audio when done to preserve memory:
audio.release() // converts the audio instance to null
There 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 execution
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 SoundBoard(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 soundBoard = SoundBoard(myPlatformContext)
// generate the sound whenever you like after
soundBoard.click()
// remember to release when you won't need the soundboard anymore.
// If you use the sound everywhere, you won't need to do this
soundBoard.release()