Generowanie miniatur multimediów

Miniatury multimediów zapewniają użytkownikom szybki podgląd obrazów i filmów, co pozwala na szybsze przeglądanie, a także sprawia, że interfejs aplikacji jest bardziej atrakcyjny i zachęca do korzystania. Miniatury są mniejsze niż pełnowymiarowe multimedia, pozwalają zaoszczędzić pamięć, miejsce na dane i przepustowość łącza, jednocześnie ulepszając multimedia wydajność przeglądania.

W zależności od typu pliku i dostępu do plików w aplikacji oraz zasobach 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 ilustruje użycie funkcji Biblioteka wczytywania obrazów Coil działa zarówno w przypadku obrazów, jak i filmów, i działa na zasobie lokalnym lub sieciowym.

// 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. Więcej informacji o wczytywaniu obrazów za pomocą Compose znajdziesz w artykule Wczytywanie obrazów, a wskazówki dotyczące pracy z dużymi obrazami – w artykule Efektywne wczytywanie dużych bitmap.

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 metodę Uri, metody loadThumbnail możesz użyć w Content resolver od Androida 10 i poziomu interfejsu API 29.

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

W narzędziu ImageDecoder, który jest dostępny w wersji od Androida 9 (poziom interfejsu API 28), występuje pełne opcje umożliwiające ponowne próbkowanie obrazu podczas dekodowania, co pozwala uniknąć dodatkowej pamięci. i ich używanie.

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, które umożliwia dekodowanie tylko granic obrazu na potrzeby próbkowania.

Najpierw zdekoduj granice bitmapy do elementu 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ć próbkę, użyj znaczników width i height z usługi BitmapFactory.Options rozmiar:

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.

Metoda createVideoThumbnail jest dobrym rozwiązaniem, jeśli masz dostęp do ścieżkę pliku wideo.

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

Najpierw sprawdź, czy film zawiera wbudowaną miniaturę. Jeśli to możliwe, użyj jej:

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

Pobierz szerokość i wysokość filmu od MediaMetadataRetriever do oblicz współczynnik skalowania:

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 Androidzie 9 i nowszych (poziom interfejsu API 28) MediaMetadataRetriever może zwracać skalowane wartości ramka:

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 zwróć pierwszą klatkę bez przeskalowania:

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