Klarity is a media player for Jetpack Compose Desktop, written in Kotlin and C++, built on top of the native FFMpeg and PortAudio libraries, and rendered using the Skiko library.
Since frames are rendered directly into the Composable
, this eliminates the need for compatibility components like
SwingPanel
, making it possible to display any Composable
as an overlay on top of a frame.
- Windows x64
- Linux x64
- macOS x64
- Media files probing
- Audio and video playback of media files
- Slow down and speed up playback speed without changing pitch
- Getting a preview of a media file
- Getting frames (snapshots) of a media file
- Coroutine/Flow API
graph TD
KlarityPlayer --> PlayerController
PlayerController --> Pipeline
PlayerController --> BufferLoop
PlayerController --> PlaybackLoop
PlayerController --> Settings
PlayerController --> PlayerState
PlayerController --> BufferTimestamp
PlayerController --> PlaybackTimestamp
PlayerController --> Renderer
PlayerController --> Events
BufferLoop --> Pipeline
PlaybackLoop --> BufferLoop
PlaybackLoop --> Pipeline
PlaybackLoop --> Renderer
PlaybackLoop --> Settings
subgraph Pipeline
Pipeline.AudioVideo --> Media
Pipeline.AudioVideo --> AudioDecoder
Pipeline.AudioVideo --> VideoDecoder
Pipeline.AudioVideo --> AudioBuffer
Pipeline.AudioVideo --> VideoBuffer
Pipeline.AudioVideo --> Sampler
Pipeline.AudioVideo --> VideoPool
Pipeline.Audio --> Media
Pipeline.Audio --> AudioDecoder
Pipeline.Audio --> AudioBuffer
Pipeline.Audio --> Sampler
Pipeline.Video --> Media
Pipeline.Video --> VideoDecoder
Pipeline.Video --> VideoBuffer
Pipeline.Video --> VideoPool
end
Sampler --> JNI\nNativeSampler --> C++\nSampler
AudioDecoder --> JNI\nNativeDecoder
VideoDecoder --> JNI\nNativeDecoder
JNI\nNativeDecoder --> C++\nDecoder
stateDiagram-v2
state PlayerState {
[*] --> Empty
Empty --> Preparing: Prepare Media
Preparing --> Ready: Media Ready
Preparing --> Empty: Release/Error
state Ready {
[*] --> Stopped
Stopped --> Playing: Play
Playing --> Paused: Pause
Playing --> Stopped: Stop
Playing --> Seeking: SeekTo
Paused --> Playing: Resume
Paused --> Stopped: Stop
Paused --> Seeking: SeekTo
Stopped --> Completed: Playback Completed
Stopped --> Seeking: SeekTo
Completed --> Stopped: Stop
Completed --> Seeking: SeekTo
Seeking --> Paused: Seek Completed
Seeking --> Stopped: Stop
Seeking --> Seeking: SeekTo
}
Ready --> Empty: Release
}
Current State \ Target State | Empty | Preparing | Ready (Stopped) | Ready (Playing) | Ready (Paused) | Ready (Completed) | Ready (Seeking) |
---|---|---|---|---|---|---|---|
Empty | N/A | Prepare | N/A | N/A | N/A | N/A | N/A |
Preparing | Release/Error | N/A | Media Ready | N/A | N/A | N/A | N/A |
Ready (Stopped) | Release | N/A | N/A | Play | N/A | N/A | SeekTo |
Ready (Playing) | N/A | N/A | Stop | N/A | Pause | N/A | SeekTo |
Ready (Paused) | N/A | N/A | Stop | Resume | N/A | N/A | SeekTo |
Ready (Completed) | N/A | N/A | Stop | N/A | N/A | N/A | SeekTo |
Ready (Seeking) | N/A | N/A | Stop | N/A | Seek Completed | N/A | SeekTo |
Download the latest release and include jar files to your project depending on your system.
Note
Check out the example to see a full implementation in Clean Architecture using the Reduce & Conquer pattern.
- The
KlarityPlayer.load()
method should be called once during the application lifecycle
KlarityPlayer.load().onFailure { t -> }.getOrThrow()
Get probe (information about a media)
val media = ProbeManager.probe("path/to/media").onFailure { t -> }.getOrThrow()
Important
Snapshot must be closed using the close()
method.
val snapshots = SnapshotManager.snapshots("path/to/media") { timestamps }.onFailure { t -> ... }.getOrThrow()
snapshots.forEach { snapshot ->
snapshot.close().onFailure { t -> }.getOrThrow()
}
val snapshot = SnapshotManager.snapshot("path/to/media") { timestamp }.onFailure { t -> ... }.getOrThrow()
snapshot.close().onFailure { t -> }.getOrThrow()
Important
PreviewManager must be closed using the
close()
method.
val previewManager = PreviewManager.create("path/to/media").onFailure { t -> ... }.getOrThrow()
previewManager.render(renderer, timestamp).onFailure { t -> }.getOrThrow()
previewManager.close().onFailure { t -> }.getOrThrow()
Important
KlarityPlayer
and Renderer must be closed using the
close()
method
val player = KlarityPlayer.create().onFailure { t -> }.getOrThrow()
val format = checkNotNull(player.state.media.videoFormat)
val renderer = Renderer.create(format).onFailure { t -> }.getOrThrow()
player.attach(renderer).getOrThrow()
player.prepare("path/to/media").onFailure { t -> }.getOrThrow()
player.play().onFailure { t -> }.getOrThrow()
player.stop().onFailure { t -> }.getOrThrow()
player.close().onFailure { t -> }.getOrThrow()
renderer.close().onFailure { t -> }.getOrThrow()