diff --git a/build.gradle.kts b/build.gradle.kts index 23b9a49..3f9e62d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.compose.multiplatform) + alias(libs.plugins.javafx) } group = "com.bolenpad.photophetch" @@ -32,21 +33,12 @@ dependencies { implementation(libs.slf4j.api) implementation(libs.logback.classic) - // JavaFX media for Live Photo video playback in preview dialog - val javafxVersion = "21.0.3" - // Detect platform: mac-aarch64 for Apple Silicon, mac for Intel - val osArch = System.getProperty("os.arch") ?: "x86_64" - val javafxPlatform = when { - System.getProperty("os.name").contains("Mac", ignoreCase = true) && osArch.contains("aarch64") -> "mac-aarch64" - System.getProperty("os.name").contains("Mac", ignoreCase = true) -> "mac" - System.getProperty("os.name").contains("Win", ignoreCase = true) -> "win" - else -> "linux" - } - implementation("org.openjfx:javafx-media:$javafxVersion:$javafxPlatform") - implementation("org.openjfx:javafx-swing:$javafxVersion:$javafxPlatform") - implementation("org.openjfx:javafx-graphics:$javafxVersion:$javafxPlatform") - implementation("org.openjfx:javafx-base:$javafxVersion:$javafxPlatform") - implementation("org.openjfx:javafx-controls:$javafxVersion:$javafxPlatform") + // JavaFX media — configured via the javafx plugin block below +} + +javafx { + version = "21.0.3" + modules = listOf("javafx.media", "javafx.swing", "javafx.graphics", "javafx.controls", "javafx.base") } compose.desktop { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7b3895b..d88051e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,6 +5,7 @@ coroutines = "1.8.0" serialization = "1.6.3" slf4j = "2.0.12" logback = "1.5.3" +javafx = "21.0.3" [libraries] kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } @@ -18,3 +19,4 @@ logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "lo kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "compose" } +javafx = { id = "org.openjfx.javafxplugin", version = "0.1.0" } diff --git a/src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt b/src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt index 44a0811..eee4ad7 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt @@ -44,9 +44,6 @@ 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) @@ -94,58 +91,3 @@ 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}") - } -}