
Wrapper for libmpv offering a typed playback API, observable player state, optional video rendering helpers, and Compose-ready video surfaces with track selection and low-level command access.
kmpv is a Kotlin Multiplatform wrapper for libmpv. It provides a typed playback API, observable player state, optional video rendering helpers, and Compose video surfaces.
The library is split into three modules:
kmpv — playback, commands, properties, tracks, events, and player state.kmpv-render — video output helpers for desktop, iOS, and Android.kmpv-compose — Compose video surfaces built on top of kmpv-render.| Platform | Core player | Rendering | Compose |
|---|---|---|---|
| Linux x64 | Yes | Yes | Yes |
| Linux arm64 | Yes | Yes | Yes |
| Windows x64 | Yes | Yes | Yes |
| macOS x64 | Yes | Yes | Yes |
| macOS arm64 | Yes | Yes | Yes |
| iOS arm64 | Yes | Yes | Yes |
| Android | Yes | Yes | Yes |
kmpv does not bundle libmpv. Your application must provide a compatible libmpv build for the platforms it ships on.
Releases are published to Maven Central from version tags.
val kmpvVersion = "<version>"
kotlin {
sourceSets {
commonMain.dependencies {
implementation("dev.brahmkshatriya.kmpv:kmpv:$kmpvVersion")
}
}
}For video rendering, add:
implementation("dev.brahmkshatriya.kmpv:kmpv-render:$kmpvVersion")For Compose video surfaces, add:
implementation("dev.brahmkshatriya.kmpv:kmpv-compose:$kmpvVersion")If your Compose setup uses development Compose artifacts, keep the Compose development repository available:
repositories {
mavenCentral()
google()
maven("https://redirector.kotlinlang.org/maven/compose-dev")
}MpvPlayer is the easiest API for normal playback.
import dev.kmpv.MpvHardwareDecoding
import dev.kmpv.MpvOptions
import dev.kmpv.MpvPlayer
import dev.kmpv.getOrThrow
val player = MpvPlayer.create {
option(MpvOptions.HardwareDecoding, MpvHardwareDecoding.AutoSafe)
}.getOrThrow()
player.load(
uri = "https://example.com/video.mp4",
playWhenReady = true,
).getOrThrow()Player state is exposed as a StateFlow:
player.state.collect { state ->
println("position = ${state.position}")
println("duration = ${state.duration}")
println("playing = ${state.isPlaying}")
println("buffering = ${state.isBuffering}")
}Playback controls are straightforward:
player.pause()
player.play()
player.setVolume(80.0)
player.setMuted(true)
player.seekTo(30.seconds)Call player.close() when the player is no longer needed.
Add kmpv-compose, create a player, and place MpvVideoSurface in your UI.
var surfaceReady by remember { mutableStateOf(false) }
MpvVideoSurface(
player = player,
modifier = Modifier.fillMaxSize(),
onReady = { surfaceReady = true },
onError = { error -> println(error.message) },
)
LaunchedEffect(surfaceReady) {
if (surfaceReady) {
player.load(url, playWhenReady = true).getOrThrow()
}
}For desktop and iOS rendering, waiting for onReady before loading media is recommended because the video surface must exist before libmpv starts video output.
Use Mpv directly when you need full control over libmpv commands and properties.
val mpv = Mpv.create()
mpv.setOption(MpvOptions.HardwareDecoding, MpvHardwareDecoding.AutoSafe).getOrThrow()
mpv.initialize().getOrThrow()
mpv.command(MpvCommands.loadFile("movie.mkv")).getOrThrow()
val duration = mpv[MpvProperties.Duration].getOrNull()
mpv[MpvProperties.Pause] = false
mpv.close()Custom or newer mpv properties and commands can also be used without waiting for a new kmpv release:
val property = MpvProperty.string("my-script/custom-property")
val value = mpv[property]
mpv.command("script-message", "my-message", "arg")MpvPlayer exposes the current track list and supports typed video, audio, and subtitle selection.
val tracks = player.tracks().getOrThrow()
val audio = tracks.first { it.type == MpvTrackType.Audio }
player.selectTrack(audio)
player.selectTrack(
MpvTrackType.Subtitle,
MpvTrackSelection.Disabled,
)Install libmpv through your distribution package manager. For example:
# Arch Linux
sudo pacman -S mpv
# Debian / Ubuntu
sudo apt install libmpv-devIf libmpv is installed in a non-standard directory, point the build to it:
export LINUX_NATIVE_LIBRARY_DIRS="$(pkg-config --variable=libdir mpv)"Provide a compatible libmpv build when linking and packaging your application. kmpv does not choose or bundle a particular mpv distribution.
Provide an iOS-compatible libmpv build and its required libraries/frameworks in the final application. The kmpv iOS artifact contains the Kotlin API and video integration, not an mpv binary.
The Android library includes the kmpv native helper automatically, but it intentionally does not include libmpv.so.
Your app must package a compatible libmpv.so for every Android ABI you support, for example:
src/main/jniLibs/arm64-v8a/libmpv.so
src/main/jniLibs/armeabi-v7a/libmpv.so
src/main/jniLibs/x86/libmpv.so
src/main/jniLibs/x86_64/libmpv.so
For a non-Compose Android Surface, configure video output before initialization and attach the surface afterward:
val mpv = Mpv.create()
mpv.configureAndroidVideoOutput().getOrThrow()
mpv.initialize().getOrThrow()
val output = mpv.createAndroidSurfaceOutput()
output.attach(surface).getOrThrow()
output.resize(width, height).getOrThrow()
// Before the Surface is destroyed:
output.detach().getOrThrow()Compose applications can use MpvVideoSurface or MpvAndroidVideoSurface instead.
The repository includes a few small examples:
samples/android — Android SurfaceView playback.samples/cli — headless playback and integration testing.samples/material-demo — Compose desktop video player demo.samples/runtime-smoke — simple desktop runtime checks.Run the main checks with:
./scripts/check.shThe project uses JDK 21. Android builds also require an Android SDK.
Normal branch pushes and manual workflow runs only build and verify the library. Maven Central publication happens only when a version tag is pushed, for example:
git tag v0.1.0
git push origin v0.1.0Release-maintainer details are documented in RELEASING.md.
Apache License 2.0. See LICENSE.
kmpv is a Kotlin Multiplatform wrapper for libmpv. It provides a typed playback API, observable player state, optional video rendering helpers, and Compose video surfaces.
The library is split into three modules:
kmpv — playback, commands, properties, tracks, events, and player state.kmpv-render — video output helpers for desktop, iOS, and Android.kmpv-compose — Compose video surfaces built on top of kmpv-render.| Platform | Core player | Rendering | Compose |
|---|---|---|---|
| Linux x64 | Yes | Yes | Yes |
| Linux arm64 | Yes | Yes | Yes |
| Windows x64 | Yes | Yes | Yes |
| macOS x64 | Yes | Yes | Yes |
| macOS arm64 | Yes | Yes | Yes |
| iOS arm64 | Yes | Yes | Yes |
| Android | Yes | Yes | Yes |
kmpv does not bundle libmpv. Your application must provide a compatible libmpv build for the platforms it ships on.
Releases are published to Maven Central from version tags.
val kmpvVersion = "<version>"
kotlin {
sourceSets {
commonMain.dependencies {
implementation("dev.brahmkshatriya.kmpv:kmpv:$kmpvVersion")
}
}
}For video rendering, add:
implementation("dev.brahmkshatriya.kmpv:kmpv-render:$kmpvVersion")For Compose video surfaces, add:
implementation("dev.brahmkshatriya.kmpv:kmpv-compose:$kmpvVersion")If your Compose setup uses development Compose artifacts, keep the Compose development repository available:
repositories {
mavenCentral()
google()
maven("https://redirector.kotlinlang.org/maven/compose-dev")
}MpvPlayer is the easiest API for normal playback.
import dev.kmpv.MpvHardwareDecoding
import dev.kmpv.MpvOptions
import dev.kmpv.MpvPlayer
import dev.kmpv.getOrThrow
val player = MpvPlayer.create {
option(MpvOptions.HardwareDecoding, MpvHardwareDecoding.AutoSafe)
}.getOrThrow()
player.load(
uri = "https://example.com/video.mp4",
playWhenReady = true,
).getOrThrow()Player state is exposed as a StateFlow:
player.state.collect { state ->
println("position = ${state.position}")
println("duration = ${state.duration}")
println("playing = ${state.isPlaying}")
println("buffering = ${state.isBuffering}")
}Playback controls are straightforward:
player.pause()
player.play()
player.setVolume(80.0)
player.setMuted(true)
player.seekTo(30.seconds)Call player.close() when the player is no longer needed.
Add kmpv-compose, create a player, and place MpvVideoSurface in your UI.
var surfaceReady by remember { mutableStateOf(false) }
MpvVideoSurface(
player = player,
modifier = Modifier.fillMaxSize(),
onReady = { surfaceReady = true },
onError = { error -> println(error.message) },
)
LaunchedEffect(surfaceReady) {
if (surfaceReady) {
player.load(url, playWhenReady = true).getOrThrow()
}
}For desktop and iOS rendering, waiting for onReady before loading media is recommended because the video surface must exist before libmpv starts video output.
Use Mpv directly when you need full control over libmpv commands and properties.
val mpv = Mpv.create()
mpv.setOption(MpvOptions.HardwareDecoding, MpvHardwareDecoding.AutoSafe).getOrThrow()
mpv.initialize().getOrThrow()
mpv.command(MpvCommands.loadFile("movie.mkv")).getOrThrow()
val duration = mpv[MpvProperties.Duration].getOrNull()
mpv[MpvProperties.Pause] = false
mpv.close()Custom or newer mpv properties and commands can also be used without waiting for a new kmpv release:
val property = MpvProperty.string("my-script/custom-property")
val value = mpv[property]
mpv.command("script-message", "my-message", "arg")MpvPlayer exposes the current track list and supports typed video, audio, and subtitle selection.
val tracks = player.tracks().getOrThrow()
val audio = tracks.first { it.type == MpvTrackType.Audio }
player.selectTrack(audio)
player.selectTrack(
MpvTrackType.Subtitle,
MpvTrackSelection.Disabled,
)Install libmpv through your distribution package manager. For example:
# Arch Linux
sudo pacman -S mpv
# Debian / Ubuntu
sudo apt install libmpv-devIf libmpv is installed in a non-standard directory, point the build to it:
export LINUX_NATIVE_LIBRARY_DIRS="$(pkg-config --variable=libdir mpv)"Provide a compatible libmpv build when linking and packaging your application. kmpv does not choose or bundle a particular mpv distribution.
Provide an iOS-compatible libmpv build and its required libraries/frameworks in the final application. The kmpv iOS artifact contains the Kotlin API and video integration, not an mpv binary.
The Android library includes the kmpv native helper automatically, but it intentionally does not include libmpv.so.
Your app must package a compatible libmpv.so for every Android ABI you support, for example:
src/main/jniLibs/arm64-v8a/libmpv.so
src/main/jniLibs/armeabi-v7a/libmpv.so
src/main/jniLibs/x86/libmpv.so
src/main/jniLibs/x86_64/libmpv.so
For a non-Compose Android Surface, configure video output before initialization and attach the surface afterward:
val mpv = Mpv.create()
mpv.configureAndroidVideoOutput().getOrThrow()
mpv.initialize().getOrThrow()
val output = mpv.createAndroidSurfaceOutput()
output.attach(surface).getOrThrow()
output.resize(width, height).getOrThrow()
// Before the Surface is destroyed:
output.detach().getOrThrow()Compose applications can use MpvVideoSurface or MpvAndroidVideoSurface instead.
The repository includes a few small examples:
samples/android — Android SurfaceView playback.samples/cli — headless playback and integration testing.samples/material-demo — Compose desktop video player demo.samples/runtime-smoke — simple desktop runtime checks.Run the main checks with:
./scripts/check.shThe project uses JDK 21. Android builds also require an Android SDK.
Normal branch pushes and manual workflow runs only build and verify the library. Maven Central publication happens only when a version tag is pushed, for example:
git tag v0.1.0
git push origin v0.1.0Release-maintainer details are documented in RELEASING.md.
Apache License 2.0. See LICENSE.