Skip to content

Latest commit

 

History

History
160 lines (143 loc) · 8.42 KB

README.md

File metadata and controls

160 lines (143 loc) · 8.42 KB

Basic-Sound

basic

Basic-Sound

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.

badge-android badge-ios badge-mac badge-watchos badge-tvos badge-nodejs badge-jsBrowser badge-wasmJsBrowser badge-jvm badge-linux badge-windows

Supported Filetypes

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.

Installation

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

Audio() Usage

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 Usage

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