Fix JavaFX media: extract native libs from jar to temp dir

The 'media type not supported' error occurs because the JavaFX native
codec .dylib files aren't extracted when loading from classpath.
extractJavafxNativeLibs() finds the javafx-media jar, extracts the
.dylib files to a temp dir, and adds it to java.library.path.

Also remove --add-modules JVM args (caused boot layer init failure).
This commit is contained in:
Kyle Bolen
2026-07-30 06:20:03 +00:00
parent d128835c0d
commit 3c2ec07bdb
2 changed files with 59 additions and 7 deletions

View File

@@ -53,11 +53,6 @@ compose.desktop {
application { application {
mainClass = "com.bolenpad.photophetch.MainKt" mainClass = "com.bolenpad.photophetch.MainKt"
jvmArgs(
"--add-modules", "javafx.media,javafx.swing,javafx.graphics",
"--add-opens", "javafx.graphics/com.sun.javafx.application=ALL-UNNAMED",
)
nativeDistributions { nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "PhotoPhetch" packageName = "PhotoPhetch"

View File

@@ -44,6 +44,9 @@ fun LivePhotoPlayer(
log.info("LivePhotoPlayer: loading ${file.name} (${file.length() / 1024}KB)") log.info("LivePhotoPlayer: loading ${file.name} (${file.length() / 1024}KB)")
// Extract JavaFX native media libs from jar if needed
extractJavafxNativeLibs()
val media = javafx.scene.media.Media(file.toURI().toString()) val media = javafx.scene.media.Media(file.toURI().toString())
val player = javafx.scene.media.MediaPlayer(media) val player = javafx.scene.media.MediaPlayer(media)
@@ -64,11 +67,10 @@ fun LivePhotoPlayer(
val root = javafx.scene.layout.StackPane(mediaView) val root = javafx.scene.layout.StackPane(mediaView)
root.style = "-fx-background-color: black;" root.style = "-fx-background-color: black;"
jfxPanel.scene = javafx.scene.Scene(root) jfxPanel.scene = javafx.scene.Scene(root)
} catch (e: Exception) { } catch (e: Exception) {
log.warn("LivePhotoPlayer: JavaFX Platform.runLater failed: ${e.message}") log.warn("LivePhotoPlayer: Platform.runLater failed: ${e.message}")
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -92,3 +94,58 @@ fun isJavaFxMediaAvailable(): Boolean {
false false
} }
} }
/**
* Extract JavaFX native libraries from the classpath jars to a temp directory
* and add that directory to java.library.path.
*
* This is needed when OpenJFX is loaded from classpath (not module path) —
* the native .dylib/.so files inside the jar need to be extracted for the
* media codecs (AVFoundation on macOS) to load correctly.
*/
private var nativeLibsExtracted = false
private fun extractJavafxNativeLibs() {
if (nativeLibsExtracted) return
nativeLibsExtracted = true
try {
val tmpDir = File(System.getProperty("java.io.tmpdir"), "photophetch-javafx-natives")
tmpDir.mkdirs()
// Find the javafx-media jar on the classpath
val classLoader = Thread.currentThread().contextClassLoader
val mediaClass = classLoader.loadClass("javafx.scene.media.MediaPlayer")
val codeSource = mediaClass.protectionDomain?.codeSource?.location
if (codeSource != null) {
val jarUrl = codeSource.toURI()
val jarFile = java.util.jar.JarFile(File(jarUrl))
// Extract .dylib files (macOS native media codecs)
jarFile.entries().asSequence()
.filter { it.name.endsWith(".dylib") || it.name.endsWith(".so") }
.forEach { entry ->
val outFile = File(tmpDir, File(entry.name).name)
if (!outFile.exists()) {
jarFile.getInputStream(entry).use { input ->
outFile.outputStream().use { output -> input.copyTo(output) }
}
log.info("Extracted native lib: ${outFile.name}")
}
}
jarFile.close()
}
// Add tmpDir to java.library.path via reflection (standard trick)
val usrPathsField = ClassLoader::class.java.getDeclaredField("usr_paths")
usrPathsField.isAccessible = true
val paths = usrPathsField.get(null) as Array<String>
if (tmpDir.absolutePath !in paths) {
usrPathsField.set(null, paths + tmpDir.absolutePath)
log.info("Added ${tmpDir.absolutePath} to java.library.path")
}
} catch (e: Exception) {
log.warn("extractJavafxNativeLibs failed (may still work): ${e.message}")
}
}