Building media player apps, video editors, or gallery viewers on Android requires retrieving technical stream metadata—such as video resolution, playback duration, audio bitrate, track titles, album cover artwork, and keyframe thumbnails—without full file playback.

Android's MediaMetadataRetriever API (android.media.MediaMetadataRetriever) provides a unified native interface to extract metadata from local device storage, Scoped Storage Content URIs, raw application resources, and remote HTTP/HTTPS streaming URLs. In this tutorial, we implement complete Kotlin examples covering file sources, thumbnail extraction, and resource cleanup.

Quick Reference: Extracting Duration and Title

Here is the fundamental pattern for initializing MediaMetadataRetriever and safely releasing native decoder memory:

QuickReference.ktkotlin
val retriever = MediaMetadataRetriever()
try {
    retriever.setDataSource(context, videoUri)
    val durationMs = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() ?: 0L
    val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() ?: 0
    val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() ?: 0
    Log.d("MediaInfo", "Duration: ${durationMs}ms, Resolution: ${width}x${height}")
} catch (e: Exception) {
    Log.e("MediaInfo", "Failed to extract metadata", e)
} finally {
    retriever.release() // Always release native C++ decoder allocations
}

1. Extracting Metadata from Local Files & Content URIs

On Android 10+ (API 29+), Scoped Storage restricts direct file path access. You should pass a Uri or open a ParcelFileDescriptor to setDataSource():

LocalMediaExtractor.ktkotlin
import android.content.Context
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.graphics.Bitmap
import android.graphics.BitmapFactory
 
data class MediaMetadata(
    val title: String?,
    val artist: String?,
    val album: String?,
    val durationMs: Long,
    val width: Int,
    val height: Int,
    val mimeType: String?,
    val albumArt: Bitmap?
)
 
fun extractLocalMetadata(context: Context, mediaUri: Uri): MediaMetadata {
    val retriever = MediaMetadataRetriever()
    return try {
        // Option A: Set via Context and Uri (Handles Scoped Storage & FileProvider)
        retriever.setDataSource(context, mediaUri)
 
        val title = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)
        val artist = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)
        val album = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM)
        val duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() ?: 0L
        val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() ?: 0
        val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() ?: 0
        val mimeType = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE)
 
        // Extract embedded MP3/AAC album cover art (JPEG/PNG byte array)
        val artBytes = retriever.embeddedPicture
        val albumArt = if (artBytes != null) {
            BitmapFactory.decodeByteArray(artBytes, 0, artBytes.size)
        } else null
 
        MediaMetadata(title, artist, album, duration, width, height, mimeType, albumArt)
    } finally {
        retriever.release()
    }
}

2. Fetching Remote HTTP Stream Metadata & Video Thumbnails

For remote HTTP/HTTPS video URLs, pass the stream URL and custom HTTP headers to setDataSource(). Additionally, use getFrameAtTime() to capture frame thumbnails:

RemoteMediaExtractor.ktkotlin
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
 
// Always execute remote URL operations on Dispatchers.IO to prevent UI thread blocking
suspend fun fetchRemoteVideoThumbnail(videoUrl: String, timeInSeconds: Long): Bitmap? = withContext(Dispatchers.IO) {
    val retriever = MediaMetadataRetriever()
    return@withContext try {
        val headers = HashMap<String, String>()
        headers["User-Agent"] = "Android-App/1.0"
        
        // Load remote HTTP streaming URL
        retriever.setDataSource(videoUrl, headers)
 
        // Convert target timestamp to microseconds (1 sec = 1,000,000 us)
        val timeUs = timeInSeconds * 1_000_000L
        
        // Extract closest video sync keyframe thumbnail
        retriever.getFrameAtTime(timeUs, MediaMetadataRetriever.OPTION_CLOSEST_SYNC)
    } catch (e: Exception) {
        e.printStackTrace()
        null
    } finally {
        retriever.release()
    }
}

Key Metadata Attribute Cheat Sheet

  • `METADATA_KEY_DURATION`: Playback length in milliseconds (String).

  • `METADATA_KEY_VIDEO_WIDTH` & `METADATA_KEY_VIDEO_HEIGHT`: Pixel dimensions of video frames.

  • `METADATA_KEY_VIDEO_ROTATION`: Video display orientation angle in degrees (0, 90, 180, 270).

  • `METADATA_KEY_BITRATE`: Average stream bitrate in bits per second (bps).

  • `METADATA_KEY_DATE`: Creation or record date of the media container.