メディア サムネイルを生成する

メディアのサムネイルでは、画像や動画をすばやく視覚的にプレビューできます。 ブラウジングを高速化しつつ アプリのインターフェースをより視覚的に 魅力的で魅力的なものであることですサムネイルはフルサイズのメディアよりも小さいため、メモリ、ストレージ容量、帯域幅を節約しながら、メディアのブラウジング パフォーマンスを向上させることができます。

ファイルの種類や、アプリとメディア アセットで利用できるファイル アクセスに応じて、さまざまな方法でサムネイルを作成できます。

画像読み込みライブラリを使用してサムネイルを作成する

画像読み込みライブラリは、キャッシュ保存と、Uri に基づいてローカル リソースまたはネットワーク リソースからソース メディアを取得するロジックを処理します。次のコードは、 Coil 画像読み込みライブラリは画像と動画の両方で機能します。 ローカルリソースまたはネットワークリソースで 機能します

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

可能な限り、サーバーサイドでサムネイルを作成します。画像の読み込みをご覧ください。 Compose を使用して画像を読み込む方法、大きなビットマップの読み込み をご覧ください。 をご覧ください。

ローカル画像ファイルからサムネイルを作成する

サムネイル画像を取得するには、視覚的な要素を維持しながら効率的なダウンスケーリングを行う必要があります メモリの過剰な使用の回避、画像処理の Exif データを適切に使用する必要があります。

createImageThumbnail メソッドが、以下のすべての処理を行います。 画像ファイルのパスにアクセスできます。

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

Uri のみがある場合は、Android 10(API レベル 29)以降の ContentResolverloadThumbnail メソッドを使用できます。

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

Android 9(API レベル 28)以降で利用可能な ImageDecoder には、 追加のメモリを防ぐためにデコード時に画像を再サンプリングするオプション あります。

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

BitmapFactory を使用すると、先にターゲットを絞ったアプリのサムネイルを作成できます。 Android リリース。BitmapFactory.Options には、 リサンプリングする目的で画像の境界を設定します。

まず、ビットマップの境界のみを 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()

BitmapFactory.Optionswidthheight を使用してサンプルを設定します。 size:

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

ストリームをデコードします。生成される画像のサイズは 2 の累乗でサンプリングされます inSampleSize に基づきます。

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

ローカルの動画ファイルからサムネイルを作成する

動画のサムネイル画像の取得には、 画像のサムネイルは表示されますが、ファイルサイズは大きく、 必ずしも最初の画像を選択するほど単純ではない 指定します。

createVideoThumbnail メソッドは、次のリソースにアクセスできる場合は確実な選択肢です。 動画ファイルのパスです。

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

コンテンツ URI のみにアクセスできる場合は、 MediaMetadataRetriever

まず、動画にサムネイルが埋め込まれていないかを確認し、 あります。

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

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)

Android 9 以降(API レベル 28)では、MediaMetadataRetriever はスケーリングされたフレームを返すことができます。

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

それ以外の場合は、最初のフレームをスケーリングせずに返します。

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