
Audio player library offers a unified API for seamless music playback, media caching, playlist management, shuffle/repeat modes, and background playback with state monitoring features.
Audio player library built with Kotlin Multiplatform (KMP). It provides a consistent API for music playback functionality across both Android and iOS.
For Kotlin Multiplatform, add the dependency below to your module's build.gradle.kts file:
sourceSets {
commonMain.dependencies {
implementation("io.github.moonggae:kmedia:$kmedia_version")
}
}Add the following permissions and service to your AndroidManifest.xml:
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<!-- Service registration -->
<service android:name="io.github.moonggae.kmedia.session.PlaybackService"
android:exported="false"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</service>Add the following to your Info.plist:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>io.github.moonggae.kmedia.assetDownloadSession</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>processing</string>
</array>KMedia can be initialized in Composable using PlatformContext
@Composable
fun KMediaSample() {
val platformContext = LocalPlatformContext.current
val media = remember {
KMedia.builder()
.cache(enabled = true, sizeInMb = 1024)
.build(platformContext)
}
}KMedia can be used as follows:
// Create a KMedia instance
val media = KMedia.Builder()
.cache(enabled = true, sizeInMb = 1024)
.build(context)
// Get music list
val musics = SampleMusicRepository().getSampleMusicList()
// Start playback
media.player.playMusics(musics, startIndex = 0)
// Play/Pause
media.player.play()
media.player.pause()
// Previous/Next track
media.player.previous()
media.player.next()
// Seek to position
media.player.seekTo(positionMs = 30000)You can monitor the playback state through Flow:
val playbackState by media.playbackState.collectAsState()
// Check current playback status
when (playbackState.playingStatus) {
PlayingStatus.PLAYING -> // Playing
PlayingStatus.PAUSED -> // Paused
PlayingStatus.BUFFERING -> // Buffering
PlayingStatus.IDLE -> // Not ready
PlayingStatus.ENDED -> // Playback completed
}
// Current position and duration
val position = playbackState.position
val duration = playbackState.duration
// Current music information
val currentMusic = playbackState.music
val currentIndex = playbackState.currentIndex// Add music to current playlist
media.player.addMusic(newMusic)
// Add multiple musics to playlist
media.player.addMusics(musicList)
// Remove music at specific index
media.player.removeMusic(index = 2)
// Clear entire playlist
media.player.clearPlaylist()You can replace music in the current playlist without interrupting playback:
// Replace music at specific index
val updatedMusic = currentMusic.copy(
uri = "https://example.com/new-track.mp3",
title = "Updated Title"
)
media.player.replaceMusic(index = 0, music = updatedMusic)
// Replace current playing music
val playbackState by media.playbackState.collectAsState()
playbackState.music?.let { currentMusic ->
if (needsUpdate(currentMusic)) {
val newMusic = currentMusic.copy(
uri = getUpdatedUri(currentMusic.id)
)
media.player.replaceMusic(playbackState.currentIndex, newMusic)
}
}Use Cases for Music Replacement:
// Set repeat mode
media.player.setRepeatMode(RepeatMode.REPEAT_MODE_ONE) // Repeat one
media.player.setRepeatMode(RepeatMode.REPEAT_MODE_ALL) // Repeat all
media.player.setRepeatMode(RepeatMode.REPEAT_MODE_OFF) // No repeat
// Set shuffle mode
media.player.setShuffleMode(true) // Enable shuffle
media.player.setShuffleMode(false) // Disable shuffle// Check cache usage
val cacheUsage by media.cache.usedSizeBytes.collectAsState(initial = 0L)
// Cache specific music
media.cache.preCacheMusic(url = "https://example.com/music.mp3", key = "music1")
// Check cache status
val isCached = media.cache.checkMusicCached("music1")
// Remove cache
media.cache.removeCachedMusic("music1")
// Clear all cache
media.cache.clearCache()An interface for monitoring cache status changes. You can implement this to track caching progress.
interface CacheStatusListener {
fun onCacheStatusChanged(musicId: String, status: CacheStatus)
enum class CacheStatus {
NONE, // Not cached
PARTIALLY_CACHED, // Partially cached
FULLY_CACHED // Fully cached
}
}Usage example:
val cacheStatusListener = object : CacheStatusListener {
override fun onCacheStatusChanged(musicId: String, status: CacheStatus) {
when (status) {
CacheStatus.NONE -> println("$musicId: No cache")
CacheStatus.PARTIALLY_CACHED -> println("$musicId: Caching in progress")
CacheStatus.FULLY_CACHED -> println("$musicId: Caching completed")
}
}
}
// Register listener when initializing KMedia
val media = KMedia.Builder()
.cache(enabled = true, sizeInMb = 1024, listener = cacheStatusListener)
.build(context)An interface for collecting playback analytics. You can track user playback patterns and statistics.
interface PlaybackAnalyticsListener {
fun onPlaybackCompleted(
musicId: String,
totalPlayTimeMs: Long,
duration: Long
)
}Usage example:
val analyticsListener = object : PlaybackAnalyticsListener {
override fun onPlaybackCompleted(musicId: String, totalPlayTimeMs: Long, duration: Long) {
// Process playback statistics
val playPercentage = (totalPlayTimeMs.toFloat() / duration.toFloat()) * 100
println("$musicId playback completed: $playPercentage% played ($totalPlayTimeMs ms / $duration ms)")
// You can send analytics data to server here
}
}
// Register listener when initializing KMedia
val media = KMedia.Builder()
.analytics(analyticsListener)
.build(context)Audio player library built with Kotlin Multiplatform (KMP). It provides a consistent API for music playback functionality across both Android and iOS.
For Kotlin Multiplatform, add the dependency below to your module's build.gradle.kts file:
sourceSets {
commonMain.dependencies {
implementation("io.github.moonggae:kmedia:$kmedia_version")
}
}Add the following permissions and service to your AndroidManifest.xml:
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<!-- Service registration -->
<service android:name="io.github.moonggae.kmedia.session.PlaybackService"
android:exported="false"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</service>Add the following to your Info.plist:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>io.github.moonggae.kmedia.assetDownloadSession</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>processing</string>
</array>KMedia can be initialized in Composable using PlatformContext
@Composable
fun KMediaSample() {
val platformContext = LocalPlatformContext.current
val media = remember {
KMedia.builder()
.cache(enabled = true, sizeInMb = 1024)
.build(platformContext)
}
}KMedia can be used as follows:
// Create a KMedia instance
val media = KMedia.Builder()
.cache(enabled = true, sizeInMb = 1024)
.build(context)
// Get music list
val musics = SampleMusicRepository().getSampleMusicList()
// Start playback
media.player.playMusics(musics, startIndex = 0)
// Play/Pause
media.player.play()
media.player.pause()
// Previous/Next track
media.player.previous()
media.player.next()
// Seek to position
media.player.seekTo(positionMs = 30000)You can monitor the playback state through Flow:
val playbackState by media.playbackState.collectAsState()
// Check current playback status
when (playbackState.playingStatus) {
PlayingStatus.PLAYING -> // Playing
PlayingStatus.PAUSED -> // Paused
PlayingStatus.BUFFERING -> // Buffering
PlayingStatus.IDLE -> // Not ready
PlayingStatus.ENDED -> // Playback completed
}
// Current position and duration
val position = playbackState.position
val duration = playbackState.duration
// Current music information
val currentMusic = playbackState.music
val currentIndex = playbackState.currentIndex// Add music to current playlist
media.player.addMusic(newMusic)
// Add multiple musics to playlist
media.player.addMusics(musicList)
// Remove music at specific index
media.player.removeMusic(index = 2)
// Clear entire playlist
media.player.clearPlaylist()You can replace music in the current playlist without interrupting playback:
// Replace music at specific index
val updatedMusic = currentMusic.copy(
uri = "https://example.com/new-track.mp3",
title = "Updated Title"
)
media.player.replaceMusic(index = 0, music = updatedMusic)
// Replace current playing music
val playbackState by media.playbackState.collectAsState()
playbackState.music?.let { currentMusic ->
if (needsUpdate(currentMusic)) {
val newMusic = currentMusic.copy(
uri = getUpdatedUri(currentMusic.id)
)
media.player.replaceMusic(playbackState.currentIndex, newMusic)
}
}Use Cases for Music Replacement:
// Set repeat mode
media.player.setRepeatMode(RepeatMode.REPEAT_MODE_ONE) // Repeat one
media.player.setRepeatMode(RepeatMode.REPEAT_MODE_ALL) // Repeat all
media.player.setRepeatMode(RepeatMode.REPEAT_MODE_OFF) // No repeat
// Set shuffle mode
media.player.setShuffleMode(true) // Enable shuffle
media.player.setShuffleMode(false) // Disable shuffle// Check cache usage
val cacheUsage by media.cache.usedSizeBytes.collectAsState(initial = 0L)
// Cache specific music
media.cache.preCacheMusic(url = "https://example.com/music.mp3", key = "music1")
// Check cache status
val isCached = media.cache.checkMusicCached("music1")
// Remove cache
media.cache.removeCachedMusic("music1")
// Clear all cache
media.cache.clearCache()An interface for monitoring cache status changes. You can implement this to track caching progress.
interface CacheStatusListener {
fun onCacheStatusChanged(musicId: String, status: CacheStatus)
enum class CacheStatus {
NONE, // Not cached
PARTIALLY_CACHED, // Partially cached
FULLY_CACHED // Fully cached
}
}Usage example:
val cacheStatusListener = object : CacheStatusListener {
override fun onCacheStatusChanged(musicId: String, status: CacheStatus) {
when (status) {
CacheStatus.NONE -> println("$musicId: No cache")
CacheStatus.PARTIALLY_CACHED -> println("$musicId: Caching in progress")
CacheStatus.FULLY_CACHED -> println("$musicId: Caching completed")
}
}
}
// Register listener when initializing KMedia
val media = KMedia.Builder()
.cache(enabled = true, sizeInMb = 1024, listener = cacheStatusListener)
.build(context)An interface for collecting playback analytics. You can track user playback patterns and statistics.
interface PlaybackAnalyticsListener {
fun onPlaybackCompleted(
musicId: String,
totalPlayTimeMs: Long,
duration: Long
)
}Usage example:
val analyticsListener = object : PlaybackAnalyticsListener {
override fun onPlaybackCompleted(musicId: String, totalPlayTimeMs: Long, duration: Long) {
// Process playback statistics
val playPercentage = (totalPlayTimeMs.toFloat() / duration.toFloat()) * 100
println("$musicId playback completed: $playPercentage% played ($totalPlayTimeMs ms / $duration ms)")
// You can send analytics data to server here
}
}
// Register listener when initializing KMedia
val media = KMedia.Builder()
.analytics(analyticsListener)
.build(context)