ExoPlayer's main demo app serves two primary purposes:

1. To provide a relatively simple yet fully-featured example of ExoPlayer usage. The demo app can be used as a convenient starting point from which to develop your own app.
2. To make it easy to try ExoPlayer. The demo app can be used to test playback of your own content in addition to the included samples.

This page describes how to get, compile, and run the demo app. It also describes
how to use it to play your own media.

## Getting the code

The source code for the main demo app can be found in the `demos/main` folder of
our [GitHub project](https://github.com/androidx/media). If you haven't already done so, clone the project into a
local directory:  

```
git clone https://github.com/androidx/media.git
```

Next, open the project in Android Studio. You should see the following in the
Android Project view (the relevant folders of the demo app have been expanded):

![The project in Android Studio](https://developer.android.com/static/guide/topics/media/exoplayer/images/demo-app-project.png)

## Compiling and running

To compile and run the demo app, select and run the `demo` configuration in
Android Studio. The demo app will install and run on a connected Android device.
We recommend using a physical device if possible. If you wish to use an emulator
instead, please read the emulators section of [Supported devices](https://developer.android.com/guide/topics/media/exoplayer/supported-devices) and ensure
that your Virtual Device uses a system image with an API level of at least 23.

![SampleChooserActivity and PlayerActivity](https://developer.android.com/static/guide/topics/media/exoplayer/images/demo-app-screenshots.png)

The demo app presents of a list of samples (`SampleChooserActivity`). Selecting
a sample will open a second activity (`PlayerActivity`) for playback. The demo
features playback controls and track selection functionality. It also uses
ExoPlayer's `EventLogger` utility class to output useful debug information to
the system log. This logging can be viewed (along with error level logging for
other tags) with the command:  

```
adb logcat EventLogger:V *:E
```

### Enabling bundled decoders

ExoPlayer has a number of extensions that allow use of bundled software
decoders, including AV1, VP9, Opus, FLAC, and FFmpeg (audio only). The demo app
can be built to include and use these extensions as follows:

1. Build each of the extensions that you want to include. Note that this is a manual process. Refer to the `README.md` file in each extension for instructions.
2. In Android Studio's Build Variants view, set the build variant for the demo
   module to `withDecoderExtensionsDebug` or `withDecoderExtensionsRelease` as
   shown in the following image.

   ![Selecting the demo `withDecoderExtensionsDebug` build variant](https://developer.android.com/static/guide/topics/media/exoplayer/images/demo-app-build-variants.png)
3. Compile, install and run the `demo` configuration as normal.

By default an extension decoder will be used only if a suitable platform decoder
does not exist. It is possible to specify that extension decoders should be
preferred, as described in the following sections.

## Playing your own content

There are multiple ways to play your own content in the demo app.

### 1. Editing assets/media.exolist.json

The samples listed in the demo app are loaded from `assets/media.exolist.json`.
By editing this JSON file, it's possible to add and remove samples from the demo
app. The schema is as follows, where \[O\] indicates an optional attribute.  

    [
      {
        "name": "Name of heading",
        "samples": [
          {
            "name": "Name of sample",
            "uri": "The URI of the sample",
            "extension": "[O] Sample type hint. Cannot be combined with mime_type. Values: mpd, ism, m3u8",
            "clip_start_position_ms": "[O] A start point to which the sample should be clipped, in milliseconds"
            "clip_end_position_ms": "[O] An end point from which the sample should be clipped, in milliseconds"
            "drm_scheme": "[O] Drm scheme if protected. Values: widevine, playready, clearkey",
            "drm_license_uri": "[O] URI of the license server if protected",
            "drm_force_default_license_uri": "[O] Whether to force use of "drm_license_uri" for key requests that include their own license URI",
            "drm_key_request_properties": "[O] Key request headers if protected",
            "drm_session_for_clear_content": "[O] Whether to attach a DRM session to clear video and audio tracks"
            "drm_multi_session": "[O] Enables key rotation if protected",
            "mime_type": "[O] The MIME type of the sample. Cannot be combined with extension.",
            "subtitle_uri": "[O] The URI of a subtitle sidecar file",
            "subtitle_mime_type": "[O] The MIME type of subtitle_uri (required if subtitle_uri is set)",
            "subtitle_language": "[O] The BCP47 language code of the subtitle file (ignored if subtitle_uri is not set)",
            "ad_tag_uri": "[O] The URI of an ad tag to load via the IMA extension"
          },
          ...etc
        ]
      },
      ...etc
    ]

Playlists of samples can be specified using the schema:  

    [
      {
        "name": "Name of heading",
        "samples": [
          {
            "name": "Name of playlist sample",
            "playlist": [
              {
                "uri": "The URI of the first sample in the playlist",
                "extension": "[O] Sample type hint. Cannot be combined with mime_type. Values: mpd, ism, m3u8"
                "clip_start_position_ms": "[O] A start point to which the sample should be clipped, in milliseconds"
                "clip_end_position_ms": "[O] An end point from which the sample should be clipped, in milliseconds"
                "drm_scheme": "[O] Drm scheme if protected. Values: widevine, playready, clearkey",
                "drm_license_uri": "[O] URI of the license server if protected",
                "drm_force_default_license_uri": "[O] Whether to force use of "drm_license_uri" for key requests that include their own license URI",
                "drm_key_request_properties": "[O] Key request headers if protected",
                "drm_session_for_clear_content": "[O] Whether to attach a DRM session to clear video and audio tracks",
                "drm_multi_session": "[O] Enables key rotation if protected",
                "mime_type": "[O] The MIME type of the sample. Cannot be combined with extension.",
                "subtitle_uri": "[O] The URI of a subtitle sidecar file",
                "subtitle_mime_type": "[O] The MIME type of subtitle_uri (required if subtitle_uri is set)",
                "subtitle_language": "[O] The BCP47 language code of the subtitle file (ignored if subtitle_uri is not set)"
              },
              {
                "uri": "The URI of the second sample in the playlist",
                ...etc
              },
              ...etc
            ]
          },
          ...etc
        ]
      },
      ...etc
    ]

If required, key request headers are specified as an object containing a string
attribute for each header:  

    "drm_key_request_properties": {
      "name1": "value1",
      "name2": "value2",
      ...etc
    }

In the sample chooser activity, the overflow menu contains options for
specifying whether to prefer extension decoders.

#### Local file URIs and scoped storage restrictions

When specifying local file URIs, the demo app requests the necessary storage
access permissions to read these files. However, from Android 13, it is not
possible to load arbitrary files that don't end in a typical media file
extension (like `.mp4`). If you need to load such a file, you can place it in
the demo app's specific storage directory that has no access restrictions. This
is typically located at `/sdcard/Android/data/androidx.media3.demo.main/files`.

### 2. Loading an external exolist.json file

The demo app can load external JSON files using the schema above and named
according to the `*.exolist.json` convention. For example if you host such a
file at `https://yourdomain.com/samples.exolist.json`, you can open it in the
demo app using:  

```
adb shell am start -a android.intent.action.VIEW \
    -d https://yourdomain.com/samples.exolist.json
```

Clicking a `*.exolist.json` link (for example, in the browser or an email
client) on a device with the demo app installed will also open it in the demo
app. Hence hosting a `*.exolist.json` JSON file provides a simple way of
distributing content for others to try in the demo app.
| **Note:** On Android TV devices and emulators on API level 31 and above, an alternative action needs to be used which is `androidx.media3.demo.main.action.BROWSE` due to limitations on such devices.

### 3. Firing an intent

Intents can be used to bypass the list of samples and launch directly into
playback. To play a single sample, set the intent's action to
`androidx.media3.demo.main.action.VIEW` and its data URI to that of the
sample to play. Such an intent can be fired from the terminal using:  

```
adb shell am start -a androidx.media3.demo.main.action.VIEW \
    -d https://yourdomain.com/sample.mp4
```

Supported optional extras for a single sample intent are:

- Sample configuration extras:
  - `mime_type` \[String\] Sample MIME type hint. For example `application/dash+xml` for DASH content.
  - `clip_start_position_ms` \[Long\] A start point to which the sample should be clipped, in milliseconds.
  - `clip_end_position_ms` \[Long\] An end point from which the sample should be clipped, in milliseconds.
  - `drm_scheme` \[String\] DRM scheme if protected. Valid values are `widevine`, `playready` and `clearkey`. DRM scheme UUIDs are also accepted.
  - `drm_license_uri` \[String\] URI of the license server if protected.
  - `drm_force_default_license_uri` \[Boolean\] Whether to force use of `drm_license_uri` for key requests that include their own license URI.
  - `drm_key_request_properties` \[String array\] Key request headers packed as name1, value1, name2, value2 etc. if protected.
  - `drm_session_for_clear_content` \[Boolean\] Whether to attach a DRM session to clear video and audio tracks.
  - `drm_multi_session` \[Boolean\] Enables key rotation if protected.
  - `subtitle_uri` \[String\] The URI of a subtitle sidecar file.
  - `subtitle_mime_type` \[String\] The MIME type of subtitle_uri (required if subtitle_uri is set).
  - `subtitle_language` \[String\] The BCP47 language code of the subtitle file (ignored if subtitle_uri is not set).
  - `ad_tag_uri` \[String\] The URI of an ad tag to load using the \[IMA extension\]\[\].
  - `prefer_extension_decoders` \[Boolean\] Whether extension decoders are preferred to platform ones.

When using `adb shell am start` to fire an intent, an optional string extra can
be set with `--es` (e.g., `--es extension mpd`). An optional boolean extra can
be set with `--ez` (e.g., `--ez prefer_extension_decoders TRUE`). An optional
long extra can be set with `--el` (e.g., `--el clip_start_position_ms 5000`). An
optional string array extra can be set with `--esa` (e.g.,
`--esa drm_key_request_properties name1,value1`).

To play a playlist of samples, set the intent's action to
`androidx.media3.demo.main.action.VIEW_LIST`. The sample configuration
extras remain the same as for `androidx.media3.demo.main.action.VIEW`,
except for two differences:

- The extras' keys should have an underscore and the 0-based index of the sample as suffix. For example, `extension_0` would hint the sample type for the first sample. `drm_scheme_1` would set the DRM scheme for the second sample.
- The uri of the sample is passed as an extra with key `uri_<sample-index>`.

Other extras, which are not sample dependant, do not change. For example, you
can run the following command in the terminal to play a playlist with two items,
overriding the extension of the second item:  

```
adb shell am start -a androidx.media3.demo.main.action.VIEW_LIST \
    --es uri_0 https://a.com/sample1.mp4 \
    --es uri_1 https://b.com/sample2.fake_mpd \
    --es extension_1 mpd
```