diff --git a/native/expo-modules/comm-expo-package/android/src/main/java/app/comm/android/media/MediaModule.kt b/native/expo-modules/comm-expo-package/android/src/main/java/app/comm/android/media/MediaModule.kt --- a/native/expo-modules/comm-expo-package/android/src/main/java/app/comm/android/media/MediaModule.kt +++ b/native/expo-modules/comm-expo-package/android/src/main/java/app/comm/android/media/MediaModule.kt @@ -1,21 +1,21 @@ package app.comm.android.media import android.content.Context +import android.graphics.Bitmap import android.graphics.ImageDecoder import android.graphics.Movie import android.graphics.drawable.AnimatedImageDrawable import android.media.MediaExtractor import android.media.MediaFormat +import android.media.MediaMetadataRetriever import android.net.Uri import android.os.Build -import android.util.Log import expo.modules.kotlin.exception.CodedException import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition import expo.modules.kotlin.records.Field import expo.modules.kotlin.records.Record -import java.io.IOException -import java.io.InputStream +import java.io.FileOutputStream class VideoInfo : Record { @Field @@ -41,6 +41,7 @@ AsyncFunction("getVideoInfo", this@MediaModule::getVideoInfo) AsyncFunction("hasMultipleFrames", this@MediaModule::hasMultipleFrames) + AsyncFunction("generateThumbnail", this@MediaModule::generateThumbnail) } @@ -105,6 +106,29 @@ } } + private fun generateThumbnail(inputPath: String, outputPath: String) { + val retriever = MediaMetadataRetriever() + val thumbnail: Bitmap? = try { + retriever.setDataSource(Uri.decode(inputPath).replace("file://", "")) + retriever.getFrameAtTime( + 0, + MediaMetadataRetriever.OPTION_CLOSEST_SYNC + ) + } catch (e: Exception) { + throw GenerateThumbnailException(inputPath, e) + } finally { + retriever.release() + } + + try { + FileOutputStream(outputPath).use { outputStream -> + thumbnail?.compress(Bitmap.CompressFormat.JPEG, 90, outputStream) + } + } catch (e: Exception) { + throw SaveThumbnailException(outputPath, e) + } + } + private val context: Context get() = requireNotNull(this.appContext.reactContext) { "React Application Context is null" @@ -124,4 +148,10 @@ private class FailedToOpenGif(uri: String, cause: Throwable) : CodedException("Failed to open file: $uri", cause) +private class GenerateThumbnailException(uri: String, cause: Throwable) : + CodedException("Could not generate thumbnail from file: $uri", cause) + +private class SaveThumbnailException(uri: String, cause: Throwable) : + CodedException("Could not save thumbnail to $uri", cause) + // endregion