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
:
- Ask some other app to help the user choose a piece of media, via
ACTION_PICK
,ACTION_GET_CONTENT
, orACTION_OPEN_DOCUMENT
- Query the
MediaStore
yourself to find options and, where relevant, derive mediaUri
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:
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
MediaStore.Image.Media.EXTERNAL_CONTENT_URI
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
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:
openInputStream()
openOutputStream()
openFileDescriptor()
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:
- Images: the image itself
- Audio: album art, if any
- Video: a frame of the video itself
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.