From 6912deac145ac29a73a4fdcfb970ffa5984b4198 Mon Sep 17 00:00:00 2001 From: Kyle Bolen Date: Thu, 30 Jul 2026 06:35:34 +0000 Subject: [PATCH] Remove video preview - JavaFX MOV playback not viable in Compose Desktop Removed LivePhotoPlayer.kt, JavaFX plugin/deps from build.gradle.kts and libs.versions.toml. PhotoPreviewDialog shows still HEIC only. LIVE badge in dialog uses the same green as in the grid. --- build.gradle.kts | 8 -- gradle/libs.versions.toml | 2 - .../bolenpad/photophetch/ui/BrowseScreen.kt | 67 ++----------- .../photophetch/ui/LivePhotoPlayer.kt | 93 ------------------- 4 files changed, 9 insertions(+), 161 deletions(-) delete mode 100644 src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt diff --git a/build.gradle.kts b/build.gradle.kts index 3f9e62d..8f8b8e1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,6 @@ 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,13 +31,6 @@ dependencies { implementation(libs.serialization.json) implementation(libs.slf4j.api) implementation(libs.logback.classic) - - // 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 d88051e..7b3895b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,6 @@ 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" } @@ -19,4 +18,3 @@ 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/BrowseScreen.kt b/src/main/kotlin/com/bolenpad/photophetch/ui/BrowseScreen.kt index c67458e..7ca5a7e 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/ui/BrowseScreen.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/ui/BrowseScreen.kt @@ -365,16 +365,11 @@ private fun PhotoPreviewDialog( val hash = (cacheKey + photo.devicePath).hashCode().toString(16) val cachePath = cacheDir.resolve("$hash.jpg") - // For Live Photos: pull companion MOV to temp for inline playback - var movPath by remember(photo.devicePath) { mutableStateOf(null) } - LaunchedEffect(photo.devicePath) { val bytes = withContext(Dispatchers.IO) { - // Always use the cached JPEG (sips-converted) — works for HEIC too if (cachePath.toFile().exists() && cachePath.toFile().length() > 0) { java.nio.file.Files.readAllBytes(cachePath) } else { - // Pull to temp, convert to JPEG via sips, cache it val tmpDir = Paths.get(System.getProperty("java.io.tmpdir"), "photophetch-preview") tmpDir.toFile().mkdirs() val tmpFile = tmpDir.resolve("preview-${photo.filename}") @@ -391,21 +386,7 @@ private fun PhotoPreviewDialog( } else null } } - if (bytes != null) { - runCatching { bitmap = SkiaImage.makeFromEncoded(bytes).toComposeImageBitmap() } - } - - // For Live Photos: pull companion MOV for inline video playback - if (photo.isLivePhotoStill && photo.companionDevicePath != null) { - val tmpDir = Paths.get(System.getProperty("java.io.tmpdir"), "photophetch-preview") - tmpDir.toFile().mkdirs() - val movFilename = photo.companionDevicePath.substringAfterLast("/") - val tmpMov = tmpDir.resolve(movFilename) - if (!tmpMov.toFile().exists()) { - withContext(Dispatchers.IO) { pullFn(photo.companionDevicePath, tmpMov) } - } - if (tmpMov.toFile().exists()) movPath = tmpMov.toString() - } + if (bytes != null) runCatching { bitmap = SkiaImage.makeFromEncoded(bytes).toComposeImageBitmap() } } Dialog(onDismissRequest = onDismiss) { @@ -420,15 +401,14 @@ private fun PhotoPreviewDialog( Text(photo.filename, fontWeight = FontWeight.SemiBold, style = MaterialTheme.typography.bodyMedium) val date = photo.dateTaken.atZone(ZoneId.systemDefault()) - Text( - "${date.toLocalDate()} ${date.toLocalTime().toString().take(5)}", + Text("${date.toLocalDate()} ${date.toLocalTime().toString().take(5)}", style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) + color = MaterialTheme.colorScheme.onSurfaceVariant) } if (photo.isLivePhotoStill) { - Badge(containerColor = MaterialTheme.colorScheme.secondary) { - Text("LIVE", style = MaterialTheme.typography.labelSmall) + Badge(containerColor = Color(0xFF00C853)) { + Text("LIVE", style = MaterialTheme.typography.labelSmall, + color = Color.White) } } } @@ -436,43 +416,14 @@ private fun PhotoPreviewDialog( Box(modifier = Modifier.fillMaxWidth().aspectRatio(4f / 3f), contentAlignment = Alignment.Center) { if (bitmap != null) { - androidx.compose.foundation.Image( - bitmap = bitmap!!, + androidx.compose.foundation.Image(bitmap = bitmap!!, contentDescription = photo.filename, - contentScale = ContentScale.Fit, - modifier = Modifier.fillMaxSize(), - ) + contentScale = ContentScale.Fit, modifier = Modifier.fillMaxSize()) } else { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CircularProgressIndicator() - if (photo.isLivePhotoStill) { - Spacer(Modifier.height(8.dp)) - Text("Loading…", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant) - } - } + CircularProgressIndicator() } } - // Live Photo motion companion — inline looping video player - val mov = movPath - if (photo.isLivePhotoStill && mov != null && isJavaFxMediaAvailable()) { - Spacer(Modifier.height(4.dp)) - Text("▶ Live Photo motion", - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant) - Spacer(Modifier.height(2.dp)) - LivePhotoPlayer( - videoPath = mov, - modifier = Modifier.fillMaxWidth().height(200.dp), - ) - } else if (photo.isLivePhotoStill && mov == null && movPath == null) { - Text("⏳ Loading motion clip…", - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant) - } - Text("${photo.sizeBytes / 1024} KB · ${photo.sourceFolder}", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant) diff --git a/src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt b/src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt deleted file mode 100644 index eee4ad7..0000000 --- a/src/main/kotlin/com/bolenpad/photophetch/ui/LivePhotoPlayer.kt +++ /dev/null @@ -1,93 +0,0 @@ -package com.bolenpad.photophetch.ui - -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.awt.SwingPanel -import org.slf4j.LoggerFactory -import java.awt.BorderLayout -import java.io.File -import javax.swing.JPanel -import javax.swing.SwingUtilities - -private val log = LoggerFactory.getLogger("LivePhotoPlayer") - -/** - * Embeds a looping JavaFX MediaPlayer inside a Compose Desktop SwingPanel. - * - * JavaFX toolkit must be initialized before use. We initialize it lazily - * via JFXPanel on first use. - */ -@Composable -fun LivePhotoPlayer( - videoPath: String, - modifier: Modifier = Modifier, -) { - SwingPanel( - modifier = modifier, - factory = { - val panel = JPanel(BorderLayout()) - - SwingUtilities.invokeLater { - try { - // Initialize JavaFX toolkit via JFXPanel - val jfxPanel = javafx.embed.swing.JFXPanel() - panel.add(jfxPanel, BorderLayout.CENTER) - panel.revalidate() - - javafx.application.Platform.runLater { - try { - val file = File(videoPath) - if (!file.exists()) { - log.warn("LivePhotoPlayer: file not found: $videoPath") - return@runLater - } - - log.info("LivePhotoPlayer: loading ${file.name} (${file.length() / 1024}KB)") - - val media = javafx.scene.media.Media(file.toURI().toString()) - val player = javafx.scene.media.MediaPlayer(media) - - player.setOnError { - log.warn("LivePhotoPlayer: MediaPlayer error: ${player.error?.message}") - } - player.setOnReady { - log.info("LivePhotoPlayer: ready, playing") - player.cycleCount = javafx.scene.media.MediaPlayer.INDEFINITE - player.isMute = true - player.play() - } - - val mediaView = javafx.scene.media.MediaView(player).apply { - isPreserveRatio = true - fitWidth = 580.0 - } - - 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: Platform.runLater failed: ${e.message}") - } - } - } catch (e: Exception) { - log.warn("LivePhotoPlayer: SwingUtilities init failed: ${e.message}") - } - } - - panel - }, - ) -} - -/** - * Check if JavaFX media is available on the classpath. - */ -fun isJavaFxMediaAvailable(): Boolean { - return try { - Class.forName("javafx.scene.media.MediaPlayer") - true - } catch (_: ClassNotFoundException) { - false - } -}