Générer des vignettes multimédias

Les miniatures de contenus multimédias offrent aux utilisateurs un aperçu rapide des images et des vidéos, pour une navigation plus rapide tout en rendant l'interface de l'application plus visuelle. attrayantes et engageantes. Comme les vignettes sont plus petites que les supports en taille réelle, ils permettent d'économiser de la mémoire, de l'espace de stockage et de la bande passante, tout en améliorant les performances de navigation.

En fonction du type de fichier et de l'accès aux fichiers dont vous disposez dans votre application et vos assets multimédias, vous pouvez créer des miniatures de différentes manières.

Créer une vignette à l'aide d'une bibliothèque de chargement d'images

Les bibliothèques de chargement d'images font le gros du travail pour vous. qu'ils peuvent gérer ainsi que la logique permettant d'extraire le contenu multimédia source ressource basée sur un URI. Le code suivant illustre l'utilisation de la classe La bibliothèque de chargement d'images Coil fonctionne aussi bien pour les images que pour les vidéos. et fonctionne sur une ressource locale ou réseau.

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

Si possible, créez des miniatures côté serveur. Consultez la section Charger des images. pour savoir comment charger des images à l'aide de Compose et Charger des bitmaps volumineux efficacement pour savoir comment utiliser des images volumineuses.

Créer une vignette à partir d'un fichier image local

L'obtention d'images miniatures implique un scaling à la baisse efficace tout en préservant en évitant une utilisation excessive de la mémoire et en gérant diverses et en utilisant correctement les données Exif.

La méthode createImageThumbnail effectue toutes ces opérations, à condition que vous ayez au chemin d'accès au fichier image.

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

Si vous ne disposez que de Uri, vous pouvez utiliser la méthode loadThumbnail dans ContentResolver à partir d'Android 10, niveau d'API 29.

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

L'outil ImageDecoder, disponible à partir d'Android 9 (niveau d'API 28), comporte des options pleines pour rééchantillonner l'image au fur et à mesure que vous la décodez, afin d'éviter toute quantité de mémoire supplémentaire utiliser.

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

Vous pouvez utiliser BitmapFactory pour créer des vignettes destinées aux applications ciblées plus tôt. Versions d'Android. BitmapFactory.Options dispose d'un paramètre permettant de ne décoder que les les limites d'une image à des fins de rééchantillonnage.

Tout d'abord, décodez uniquement les limites du bitmap dans 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()

Utilisez width et height à partir de BitmapFactory.Options pour définir la taille de l'échantillon :

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

Décodez le flux. La taille de l'image résultante est échantillonnée par des puissances de deux en fonction des inSampleSize.

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

Créer une vignette à partir d'un fichier vidéo local

Obtenir des images de miniatures de vidéos implique de nombreux défis similaires à ceux rencontrés pour les miniatures d'images, mais les tailles de fichiers peuvent être beaucoup plus importantes, et obtenir un frame vidéo représentatif n'est pas toujours aussi simple que de choisir le premier frame de la vidéo.

La méthode createVideoThumbnail est un choix judicieux si vous avez accès à le chemin d'accès au fichier vidéo.

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

Si vous n'avez accès qu'à un URI de contenu, vous pouvez utiliser MediaMetadataRetriever

Tout d'abord, vérifiez si la vidéo dispose d'une miniature intégrée et utilisez-la si possible:

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

Extrayez la largeur et la hauteur de la vidéo de MediaMetadataRetriever à calculer le facteur de scaling:

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)

Sous Android 9 ou version ultérieure (niveau d'API 28), MediaMetadataRetriever peut renvoyer un frame mis à l'échelle :

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

Sinon, renvoyez le premier frame non mis à l'échelle :

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