AuraPlayer is a high-performance cross-platform desktop media playback engine written in Kotlin.
It uses JNI and JAWT to bridge the JVM with native GPU-accelerated rendering, allowing video frames to be drawn directly to desktop UI surfaces.
AuraPlayer supports both audio and video playback and is designed to be embedded into JVM desktop applications while maintaining hardware-accelerated performance.
The project was created to fill a gap in the JVM ecosystem: there was no modern desktop media engine comparable in capability to players like ExoPlayer while still integrating cleanly with desktop UI frameworks.
- Hardware-accelerated decoding
- GPU rendering pipeline
- Smooth high-resolution playback
- Reduced CPU usage
AuraPlayer uses JNI to connect the Kotlin/JVM API with a native media backend.
This bridge handles:
- Media decoding
- Frame rendering
- Surface communication
- Performance-critical operations
AuraPlayer uses JAWT (Java AWT Native Interface) to obtain native drawing surfaces from Java UI components.
This enables:
- Rendering directly into
JPanel - Tight integration with desktop UIs
- No external windows or embedded players
- Smooth GPU-accelerated rendering
AuraPlayer supports:
- Video playback with synchronized audio
- Audio-only playback
- Most common media containers and codecs
(depending on native backend support)
AuraPlayer is split into two modules to allow flexibility depending on how you want to integrate it.
The core playback engine.
This module contains:
- Media playback engine
- JNI bridge
- Native rendering integration
- Audio/video decoding pipeline
It does not depend on any UI framework, making it usable with:
- Swing
- JavaFX
- Compose Desktop
- LWJGL
- custom rendering environments
This is the module to use if you want full control over UI integration.
A ready-to-use module for Compose Desktop applications.
It includes:
auraplayer-core- Compose UI components
- media player controls
- video surface composables
This module lets you quickly build a media player UI using Jetpack Compose for Desktop.
flowchart TD
AuraPlayer["AuraPlayer"]
Core["auraplayer-core"]
Compose["auraplayer-compose"]
Native["Native Layer"]
AuraPlayer --> Core
AuraPlayer --> Compose
AuraPlayer --> Native
Core --> CoreAPI["Kotlin Playback API"]
Core --> MediaCtrl["Media Control (play/pause/seek)"]
Core --> JNI["JNI Bridge"]
Core --> NativeRender["Native Rendering Integration"]
Compose --> ComposeUI["Compose Desktop UI"]
Compose --> Components["Player Components"]
Compose --> UIControls["UI Controls"]
Native --> HWDecode["Hardware Decoding"]
Native --> GPU["GPU Rendering"]
Native --> Codec["Codec Pipeline"]
Rendering flow:
UI Surface (Swing / Compose / etc.) ↓ JAWT Native Surface ↓ JNI Bridge ↓ Native Renderer ↓ GPU Frame Output
@Composable
fun AuraPlayerSurface(
auraPlayer: AuraPlayer,
audioOnly: Boolean = false,
modifier: Modifier = Modifier,
content: @Composable () -> Unit = {}
) {
// Observe initialization state from the player
val isInitialized by auraPlayer.isInitialized.collectAsState()
// Native drawing surface used by JAWT
val canvas = remember {
Canvas().apply {
background = java.awt.Color.BLACK
}
}
Box(modifier) {
// Compose → Swing bridge
SwingPanel(
background = Color.Black,
factory = {
canvas
},
update = {
// Ensure the component has a valid native surface
if (canvas.isDisplayable && canvas.graphicsConfiguration != null) {
if (!isInitialized) {
auraPlayer.initialize(canvas, audioOnly)
}
}
},
modifier = Modifier.fillMaxSize()
)
// Optional overlay content
// Useful for player controls, subtitles, UI overlays, etc.
content()
}
}You just call in auraplayer-compose which already provides AuraPlayerSurface
This is how you implement it.
// MUST use Remember to prevent recreation upon recomposition.
val engine = remember { AuraPlayer() }
AuraPlayerSurface(
player = engine,
)