Generowanie miniatur multimediów

Miniatury multimediów dają użytkownikom szybki podgląd obrazów i filmów. co pozwala na szybsze przeglądanie, a jednocześnie jest bardziej wizualnym interfejsem aplikacji atrakcyjne i angażujące. Ponieważ miniatury są mniejsze niż multimedia w pełnej wielkości, pomagają oszczędzać pamięć, miejsce na dysku i przepustowość, a zarazem poprawiają wydajność przeglądania multimediów.

W zależności od typu pliku i dostępu do plików w aplikacji oraz zasobów multimedialnych możesz tworzyć miniatury na różne sposoby.

Tworzenie miniatury za pomocą biblioteki wczytywania obrazów

Biblioteki do wczytywania obrazów wykonują za Ciebie wiele ciężkiej pracy. Mogą obsługiwać buforowanie wraz z logiką pobierania mediów źródłowych z zasobu lokalnego lub sieciowego na podstawie identyfikatora URI. Poniższy kod pokazuje, jak używać biblioteki ładowania obrazów Coil, która działa zarówno w przypadku obrazów, jak i filmów, oraz działa na zasoby lokalne lub sieciowe.

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

Jeśli to możliwe, utwórz miniatury po stronie serwera. Patrz Wczytywanie obrazów , aby dowiedzieć się, jak ładować obrazy za pomocą funkcji tworzenia wiadomości i wczytywania dużych map bitowych. wskazówki dotyczące pracy z dużymi obrazami.

Tworzenie miniatury z lokalnego pliku graficznego

Uzyskiwanie miniatur obrazów umożliwia efektywne skalowanie przy jednoczesnym zachowaniu obrazów jakość, unikanie nadmiernego wykorzystania pamięci, obsługa różnych obrazów oraz poprawne korzystanie z danych Exif.

Metoda createImageThumbnail wykonuje wszystkie te czynności, o ile masz dostęp do ścieżki do pliku obrazu.

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

Jeśli masz tylko Uri, możesz użyć metody loadThumbnailContentResolver, począwszy od Androida 10 (poziom API 29).

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

Interfejs ImageDecoder, dostępny od Androida 9 (poziom interfejsu API 28), zawiera kilka solidnych opcji umożliwiających przepróbkowanie obrazu podczas dekodowania, aby zapobiec dodatkowemu wykorzystaniu pamięci.

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

Możesz użyć BitmapFactory, aby utworzyć miniatury aplikacji, na które są kierowane reklamy wcześniej Wersje Androida. BitmapFactory.Options ma ustawienie pozwalające na dekodowanie tylko granic obrazu w celu ponownego próbkowania.

Najpierw zdekoduj tylko granice bitmapy do 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()

Aby ustawić rozmiar próbki, użyj parametrów widthheight z usługi BitmapFactory.Options:

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

zdekodować strumień; Rozmiar wynikowego obrazu jest próbkowany przez potęgi 2 na podstawie inSampleSize.

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

Tworzenie miniatury na podstawie lokalnego pliku wideo

Robienie miniatur filmów wiąże się z wieloma takimi samymi wyzwaniami, jak w przypadku ale ich rozmiary mogą być znacznie większe i reprezentatywna klatka filmu nie zawsze jest tak prosta, jak wybranie pierwszej każdej klatki filmu.

Jeśli masz dostęp do ścieżki pliku wideo, możesz użyć metody createVideoThumbnail.

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

Jeśli masz dostęp tylko do identyfikatora URI treści, możesz używać MediaMetadataRetriever

Po pierwsze, sprawdź, czy film ma osadzoną miniaturę, i użyj jej, jeśli możliwe:

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

Aby obliczyć współczynnik skalowania, pobierz szerokość i wysokość filmu z MediaMetadataRetriever:

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)

W przypadku Androida 9 lub nowszego (poziom interfejsu API 28) funkcja MediaMetadataRetriever może zwrócić kadrowany kadr:

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

W przeciwnym razie zwraca pierwszą nieprzeskalowaną klatkę:

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