Skip to content
/ Klarity Public

A media (video and audio) player for Compose Multiplatform (desktop-only), built on top of the native FFMpeg and PortAudio libraries, and rendered using the Skiko library

License

Notifications You must be signed in to change notification settings

numq/Klarity

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Buy Me a Coffee

Klarity

logo

Klarity is a media (video and audio) player for Compose Multiplatform (desktop-only), 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.


preview

Table of Content

Changelog

Changed

  • Renderer no longer depends on video format - uses width and height instead

📦 Previous versions

Fixed

  • Fixed incorrect argument names

Changed

  • Enhanced seeking precision
  • Extended external state machine
  • Changed rendering behavior - renders first video frame during: preparation, playback stop, seeking
  • Performance and stability enhancements

Fixed

  • Fixed delayed external state updates after command execution
  • Fixed incorrect first frame display after seeking
  • Fixed event handling issues
  • Fixed improper loop stopping
  • Fixed buffer cleanup errors during channel closure

Changed

  • Improved internal state machine
  • Improved playback synchronization
  • Improved seeking
  • Stable release

Supported operating systems

  • Windows x64
  • Linux x64
  • macOS x64

Features

  • 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

Architecture

Dependency graph

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
Loading

State diagram

stateDiagram-v2
    state PlayerState {
        [*] --> Empty
        Empty --> Preparing: Prepare
        Preparing --> Ready: Success
        Preparing --> Error: Error
        Preparing --> Empty: Release
        
        state Ready {
            [*] --> Stopped
            Stopped --> Playing: Play
            Playing --> Paused: Pause
            Playing --> Stopped: Stop
            Playing --> Seeking: SeekTo
            Playing --> Error: Error
            Paused --> Playing: Resume
            Paused --> Stopped: Stop
            Paused --> Seeking: SeekTo
            Paused --> Error: Error
            Stopped --> Completed: Playback Complete
            Stopped --> Seeking: SeekTo
            Stopped --> Error: Error
            Completed --> Stopped: Stop
            Completed --> Seeking: SeekTo
            Completed --> Error: Error
            Seeking --> Paused: Seek Complete
            Seeking --> Stopped: Stop
            Seeking --> Seeking: SeekTo
            Seeking --> Error: Error
        }
        
        Ready --> Releasing: Release
        Releasing --> Empty: Success
        Releasing --> Error: Error
        Error --> Empty: Reset
    }
Loading

Transition table

Current State \ Action Empty Preparing Releasing Ready.Stopped Ready.Playing Ready.Paused Ready.Completed Ready.Seeking Error
Empty - Prepare - - - - - - -
Preparing Release - - Success - - - - Error
Releasing Success - - - - - - - Error
Error Reset - - - - - - - -
Ready.Stopped - - Release - Play - Playback Complete SeekTo Error
Ready.Playing - - Release Stop - Pause - SeekTo Error
Ready.Paused - - Release Stop Resume - - SeekTo Error
Ready.Completed - - Release Stop - - - SeekTo Error
Ready.Seeking - - Release Stop - Seek Complete - SeekTo Error

Installation

Download the latest release and include jar files to your project depending on your system.

Usage

Note

Check out the example to see a full implementation in Clean Architecture using the Reduce & Conquer pattern.

Load library

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

Get video frames (snapshots)

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

Get preview frames (for example, for the timeline)

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

Get media file played

Important

KlarityPlayer and Renderer must be closed using the close() method

val playback = KlarityPlayer.create().onFailure { t -> }.getOrThrow()

val format = checkNotNull(playback.state.media.videoFormat)

val renderer = Renderer.create(format).onFailure { t -> }.getOrThrow()

playback.attach(renderer).getOrThrow()

playback.prepare("path/to/media").onFailure { t -> }.getOrThrow()

playback.play().onFailure { t -> }.getOrThrow()

playback.stop().onFailure { t -> }.getOrThrow()

playback.close().onFailure { t -> }.getOrThrow()

renderer.close().onFailure { t -> }.getOrThrow()

Used libraries