Skip to content

Commit 736135b

Browse files
authored
Merge pull request #185 from kdroidFilter/feature/linux-jni-native-gstreamer
Migrate Linux player from GStreamer Java/JNA to native C via JNI
2 parents de423fc + b46c441 commit 736135b

18 files changed

Lines changed: 1940 additions & 1101 deletions

File tree

mediaplayer/build.gradle.kts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,6 @@ kotlin {
108108

109109
jvmMain.dependencies {
110110
implementation(libs.kotlinx.coroutines.swing)
111-
implementation(libs.gst1.java.core)
112-
implementation(libs.jna.jpms)
113-
implementation(libs.jna.platform.jpms)
114111
implementation(libs.slf4j.simple)
115112
}
116113

@@ -197,13 +194,30 @@ val buildNativeWindows by tasks.registering(Exec::class) {
197194
commandLine("cmd", "/c", nativeDir.file("build.bat").asFile.absolutePath)
198195
}
199196

197+
val buildNativeLinux by tasks.registering(Exec::class) {
198+
description = "Compiles the C native library into Linux .so (GStreamer + JNI)"
199+
group = "build"
200+
val hasPrebuilt = nativeResourceDir
201+
.dir("linux-x86-64")
202+
.file("libNativeVideoPlayer.so")
203+
.asFile
204+
.exists()
205+
enabled = Os.isFamily(Os.FAMILY_UNIX) && !Os.isFamily(Os.FAMILY_MAC) && !hasPrebuilt
206+
207+
val nativeDir = layout.projectDirectory.dir("src/jvmMain/native/linux")
208+
inputs.dir(nativeDir)
209+
outputs.dir(nativeResourceDir)
210+
workingDir(nativeDir)
211+
commandLine("bash", "build.sh")
212+
}
213+
200214
tasks.named("jvmProcessResources") {
201-
dependsOn(buildNativeMacOs, buildNativeWindows)
215+
dependsOn(buildNativeMacOs, buildNativeWindows, buildNativeLinux)
202216
}
203217

204218
tasks.configureEach {
205219
if (name == "sourcesJar") {
206-
dependsOn(buildNativeMacOs, buildNativeWindows)
220+
dependsOn(buildNativeMacOs, buildNativeWindows, buildNativeLinux)
207221
}
208222
}
209223

mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerState.jvm.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package io.github.kdroidfilter.composemediaplayer
33
import androidx.compose.runtime.Stable
44
import androidx.compose.ui.graphics.Color
55
import androidx.compose.ui.text.TextStyle
6-
import com.sun.jna.Platform
76
import io.github.kdroidfilter.composemediaplayer.linux.LinuxVideoPlayerState
87
import io.github.kdroidfilter.composemediaplayer.mac.MacVideoPlayerState
8+
import io.github.kdroidfilter.composemediaplayer.util.CurrentPlatform
99
import io.github.kdroidfilter.composemediaplayer.windows.WindowsVideoPlayerState
1010
import io.github.vinceglb.filekit.PlatformFile
1111

@@ -37,11 +37,10 @@ actual fun createVideoPlayerState(): VideoPlayerState = DefaultVideoPlayerState(
3737
*/
3838
@Stable
3939
open class DefaultVideoPlayerState: VideoPlayerState {
40-
val delegate: VideoPlayerState = when {
41-
Platform.isWindows() -> WindowsVideoPlayerState()
42-
Platform.isMac() -> MacVideoPlayerState()
43-
Platform.isLinux() -> LinuxVideoPlayerState()
44-
else -> throw UnsupportedOperationException("Unsupported platform")
40+
val delegate: VideoPlayerState = when (CurrentPlatform.os) {
41+
CurrentPlatform.OS.WINDOWS -> WindowsVideoPlayerState()
42+
CurrentPlatform.OS.MAC -> MacVideoPlayerState()
43+
CurrentPlatform.OS.LINUX -> LinuxVideoPlayerState()
4544
}
4645

4746
override val hasMedia: Boolean get() = delegate.hasMedia

mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/linux/GStreamerInit.kt

Lines changed: 0 additions & 94 deletions
This file was deleted.

mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/linux/LinuxFrameUtils.kt

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,7 @@ package io.github.kdroidfilter.composemediaplayer.linux
22

33
import java.nio.ByteBuffer
44

5-
/**
6-
* Calculates a fast hash of the frame buffer to detect frame changes.
7-
* Samples approximately 200 pixels evenly distributed across the frame.
8-
*
9-
* @param buffer The source buffer containing RGBA pixel data
10-
* @param pixelCount Total number of pixels in the frame
11-
* @return A hash value representing the frame content
12-
*/
13-
internal fun calculateFrameHash(buffer: ByteBuffer, pixelCount: Int): Int {
14-
if (pixelCount <= 0) return 0
15-
16-
var hash = 1
17-
val step = if (pixelCount <= 200) 1 else pixelCount / 200
18-
for (i in 0 until pixelCount step step) {
19-
hash = 31 * hash + buffer.getInt(i * 4)
20-
}
21-
return hash
22-
}
23-
24-
/**
25-
* Copies RGBA frame data from source to destination buffer with minimal overhead.
26-
* Handles row padding when destination stride differs from source.
27-
*
28-
* This function performs a single memory copy operation when strides match,
29-
* achieving zero-copy performance (beyond the necessary single copy from
30-
* GStreamer buffer to Skia bitmap).
31-
*
32-
* @param src Source buffer containing RGBA pixel data from GStreamer
33-
* @param dst Destination buffer (Skia bitmap pixels via peekPixels)
34-
* @param width Frame width in pixels
35-
* @param height Frame height in pixels
36-
* @param dstRowBytes Destination row stride (may include padding)
37-
*/
38-
internal fun copyRgbaFrame(
5+
internal fun copyBgraFrame(
396
src: ByteBuffer,
407
dst: ByteBuffer,
418
width: Int,
@@ -63,15 +30,13 @@ internal fun copyRgbaFrame(
6330
srcBuf.rewind()
6431
dstBuf.rewind()
6532

66-
// Fast path: when strides match, do a single bulk copy
6733
if (dstRowBytes == srcRowBytes) {
6834
srcBuf.limit(requiredSrcBytes.toInt())
6935
dstBuf.limit(requiredSrcBytes.toInt())
7036
dstBuf.put(srcBuf)
7137
return
7238
}
7339

74-
// Slow path: copy row by row when there's padding
7540
val srcCapacity = srcBuf.capacity()
7641
val dstCapacity = dstBuf.capacity()
7742
for (row in 0 until height) {

0 commit comments

Comments
 (0)