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. Étant donné que les miniatures sont plus petites que les contenus multimédias de taille réelle, elles 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 multimédia.

Selon le type de fichier et l'accès dont vous disposez dans votre application et vos éléments 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 montre comment utiliser la bibliothèque de chargement d'images Coil pour les images et les vidéos, et 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. Pour savoir comment charger des images à l'aide de Compose, consultez Charger des images. Pour savoir comment travailler avec de grandes images, consultez Charger efficacement de grands bitmaps.

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

Obtenir des images miniatures implique une réduction de taille efficace tout en préservant la qualité visuelle, en évitant une utilisation excessive de la mémoire, en gérant différents formats d'images et en utilisant correctement les données Exif.

La méthode createImageThumbnail effectue toutes ces opérations, à condition que vous ayez accès au chemin d'accès du 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 miniatures pour les applications ciblant des versions Android antérieures. BitmapFactory.Options dispose d'un paramètre permettant de ne décoder que les limites d'une image à des fins de reéchantillonnage.

Tout d'abord, décodez uniquement les limites du bitmap dans le 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 miniature à 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.

Vérifiez d'abord si la vidéo comporte une vignette 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
}