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.
This commit is contained in:
@@ -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<String?>(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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user