Gerar miniaturas de mídia

As miniaturas de mídia fornecem aos usuários uma prévia visual rápida de imagens e vídeos, permitindo uma navegação mais rápida e tornando a interface do aplicativo mais visualmente atraente e envolvente. Como as miniaturas são menores do que as mídias de tamanho normal, elas ajudam a economizar memória, espaço de armazenamento e largura de banda, além de melhorar a performance da navegação de mídia.

Dependendo do tipo de arquivo e do acesso a arquivos que você tem em seu aplicativo e seus recursos de mídia, é possível criar miniaturas de várias formas.

Criar uma miniatura usando uma biblioteca de carregamento de imagens

As bibliotecas de carregamento de imagens fazem grande parte do trabalho pesado para você. podem lidar com armazenamento em cache, além da lógica para buscar a mídia de origem do servidor com base em um Uri. O código a seguir demonstra o uso da A biblioteca de carregamento de imagem Coil funciona para imagens e vídeos, e funciona em um recurso local ou de rede.

// Use Coil to create and display a thumbnail of a video or image with a specific height
// ImageLoader has its own memory and storage cache, and this one is configured to also
// load frames from videos
val videoEnabledLoader = ImageLoader.Builder(context)
    .components {
        add(VideoFrameDecoder.Factory())
    }.build()
// Coil requests images that match the size of the AsyncImage composable, but this allows
// for precise control of the height
val request = ImageRequest.Builder(context)
    .data(mediaUri)
    .size(Int.MAX_VALUE, THUMBNAIL_HEIGHT)
    .build()
AsyncImage(
    model = request,
    imageLoader = videoEnabledLoader,
    modifier = Modifier
        .clip(RoundedCornerShape(20))    ,
    contentDescription = null
)

Se possível, crie miniaturas do lado do servidor. Consulte Como carregar imagens. para detalhes sobre como carregar imagens usando o Compose e Como carregar bitmaps grandes eficiente para saber como trabalhar com imagens grandes.

Criar uma miniatura de um arquivo de imagem local

A obtenção de imagens em miniatura envolve uma redução eficiente da escala, preservando a imagem qualidade, evitando o uso excessivo de memória, lidando com diversas imagens formatos e fazer o uso correto dos dados Exif.

O método createImageThumbnail faz tudo isso, desde que você tenha acesso ao caminho do arquivo de imagem.

val bitmap = ThumbnailUtils.createImageThumbnail(File(file_path), Size(640, 480), null)

Se você tiver apenas o Uri, poderá usar o método loadThumbnail na ContentResolver a partir do Android 10, API de nível 29.

val thumbnail: Bitmap =
        applicationContext.contentResolver.loadThumbnail(
        content-uri, Size(640, 480), null)

O ImageDecoder, disponível a partir do Android 9, API de nível 28, tem alguns opções sólidas para reamostrar a imagem à medida que você a decodifica para evitar memória extra usar.

class DecodeResampler(val size: Size, val signal: CancellationSignal?) : OnHeaderDecodedListener {
    private val size: Size

   override fun onHeaderDecoded(decoder: ImageDecoder, info: ImageInfo, source:
       // sample down if needed.
        val widthSample = info.size.width / size.width
        val heightSample = info.size.height / size.height
        val sample = min(widthSample, heightSample)
        if (sample > 1) {
            decoder.setTargetSampleSize(sample)
        }
    }
}

val resampler = DecoderResampler(size, null)
val source = ImageDecoder.createSource(context.contentResolver, imageUri)
val bitmap = ImageDecoder.decodeBitmap(source, resampler);

Você pode usar BitmapFactory para criar miniaturas para apps destinados anteriormente Versões do Android. BitmapFactory.Options tem uma configuração para decodificar apenas os limites de uma imagem para fins de reamostragem.

Primeiro, decodifique apenas os limites do bitmap no BitmapFactory.Options:

private fun decodeResizedBitmap(context: Context, uri: Uri, size: Size): Bitmap?{
    val boundsStream = context.contentResolver.openInputStream(uri)
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    BitmapFactory.decodeStream(boundsStream, null, options)
    boundsStream?.close()

Usar width e height de BitmapFactory.Options para definir a amostra tamanho:

if ( options.outHeight != 0 ) {
        // we've got bounds
        val widthSample = options.outWidth / size.width
        val heightSample = options.outHeight / size.height
        val sample = min(widthSample, heightSample)
        if (sample > 1) {
            options.inSampleSize = sample
        }
    }

Decodifique o stream. O tamanho da imagem resultante é amostrado por potências de dois com base no inSampleSize.

    options.inJustDecodeBounds = false
    val decodeStream = context.contentResolver.openInputStream(uri)
    val bitmap =  BitmapFactory.decodeStream(decodeStream, null, options)
    decodeStream?.close()
    return bitmap
}

Criar uma miniatura a partir de um arquivo de vídeo local

Conseguir imagens de miniaturas de vídeo envolve muitos dos mesmos desafios que o de imagens, mas os tamanhos de arquivo podem ser muito maiores e conseguir um frame de vídeo representativo nem sempre é tão simples quanto escolher o primeiro frame do vídeo.

O método createVideoThumbnail é uma ótima escolha se você tiver acesso a o caminho do arquivo de vídeo.

val bitmap = ThumbnailUtils.createVideoThumbnail(File(file_path), Size(640, 480), null)

Se você só tiver acesso a um URI de conteúdo, poderá usar MediaMetadataRetriever

Primeiro, verifique se o vídeo tem uma miniatura incorporada e use-a se possível:

private suspend fun getVideoThumbnailFromMediaMetadataRetriever(context: Context, uri: Uri, size: Size): Bitmap? {
    val mediaMetadataRetriever = MediaMetadataRetriever()
    mediaMetadataRetriever.setDataSource(context, uri)
    val thumbnailBytes = mediaMetadataRetriever.embeddedPicture
    val resizer = Resizer(size, null)
    ImageDecoder.createSource(context.contentResolver, uri)
    // use a built-in thumbnail if the media file has it
    thumbnailBytes?.let {
        return ImageDecoder.decodeBitmap(ImageDecoder.createSource(it));
    }

Busque a largura e a altura do vídeo do MediaMetadataRetriever para calcule o fator de escalonamento:

val width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
            ?.toFloat() ?: size.width.toFloat()
    val height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
            ?.toFloat() ?: size.height.toFloat()
    val widthRatio = size.width.toFloat() / width
    val heightRatio = size.height.toFloat() / height
    val ratio = max(widthRatio, heightRatio)

No Android 9 e versões mais recentes (nível 28 da API), a MediaMetadataRetriever pode retornar frame:

if (ratio > 1) {
        val requestedWidth = width * ratio
        val requestedHeight = height * ratio
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            val frame = mediaMetadataRetriever.getScaledFrameAtTime(
                -1, OPTION_PREVIOUS_SYNC,
                requestedWidth.toInt(), requestedHeight.toInt())
            mediaMetadataRetriever.close()
            return frame
        }
    }

Caso contrário, retorna o primeiro frame sem escalonamento:

    // consider scaling this after the fact
    val frame = mediaMetadataRetriever.frameAtTime
    mediaMetadataRetriever.close()
    return frame
}