How to Consume Media

First, let’s look at how you can get access to media that has been indexed by the MediaStore.

In particular, we will examine the ConferenceVideos sample module in the book’s sample project. This app has a list of videos of presentations delivered by the book’s author at various conferences. The app will see if these videos are already downloaded and indexed by MediaStore. For those that are not, the user will be able to download them, with the app handing the videos over to MediaStore. For the downloaded videos, the user can request to play the video using some existing video player app on the device.

Querying MediaStore

Consuming media centers around the media’s Uri. You have two main approaches for getting such a Uri:

  1. Ask some other app to help the user choose a piece of media, via ACTION_PICK, ACTION_GET_CONTENT, or ACTION_OPEN_DOCUMENT
  2. Query the MediaStore yourself to find options and, where relevant, derive media Uri values for them

In the case of the ConferenceVideos app, we know what the videos are supposed to be, but we do not know if they have been downloaded or not. So, the second option is the right choice, as we can query to see which videos of our set are already known to MediaStore or not.

Getting the Root Uri

Classically, we would use some fixed values for querying MediaStore, typed by the sort of media we wanted:

In principle, those should still work.

Another, albeit ill-used option, is getContentUri(). This is a method on classes like MediaStore.Video.Media. Given the name of some “volume”, it returns a Uri for querying that volume. The Android 10 documentation steers you in the direction of getContentUri(), in part because there is a getExternalVolumeNames() method on MediaStore that returns a list of values that you could supply to getContentUri(). Mediastore.VOLUME_EXTERNAL should give you a representation including both external and removable storage.

ConferenceVideos is a fairly unsophisticated app, so it just uses MediaStore.VOLUME_EXTERNAL on Android 10 devices, falling back to MediaStore.Video.Media.EXTERNAL_CONTENT_URI on older devices:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) MediaStore.Video.Media.getContentUri(
      MediaStore.VOLUME_EXTERNAL
    ) else MediaStore.Video.Media.EXTERNAL_CONTENT_URI

MediaStore also has some “decorator” methods that can be used to augment a Uri with some parameters to opt into certain behavior. These methods take a Uri and return a new Uri based on the one that you supplied, “decorated” with additional information. For example, setIncludePending() will decorate a Uri to indicate that you want to see pending and final results in your query results, where a “pending” result means that the content may not yet be ready (e.g., it is being downloaded).

Deriving a Media Content Uri

Given a root Uri, query() on ContentResolver is unchanged in Android 10. You provide the root Uri, a “projection” of columns to return, your query, any arguments to that query, and something to serve as the equivalent of a SQL ORDER BY clause. It returns a Cursor with the results.

The ConferenceVideos sample module has a VideoRepository that is responsible for communications with MediaStore and the CommonsWare Web server (where the videos are available for download). It has a getLocalUri() function that tries to derive the Uri for a video, given the video’s filename:

  suspend fun getLocalUri(filename: String): Uri? =
    withContext(Dispatchers.IO) {
      val resolver = context.contentResolver

      resolver.query(collection, PROJECTION, QUERY, arrayOf(filename), null)
        ?.use { cursor ->
          if (cursor.count > 0) {
            cursor.moveToFirst()
            return@withContext ContentUris.withAppendedId(
              collection,
              cursor.getLong(0)
            )
          }
        }

      null
    }

Here, PROJECTION and QUERY are defined as constants:

private val PROJECTION = arrayOf(MediaStore.Video.Media._ID)
private const val QUERY = MediaStore.Video.Media.DISPLAY_NAME + " = ?"

If the query returns a Cursor with a row, we move to that row and use ContentUris.withAppendedId() to assemble the Uri from the collection root Uri and the _ID value returned from the query.

Using a Media Content Uri

Given the Uri to a piece of media, you have lots of options. The sample module just wraps it in an ACTION_VIEW Intent when the user taps on the video (represented in a row in a RecyclerView), to play back that video.

If you want to consume the content directly in your app, you have lots of options on ContentResolver, including:

Android 10 adds a loadThumbnail() method on ContentResolver. This will attempt to give you a Bitmap representation of the content identified by the Uri. The exact source of the data is undocumented but probably amounts to:


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.