Generare miniature dei contenuti multimediali

Le miniature dei contenuti multimediali offrono agli utenti una rapida anteprima visiva di immagini e video, consentendo una navigazione più rapida e rendendo più visivamente l'interfaccia dell'app accattivanti e coinvolgenti. Poiché le miniature sono più piccole dei contenuti multimediali a grandezza naturale, contribuiscono a risparmiare memoria, spazio di archiviazione e larghezza di banda, migliorando al contempo le prestazioni di navigazione dei contenuti multimediali.

A seconda del tipo di file e dell'accesso ai file che hai nella tua applicazione e nei tuoi asset multimediali, puoi creare miniature in diversi modi.

Creare una miniatura utilizzando una libreria di caricamento immagini

Le librerie di caricamento delle immagini svolgono gran parte del lavoro pesante per te; possono gestire la memorizzazione nella cache e la logica per recuperare i contenuti multimediali di origine dalla risorsa locale o di rete in base a un URI. Il seguente codice illustra l'uso dell'API La libreria di caricamento delle immagini Coil funziona sia per le immagini che per i video, e funziona su una risorsa locale o di rete.

// 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 possibile, crea miniature lato server. Consulta la sezione Caricamento di immagini. per maggiori dettagli su come caricare immagini utilizzando Compose e Caricamento di bitmap di grandi dimensioni in modo efficiente per indicazioni su come lavorare con immagini di grandi dimensioni.

Creare una miniatura da un file immagine locale

Ottenere immagini in miniatura implica uno scale down efficiente, senza compromettere l'immagine la qualità dell'immagine, evitando un uso eccessivo di memoria e la gestione di una varietà di formati e un corretto utilizzo dei dati Exif.

Il metodo createImageThumbnail esegue tutte queste operazioni, fornendo l'accesso al percorso del file immagine.

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

Se hai solo l'Uri, puoi utilizzare il metodo loadThumbnail in ContentResolver a partire da Android 10, livello API 29.

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

ImageDecoder, disponibile a partire da Android 9, livello API 28, include alcune opzioni continue per ricampionare l'immagine mentre la decodifica per evitare memoria aggiuntiva per gli utilizzi odierni.

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);

Puoi utilizzare Bitmapfabbrica per creare miniature per le app scelte come target Release di Android. Bitmapfabbrica.Options ha un'impostazione per decodificare solo limiti di un'immagine ai fini del ricampionamento.

Innanzitutto, decodifica solo i limiti della bitmap in 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()

Usa width e height di BitmapFactory.Options per impostare l'anteprima dimensioni:

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
        }
    }

Decodifica lo stream. La dimensione dell'immagine risultante viene campionata con la potenza di due in base al inSampleSize.

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

Creare una miniatura da un file video locale

La creazione di miniature video comporta molte delle sfide che si presentano ottenere miniature delle immagini, ma le dimensioni dei file possono essere molto più grandi e ottenere rappresentativo non è sempre così semplice come scegliere il primo fotogramma del video.

Il metodo createVideoThumbnail è una scelta valida se hai accesso a il percorso del file video.

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

Se hai accesso solo a un URI dei contenuti, puoi utilizzare MediaMetadataRetriever.

Innanzitutto, controlla se il video ha una miniatura incorporata e utilizzala se possibili:

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));
    }

Recupera la larghezza e l'altezza del video da MediaMetadataRetriever per calcolare il fattore di scalabilità:

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)

Su Android 9 e versioni successive (livello API 28), MediaMetadataRetriever può restituire un 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
        }
    }

In caso contrario, restituisce il primo frame non scalato:

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