From 3c2ec07bdb059d33b083c64c1a1ca82fefc5c6c1 Mon Sep 17 00:00:00 2001 From: Kyle Bolen Date: Thu, 30 Jul 2026 06:20:03 +0000 Subject: [PATCH] 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). --- build.gradle.kts | 5 -- .../photophetch/ui/LivePhotoPlayer.kt | 61 ++++++++++++++++++- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0398f70..23b9a49 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,11 +53,6 @@ compose.desktop { application { 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 { targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) packageName = "PhotoPhetch" diff --git a/src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt b/src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt index 3ae242a..44a0811 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt @@ -44,6 +44,9 @@ fun LivePhotoPlayer( 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 player = javafx.scene.media.MediaPlayer(media) @@ -64,11 +67,10 @@ fun LivePhotoPlayer( val root = javafx.scene.layout.StackPane(mediaView) root.style = "-fx-background-color: black;" - jfxPanel.scene = javafx.scene.Scene(root) } catch (e: Exception) { - log.warn("LivePhotoPlayer: JavaFX Platform.runLater failed: ${e.message}") + log.warn("LivePhotoPlayer: Platform.runLater failed: ${e.message}") } } } catch (e: Exception) { @@ -92,3 +94,58 @@ fun isJavaFxMediaAvailable(): Boolean { 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 + 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}") + } +}