Я создаю приложение для Android означало, что объединить файл изображения и видеофайл с MotionPhoto. Я написал пользовательский интерфейс и логику в один файл для доказательства концепции. Код ниже. Но с приложением файла может просмотреть это изображение как статическое фото. < /P>
import android.media.MediaScannerConnection
import android.net.Uri
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowForward
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.exifinterface.media.ExifInterface
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import coil3.compose.AsyncImage
import java.io.File
import java.io.InputStream
@Composable
fun ImageItem(
getInputStream: (Uri) -> InputStream?
) {
var imageUri by remember { mutableStateOf(null) }
var videoUri by remember { mutableStateOf(null) }
var imageInputStream by remember { mutableStateOf(null) }
var videoInputStream by remember { mutableStateOf(null) }
val imagePicker =
rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri != null) {
imageUri = uri
imageInputStream = getInputStream(uri)
} else {
Log.d("PhotoPicker", "No media selected")
}
}
val videoPicker =
rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri != null) {
videoUri = uri
videoInputStream = getInputStream(uri)
} else {
Log.d("PhotoPicker", "No media selected")
}
}
Card {
Row(
modifier = Modifier.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.clip(
RoundedCornerShape(10.dp)
)
.border(
1.dp,
MaterialTheme.colorScheme.surfaceTint,
RoundedCornerShape(10.dp)
)
.clickable {
imagePicker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
}
.size(75.dp),
contentAlignment = Alignment.Center
) {
when (imageUri) {
null -> Image(
painter = painterResource(R.drawable.ic_image),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.surfaceTint)
)
else -> AsyncImage(
model = imageUri,
contentDescription = null,
contentScale = ContentScale.Crop
)
}
}
Spacer(modifier = Modifier.width(8.dp))
Box(
modifier = Modifier
.clip(
RoundedCornerShape(10.dp)
)
.border(
1.dp,
MaterialTheme.colorScheme.surfaceTint,
RoundedCornerShape(10.dp)
)
.clickable {
videoPicker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly))
}
.size(75.dp),
contentAlignment = Alignment.Center
) {
when (videoUri) {
null -> Image(
painter = painterResource(R.drawable.ic_video),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.surfaceTint)
)
else -> {
val context = LocalContext.current
val exoPlayer = remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(videoUri!!))
prepare()
playWhenReady = true
repeatMode = Player.REPEAT_MODE_ONE
}
}
PlayerSurface(
player = exoPlayer,
surfaceType = SURFACE_TYPE_SURFACE_VIEW
)
}
}
}
Spacer(modifier = Modifier.weight(1f))
val context = LocalContext.current
Icon(
Icons.AutoMirrored.Rounded.ArrowForward,
contentDescription = null,
modifier = Modifier.clickable {
if (imageInputStream != null && videoInputStream != null) {
// Create temp JPEG file to store image data and EXIF data
val tempImageFile =
File.createTempFile("temp_image", ".jpg", context.cacheDir)
val motionPhotoFile = File(
context.filesDir,
"dynamic_photo_${System.currentTimeMillis()}.MP.jpg"
)
try {
// Write the image to the temp file
tempImageFile.outputStream().use { imageOutput ->
imageInputStream?.copyTo(imageOutput)
}
// Write image and video to final file
var videoLength: Int
motionPhotoFile.outputStream().use { outputStream ->
// Write image data
tempImageFile.inputStream().use { it.copyTo(outputStream) }
// Record video offset
val videoStartOffset = outputStream.channel.position()
// Write video data
videoInputStream?.copyTo(outputStream)
// Record video length
videoLength = (outputStream.channel.size() - videoStartOffset).toInt()
}
android.util.Log.e("MotionPhoto", "stream size: ${videoInputStream?.readBytes()?.size}, length: $videoLength")
// Add EXIF and XMP metadata to temp file
val exifInterface = ExifInterface(motionPhotoFile)
exifInterface.setAttribute(ExifInterface.TAG_XMP, getXmpMetadata(videoLength))
exifInterface.saveAttributes()
MediaScannerConnection.scanFile(
context,
arrayOf(motionPhotoFile.absolutePath),
arrayOf("image/jpeg", "image/heic")
) { _, uri ->
Log.d("MotionPhoto", "Save success, URI: $uri")
}
} catch (e: Exception) {
Log.e("MotionPhoto", "MotionPhoto save failed: ${e.message}")
} finally {
// delete temp file
tempImageFile.delete()
}
} else {
Log.e("MotionPhoto", "Image or video input stream is null")
}
}
)
Spacer(modifier = Modifier.weight(1f))
Box(
modifier = Modifier
.border(
1.dp,
MaterialTheme.colorScheme.surfaceTint,
RoundedCornerShape(10.dp)
)
.size(75.dp),
contentAlignment = Alignment.Center
) {
when (videoUri) {
else -> Image(
painter = painterResource(R.drawable.ic_image),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.surfaceTint)
)
}
}
}
}
}
@Preview(showBackground = true)
@Composable
fun ImageItemPreview() {
ImageItem { null }
}
fun getXmpMetadata(videoLengthInBytes: Int): String {
return """
""".trimIndent()
}
Я ссылался на DOC о Formato Format Version 1.0 https://developer.android.com/media/pla ... matобразно Похоже, метаданные XMP MotionPhoto. Но я понятия не имею, как изменить свой код выше, чтобы написать их. Или неправильное место, которое я пытаюсь написать метаданные.>
Я создаю приложение для Android означало, что объединить файл изображения и видеофайл с MotionPhoto. Я написал пользовательский интерфейс и логику в один файл для доказательства концепции. Код ниже. Но с приложением файла может просмотреть это изображение как статическое фото. < /P> [code]import android.media.MediaScannerConnection import android.net.Uri import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.Image import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowForward import androidx.compose.material3.Card import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.exifinterface.media.ExifInterface import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer import coil3.compose.AsyncImage import java.io.File import java.io.InputStream
@Composable fun ImageItem( getInputStream: (Uri) -> InputStream? ) {
var imageUri by remember { mutableStateOf(null) } var videoUri by remember { mutableStateOf(null) }
var imageInputStream by remember { mutableStateOf(null) } var videoInputStream by remember { mutableStateOf(null) }
val imagePicker = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> if (uri != null) { imageUri = uri imageInputStream = getInputStream(uri) } else { Log.d("PhotoPicker", "No media selected") } }
val videoPicker = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> if (uri != null) { videoUri = uri videoInputStream = getInputStream(uri) } else { Log.d("PhotoPicker", "No media selected") } }
// Create temp JPEG file to store image data and EXIF data val tempImageFile = File.createTempFile("temp_image", ".jpg", context.cacheDir) val motionPhotoFile = File( context.filesDir, "dynamic_photo_${System.currentTimeMillis()}.MP.jpg" )
try { // Write the image to the temp file tempImageFile.outputStream().use { imageOutput -> imageInputStream?.copyTo(imageOutput) }
// Write image and video to final file var videoLength: Int motionPhotoFile.outputStream().use { outputStream -> // Write image data tempImageFile.inputStream().use { it.copyTo(outputStream) }
// Record video offset val videoStartOffset = outputStream.channel.position()
// Write video data videoInputStream?.copyTo(outputStream)
// Record video length videoLength = (outputStream.channel.size() - videoStartOffset).toInt() }
android.util.Log.e("MotionPhoto", "stream size: ${videoInputStream?.readBytes()?.size}, length: $videoLength") // Add EXIF and XMP metadata to temp file val exifInterface = ExifInterface(motionPhotoFile) exifInterface.setAttribute(ExifInterface.TAG_XMP, getXmpMetadata(videoLength)) exifInterface.saveAttributes()
fun getXmpMetadata(videoLengthInBytes: Int): String { return """
""".trimIndent() } [/code] Я ссылался на DOC о Formato Format Version 1.0 https://developer.android.com/media/platform/motion-photo-formatобразно Похоже, метаданные XMP MotionPhoto. Но я понятия не имею, как изменить свой код выше, чтобы написать их. Или неправильное место, которое я пытаюсь написать метаданные.>