Commit ee6ba28d by ibaker Committed by Ian Baker

Redirect exoplayer.dev pages to media3 on d.android.com

Two pages don't have media3 equivalents, so are redirected to
https://developer.android.com/media/media3/exoplayer instead:

* https://exoplayer.dev/design-documents.html
* https://exoplayer.dev/pros-and-cons.html

PiperOrigin-RevId: 621497706
(cherry picked from commit e6e7240df758b9ed6d757c77ca8855edd6aa7976)
parent d90d7290
---
title: Ad insertion
permalink: /ad-insertion.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/ad-insertion
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
ExoPlayer can be used for both client-side and server-side ad insertion.
## Client-side ad insertion ##
In client-side ad insertion, the player switches between loading media from
different URLs as it transitions between playing content and ads. Information
about ads is loaded separately from the media, such as from an XML [VAST][] or
[VMAP][] ad tag. This can include ad cue positions relative to the start of the
content, the actual ad media URIs and metadata such as whether a given ad is
skippable.
When using ExoPlayer's `AdsMediaSource` for client-side ad insertion, the player
has information about the ads to be played. This has several benefits:
* The player can expose metadata and functionality relating to ads via its API.
* [ExoPlayer UI components][] can show markers for ad positions automatically,
and change their behavior depending on whether ad is playing.
* Internally, the player can keep a consistent buffer across transitions between
ads and content.
In this setup, the player takes care of switching between ads and content, which
means that apps don't need to take care of controlling multiple separate
background/foreground players for ads and content.
When preparing content videos and ad tags for use with client-side ad insertion,
ads should ideally be positioned at synchronization samples (keyframes) in the
content video so that the player can resume content playback seamlessly.
### Declarative ad support ###
An ad tag URI can be specified when building a `MediaItem`:
~~~
MediaItem mediaItem =
new MediaItem.Builder()
.setUri(videoUri)
.setAdsConfiguration(
new MediaItem.AdsConfiguration.Builder(adTagUri).build())
.build();
~~~
{: .language-java}
To enable player support for media items that specify ad tags, it's necessary to
build and inject a `DefaultMediaSourceFactory` configured with an
`AdsLoader.Provider` and an `AdViewProvider` when creating the player:
~~~
MediaSource.Factory mediaSourceFactory =
new DefaultMediaSourceFactory(context)
.setLocalAdInsertionComponents(
adsLoaderProvider, /* adViewProvider= */ playerView);
ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(mediaSourceFactory)
.build();
~~~
{: .language-java}
Internally, `DefaultMediaSourceFactory` will wrap the content media source in an
`AdsMediaSource`. The `AdsMediaSource` will obtain an `AdsLoader` from the
`AdsLoader.Provider` and use it to insert ads as defined by the media item's ad
tag.
ExoPlayer's `StyledPlayerView` implements `AdViewProvider`. The IMA extension
provides an easy to use `AdsLoader`, as described below.
### Playlists with ads ###
When playing a [playlist][] with multiple media items, the default behavior is
to request the ad tag and store ad playback state once for each media ID,
content URI and ad tag URI combination. This means that users will see ads for
every media item with ads that has a distinct media ID or content URI, even if
the ad tag URIs match. If a media item is repeated, the user will see the
corresponding ads only once (the ad playback state stores whether ads have been
played, so they are skipped after their first occurrence).
It's possible to customize this behavior by passing an opaque ads identifier
with which ad playback state for a given media item is linked, based on object
equality. Here is an example where ad playback state is linked to the ad tag
URI only, rather than the combination of the media ID and ad tag URI, by
passing the ad tag URI as the ads identifier. The effect is that ads will load
only once and the user will not see ads on the second item when playing the
playlist from start to finish.
~~~
// Build the media items, passing the same ads identifier for both items,
// which means they share ad playback state so ads play only once.
MediaItem firstItem =
new MediaItem.Builder()
.setUri(firstVideoUri)
.setAdsConfiguration(
new MediaItem.AdsConfiguration.Builder(adTagUri)
.setAdsId(adTagUri)
.build())
.build();
MediaItem secondItem =
new MediaItem.Builder()
.setUri(secondVideoUri)
.setAdsConfiguration(
new MediaItem.AdsConfiguration.Builder(adTagUri)
.setAdsId(adTagUri)
.build())
.build();
player.addMediaItem(firstItem);
player.addMediaItem(secondItem);
~~~
{: .language-java}
### IMA extension ###
The [ExoPlayer IMA extension][] provides `ImaAdsLoader`, making it easy to
integrate client-side ad insertion into your app. It wraps the functionality of
the [client-side IMA SDK][] to support insertion of VAST/VMAP ads. For
instructions on how to use the extension, including how to handle backgrounding
and resuming playback, please see the [README][].
The [demo application][] uses the IMA extension, and includes several sample
VAST/VMAP ad tags in the sample list.
#### UI considerations ####
`StyledPlayerView` hides its transport controls during playback of ads by
default, but apps can toggle this behavior by calling
`setControllerHideDuringAds`. The IMA SDK will show additional views on top of
the player while an ad is playing (e.g., a 'more info' link and a skip button,
if applicable).
Since advertisers expect a consistent experience across apps, the IMA SDK does
not allow customization of the views that it shows while an ad is playing. It is
therefore not possible to remove or reposition the skip button, change the
fonts, or make other customizations to the visual appearance of these views.
{:.info}
The IMA SDK may report whether ads are obscured by application provided views
rendered on top of the player. Apps that need to overlay views that are
essential for controlling playback must register them with the IMA SDK so that
they can be omitted from viewability calculations. When using `StyledPlayerView`
as the `AdViewProvider`, it will automatically register its control overlays.
Apps that use a custom player UI must register overlay views by returning them
from `AdViewProvider.getAdOverlayInfos`.
For more information about overlay views, see
[Open Measurement in the IMA SDK][].
#### Companion ads ####
Some ad tags contain additional companion ads that can be shown in 'slots' in an
app UI. These slots can be passed via
`ImaAdsLoader.Builder.setCompanionAdSlots(slots)`. For more information see
[Adding Companion Ads][].
#### Standalone ads ####
The IMA SDK is designed for inserting ads into media content, not for playing
standalone ads by themselves. Hence playback of standalone ads is not supported
by the IMA extension. We recommend using the [Google Mobile Ads SDK][] instead
for this use case.
### Using a third-party ads SDK ###
If you need to load ads via a third-party ads SDK, it's worth checking whether
it already provides an ExoPlayer integration. If not, implementing a custom
`AdsLoader` that wraps the third-party ads SDK is the recommended approach,
since it provides the benefits of `AdsMediaSource` described above.
`ImaAdsLoader` acts as an example implementation.
Alternatively, you can use ExoPlayer's [playlist support][] to build a sequence
of ads and content clips:
~~~
// A pre-roll ad.
MediaItem preRollAd = MediaItem.fromUri(preRollAdUri);
// The start of the content.
MediaItem contentStart =
new MediaItem.Builder()
.setUri(contentUri)
.setClippingConfiguration(
new ClippingConfiguration.Builder()
.setEndPositionMs(120_000)
.build())
.build();
// A mid-roll ad.
MediaItem midRollAd = MediaItem.fromUri(midRollAdUri);
// The rest of the content
MediaItem contentEnd =
new MediaItem.Builder()
.setUri(contentUri)
.setClippingConfiguration(
new ClippingConfiguration.Builder()
.setStartPositionMs(120_000)
.build())
.build();
// Build the playlist.
player.addMediaItem(preRollAd);
player.addMediaItem(contentStart);
player.addMediaItem(midRollAd);
player.addMediaItem(contentEnd);
~~~
{: .language-java}
## Server-side ad insertion ##
In server-side ad insertion (also called dynamic ad insertion, or DAI), the
media stream contains both ads and content. A DASH manifest may point to both
content and ad segments, possibly in separate periods. For HLS, see the Apple
documentation on [incorporating ads into a playlist][].
When using server-side ad insertion, the client may need to resolve the media
URL dynamically to get the stitched stream, it may need to display ads overlays
in the UI or it may need to report events to an ads SDK or ad server.
ExoPlayer's `DefaultMediaSourceFactory` can delegate all these tasks to a
server-side ad insertion `MediaSource` for URIs using the `ssai://` scheme:
```
Player player =
new ExoPlayer.Builder(context)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(context)
.setServerSideAdInsertionMediaSourceFactory(ssaiFactory))
.build();
```
### IMA extension ###
The [ExoPlayer IMA extension][] provides `ImaServerSideAdInsertionMediaSource`,
making it easy to integrate with IMA's server-side inserted ad streams in your
app. It wraps the functionality of the [IMA DAI SDK for Android][] and fully
integrates the provided ad metadata into the player. For example, this allows
you to use methods like `Player.isPlayingAd()`, listen to content-ad transitions
and let the player handle ad playback logic like skipping already played ads.
In order to use this class, you need to set up the
`ImaServerSideAdInsertionMediaSource.AdsLoader` and the
`ImaServerSideAdInsertionMediaSource.Factory` and connect them to the player:
```
// MediaSource.Factory to load the actual media stream.
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory(context);
// AdsLoader that can be reused for multiple playbacks.
ImaServerSideAdInsertionMediaSource.AdsLoader adsLoader =
new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(context, adViewProvider)
.build();
// MediaSource.Factory to create the ad sources for the current player.
ImaServerSideAdInsertionMediaSource.Factory adsMediaSourceFactory =
new ImaServerSideAdInsertionMediaSource.Factory(adsLoader, defaultMediaSourceFactory);
// Configure DefaultMediaSourceFactory to create both IMA DAI sources and
// regular media sources. If you just play IMA DAI streams, you can also use
// adsMediaSourceFactory directly.
defaultMediaSourceFactory.setServerSideAdInsertionMediaSourceFactory(adsMediaSourceFactory);
// Set the MediaSource.Factory on the Player.
Player player =
new ExoPlayer.Builder(context)
.setMediaSourceFactory(defaultMediaSourceFactory)
.build();
// Set the player on the AdsLoader
adsLoader.setPlayer(player);
```
Load your IMA asset key, or content source id and video id, by building an URL
with `ImaServerSideAdInsertionUriBuilder`:
```
Uri ssaiUri =
new ImaServerSideAdInsertionUriBuilder()
.setAssetKey(assetKey)
.setFormat(C.TYPE_HLS)
.build();
player.setMediaItem(MediaItem.fromUri(ssaiUri));
```
Finally, release your ads loader once it's no longer used:
```
adsLoader.release();
```
Currently only a single IMA server-side ad insertion stream is supported in the
same playlist. You can combine the stream with other media but not with another
IMA server-side ad insertion stream.
{:.info}
#### UI considerations ####
The same [UI considerations as for client-side ad insertion][] apply to
server-side ad insertion too.
#### Companion ads ####
Some ad tags contain additional companion ads that can be shown in 'slots' in an
app UI. These slots can be passed via
`ImaServerSideAdInsertionMediaSource.AdsLoader.Builder.setCompanionAdSlots(slots)`.
For more information see [Adding Companion Ads][].
### Using a third-party ads SDK ###
If you need to load ads via a third-party ads SDK, it’s worth checking whether
it already provides an ExoPlayer integration. If not, it's recommended to
provide a custom `MediaSource` that accepts URIs with the `ssai://` scheme
similar to `ImaServerSideAdInsertionMediaSource`.
The actual logic of creating the ad structure can be delegated to the general
purpose `ServerSideAdInsertionMediaSource`, which wraps a stream `MediaSource`
and allows the user to set and update the `AdPlaybackState` representing the ad
metadata.
Often, server-side inserted ad streams contain timed events to notify the player
about ad metadata. Please see [supported formats][] for information on what
timed metadata formats are supported by ExoPlayer. Custom ads SDK `MediaSource`
implementations can listen for timed metadata events from the player via
`Player.Listener.onMetadata`.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/ad-insertion
[VAST]: https://www.iab.com/wp-content/uploads/2015/06/VASTv3_0.pdf
[VMAP]: https://www.iab.com/guidelines/digital-video-multiple-ad-playlist-vmap-1-0-1/
[ExoPlayer UI components]: {{ site.baseurl }}/ui-components.html
[ExoPlayer IMA extension]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/ima
[client-side IMA SDK]: https://developers.google.com/interactive-media-ads/docs/sdks/android
[README]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/ima
[demo application]: {{ site.baseurl }}/demo-application.html
[Open Measurement in the IMA SDK]: https://developers.google.com/interactive-media-ads/docs/sdks/android/omsdk
[Adding Companion Ads]: https://developers.google.com/interactive-media-ads/docs/sdks/android/companions
[playlist]: {{ site.baseurl }}/playlists.html
[playlist support]: {{ site.baseurl }}/playlists.html
[incorporating ads into a playlist]: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/incorporating_ads_into_a_playlist
[supported formats]: {{ site.baseurl }}/supported-formats.html
[Google Mobile Ads SDK]: https://developers.google.com/admob/android/quick-start
[IMA DAI SDK for Android]: https://developers.google.com/interactive-media-ads/docs/sdks/android/dai
[UI considerations as for client-side ad insertion]: #ui-considerations
---
title: Analytics
permalink: /analytics.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/analytics
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
ExoPlayer supports a wide range of playback analytics needs. Ultimately,
analytics is about collecting, interpreting, aggregating and summarizing data
from playbacks. This data can be used either on the device, for example for
logging, debugging, or to inform future playback decisions, or reported to a
server to monitor playbacks across all devices.
An analytics system usually needs to collect events first, and then process them
further to make them meaningful:
* **Event collection**:
This can be done by registering an `AnalyticsListener` on an `ExoPlayer`
instance. Registered analytics listeners receive events as they occur during
usage of the player. Each event is associated with the corresponding media
item in the playlist, as well as playback position and timestamp metadata.
* **Event processing**:
Some analytics systems upload raw events to a server, with all event
processing performed server-side. It's also possible to process events on the
device, and doing so may be simpler or reduce the amount of information that
needs to be uploaded. ExoPlayer provides `PlaybackStatsListener`, which
allows you to perform the following processing steps:
1. **Event interpretation**: To be useful for analytics purposes, events need
to be interpreted in the context of a single playback. For example the raw
event of a player state change to `STATE_BUFFERING` may correspond to
initial buffering, a rebuffer, or buffering that happens after a seek.
1. **State tracking**: This step converts events to counters. For example,
state change events can be converted to counters tracking how much time is
spent in each playback state. The result is a basic set of analytics data
values for a single playback.
1. **Aggregation**: This step combines the analytics data across multiple
playbacks, typically by adding up counters.
1. **Calculation of summary metrics**: Many of the most useful metrics are
those that compute averages or combine the basic analytics data values in
other ways. Summary metrics can be calculated for single or multiple
playbacks.
## Event collection with AnalyticsListener ##
Raw playback events from the player are reported to `AnalyticsListener`
implementations. You can easily add your own listener and override only the
methods you are interested in:
~~~
exoPlayer.addAnalyticsListener(new AnalyticsListener() {
@Override
public void onPlaybackStateChanged(
EventTime eventTime, @Player.State int state) {
}
@Override
public void onDroppedVideoFrames(
EventTime eventTime, int droppedFrames, long elapsedMs) {
}
});
~~~
{: .language-java}
The `EventTime` that's passed to each callback associates the event to a media
item in the playlist, as well as playback position and timestamp metadata:
* `realtimeMs`: The wall clock time of the event.
* `timeline`, `windowIndex` and `mediaPeriodId`: Defines the playlist and the
item within the playlist to which the event belongs. The `mediaPeriodId`
contains optional additional information, for example indicating whether the
event belongs to an ad within the item.
* `eventPlaybackPositionMs`: The playback position in the item when the event
occurred.
* `currentTimeline`, `currentWindowIndex`, `currentMediaPeriodId` and
`currentPlaybackPositionMs`: As above but for the currently playing item. The
currently playing item may be different from the item to which the event
belongs, for example if the event corresponds to pre-buffering of the next
item to be played.
## Event processing with PlaybackStatsListener ##
`PlaybackStatsListener` is an `AnalyticsListener` that implements on device
event processing. It calculates `PlaybackStats`, with counters and derived
metrics including:
* Summary metrics, for example the total playback time.
* Adaptive playback quality metrics, for example the average video resolution.
* Rendering quality metrics, for example the rate of dropped frames.
* Resource usage metrics, for example the number of bytes read over the network.
You will find a complete list of the available counts and derived metrics in the
[`PlaybackStats` Javadoc][].
`PlaybackStatsListener` calculates separate `PlaybackStats` for each media item
in the playlist, and also each client-side ad inserted within these items. You
can provide a callback to `PlaybackStatsListener` to be informed about finished
playbacks, and use the `EventTime` passed to the callback to identify which
playback finished. It's possible to [aggregate the analytics data][] for
multiple playbacks. It's also possible to query the `PlaybackStats` for the
current playback session at any time using
`PlaybackStatsListener.getPlaybackStats()`.
~~~
exoPlayer.addAnalyticsListener(
new PlaybackStatsListener(
/* keepHistory= */ true, (eventTime, playbackStats) -> {
// Analytics data for the session started at `eventTime` is ready.
}));
~~~
{: .language-java}
The constructor of `PlaybackStatsListener` gives the option to keep the full
history of processed events. Note that this may incur an unknown memory overhead
depending on the length of the playback and the number of events. Therefore you
should only turn it on if you need access to the full history of processed
events, rather than just to the final analytics data.
Note that `PlaybackStats` uses an extended set of states to indicate not only
the state of the media, but also the user intention to play and more detailed
information such as why playback was interrupted or ended:
| Playback state | User intention to play | No intention to play |
|:---|:---|:---|
| Before playback | `JOINING_FOREGROUND` | `NOT_STARTED`, `JOINING_BACKGROUND` |
| Active playback | `PLAYING` | |
| Interrupted playback | `BUFFERING`, `SEEKING` | `PAUSED`, `PAUSED_BUFFERING`, `SUPPRESSED`, `SUPPRESSED_BUFFERING`, `INTERRUPTED_BY_AD` |
| End states | | `ENDED`, `STOPPED`, `FAILED`, `ABANDONED` |
The user intention to play is important to distinguish times when the user was
actively waiting for playback to continue from passive wait times. For example,
`PlaybackStats.getTotalWaitTimeMs` returns the total time spent in the
`JOINING_FOREGROUND`, `BUFFERING` and `SEEKING` states, but not the time when
playback was paused. Similarly, `PlaybackStats.getTotalPlayAndWaitTimeMs` will
return the total time with a user intention to play, that is the total active
wait time and the total time spent in the `PLAYING` state.
### Processed and interpreted events ###
You can record processed and interpreted events by using `PlaybackStatsListener`
with `keepHistory=true`. The resulting `PlaybackStats` will contain the
following event lists:
* `playbackStateHistory`: An ordered list of extended playback states with
the `EventTime` at which they started to apply. You can also use
`PlaybackStats.getPlaybackStateAtTime` to look up the state at a given wall
clock time.
* `mediaTimeHistory`: A history of wall clock time and media time pairs allowing
you to reconstruct which parts of the media were played at which time. You can
also use `PlaybackStats.getMediaTimeMsAtRealtimeMs` to look up the playback
position at a given wall clock time.
* `videoFormatHistory` and `audioFormatHistory`: Ordered lists of video and
audio formats used during playback with the `EventTime` at which they started
to be used.
* `fatalErrorHistory` and `nonFatalErrorHistory`: Ordered lists of fatal and
non-fatal errors with the `EventTime` at which they occurred. Fatal errors are
those that ended playback, whereas non-fatal errors may have been recoverable.
### Single-playback analytics data ###
This data is automatically collected if you use `PlaybackStatsListener`, even
with `keepHistory=false`. The final values are the public fields that you can
find in the [`PlaybackStats` Javadoc][] and the playback state durations
returned by `getPlaybackStateDurationMs`. For convenience, you'll also find
methods like `getTotalPlayTimeMs` and `getTotalWaitTimeMs` that return the
duration of specific playback state combinations.
~~~
Log.d("DEBUG", "Playback summary: "
+ "play time = " + playbackStats.getTotalPlayTimeMs()
+ ", rebuffers = " + playbackStats.totalRebufferCount);
~~~
{: .language-java}
Some values like `totalVideoFormatHeightTimeProduct` are only useful when
calculating derived summary metrics like the average video height, but are
required to correctly combine multiple `PlaybackStats` together.
{:.info}
### Aggregate analytics data of multiple playbacks ###
You can combine multiple `PlaybackStats` together by calling
`PlaybackStats.merge`. The resulting `PlaybackStats` will contain the aggregated
data of all merged playbacks. Note that it won't contain the history of
individual playback events, since these cannot be aggregated.
`PlaybackStatsListener.getCombinedPlaybackStats` can be used to get an
aggregated view of all analytics data collected in the lifetime of a
`PlaybackStatsListener`.
### Calculated summary metrics ###
In addition to the basic analytics data, `PlaybackStats` provides many methods
to calculate summary metrics.
~~~
Log.d("DEBUG", "Additional calculated summary metrics: "
+ "average video bitrate = " + playbackStats.getMeanVideoFormatBitrate()
+ ", mean time between rebuffers = "
+ playbackStats.getMeanTimeBetweenRebuffers());
~~~
{: .language-java}
## Advanced topics ##
### Associating analytics data with playback metadata ###
When collecting analytics data for individual playbacks, you may wish to
associate the playback analytics data with metadata about the media being
played.
It's advisable to set media-specific metadata with `MediaItem.Builder.setTag`.
The media tag is part of the `EventTime` reported for raw events and when
`PlaybackStats` are finished, so it can be easily retrieved when handling the
corresponding analytics data:
~~~
new PlaybackStatsListener(
/* keepHistory= */ false, (eventTime, playbackStats) -> {
Object mediaTag =
eventTime.timeline.getWindow(eventTime.windowIndex, new Window())
.mediaItem.localConfiguration.tag;
// Report playbackStats with mediaTag metadata.
});
~~~
{: .language-java}
### Reporting custom analytics events ###
In case you need to add custom events to the analytics data, you need to save
these events in your own data structure and combine them with the reported
`PlaybackStats` later. If it helps, you can extend `DefaultAnalyticsCollector`
to be able to generate `EventTime` instances for your custom events and send
them to the already registered listeners as shown in the following example.
~~~
interface ExtendedListener extends AnalyticsListener {
void onCustomEvent(EventTime eventTime);
}
class ExtendedCollector extends DefaultAnalyticsCollector {
public void customEvent() {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
sendEvent(eventTime, CUSTOM_EVENT_ID, listener -> {
if (listener instanceof ExtendedListener) {
((ExtendedListener) listener).onCustomEvent(eventTime);
}
});
}
}
// Usage - Setup and listener registration.
ExoPlayer player = new ExoPlayer.Builder(context)
.setAnalyticsCollector(new ExtendedCollector())
.build();
player.addAnalyticsListener(new ExtendedListener() {
@Override
public void onCustomEvent(EventTime eventTime) {
// Save custom event for analytics data.
}
});
// Usage - Triggering the custom event.
((ExtendedCollector) player.getAnalyticsCollector()).customEvent();
~~~
{: .language-java}
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/analytics
[`PlaybackStats` Javadoc]: {{ site.exo_sdk }}/analytics/PlaybackStats.html
[aggregate the analytics data]: {{ site.baseurl }}/analytics.html#aggregate-analytics-data-of-multiple-playbacks
---
title: Battery consumption
permalink: /battery-consumption.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/battery-consumption
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
## How important is battery consumption due to media playback? ##
Avoiding unnecessary battery consumption is an important aspect of developing a
good Android application. Media playback can be a major cause of battery drain,
however its importance for a particular app heavily depends on its usage
patterns. If an app is only used to play small amounts of media each day, then
the corresponding battery consumption will only be a small percentage of the
total consumption of the device. In such it makes sense to prioritize feature
set and reliability over optimizing for battery when selecting which player to
use. On the other hand, if an app is often used to play large amounts of media
each day, then optimizing for battery consumption should be weighted more
heavily when choosing between a number of viable options.
## How power efficient is ExoPlayer? ##
The diverse nature of the Android device and media content ecosystems means that
it’s difficult to make widely applicable statements about ExoPlayer’s battery
consumption, and in particular how it compares with Android’s MediaPlayer API.
Both absolute and relative performance vary by hardware, Android version and the
media being played. Hence the information provided below should be treated as
guidance only.
### Video playback ###
For video playback, our measurements show that ExoPlayer and MediaPlayer draw
similar amounts of power. The power required for the display and decoding the
video stream are the same in both cases, and these account for most of the power
consumed during playback.
Regardless of which media player is used, choosing between `SurfaceView` and
`TextureView` for output can have a significant impact on power consumption.
`SurfaceView` is more power efficient, with `TextureView` increasing total power
draw during video playback by as much as 30% on some devices. `SurfaceView`
should therefore be preferred where possible. Read more about choosing between
`SurfaceView` and `TextureView`
[here]({{ site.baseurl }}/ui-components.html#choosing-a-surface-type).
Below are some power consumption measurements for playing 1080p and 480p video
on Pixel 2, measured using a [Monsoon power monitor][]. As mentioned above,
these numbers should not be used to draw general conclusions about power
consumption across the Android device and media content ecosystems.
| | MediaPlayer | ExoPlayer |
|-------------------|:-----------:|:----------|
| SurfaceView 1080p | 202 mAh | 214 mAh |
| TextureView 1080p | 219 mAh | 221 mAh |
| SurfaceView 480p | 194 mAh | 207 mAh |
| TextureView 480p | 212 mAh | 215 mAh |
### Audio playback ###
For short audio playbacks or playbacks when the screen is on, using ExoPlayer
does not have a significant impact on power compared to using MediaPlayer.
For long playbacks with the screen off, ExoPlayer's audio offload mode needs to
be used or ExoPlayer may consume significantly more power than MediaPlayer.
Audio offload allows audio processing to be offloaded from the CPU to a
dedicated signal processor. It is used by default by MediaPlayer but not
ExoPlayer. ExoPlayer introduced support for audio offload in 2.12 as an
experimental feature. See `DefaultRenderersFactory.setEnableAudioOffload` and
`ExoPlayer.experimentalSetOffloadSchedulingEnabled` for more details on how
to enable it.
Due to SDK API limitations, ExoPlayer's audio offload mode is only available on
devices running Android 10 and above. MediaPlayer can use audio offload on
devices running earlier versions of Android. Whether the increased robustness,
flexibility and feature set that ExoPlayer provides over MediaPlayer is worth
the increased power consumption for audio only use cases on older devices is
something an app developer must decide, taking their requirements and app usage
patterns into account.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/battery-consumption
[Monsoon power monitor]: https://www.msoon.com/battery-configuration
---
title: Customization
permalink: /customization.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/customization
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
At the core of the ExoPlayer library is the `Player` interface. A `Player`
exposes traditional high-level media player functionality such as the ability to
buffer media, play, pause and seek. The default implementation `ExoPlayer` is
designed to make few assumptions about (and hence impose few restrictions on)
the type of media being played, how and where it is stored, and how it is
rendered. Rather than implementing the loading and rendering of media directly,
`ExoPlayer` implementations delegate this work to components that are injected
when a player is created or when new media sources are passed to the player.
Components common to all `ExoPlayer` implementations are:
* `MediaSource` instances that define media to be played, load the media, and
from which the loaded media can be read. `MediaSource` instances are created
from `MediaItem`s by a `MediaSource.Factory` inside the player. They can also
be passed directly to the player using the [media source based playlist API].
* A `MediaSource.Factory` that converts `MediaItem`s to `MediaSource`s. The
`MediaSource.Factory` is injected when the player is created.
* `Renderer`s that render individual components of the media. `Renderer`s are
injected when the player is created.
* A `TrackSelector` that selects tracks provided by the `MediaSource` to be
consumed by each of the available `Renderer`s. A `TrackSelector` is injected
when the player is created.
* A `LoadControl` that controls when the `MediaSource` buffers more media, and
how much media is buffered. A `LoadControl` is injected when the player is
created.
* A `LivePlaybackSpeedControl` that controls the playback speed during live
playbacks to allow the player to stay close to a configured live offset. A
`LivePlaybackSpeedControl` is injected when the player is created.
The concept of injecting components that implement pieces of player
functionality is present throughout the library. The default implementations of
some components delegate work to further injected components. This allows many
sub-components to be individually replaced with implementations that are
configured in a custom way.
## Player customization ##
Some common examples of customizing the player by injecting components are
described below.
### Configuring the network stack ###
We have a page about [customizing the network stack used by ExoPlayer].
### Caching data loaded from the network ###
To temporarily cache media, or for
[playing downloaded media]({{ site.baseurl }}/downloading-media.html#playing-downloaded-content),
you can inject a `CacheDataSource.Factory` into the `DefaultMediaSourceFactory`:
~~~
DataSource.Factory cacheDataSourceFactory =
new CacheDataSource.Factory()
.setCache(simpleCache)
.setUpstreamDataSourceFactory(httpDataSourceFactory);
ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(context)
.setDataSourceFactory(cacheDataSourceFactory))
.build();
~~~
{: .language-java}
### Customizing server interactions ###
Some apps may want to intercept HTTP requests and responses. You may want to
inject custom request headers, read the server's response headers, modify the
requests' URIs, etc. For example, your app may authenticate itself by injecting
a token as a header when requesting the media segments.
The following example demonstrates how to implement these behaviors by
injecting a custom `DataSource.Factory` into the `DefaultMediaSourceFactory`:
~~~
DataSource.Factory dataSourceFactory = () -> {
HttpDataSource dataSource = httpDataSourceFactory.createDataSource();
// Set a custom authentication request header.
dataSource.setRequestProperty("Header", "Value");
return dataSource;
};
ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(context)
.setDataSourceFactory(dataSourceFactory))
.build();
~~~
{: .language-java}
In the code snippet above, the injected `HttpDataSource` includes the header
`"Header: Value"` in every HTTP request. This behavior is *fixed* for every
interaction with an HTTP source.
For a more granular approach, you can inject just-in-time behavior using a
`ResolvingDataSource`. The following code snippet shows how to inject
request headers just before interacting with an HTTP source:
~~~
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory(
httpDataSourceFactory,
// Provide just-in-time request headers.
dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));
~~~
{: .language-java}
You may also use a `ResolvingDataSource` to perform
just-in-time modifications of the URI, as shown in the following snippet:
~~~
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory(
httpDataSourceFactory,
// Provide just-in-time URI resolution logic.
dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));
~~~
{: .language-java}
### Customizing error handling ###
Implementing a custom [LoadErrorHandlingPolicy][] allows apps to customize the
way ExoPlayer reacts to load errors. For example, an app may want to fail fast
instead of retrying many times, or may want to customize the back-off logic that
controls how long the player waits between each retry. The following snippet
shows how to implement custom back-off logic:
~~~
LoadErrorHandlingPolicy loadErrorHandlingPolicy =
new DefaultLoadErrorHandlingPolicy() {
@Override
public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) {
// Implement custom back-off logic here.
}
};
ExoPlayer player =
new ExoPlayer.Builder(context)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(context)
.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy))
.build();
~~~
{: .language-java}
The `LoadErrorInfo` argument contains more information about the failed load to
customize the logic based on the error type or the failed request.
### Customizing extractor flags ###
Extractor flags can be used to customize how individual formats are extracted
from progressive media. They can be set on the `DefaultExtractorsFactory` that's
provided to the `DefaultMediaSourceFactory`. The following example passes a flag
that enables index-based seeking for MP3 streams.
~~~
DefaultExtractorsFactory extractorsFactory =
new DefaultExtractorsFactory()
.setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING);
ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(context, extractorsFactory))
.build();
~~~
{: .language-java}
### Enabling constant bitrate seeking ###
For MP3, ADTS and AMR streams, you can enable approximate seeking using a
constant bitrate assumption with `FLAG_ENABLE_CONSTANT_BITRATE_SEEKING` flags.
These flags can be set for individual extractors using the individual
`DefaultExtractorsFactory.setXyzExtractorFlags` methods as described above. To
enable constant bitrate seeking for all extractors that support it, use
`DefaultExtractorsFactory.setConstantBitrateSeekingEnabled`.
~~~
DefaultExtractorsFactory extractorsFactory =
new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
~~~
{: .language-java}
The `ExtractorsFactory` can then be injected via `DefaultMediaSourceFactory` as
described for customizing extractor flags above.
### Enabling asynchronous buffer queueing ###
Asynchronous buffer queueing is an enhancement in ExoPlayer's rendering
pipeline, which operates `MediaCodec` instances in [asynchronous mode][] and
uses additional threads to schedule decoding and rendering of data. Enabling it
can reduce dropped frames and audio underruns.
Asynchronous buffer queueing is enabled by default on devices running Android 12
and above, and can be enabled manually from Android 6. Consider enabling the
feature for specific devices on which you observe dropped frames or audio
underruns, particularly when playing DRM protected or high frame rate content.
In the simplest case, you need to inject a `DefaultRenderersFactory` to the
player as follows:
~~~
DefaultRenderersFactory renderersFactory =
new DefaultRenderersFactory(context)
.forceEnableMediaCodecAsynchronousQueueing();
ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();
~~~
{: .language-java}
If you're instantiating renderers directly, pass a
`AsynchronousMediaCodecAdapter.Factory` to the `MediaCodecVideoRenderer` and
`MediaCodecAudioRenderer` constructors.
### Intercepting method calls with `ForwardingPlayer` ###
You can customize some of the behavior of a `Player` instance by wrapping it in
a subclass of `ForwardingPlayer` and overriding methods in order to do any of
the following:
* Access parameters before passing them to the delegate `Player`.
* Access the return value from the delegate `Player` before returning it.
* Re-implement the method completely.
When overriding `ForwardingPlayer` methods it's important to ensure the
implementation remains self-consistent and compliant with the `Player`
interface, especially when dealing with methods that are intended to have
identical or related behavior. For example:
* If you want to override every 'play' operation, you need to override both
`ForwardingPlayer.play` and `ForwardingPlayer.setPlayWhenReady`, because a
caller will expect the behavior of these methods to be identical when
`playWhenReady = true`.
* If you want to change the seek-forward increment you need to override both
`ForwardingPlayer.seekForward` to perform a seek with your customized
increment, and `ForwardingPlayer.getSeekForwardIncrement` in order to report
the correct customized increment back to the caller.
* If you want to control what `Player.Commands` are advertised by a player
instance, you must override `Player.getAvailableCommands()`,
`Player.isCommandAvailable()` and also listen to the
`Player.Listener.onAvailableCommandsChanged()` callback to get notified of
changes coming from the underlying player.
## MediaSource customization ##
The examples above inject customized components for use during playback of all
`MediaItem`s that are passed to the player. Where fine-grained customization is
required, it's also possible to inject customized components into individual
`MediaSource` instances, which can be passed directly to the player. The example
below shows how to customize a `ProgressiveMediaSource` to use a custom
`DataSource.Factory`, `ExtractorsFactory` and `LoadErrorHandlingPolicy`:
~~~
ProgressiveMediaSource mediaSource =
new ProgressiveMediaSource.Factory(
customDataSourceFactory, customExtractorsFactory)
.setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
.createMediaSource(MediaItem.fromUri(streamUri));
~~~
{: .language-java}
## Creating custom components ##
The library provides default implementations of the components listed at the top
of this page for common use cases. An `ExoPlayer` can use these components, but
may also be built to use custom implementations if non-standard behaviors are
required. Some use cases for custom implementations are:
* `Renderer` – You may want to implement a custom `Renderer` to handle a
media type not supported by the default implementations provided by the
library.
* `TrackSelector` – Implementing a custom `TrackSelector` allows an app
developer to change the way in which tracks exposed by a `MediaSource` are
selected for consumption by each of the available `Renderer`s.
* `LoadControl` – Implementing a custom `LoadControl` allows an app
developer to change the player's buffering policy.
* `Extractor` – If you need to support a container format not currently
supported by the library, consider implementing a custom `Extractor` class.
* `MediaSource` – Implementing a custom `MediaSource` class may be
appropriate if you wish to obtain media samples to feed to renderers in a
custom way, or if you wish to implement custom `MediaSource` compositing
behavior.
* `MediaSource.Factory` – Implementing a custom `MediaSource.Factory`
allows an application to customize the way in which `MediaSource`s are created
from `MediaItem`s.
* `DataSource` – ExoPlayer’s upstream package already contains a number of
`DataSource` implementations for different use cases. You may want to
implement you own `DataSource` class to load data in another way, such as over
a custom protocol, using a custom HTTP stack, or from a custom persistent
cache.
When building custom components, we recommend the following:
* If a custom component needs to report events back to the app, we recommend
that you do so using the same model as existing ExoPlayer components, for
example using `EventDispatcher` classes or passing a `Handler` together with
a listener to the constructor of the component.
* We recommended that custom components use the same model as existing ExoPlayer
components to allow reconfiguration by the app during playback. To do this,
custom components should implement `PlayerMessage.Target` and receive
configuration changes in the `handleMessage` method. Application code should
pass configuration changes by calling ExoPlayer’s `createMessage` method,
configuring the message, and sending it to the component using
`PlayerMessage.send`. Sending messages to be delivered on the playback thread
ensures that they are executed in order with any other operations being
performed on the player.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/customization
[customizing the network stack used by ExoPlayer]: {{ site.baseurl }}/network-stacks.html
[LoadErrorHandlingPolicy]: {{ site.exo_sdk }}/upstream/LoadErrorHandlingPolicy.html
[media source based playlist API]: {{ site.baseurl }}/media-sources.html#media-source-based-playlist-api
[asynchronous mode]: https://developer.android.com/reference/android/media/MediaCodec#asynchronous-processing-using-buffers
---
title: DASH
permalink: /dash.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/dash
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
{% include_relative _page_fragments/supported-formats-dash.md %}
## Using MediaItem ##
To play a DASH stream, you need to depend on the DASH module.
~~~
implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X'
~~~
{: .language-gradle}
You can then create a `MediaItem` for a DASH MPD URI and pass it to the player.
~~~
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media item to be played.
player.setMediaItem(MediaItem.fromUri(dashUri));
// Prepare the player.
player.prepare();
~~~
{: .language-java}
If your URI doesn't end with `.mpd`, you can pass `MimeTypes.APPLICATION_MPD`
to `setMimeType` of `MediaItem.Builder` to explicitly indicate the type of the
content.
ExoPlayer will automatically adapt between representations defined in the
manifest, taking into account both available bandwidth and device capabilities.
## Using DashMediaSource ##
For more customization options, you can create a `DashMediaSource` and pass it
directly to the player instead of a `MediaItem`.
~~~
// Create a data source factory.
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
// Create a DASH media source pointing to a DASH manifest uri.
MediaSource mediaSource =
new DashMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(dashUri));
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media source to be played.
player.setMediaSource(mediaSource);
// Prepare the player.
player.prepare();
~~~
{: .language-java}
## Accessing the manifest ##
You can retrieve the current manifest by calling `Player.getCurrentManifest`.
For DASH you should cast the returned object to `DashManifest`. The
`onTimelineChanged` callback of `Player.Listener` is also called whenever
the manifest is loaded. This will happen once for a on-demand content, and
possibly many times for live content. The code snippet below shows how an app
can do something whenever the manifest is loaded.
~~~
player.addListener(
new Player.Listener() {
@Override
public void onTimelineChanged(
Timeline timeline, @Player.TimelineChangeReason int reason) {
Object manifest = player.getCurrentManifest();
if (manifest != null) {
DashManifest dashManifest = (DashManifest) manifest;
// Do something with the manifest.
}
}
});
~~~
{: .language-java}
## Customizing playback ##
ExoPlayer provides multiple ways for you to tailor playback experience to your
app's needs. See the [Customization page][] for examples.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/dash
[Customization page]: {{ site.baseurl }}/customization.html
---
title: Debug logging
permalink: /debug-logging.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/debug-logging
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
By default ExoPlayer only logs errors. To log player events, the `EventLogger`
class can be used. The additional logging it provides can be helpful for
understanding what the player is doing, as well as for debugging playback
issues. `EventLogger` implements `AnalyticsListener`, so registering an instance
with an `ExoPlayer` is easy:
```
player.addAnalyticsListener(new EventLogger());
```
{: .language-java}
The easiest way to observe the log is using Android Studio's [logcat tab][]. You
can select your app as debuggable process by the package name (
`com.google.android.exoplayer2.demo` if using the demo app) and tell the logcat
tab to log only for that app by selecting 'show only selected application'. It's
possible to further filter the logging with the expression
`EventLogger|ExoPlayerImpl`, to get only logging from `EventLogger` and the
player itself.
An alternative to using Android Studio's logcat tab is to use the console. For
example:
~~~
adb logcat EventLogger:* ExoPlayerImpl:* *:s
~~~
{: .language-shell}
### Player information ###
The `ExoPlayerImpl` class delivers two important lines about the player version,
the device and OS the app is running on and the modules of ExoPlayer that have
been loaded:
```
ExoPlayerImpl: Release 2cd6e65 [ExoPlayerLib/2.12.0] [marlin, Pixel XL, Google, 26] [goog.exo.core, goog.exo.ui, goog.exo.dash]
ExoPlayerImpl: Init 2e5194c [ExoPlayerLib/2.12.0] [marlin, Pixel XL, Google, 26]
```
### Playback state ###
Player state changes are logged in lines like the ones below:
```
EventLogger: playWhenReady [eventTime=0.00, mediaPos=0.00, window=0, true, USER_REQUEST]
EventLogger: state [eventTime=0.01, mediaPos=0.00, window=0, BUFFERING]
EventLogger: state [eventTime=0.93, mediaPos=0.00, window=0, period=0, READY]
EventLogger: isPlaying [eventTime=0.93, mediaPos=0.00, window=0, period=0, true]
EventLogger: playWhenReady [eventTime=9.40, mediaPos=8.40, window=0, period=0, false, USER_REQUEST]
EventLogger: isPlaying [eventTime=9.40, mediaPos=8.40, window=0, period=0, false]
EventLogger: playWhenReady [eventTime=10.40, mediaPos=8.40, window=0, period=0, true, USER_REQUEST]
EventLogger: isPlaying [eventTime=10.40, mediaPos=8.40, window=0, period=0, true]
EventLogger: state [eventTime=20.40, mediaPos=18.40, window=0, period=0, ENDED]
EventLogger: isPlaying [eventTime=20.40, mediaPos=18.40, window=0, period=0, false]
```
In this example playback starts 0.93 seconds after the player is prepared. The
user pauses playback after 9.4 seconds, and resumes playback one second later at
10.4 seconds. Playback ends ten seconds later at 20.4 seconds. The common
elements within the square brackets are:
* `[eventTime=float]`: The wall clock time since player creation.
* `[mediaPos=float]`: The current playback position.
* `[window=int]`: The current window index.
* `[period=int]`: The current period in that window.
The final elements in each line indicate the value of the state being reported.
### Media tracks ###
Track information is logged when the available or selected tracks change. This
happens at least once at the start of playback. The example below shows track
logging for an adaptive stream:
```
EventLogger: tracks [eventTime=0.30, mediaPos=0.00, window=0, period=0,
EventLogger: group [
EventLogger: [X] Track:0, id=133, mimeType=video/avc, bitrate=261112, codecs=avc1.4d4015, res=426x240, fps=30.0, supported=YES
EventLogger: [X] Track:1, id=134, mimeType=video/avc, bitrate=671331, codecs=avc1.4d401e, res=640x360, fps=30.0, supported=YES
EventLogger: [X] Track:2, id=135, mimeType=video/avc, bitrate=1204535, codecs=avc1.4d401f, res=854x480, fps=30.0, supported=YES
EventLogger: [X] Track:3, id=160, mimeType=video/avc, bitrate=112329, codecs=avc1.4d400c, res=256x144, fps=30.0, supported=YES
EventLogger: [ ] Track:4, id=136, mimeType=video/avc, bitrate=2400538, codecs=avc1.4d401f, res=1280x720, fps=30.0, supported=NO_EXCEEDS_CAPABILITIES
EventLogger: ]
EventLogger: group [
EventLogger: [ ] Track:0, id=139, mimeType=audio/mp4a-latm, bitrate=48582, codecs=mp4a.40.5, channels=2, sample_rate=22050, supported=YES
EventLogger: [X] Track:1, id=140, mimeType=audio/mp4a-latm, bitrate=127868, codecs=mp4a.40.2, channels=2, sample_rate=44100, supported=YES
EventLogger: ]
EventLogger: ]
```
In this example, the player has selected four of the five available video
tracks. The fifth video track is not selected because it exceeds the
capabilities of the device, as indicated by `supported=NO_EXCEEDS_CAPABILITIES`.
The player will adapt between the selected video tracks during playback. When
the player adapts from one track to another, it's logged in a line like the one
below:
```
EventLogger: downstreamFormat [eventTime=3.64, mediaPos=3.00, window=0, period=0, id=134, mimeType=video/avc, bitrate=671331, codecs=avc1.4d401e, res=640x360, fps=30.0]
```
This log line indicates that the player switched to the 640x360 resolution video
track three seconds into the media.
### Decoder selection ###
In most cases ExoPlayer renders media using a `MediaCodec` acquired from the
underlying platform. When a decoder is initialized, this is logged in lines like
the ones below:
```
EventLogger: videoDecoderInitialized [0.77, 0.00, window=0, period=0, video, OMX.qcom.video.decoder.avc]
EventLogger: audioDecoderInitialized [0.79, 0.00, window=0, period=0, audio, OMX.google.aac.decoder]
```
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/debug-logging
[logcat tab]: https://developer.android.com/studio/debug/am-logcat
---
title: Demo application
permalink: /demo-application.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/demo-application
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
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 application.
1. 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][]. If you haven't already done so, clone the project into a
local directory:
~~~
git clone https://github.com/google/ExoPlayer.git
~~~
{: .language-shell}
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):
{% include figure.html url="/images/demo-app-project.png" index="1" caption="The project in Android Studio" %}
## 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][] and ensure
that your Virtual Device uses a system image with an API level of at least 23.
{% include figure.html url="/images/demo-app-screenshots.png" index="2" caption="SampleChooserActivity and PlayerActivity" %}
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
~~~
{: .language-shell}
### Enabling extension 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.
1. In Android Studio's Build Variants view, set the build variant for the demo
module to `withDecoderExtensionsDebug` or `withDecoderExtensionsRelease` as
shown below.
1. Compile, install and run the `demo` configuration as normal.
{% include figure.html url="/images/demo-app-build-variants.png" index="3" caption="Selecting the demo withDecoderExtensionsDebug build variant" %}
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 sections below.
## 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. 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",
"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
]
~~~
{: .language-json}
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. 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",
"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
]
~~~
{: .language-json}
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
}
~~~
{: .language-json}
In the sample chooser activity, the overflow menu contains options for
specifying whether to prefer extension decoders.
### 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
~~~
{: .language-shell}
Clicking a `*.exolist.json` link (e.g., 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.
### 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
`com.google.android.exoplayer.demo.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 com.google.android.exoplayer.demo.action.VIEW \
-d https://yourdomain.com/sample.mp4
~~~
{: .language-shell}
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
`com.google.android.exoplayer.demo.action.VIEW_LIST`. The sample configuration
extras remain the same as for `com.google.android.exoplayer.demo.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 com.google.android.exoplayer.demo.action.VIEW_LIST \
--es uri_0 https://a.com/sample1.mp4 \
--es uri_1 https://b.com/sample2.fake_mpd \
--es extension_1 mpd
~~~
{: .language-shell}
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/demo-application
[IMA extension]: {{ site.release_v2 }}/extensions/ima
[GitHub project]: https://github.com/google/ExoPlayer
[Supported devices]: {{ site.baseurl }}/supported-devices.html
---
title: Design documents
permalink: /design-documents.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer
---
To get early feedback from developers, we publish design documents for larger
changes on this page. Feel free to comment on the documents that are still in
"request for comments" (RFC) status. Note that we do not typically update
documents once changes are implemented.
### RFC status ###
* There are no documents in RFC status at this time
### Archive ###
* [Add support for partially fragmented MP4s][] (July 2020)
* [Audio offload][] (April 2020)
* [Sniffing order optimization][] (April 2020)
* [Masking seek state changes][] (March 2020)
* [Frame-accurate pausing][] (January 2020)
* [Index seeking in MP3 streams][] (January 2020)
* [Low-latency live playback][] (September 2019)
* [Unwrapping Nested Metadata][] (September 2019)
* [Playlist API][] (July 2019)
* [Bandwidth estimation analysis][] (July 2019)
[Add support for partially fragmented MP4s]: https://docs.google.com/document/d/1NUheADYlqIVVPT8Ch5UbV8DJHLDoxMoOD_L8mvU8tTM
[Audio offload]: https://docs.google.com/document/d/1r6wi6OtJUaI1QU8QLrLJTZieQBFTN1fyBK4U_PoPp3g
[Sniffing order optimization]: https://docs.google.com/document/d/1w2mKaWMxfz2Ei8-LdxqbPs1VLe_oudB-eryXXw9OvQQ
[Masking seek state changes]: https://docs.google.com/document/d/1XeOduvYus9HfwXtOtoRC185T4PK-L4u7JRmNM46Ee4w
[Frame-accurate pausing]: https://docs.google.com/document/d/1xXGvIMAYDWN4BGUNqAplNN-T7rjrW_1EAVXpyCcAqUI
[Index seeking in MP3 streams]: https://docs.google.com/document/d/1ZtQsCFvi_LiwFqhHWy20dJ1XwHLOXE4BJ5SzXWJ9a9E
[Low-latency live playback]: https://docs.google.com/document/d/1z9qwuP7ff9sf3DZboXnhEF9hzW3Ng5rfJVqlGn8N38k
[Unwrapping Nested Metadata]: https://docs.google.com/document/d/1TS13CVmexaLG1C4TdD-4NkX-BCSr_76FaHVOPo6XP1E
[Playlist API]: https://docs.google.com/document/d/11h0S91KI5TB3NNZUtsCzg0S7r6nyTnF_tDZZAtmY93g
[Bandwidth estimation analysis]: https://docs.google.com/document/d/1e3jVkZ6nxNWgCqTNibqV8uJcKo8d597XVl3nJkY7P8c
---
title: Downloading media
permalink: /downloading-media.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/downloading-media
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
ExoPlayer provides functionality to download media for offline playback. In most
use cases it's desirable for downloads to continue even when your app is in the
background. For these use cases your app should subclass `DownloadService`, and
send commands to the service to add, remove and control the downloads. The
diagram below shows the main classes that are involved.
{% include figure.html url="/images/downloading.svg" index="1" caption="Classes
for downloading media. The arrow directions indicate the flow of data."
width="85%" %}
* `DownloadService`: Wraps a `DownloadManager` and forwards commands to it. The
service allows the `DownloadManager` to keep running even when the app is in
the background.
* `DownloadManager`: Manages multiple downloads, loading (and storing) their
states from (and to) a `DownloadIndex`, starting and stopping downloads based
on requirements such as network connectivity, and so on. To download the
content, the manager will typically read the data being downloaded from a
`HttpDataSource`, and write it into a `Cache`.
* `DownloadIndex`: Persists the states of the downloads.
## Creating a DownloadService ##
To create a `DownloadService`, you need to subclass it and implement its
abstract methods:
* `getDownloadManager()`: Returns the `DownloadManager` to be used.
* `getScheduler()`: Returns an optional `Scheduler`, which can restart the
service when requirements needed for pending downloads to progress are met.
ExoPlayer provides these implementations:
* `PlatformScheduler`, which uses [JobScheduler][] (Minimum API is 21). See
the [PlatformScheduler][] javadocs for app permission requirements.
* `WorkManagerScheduler`, which uses [WorkManager][].
* `getForegroundNotification()`: Returns a notification to be displayed when the
service is running in the foreground. You can use
`DownloadNotificationHelper.buildProgressNotification` to create a
notification in default style.
Finally, you need to define the service in your `AndroidManifest.xml` file:
~~~
<service android:name="com.myapp.MyDownloadService"
android:exported="false">
<!-- This is needed for Scheduler -->
<intent-filter>
<action android:name="com.google.android.exoplayer.downloadService.action.RESTART"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
~~~
{: .language-xml}
See [`DemoDownloadService`][] and [`AndroidManifest.xml`][] in the ExoPlayer
demo app for a concrete example.
## Creating a DownloadManager ##
The following code snippet demonstrates how to instantiate a `DownloadManager`,
which can be returned by `getDownloadManager()` in your `DownloadService`:
~~~
// Note: This should be a singleton in your app.
databaseProvider = new StandaloneDatabaseProvider(context);
// A download cache should not evict media, so should use a NoopCacheEvictor.
downloadCache = new SimpleCache(
downloadDirectory,
new NoOpCacheEvictor(),
databaseProvider);
// Create a factory for reading the data from the network.
dataSourceFactory = new DefaultHttpDataSource.Factory();
// Choose an executor for downloading data. Using Runnable::run will cause each download task to
// download data on its own thread. Passing an executor that uses multiple threads will speed up
// download tasks that can be split into smaller parts for parallel execution. Applications that
// already have an executor for background downloads may wish to reuse their existing executor.
Executor downloadExecutor = Runnable::run;
// Create the download manager.
downloadManager = new DownloadManager(
context,
databaseProvider,
downloadCache,
dataSourceFactory,
downloadExecutor);
// Optionally, setters can be called to configure the download manager.
downloadManager.setRequirements(requirements);
downloadManager.setMaxParallelDownloads(3);
~~~
{: .language-java}
See [`DemoUtil`][] in the demo app for a concrete example.
## Adding a download ##
To add a download you need to create a `DownloadRequest` and send it to your
`DownloadService`. For adaptive streams `DownloadHelper` can be used to help
build a `DownloadRequest`, as described [further down this page][]. The example
below shows how to create a download request:
~~~
DownloadRequest downloadRequest =
new DownloadRequest.Builder(contentId, contentUri).build();
~~~
{: .language-java}
where `contentId` is a unique identifier for the content. In simple cases, the
`contentUri` can often be used as the `contentId`, however apps are free to use
whatever ID scheme best suits their use case. `DownloadRequest.Builder` also has
some optional setters. For example, `setKeySetId` and `setData` can be used to
set DRM and custom data that the app wishes to associate with the download,
respectively. The content's MIME type can also be specified using `setMimeType`,
as a hint for cases where the content type cannot be inferred from `contentUri`.
Once created, the request can be sent to the `DownloadService` to add the
download:
~~~
DownloadService.sendAddDownload(
context,
MyDownloadService.class,
downloadRequest,
/* foreground= */ false)
~~~
{: .language-java}
where `MyDownloadService` is the app's `DownloadService` subclass, and the
`foreground` parameter controls whether the service will be started in the
foreground. If your app is already in the foreground then the `foreground`
parameter should normally be set to `false`, since the `DownloadService` will
put itself in the foreground if it determines that it has work to do.
## Removing downloads ##
A download can be removed by sending a remove command to the `DownloadService`,
where `contentId` identifies the download to be removed:
~~~
DownloadService.sendRemoveDownload(
context,
MyDownloadService.class,
contentId,
/* foreground= */ false)
~~~
{: .language-java}
You can also remove all downloaded data with
`DownloadService.sendRemoveAllDownloads`.
## Starting and stopping downloads ##
A download will only progress if four conditions are met:
* The download doesn't have a stop reason.
* Downloads aren't paused.
* The requirements for downloads to progress are met. Requirements can specify
constraints on the allowed network types, as well as whether the device should
be idle or connected to a charger.
* The maximum number of parallel downloads is not exceeded.
All of these conditions can be controlled by sending commands to your
`DownloadService`.
#### Setting and clearing download stop reasons ####
It's possible to set a reason for one or all downloads being stopped:
~~~
// Set the stop reason for a single download.
DownloadService.sendSetStopReason(
context,
MyDownloadService.class,
contentId,
stopReason,
/* foreground= */ false);
// Clear the stop reason for a single download.
DownloadService.sendSetStopReason(
context,
MyDownloadService.class,
contentId,
Download.STOP_REASON_NONE,
/* foreground= */ false);
~~~
{: .language-java}
where `stopReason` can be any non-zero value (`Download.STOP_REASON_NONE = 0` is
a special value meaning that the download is not stopped). Apps that have
multiple reasons for stopping downloads can use different values to keep track
of why each download is stopped. Setting and clearing the stop reason for all
downloads works the same way as setting and clearing the stop reason for a
single download, except that `contentId` should be set to `null`.
Setting a stop reason does not remove a download. The partial download will be
retained, and clearing the stop reason will cause the download to continue.
{:.info}
When a download has a non-zero stop reason, it will be in the
`Download.STATE_STOPPED` state. Stop reasons are persisted in the
`DownloadIndex`, and so are retained if the application process is killed and
later restarted.
#### Pausing and resuming all downloads ####
All downloads can be paused and resumed as follows:
~~~
// Pause all downloads.
DownloadService.sendPauseDownloads(
context,
MyDownloadService.class,
/* foreground= */ false);
// Resume all downloads.
DownloadService.sendResumeDownloads(
context,
MyDownloadService.class,
/* foreground= */ false);
~~~
{: .language-java}
When downloads are paused, they will be in the `Download.STATE_QUEUED` state.
Unlike [setting stop reasons][], this approach does not persist any state
changes. It only affects the runtime state of the `DownloadManager`.
#### Setting the requirements for downloads to progress ####
[`Requirements`][] can be used to specify constraints that must be met for
downloads to proceed. The requirements can be set by calling
`DownloadManager.setRequirements()` when creating the `DownloadManager`, as in
the example [above][]. They can also be changed dynamically by sending a command
to the `DownloadService`:
~~~
// Set the download requirements.
DownloadService.sendSetRequirements(
context,
MyDownloadService.class,
requirements,
/* foreground= */ false);
~~~
{: .language-java}
When a download cannot proceed because the requirements are not met, it
will be in the `Download.STATE_QUEUED` state. You can query the not met
requirements with `DownloadManager.getNotMetRequirements()`.
#### Setting the maximum number of parallel downloads ####
The maximum number of parallel downloads can be set by calling
`DownloadManager.setMaxParallelDownloads()`. This would normally be done when
creating the `DownloadManager`, as in the example [above][].
When a download cannot proceed because the maximum number of parallel downloads
are already in progress, it will be in the `Download.STATE_QUEUED` state.
## Querying downloads ##
The `DownloadIndex` of a `DownloadManager` can be queried for the state of all
downloads, including those that have completed or failed. The `DownloadIndex`
can be obtained by calling `DownloadManager.getDownloadIndex()`. A cursor that
iterates over all downloads can then be obtained by calling
`DownloadIndex.getDownloads()`. Alternatively, the state of a single download
can be queried by calling `DownloadIndex.getDownload()`.
`DownloadManager` also provides `DownloadManager.getCurrentDownloads()`, which
returns the state of current (i.e. not completed or failed) downloads only. This
method is useful for updating notifications and other UI components that display
the progress and status of current downloads.
## Listening to downloads ##
You can add a listener to `DownloadManager` to be informed when current
downloads change state:
~~~
downloadManager.addListener(
new DownloadManager.Listener() {
// Override methods of interest here.
});
~~~
{: .language-java}
See `DownloadManagerListener` in the demo app's [`DownloadTracker`][] class for
a concrete example.
Download progress updates do not trigger calls on `DownloadManager.Listener`. To
update a UI component that shows download progress, you should periodically
query the `DownloadManager` at your desired update rate. [`DownloadService`][]
contains an example of this, which periodically updates the service foreground
notification.
{:.info}
## Playing downloaded content ##
Playing downloaded content is similar to playing online content, except that
data is read from the download `Cache` instead of over the network.
It's important that you do not try and read files directly from the download
directory. Instead, use ExoPlayer library classes as described below.
{:.info}
To play downloaded content, create a `CacheDataSource.Factory` using the same
`Cache` instance that was used for downloading, and inject it into
`DefaultMediaSourceFactory` when building the player:
~~~
// Create a read-only cache data source factory using the download cache.
DataSource.Factory cacheDataSourceFactory =
new CacheDataSource.Factory()
.setCache(downloadCache)
.setUpstreamDataSourceFactory(httpDataSourceFactory)
.setCacheWriteDataSinkFactory(null); // Disable writing.
ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(context)
.setDataSourceFactory(cacheDataSourceFactory))
.build();
~~~
{: .language-java}
If the same player instance will also be used to play non-downloaded content
then the `CacheDataSource.Factory` should be configured as read-only to avoid
downloading that content as well during playback.
Once the player has been configured with the `CacheDataSource.Factory`, it will
have access to the downloaded content for playback. Playing a download is then
as simple as passing the corresponding `MediaItem` to the player. A `MediaItem`
can be obtained from a `Download` using `Download.request.toMediaItem`, or
directly from a `DownloadRequest` using `DownloadRequest.toMediaItem`.
### MediaSource configuration ###
The example above makes the download cache available for playback of all
`MediaItem`s. It's also possible to make the download cache available for
individual `MediaSource` instances, which can be passed directly to the player:
~~~
ProgressiveMediaSource mediaSource =
new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(MediaItem.fromUri(contentUri));
player.setMediaSource(mediaSource);
player.prepare();
~~~
{: .language-java}
## Downloading and playing adaptive streams ##
Adaptive streams (e.g. DASH, SmoothStreaming and HLS) normally contain multiple
media tracks. There are often multiple tracks that contain the same content in
different qualities (e.g. SD, HD and 4K video tracks). There may also be
multiple tracks of the same type containing different content (e.g. multiple
audio tracks in different languages).
For streaming playbacks, a track selector can be used to choose which of the
tracks are played. Similarly, for downloading, a `DownloadHelper` can be used to
choose which of the tracks are downloaded. Typical usage of a `DownloadHelper`
follows these steps:
1. Build a `DownloadHelper` using one of the `DownloadHelper.forMediaItem`
methods. Prepare the helper and wait for the callback.
~~~
DownloadHelper downloadHelper =
DownloadHelper.forMediaItem(
context,
MediaItem.fromUri(contentUri),
new DefaultRenderersFactory(context),
dataSourceFactory);
downloadHelper.prepare(myCallback);
~~~
{: .language-java}
1. Optionally, inspect the default selected tracks using `getMappedTrackInfo`
and `getTrackSelections`, and make adjustments using `clearTrackSelections`,
`replaceTrackSelections` and `addTrackSelection`.
1. Create a `DownloadRequest` for the selected tracks by calling
`getDownloadRequest`. The request can be passed to your `DownloadService` to
add the download, as described above.
1. Release the helper using `release()`.
Playback of downloaded adaptive content requires configuring the player and
passing the corresponding `MediaItem`, as described above.
When building the `MediaItem`, `MediaItem.localConfiguration.streamKeys` must be
set to match those in the `DownloadRequest` so that the player only tries to
play the subset of tracks that have been downloaded. Using
`Download.request.toMediaItem` and `DownloadRequest.toMediaItem` to build the
`MediaItem` will take care of this for you.
If you see data being requested from the network when trying to play downloaded
adaptive content, the most likely cause is that the player is trying to adapt to
a track that was not downloaded. Ensure you've set the stream keys correctly.
{:.info}
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/downloading-media
[JobScheduler]: {{ site.android_sdk }}/android/app/job/JobScheduler
[PlatformScheduler]: {{ site.exo_sdk }}/scheduler/PlatformScheduler.html
[WorkManager]: https://developer.android.com/topic/libraries/architecture/workmanager/
[`DemoDownloadService`]: {{ site.release_v2 }}/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java
[`AndroidManifest.xml`]: {{ site.release_v2 }}/demos/main/src/main/AndroidManifest.xml
[`DemoUtil`]: {{ site.release_v2 }}/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.java
[`DownloadTracker`]: {{ site.release_v2 }}/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
[`DownloadService`]: {{ site.release_v2 }}/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java
[`Requirements`]: {{ site.exo_sdk }}/scheduler/Requirements.html
[further down this page]: #downloading-and-playing-adaptive-streams
[above]: #creating-a-downloadmanager
[setting stop reasons]: #setting-and-clearing-download-stop-reasons
---
title: Digital rights management
permalink: /drm.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/drm
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
ExoPlayer uses Android's [`MediaDrm`][] API to support DRM protected playbacks.
The minimum Android versions required for different supported DRM schemes, along
with the streaming formats for which they're supported, are:
| DRM scheme | Android version number | Android API level | Supported formats |
|---------|:------------:|:------------:|:---------------------|
| Widevine "cenc" | 4.4 | 19 | DASH, HLS (FMP4 only) |
| Widevine "cbcs" | 7.1 | 25 | DASH, HLS (FMP4 only) |
| ClearKey "cenc" | 5.0 | 21 | DASH |
| PlayReady SL2000 "cenc" | AndroidTV | AndroidTV | DASH, SmoothStreaming, HLS (FMP4 only) |
In order to play DRM protected content with ExoPlayer, the UUID of the DRM
system and the license server URI should be specified
[when building a media item]({{ site.baseurl }}/media-items.html#protected-content).
The player will then use these properties to build a default implementation of
`DrmSessionManager`, called `DefaultDrmSessionManager`, that's suitable for most
use cases. For some use cases additional DRM properties may be necessary, as
outlined in the sections below.
### Key rotation ###
To play streams with rotating keys, pass `true` to
`MediaItem.DrmConfiguration.Builder.setMultiSession` when building the media
item.
### Multi-key content ###
Multi-key content consists of multiple streams, where some streams use different
keys than others. Multi-key content can be played in one of two ways, depending
on how the license server is configured.
##### Case 1: License server responds with all keys for the content #####
In this case, the license server is configured so that when it receives a
request for one key, it responds with all keys for the content. This case is
handled by ExoPlayer without the need for any special configuration. Adaptation
between streams (e.g. SD and HD video) is seamless even if they use different
keys.
Where possible, we recommend configuring your license server to behave in this
way. It's the most efficient and robust way to support playback of multikey
content, because it doesn't require the client to make multiple license requests
to access the different streams.
##### Case 2: License server responds with requested key only #####
In this case, the license server is configured to respond with only the key
specified in the request. Multi-key content can be played with this license
server configuration by passing `true` to
`MediaItem.DrmConfiguration.Builder.setMultiSession` when building the media
item.
We do not recommend configuring your license server to behave in this way. It
requires extra license requests to play multi-key content, which is less
efficient and robust than the alternative described above.
### Offline keys ###
An offline key set can be loaded by passing the key set ID to
`MediaItem.DrmConfiguration.Builder.setKeySetId` when building the media item.
This allows playback using the keys stored in the offline key set with the
specified ID.
{% include known-issue-box.html issue-id="3872" description="Only one offline
key set can be specified per playback. As a result, offline playback of
multi-key content is currently supported only when the license server is
configured as described in Case 1 above." %}
### DRM sessions for clear content ###
Use of placeholder `DrmSessions` allows `ExoPlayer` to use the same decoders for
clear content as are used when playing encrypted content. When media contains
both clear and encrypted sections, you may want to use placeholder `DrmSessions`
to avoid re-creation of decoders when transitions between clear and encrypted
sections occur. Use of placeholder `DrmSessions` for audio and video tracks can
be enabled by passing `true` to
`MediaItem.DrmConfiguration.Builder.forceSessionsForAudioAndVideoTracks` when
building the media item.
### Using a custom DrmSessionManager ###
If an app wants to customise the `DrmSessionManager` used for playback, they can
implement a `DrmSessionManagerProvider` and pass this to the
`MediaSource.Factory` which is [used when building the player]. The provider can
choose whether to instantiate a new manager instance each time or not. To always
use the same instance:
~~~
DrmSessionManager customDrmSessionManager =
new CustomDrmSessionManager(/* ... */);
// Pass a drm session manager provider to the media source factory.
MediaSource.Factory mediaSourceFactory =
new DefaultMediaSourceFactory(context)
.setDrmSessionManagerProvider(mediaItem -> customDrmSessionManager);
~~~
{: .language-java}
### Improving playback performance ###
If you're experiencing video stuttering on a device running Android 6 to 11 when
playing DRM protected content, you can try [enabling asynchronous buffer
queueing].
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/drm
[main demo app]: {{ site.release_v2 }}/demos/main
[`MediaDrm`]: {{ site.android_sdk }}/android/media/MediaDrm.html
[used when building the player]: {{ site.baseurl }}/media-sources.html#customizing-media-source-creation
[enabling asynchronous buffer queueing]: {{ site.baseurl }}/customization.html#enabling-asynchronous-buffer-queueing
---
title: Glossary
permalink: /glossary.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/glossary
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/glossary
## General - Media ##
###### ABR
Adaptive Bitrate. An ABR algorithm is an algorithm that selects between a number
of [tracks](#track) during playback, where each track presents the same media
but at different bitrates.
###### Adaptive streaming
In adaptive streaming, multiple [tracks](#track) are available that present the
same media at different bitrates. The selected track is chosen dynamically
during playback using an [ABR](#abr) algorithm.
###### Access unit
A data item within a media [container](#container). Generally refers to a small
piece of the compressed media bitstream that can be decoded and presented to the
user (a video picture or fragment of playable audio).
###### AV1
AOMedia Video 1 [codec](#codec).
For more information, see the
[Wikipedia page](https://en.wikipedia.org/wiki/AV1).
###### AVC
Advanced Video Coding, also known as the H.264 video [codec](#codec).
For more information, see the
[Wikipedia page](https://en.wikipedia.org/wiki/Advanced_Video_Coding).
###### Codec
This term is overloaded and has multiple meanings depending on the context. The
two following definitions are the most commonly used:
* Hardware or software component for encoding or decoding
[access units](#access-unit).
* Audio or video sample format specification.
###### Container
A media container format such as MP4 and Matroska. Such formats are called
container formats because they contain one or more [tracks](#track) of media,
where each track uses a particular [codec](#codec) (e.g. AAC audio and H.264
video in an MP4 file). Note that some media formats are both a container format
and a codec (e.g. MP3).
###### DASH
Dynamic [Adaptive Streaming](#adaptive-streaming) over HTTP. An industry driven
adaptive streaming protocol. It is defined by ISO/IEC 23009, which can be found
on the
[ISO Publicly Available Standards page](https://standards.iso.org/ittf/PubliclyAvailableStandards/).
###### DRM
Digital Rights Management.
For more information, see the
[Wikipedia page](https://en.wikipedia.org/wiki/Digital_rights_management).
###### Gapless playback
Process by which the end of a [track](#track) and/or the beginning of the next
track are skipped to avoid a silent gap between tracks.
For more information, see the
[Wikipedia page](https://en.wikipedia.org/wiki/Gapless_playback).
###### HEVC
High Efficiency Video Coding, also known as the H.265 video [codec](#codec).
###### HLS
HTTP Live Streaming. Apple’s [adaptive streaming](#adaptive-streaming) protocol.
For more information, see the
[Apple documentation](https://developer.apple.com/streaming/).
###### Manifest
A file that defines the structure and location of media in
[adaptive streaming](#adaptive-streaming) protocols. Examples include
[DASH](#dash) [MPD](#mpd) files, [HLS](#hls) multivariant playlist files and
[Smooth Streaming](#smooth-streaming) manifest files. Not to be confused with an
AndroidManifest XML file.
###### MPD
Media Presentation Description. The [manifest](#manifest) file format used in
the [DASH](#dash) [adaptive streaming](#adaptive-streaming) protocol.
###### PCM
Pulse-Code Modulation.
For more information, see the
[Wikipedia page](https://en.wikipedia.org/wiki/Pulse-code_modulation).
###### Smooth Streaming
Microsoft’s [adaptive streaming](#adaptive-streaming) protocol.
For more information, see the
[Microsoft documentation](https://www.iis.net/downloads/microsoft/smooth-streaming).
###### Track
A single audio, video, text or metadata stream within a piece of media. A media
file will often contain multiple tracks. For example a video track and an audio
track in a video file, or multiple audio tracks in different languages. In
[adaptive streaming](#adaptive-streaming) there are also multiple tracks
containing the same content at different bitrates.
## General - Android ##
###### AudioTrack
An Android API for playing audio.
For more information, see the
[Javadoc]({{ site.android_sdk }}/android/media/AudioTrack).
###### CDM
Content Decryption Module. A component in the Android platform responsible for
decrypting [DRM](#drm) protected content. CDMs are accessed via Android’s
[`MediaDrm`](#mediadrm) API.
For more information, see the
[Javadoc]({{ site.android_sdk }}/android/media/MediaDrm).
###### IMA
Interactive Media Ads. IMA is an SDK that makes it easy to integrate multimedia
ads into an app.
For more information, see the
[IMA documentation](https://developers.google.com/interactive-media-ads).
###### MediaCodec
An Android API for accessing media [codecs](#codec) (i.e. encoder and decoder
components) in the platform.
For more information, see the
[Javadoc]({{ site.android_sdk }}/android/media/MediaCodec).
###### MediaDrm
An Android API for accessing [CDMs](#cdm) in the platform.
For more information, see the
[Javadoc]({{ site.android_sdk }}/android/media/MediaDrm).
###### Audio offload
The ability to send compressed audio directly to a digital signal processor
(DSP) provided by the device. Audio offload functionality is useful for low
power audio playback.
For more information, see the
[Android interaction documentation](https://source.android.com/devices/tv/multimedia-tunneling).
###### Passthrough
The ability to send compressed audio directly over HDMI, without decoding it
first. This is for example used to play 5.1 surround sound on an Android TV.
For more information, see the
[Android interaction documentation](https://source.android.com/devices/tv/multimedia-tunneling).
###### Surface
See the [Javadoc]({{ site.android_sdk }}/android/view/Surface)
and the
[Android graphics documentation](https://source.android.com/devices/graphics/arch-sh).
###### Tunneling
Process by which the Android framework receives compressed video and either
compressed or [PCM](#pcm) audio data and assumes the responsibility for
decoding, synchronizing and rendering it, taking over some tasks usually handled
by the application. Tunneling may improve audio-to-video (AV) synchronization,
may smooth video playback and can reduce the load on the application processor.
It is mostly used on Android TVs.
For more information, see the
[Android interaction documentation](https://source.android.com/devices/tv/multimedia-tunneling)
and the
[ExoPlayer article](https://medium.com/google-exoplayer/tunneled-video-playback-in-exoplayer-84f084a8094d).
## ExoPlayer ##
{% include figure.html url="/images/glossary-exoplayer-architecture.png" index="1" caption="ExoPlayer architecture overview" %}
{% include figure.html url="/images/glossary-rendering-architecture.png" index="1" caption="ExoPlayer rendering overview" %}
###### BandwidthMeter
Component that estimates the network bandwidth, for example by listening to data
transfers. In [adaptive streaming](#adaptive-streaming), bandwidth estimates can
be used to select between different bitrate [tracks](#track) during playback.
For more information, see the component
[Javadoc]({{ site.exo_sdk }}/upstream/BandwidthMeter.html).
###### DataSource
Component for requesting data (e.g. over HTTP, from a local file, etc).
For more information, see the component
[Javadoc]({{ site.exo_sdk }}/upstream/DataSource.html).
###### Extractor
Component that parses a media [container](#container) format, outputting
[track](#track) information and individual [access units](#access-unit)
belonging to each track suitable for consumption by a decoder.
For more information, see the component
[Javadoc]({{ site.exo_sdk }}/extractor/Extractor.html).
###### LoadControl
Component that decides when to start and stop loading, and when to start
playback.
For more information, see the component
[Javadoc]({{ site.exo_sdk }}/LoadControl.html).
###### MediaSource
Provides high-level information about the structure of media (as a
[`Timeline`](#timeline)) and creates [`MediaPeriod`](#mediaperiod) instances
(corresponding to periods of the `Timeline`) for playback.
For more information, see the component
[Javadoc]({{ site.exo_sdk }}/source/MediaSource.html).
###### MediaPeriod
Loads a single piece of media (e.g. audio file, ad, content interleaved between
two ads, etc.), and allows the loaded media to be read (typically by
[`Renderers`](#renderer)). The decisions about which [tracks](#track) within the
media are loaded and when loading starts and stops are made by the
[`TrackSelector`](#trackselector) and the [`LoadControl`](#loadcontrol)
respectively.
For more information, see the component
[Javadoc]({{ site.exo_sdk }}/source/MediaPeriod.html).
###### Renderer
Component that reads, decodes and renders media samples. [`Surface`](#surface)
and [`AudioTrack`](#audiotrack) are the standard Android platform components to
which video and audio data are rendered.
For more information, see the component
[Javadoc]({{ site.exo_sdk }}/Renderer.html).
###### Timeline
Represents the structure of media, from simple cases like a single media file
through to complex compositions of media such as playlists and streams with
inserted ads.
For more information, see the component
[Javadoc]({{ site.exo_sdk }}/Timeline.html).
###### TrackGroup
Group containing one or more representations of the same video, audio or text
content, normally at different bitrates for
[adaptive streaming](#adaptive-streaming).
For more information, see the component
[Javadoc]({{ site.exo_sdk }}/source/TrackGroup.html).
###### TrackSelection
A selection consisting of a static subset of [tracks](#track) from a
[`TrackGroup`](#trackgroup), and a possibly varying selected track from the
subset. For [adaptive streaming](#adaptive-streaming), the `TrackSelection` is
responsible for selecting the appropriate track whenever a new media chunk
starts being loaded.
For more information, see the component
[Javadoc]({{ site.exo_sdk }}/trackselection/TrackSelection.html).
###### TrackSelector
Selects [tracks](#track) for playback. Given track information for the
[`MediaPeriod`](#mediaperiod) to be played, along with the capabilities of the
player’s [`Renderers`](#renderer), a `TrackSelector` will generate a
[`TrackSelection`](#trackselection) for each `Renderer`.
For more information, see the component
[Javadoc]({{ site.exo_sdk }}/trackselection/TrackSelector.html).
---
title: Hello world!
redirect_from:
- /guide.html
- /guide-v1.html
- /getting-started.html
permalink: /hello-world.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/hello-world
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
Another way to get started is to work through
[the ExoPlayer codelab](https://codelabs.developers.google.com/codelabs/exoplayer-intro/).
{:.info}
For simple use cases, getting started with `ExoPlayer` consists of implementing
the following steps:
1. Add ExoPlayer as a dependency to your project.
1. Create an `ExoPlayer` instance.
1. Attach the player to a view (for video output and user input).
1. Prepare the player with a `MediaItem` to play.
1. Release the player when done.
These steps are described in more detail below. For a complete example, refer to
`PlayerActivity` in the [main demo app][].
## Adding ExoPlayer as a dependency ##
### Add ExoPlayer modules ###
The easiest way to get started using ExoPlayer is to add it as a gradle
dependency in the `build.gradle` file of your app module. The following will add
a dependency to the full library:
~~~
implementation 'com.google.android.exoplayer:exoplayer:2.X.X'
~~~
{: .language-gradle}
where `2.X.X` is your preferred version (the latest version can be found by
consulting the [release notes][]).
As an alternative to the full library, you can depend on only the library
modules that you actually need. For example the following will add dependencies
on the Core, DASH and UI library modules, as might be required for an app that
only plays DASH content:
~~~
implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X'
~~~
{: .language-gradle}
When depending on individual modules, they must all be the same version. You can
browse the list of available modules on the [Google Maven ExoPlayer page][]. The
full library includes all of the library modules prefixed with `exoplayer-`,
except for `exoplayer-transformer`.
In addition to library modules, ExoPlayer has extension modules that depend on
external libraries to provide additional functionality. Some extensions are
available from the Maven repository, whereas others must be built manually.
Browse the [extensions directory][] and their individual READMEs for details.
### Turn on Java 8 support ###
If not enabled already, you need to turn on Java 8 support in all `build.gradle`
files depending on ExoPlayer, by adding the following to the `android` section:
~~~
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
}
~~~
{: .language-gradle}
### Enable multidex ###
If your Gradle `minSdkVersion` is 20 or lower, you should
[enable multidex](https://developer.android.com/studio/build/multidex) in order
to prevent build errors.
## Creating the player ##
You can create an `ExoPlayer` instance using `ExoPlayer.Builder`, which provides
a range of customization options. The code below is the simplest example of
creating an instance.
~~~
ExoPlayer player = new ExoPlayer.Builder(context).build();
~~~
{: .language-java}
### A note on threading ###
ExoPlayer instances must be accessed from a single application thread. For the
vast majority of cases this should be the application's main thread. Using the
application's main thread is a requirement when using ExoPlayer's UI components
or the IMA extension.
The thread on which an ExoPlayer instance must be accessed can be explicitly
specified by passing a `Looper` when creating the player. If no `Looper` is
specified, then the `Looper` of the thread that the player is created on is
used, or if that thread does not have a `Looper`, the `Looper` of the
application's main thread is used. In all cases the `Looper` of the thread from
which the player must be accessed can be queried using
`Player.getApplicationLooper`.
If you see `IllegalStateException` being thrown with the message "Player is
accessed on the wrong thread", then some code in your app is accessing an
`ExoPlayer` instance on the wrong thread (the exception's stack trace shows you
where).
{:.info}
For more information about ExoPlayer's threading model, see the
["Threading model" section of the ExoPlayer Javadoc][].
## Attaching the player to a view ##
The ExoPlayer library provides a range of pre-built UI components for media
playback. These include `StyledPlayerView`, which encapsulates a
`StyledPlayerControlView`, a `SubtitleView`, and a `Surface` onto which video is
rendered. A `StyledPlayerView` can be included in your application's layout xml.
Binding the player to the view is as simple as:
~~~
// Bind the player to the view.
playerView.setPlayer(player);
~~~
{: .language-java}
You can also use `StyledPlayerControlView` as a standalone component, which is
useful for audio only use cases.
Use of ExoPlayer's pre-built UI components is optional. For video applications
that implement their own UI, the target `SurfaceView`, `TextureView`,
`SurfaceHolder` or `Surface` can be set using `ExoPlayer`'s
`setVideoSurfaceView`, `setVideoTextureView`, `setVideoSurfaceHolder` and
`setVideoSurface` methods respectively. `ExoPlayer`'s `addTextOutput` method can
be used to receive captions that should be rendered during playback.
## Populating the playlist and preparing the player ##
In ExoPlayer every piece of media is represented by a `MediaItem`. To play a
piece of media you need to build a corresponding `MediaItem`, add it to the
player, prepare the player, and call `play` to start the playback:
~~~
// Build the media item.
MediaItem mediaItem = MediaItem.fromUri(videoUri);
// Set the media item to be played.
player.setMediaItem(mediaItem);
// Prepare the player.
player.prepare();
// Start the playback.
player.play();
~~~
{: .language-java}
ExoPlayer supports playlists directly, so it's possible to prepare the player
with multiple media items to be played one after the other:
~~~
// Build the media items.
MediaItem firstItem = MediaItem.fromUri(firstVideoUri);
MediaItem secondItem = MediaItem.fromUri(secondVideoUri);
// Add the media items to be played.
player.addMediaItem(firstItem);
player.addMediaItem(secondItem);
// Prepare the player.
player.prepare();
// Start the playback.
player.play();
~~~
{: .language-java}
The playlist can be updated during playback without the need to prepare the
player again. Read more about populating and manipulating the playlist on the
[Playlists page][]. Read more about the different options available when
building media items, such as clipping and attaching subtitle files, on the
[Media items page][].
Prior to ExoPlayer 2.12, the player needed to be given a `MediaSource` rather
than media items. From 2.12 onwards, the player converts media items to the
`MediaSource` instances that it needs internally. Read more about this process
and how it can be customized on the [Media sources page][]. It's still possible
to provide `MediaSource` instances directly to the player using
`ExoPlayer.setMediaSource(s)` and `ExoPlayer.addMediaSource(s)`.
{:.info}
## Controlling the player ##
Once the player has been prepared, playback can be controlled by calling methods
on the player. Some of the most commonly used methods are listed below.
* `play` and `pause` start and pause playback.
* `seekTo` allows seeking within the media.
* `hasPrevious`, `hasNext`, `previous` and `next` allow navigating through the
playlist.
* `setRepeatMode` controls if and how media is looped.
* `setShuffleModeEnabled` controls playlist shuffling.
* `setPlaybackParameters` adjusts playback speed and audio pitch.
If the player is bound to a `StyledPlayerView` or `StyledPlayerControlView`,
then user interaction with these components will cause corresponding methods on
the player to be invoked.
## Releasing the player ##
It's important to release the player when it's no longer needed, so as to free
up limited resources such as video decoders for use by other applications. This
can be done by calling `ExoPlayer.release`.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/hello-world
[main demo app]: {{ site.release_v2 }}/demos/main/
[extensions directory]: {{ site.release_v2 }}/extensions/
[release notes]: {{ site.release_v2 }}/RELEASENOTES.md
["Threading model" section of the ExoPlayer Javadoc]: {{ site.exo_sdk }}/ExoPlayer.html
[Playlists page]: {{ site.baseurl }}/playlists.html
[Media items page]: {{ site.baseurl }}/media-items.html
[Media sources page]: {{ site.baseurl }}/media-sources.html
[Google Maven ExoPlayer page]: https://maven.google.com/web/index.html#com.google.android.exoplayer
---
title: HLS
permalink: /hls.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/hls
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
{% include_relative _page_fragments/supported-formats-hls.md %}
## Using MediaItem ##
To play an HLS stream, you need to depend on the HLS module.
~~~
implementation 'com.google.android.exoplayer:exoplayer-hls:2.X.X'
~~~
{: .language-gradle}
You can then create a `MediaItem` for an HLS playlist URI and pass it to the
player.
~~~
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media item to be played.
player.setMediaItem(MediaItem.fromUri(hlsUri));
// Prepare the player.
player.prepare();
~~~
{: .language-java}
If your URI doesn't end with `.m3u8`, you can pass `MimeTypes.APPLICATION_M3U8`
to `setMimeType` of `MediaItem.Builder` to explicitly indicate the type of the
content.
The URI of the media item may point to either a media playlist or a multivariant
playlist. If the URI points to a multivariant playlist that declares multiple
`#EXT-X-STREAM-INF` tags then ExoPlayer will automatically adapt between
variants, taking into account both available bandwidth and device capabilities.
## Using HlsMediaSource ##
For more customization options, you can create a `HlsMediaSource` and pass it
directly to the player instead of a `MediaItem`.
~~~
// Create a data source factory.
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
// Create a HLS media source pointing to a playlist uri.
HlsMediaSource hlsMediaSource =
new HlsMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(hlsUri));
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media source to be played.
player.setMediaSource(hlsMediaSource);
// Prepare the player.
player.prepare();
~~~
{: .language-java}
## Accessing the manifest ##
You can retrieve the current manifest by calling `Player.getCurrentManifest`.
For HLS you should cast the returned object to `HlsManifest`. The
`onTimelineChanged` callback of `Player.Listener` is also called whenever
the manifest is loaded. This will happen once for a on-demand content, and
possibly many times for live content. The code snippet below shows how an app
can do something whenever the manifest is loaded.
~~~
player.addListener(
new Player.Listener() {
@Override
public void onTimelineChanged(
Timeline timeline, @Player.TimelineChangeReason int reason) {
Object manifest = player.getCurrentManifest();
if (manifest != null) {
HlsManifest hlsManifest = (HlsManifest) manifest;
// Do something with the manifest.
}
}
});
~~~
{: .language-java}
## Customizing playback ##
ExoPlayer provides multiple ways for you to tailor playback experience to your
app's needs. See the [Customization page][] for examples.
### Disabling chunkless preparation ###
By default, ExoPlayer will use chunkless preparation. This means that ExoPlayer
will only use the information in the multivariant playlist to prepare the
stream, which works if the `#EXT-X-STREAM-INF` tags contain the `CODECS`
attribute.
You may need to disable this feature if your media segments contain muxed
closed-caption tracks that are not declared in the multivariant playlist with a
`#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS` tag. Otherwise, these closed-caption tracks
won't be detected and played. You can disable chunkless preparation in the
`HlsMediaSource.Factory` as shown in the following snippet. Note that this
will increase start up time as ExoPlayer needs to download a media segment to
discover these additional tracks and it is preferable to declare the
closed-caption tracks in the multivariant playlist instead.
~~~
HlsMediaSource hlsMediaSource =
new HlsMediaSource.Factory(dataSourceFactory)
.setAllowChunklessPreparation(false)
.createMediaSource(MediaItem.fromUri(hlsUri));
~~~
{: .language-java}
## Creating high quality HLS content ##
In order to get the most out of ExoPlayer, there are certain guidelines you can
follow to improve your HLS content. Read our [Medium post about HLS playback in
ExoPlayer][] for a full explanation. The main points are:
* Use precise segment durations.
* Use a continuous media stream; avoid changes in the media structure across
segments.
* Use the `#EXT-X-INDEPENDENT-SEGMENTS` tag.
* Prefer demuxed streams, as opposed to files that include both video and audio.
* Include all information you can in the Multivariant Playlist.
The following guidelines apply specifically for live streams:
* Use the `#EXT-X-PROGRAM-DATE-TIME` tag.
* Use the `#EXT-X-DISCONTINUITY-SEQUENCE` tag.
* Provide a long live window. One minute or more is great.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/hls
[HlsMediaSource]: {{ site.exo_sdk }}/source/hls/HlsMediaSource.html
[HTTP Live Streaming]: https://tools.ietf.org/html/rfc8216
[Customization page]: {{ site.baseurl }}/customization.html
[Medium post about HLS playback in ExoPlayer]: https://medium.com/google-exoplayer/hls-playback-in-exoplayer-a33959a47be7
---
layout: article
permalink: /index.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
ExoPlayer is an application level media player for Android. It provides an
alternative to Android’s MediaPlayer API for playing audio and video both
locally and over the Internet. ExoPlayer supports features not currently
supported by Android’s MediaPlayer API, including DASH and SmoothStreaming
adaptive playbacks. Unlike the MediaPlayer API, ExoPlayer is easy to customize
and extend, and can be updated through Play Store application updates.
This website provides a wealth of information to help you get started. In
addition, you can:
* Learn how to add ExoPlayer to your app by [completing the codelab][] or
reading the [Hello world][] documentation.
* Read news, hints and tips on our [developer blog][].
* Read the latest [release notes][].
* Browse the library [Javadoc][].
* Browse the source code for the [latest release][] and current [tip of tree][].
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer
[completing the codelab]: https://codelabs.developers.google.com/codelabs/exoplayer-intro/
[Hello world]: {{ site.baseurl }}/hello-world.html
[developer blog]: https://medium.com/google-exoplayer
[release notes]: {{ site.release_v2 }}/RELEASENOTES.md
[Javadoc]: {{ site.baseurl }}/doc/reference
[latest release]: {{ site.release_v2 }}
[tip of tree]: https://github.com/google/ExoPlayer/tree/dev-v2
---
title: Player events
permalink: /listening-to-player-events.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/listening-to-player-events
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
## Listening to playback events ##
Events such as changes in state and playback errors are reported to registered
[`Player.Listener`][] instances. Registering a listener to receive such
events is easy:
~~~
// Add a listener to receive events from the player.
player.addListener(listener);
~~~
{: .language-java }
`Player.Listener` has empty default methods, so you only need to implement
the methods you're interested in. See the [Javadoc][] for a full description of
the methods and when they're called. Some of the most important methods are
described in more detail below.
Listeners have the choice between implementing individual event callbacks or a
generic `onEvents` callback that's called after one or more events occur
together. See [`Individual callbacks vs onEvents`][] for an explanation of which
should be preferred for different use cases.
### Playback state changes ###
Changes in player state can be received by implementing
`onPlaybackStateChanged(@State int state)` in a registered
`Player.Listener`. The player can be in one of four playback states:
* `Player.STATE_IDLE`: This is the initial state, the state when the player is
stopped, and when playback failed. The player will hold only limited resources
in this state.
* `Player.STATE_BUFFERING`: The player is not able to immediately play from its
current position. This mostly happens because more data needs to be loaded.
* `Player.STATE_READY`: The player is able to immediately play from its current
position.
* `Player.STATE_ENDED`: The player finished playing all media.
In addition to these states, the player has a `playWhenReady` flag to indicate
the user intention to play. Changes in this flag can be received by implementing
`onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason)`.
A player is playing (i.e., its position is advancing and media is being
presented to the user) when it's in the `Player.STATE_READY` state,
`playWhenReady` is `true`, and playback is not suppressed for a reason returned
by `Player.getPlaybackSuppressionReason`. Rather than having to check these
properties individually, `Player.isPlaying` can be called. Changes to this
state can be received by implementing `onIsPlayingChanged(boolean isPlaying)`:
~~~
@Override
public void onIsPlayingChanged(boolean isPlaying) {
if (isPlaying) {
// Active playback.
} else {
// Not playing because playback is paused, ended, suppressed, or the player
// is buffering, stopped or failed. Check player.getPlayWhenReady,
// player.getPlaybackState, player.getPlaybackSuppressionReason and
// player.getPlaybackError for details.
}
}
~~~
{: .language-java }
### Playback errors ###
Errors that cause playback to fail can be received by implementing
`onPlayerError(PlaybackException error)` in a registered
`Player.Listener`. When a failure occurs, this method will be called
immediately before the playback state transitions to `Player.STATE_IDLE`.
Failed or stopped playbacks can be retried by calling `ExoPlayer.prepare`.
Note that some [`Player`][] implementations pass instances of subclasses of
`PlaybackException` to provide additional information about the failure. For
example, [`ExoPlayer`][] passes [`ExoPlaybackException`][] which has `type`,
`rendererIndex` and other ExoPlayer-specific fields.
The following example shows how to detect when a playback has failed due to an
HTTP networking issue:
~~~
@Override
public void onPlayerError(PlaybackException error) {
Throwable cause = error.getCause();
if (cause instanceof HttpDataSourceException) {
// An HTTP error occurred.
HttpDataSourceException httpError = (HttpDataSourceException) cause;
// It's possible to find out more about the error both by casting and by
// querying the cause.
if (httpError instanceof HttpDataSource.InvalidResponseCodeException) {
// Cast to InvalidResponseCodeException and retrieve the response code,
// message and headers.
} else {
// Try calling httpError.getCause() to retrieve the underlying cause,
// although note that it may be null.
}
}
}
~~~
{: .language-java }
### Playlist transitions ###
Whenever the player changes to a new media item in the playlist
`onMediaItemTransition(MediaItem mediaItem,
@MediaItemTransitionReason int reason)` is called on registered
`Player.Listener`s. The reason indicates whether this was an automatic
transition, a seek (for example after calling `player.next()`), a repetition of
the same item, or caused by a playlist change (e.g., if the currently playing
item is removed).
### Seeking ###
Calling `Player.seekTo` methods results in a series of callbacks to registered
`Player.Listener` instances:
1. `onPositionDiscontinuity` with `reason=DISCONTINUITY_REASON_SEEK`. This is
the direct result of calling `Player.seekTo`.
1. `onPlaybackStateChanged` with any immediate state change related to the seek.
Note that there might not be such a change.
If you are using an `AnalyticsListener`, there will be an additional event
`onSeekStarted` just before `onPositionDiscontinuity`, to indicate the playback
position immediately before the seek started.
### Individual callbacks vs onEvents ###
Listeners can choose between implementing individual callbacks like
`onIsPlayingChanged(boolean isPlaying)`, and the generic
`onEvents(Player player, Events events)` callback. The generic callback provides
access to the `Player` object and specifies the set of `events` that occurred
together. It's always called after the callbacks that correspond to the
individual events.
~~~
@Override
public void onEvents(Player player, Events events) {
if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)
|| events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) {
uiModule.updateUi(player);
}
}
~~~
{: .language-java }
Individual events should be preferred in the following cases:
* The listener is interested in the reasons for changes. For example the reasons
provided for `onPlayWhenReadyChanged` or `onMediaItemTransition`.
* The listener only acts on the new values provided through callback parameters,
or triggers something else that doesn't depend on the callback parameters.
* The listener implementation prefers a clear readable indication of what
triggered the event in the method name.
* The listener reports to an analytics system that needs to know about all
individual events and state changes.
The generic `onEvents(Player player, Events events)` should be preferred in the
following cases:
* The listener wants to trigger the same logic for multiple events. For
example updating a UI for both `onPlaybackStateChanged` and
`onPlayWhenReadyChanged`.
* The listener needs access the `Player` object to trigger further events,
for example seeking after a media item transition.
* The listener intends to use multiple state values that are reported
through separate callbacks together, or in combination with `Player` getter
methods. For example, using `Player.getCurrentWindowIndex()` with the
`Timeline` provided in `onTimelineChanged` is only safe from within the
`onEvents` callback.
* The listener is interested in whether events logically occurred together. For
example `onPlaybackStateChanged` to `STATE_BUFFERING` because of a media item
transition.
In some cases, listeners may need to combine the individual callbacks with the
generic `onEvents` callback, for example to record media item change reasons
with `onMediaItemTransition`, but only act once all state changes can be used
together in `onEvents`.
## Using AnalyticsListener ##
When using `ExoPlayer`, an `AnalyticsListener` can be registered with the player
by calling `addAnalyticsListener`. `AnalyticsListener` implementations are able
to listen to detailed events that may be useful for analytics and logging
purposes. Please refer to the [analytics page][] for more details.
### Using EventLogger ###
`EventLogger` is an `AnalyticsListener` provided directly by the library for
logging purposes. It can be added to an `ExoPlayer` to enable useful
additional logging with a single line.
```
player.addAnalyticsListener(new EventLogger());
```
{: .language-java }
See the [debug logging page][] for more details.
## Firing events at specified playback positions ##
Some use cases require firing events at specified playback positions. This is
supported using `PlayerMessage`. A `PlayerMessage` can be created using
`ExoPlayer.createMessage`. The playback position at which it should be executed
can be set using `PlayerMessage.setPosition`. Messages are executed on the
playback thread by default, but this can be customized using
`PlayerMessage.setLooper`. `PlayerMessage.setDeleteAfterDelivery` can be used
to control whether the message will be executed every time the specified
playback position is encountered (this may happen multiple times due to seeking
and repeat modes), or just the first time. Once the `PlayerMessage` is
configured, it can be scheduled using `PlayerMessage.send`.
~~~
player
.createMessage(
(messageType, payload) -> {
// Do something at the specified playback position.
})
.setLooper(Looper.getMainLooper())
.setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120_000)
.setPayload(customPayloadData)
.setDeleteAfterDelivery(false)
.send();
~~~
{: .language-java }
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/listening-to-player-events
[`Player.Listener`]: {{ site.exo_sdk }}/Player.Listener.html
[Javadoc]: {{ site.exo_sdk }}/Player.Listener.html
[`Individual callbacks vs onEvents`]: #individual-callbacks-vs-onevents
[`Player`]: {{ site.exo_sdk }}/Player.html
[`ExoPlayer`]: {{ site.exo_sdk }}/ExoPlayer.html
[`ExoPlaybackException`]: {{ site.exo_sdk }}/ExoPlaybackException.html
[log output]: event-logger.html
[`Parameters`]: {{ site.exo_sdk }}/trackselection/DefaultTrackSelector.Parameters.html
[`ParametersBuilder`]: {{ site.exo_sdk }}/trackselection/DefaultTrackSelector.ParametersBuilder.html
[`DefaultTrackSelector`]: {{ site.exo_sdk }}/trackselection/DefaultTrackSelector.html
[ExoPlayer demo app]: {{ site.baseurl }}/demo-application.html#playing-your-own-content
[latest ExoPlayer version]: https://github.com/google/ExoPlayer/releases
[analytics page]: {{ site.baseurl }}/analytics.html
[debug logging page]: {{ site.baseurl }}/debug-logging.html
---
title: Live streaming
permalink: /live-streaming.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/live-streaming
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
ExoPlayer plays most adaptive live streams out-of-the-box without any special
configuration. See the [Supported Formats page][] for more details.
Adaptive live streams offer a window of available media that is updated in
regular intervals to move with the current real-time. That means the playback
position will always be somewhere in this window, in most cases close to the
current real-time at which the stream is being produced. The difference between
the current real-time and the playback position is called the *live offset*.
Unlike adaptive live streams, progressive live streams do not have a live window
and can only be played at one position. The documentation on this page is only
relevant to adaptive live streams.
{:.info}
## Detecting and monitoring live playbacks ##
Every time a live window is updated, registered `Player.Listener` instances
will receive an `onTimelineChanged` event. You can retrieve details about the
current live playback by querying various `Player` and `Timeline.Window`
methods, as listed below and shown in the following figure.
{% include figure.html url="/images/live-window.png" index="1" caption="Live window" %}
* `Player.isCurrentWindowLive` indicates whether the currently playing media
item is a live stream. This value is still true even if the live stream has
ended.
* `Player.isCurrentWindowDynamic` indicates whether the currently playing media
item is still being updated. This is usually true for live streams that are
not yet ended. Note that this flag is also true for non-live streams in some
cases.
* `Player.getCurrentLiveOffset` returns the offset between the current real
time and the playback position (if available).
* `Player.getDuration` returns the length of the current live window.
* `Player.getCurrentPosition` returns the playback position relative to the
start of the live window.
* `Player.getCurrentMediaItem` returns the current media item, where
`MediaItem.liveConfiguration` contains app-provided overrides for the target
live offset and live offset adjustment parameters.
* `Player.getCurrentTimeline` returns the current media structure in a
`Timeline`. The current `Timeline.Window` can be retrieved from the `Timeline`
using `Player.getCurrentWindowIndex` and `Timeline.getWindow`. Within the
`Window`:
* `Window.liveConfiguration` contains the target live offset and live offset
adjustment parameters. These values are based on information in the media
and any app-provided overrides set in `MediaItem.liveConfiguration`.
* `Window.windowStartTimeMs` is the time since the Unix Epoch at which the
live window starts.
* `Window.getCurrentUnixTimeMs` is the time since the Unix Epoch of the
current real-time. This value may be corrected by a known clock difference
between the server and the client.
* `Window.getDefaultPositionMs` is the position in the live window at which
the player will start playback by default.
## Seeking in live streams ##
You can seek to anywhere within the live window using `Player.seekTo`. The seek
position passed is relative to the start of the live window. For example,
`seekTo(0)` will seek to the start of the live window. The player will try to
keep the same live offset as the seeked-to position after a seek.
The live window also has a default position at which playback is supposed to
start. This position is usually somewhere close to the live edge. You can seek
to the default position by calling `Player.seekToDefaultPosition`.
## Live playback UI ##
ExoPlayer's [default UI components][] show the duration of the live window and
the current playback position within it. This means the position will appear to
jump backwards each time the live window is updated. If you need different
behavior, for example showing the Unix time or the current live offset, you can
fork `StyledPlayerControlView` and modify it to suit your needs.
There is a [pending feature request (#2213)][] for ExoPlayer's default UI
components to support additional modes when playing live streams.
{:.info}
## Configuring live playback parameters ##
ExoPlayer uses some parameters to control the offset of the playback position
from the live edge, and the range of playback speeds that can be used to
adjust this offset.
ExoPlayer gets values for these parameters from three places, in descending
order of priority (the first value found is used):
* Per `MediaItem` values passed to `MediaItem.Builder.setLiveConfiguration`.
* Global default values set on `DefaultMediaSourceFactory`.
* Values read directly from the media.
~~~
// Global settings.
ExoPlayer player =
new ExoPlayer.Builder(context)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(context).setLiveTargetOffsetMs(5000))
.build();
// Per MediaItem settings.
MediaItem mediaItem =
new MediaItem.Builder()
.setUri(mediaUri)
.setLiveConfiguration(
new MediaItem.LiveConfiguration.Builder()
.setMaxPlaybackSpeed(1.02f)
.build())
.build();
player.setMediaItem(mediaItem);
~~~
{: .language-java}
Available configuration values are:
* `targetOffsetMs`: The target live offset. The player will attempt to get
close to this live offset during playback if possible.
* `minOffsetMs`: The minimum allowed live offset. Even when adjusting the
offset to current network conditions, the player will not attempt to get below
this offset during playback.
* `maxOffsetMs`: The maximum allowed live offset. Even when adjusting the
offset to current network conditions, the player will not attempt to get above
this offset during playback.
* `minPlaybackSpeed`: The minimum playback speed the player can use to fall back
when trying to reach the target live offset.
* `maxPlaybackSpeed`: The maximum playback speed the player can use to catch up
when trying to reach the target live offset.
## Playback speed adjustment ##
When playing a low-latency live stream ExoPlayer adjusts the live offset by
slightly changing the playback speed. The player will try to match the target
live offset provided by the media or the app, but will also try to react to
changing network conditions. For example, if rebuffers occur during playback,
the player will slow down playback slightly to move further away from the live
edge. If the network then becomes stable enough to support playing closer to the
live edge again, the player will speed up playback to move back toward the
target live offset.
If automatic playback speed adjustment is not desired, it can be disabled by
setting `minPlaybackSpeed` and `maxPlaybackSpeed` properties to `1.0f`.
Similarly, it can be enabled for non-low-latency live streams by setting these
explicitly to values other than `1.0f`. See
[the configuration section above](#configuring-live-playback-parameters) for
more details on how these properties can be set.
### Customizing the playback speed adjustment algorithm ###
If speed adjustment is enabled, a `LivePlaybackSpeedControl` defines what
adjustments are made. It's possible to implement a custom
`LivePlaybackSpeedControl`, or to customize the default implementation, which is
`DefaultLivePlaybackSpeedControl`. In both cases an instance can be set when
building the player:
~~~
ExoPlayer player =
new ExoPlayer.Builder(context)
.setLivePlaybackSpeedControl(
new DefaultLivePlaybackSpeedControl.Builder()
.setFallbackMaxPlaybackSpeed(1.04f)
.build())
.build();
~~~
{: .language-java}
Relevant customization parameters of `DefaultLivePlaybackSpeedControl` are:
* `fallbackMinPlaybackSpeed` and `fallbackMaxPlaybackSpeed`: The minimum and
maximum playback speeds that can be used for adjustment if neither the media
nor the app-provided `MediaItem` define limits.
* `proportionalControlFactor`: Controls how smooth the speed adjustment is. A
high value makes adjustments more sudden and reactive, but also more likely to
be audible. A smaller value results in a smoother transition between speeds,
at the cost of being slower.
* `targetLiveOffsetIncrementOnRebufferMs`: This value is added to the target
live offset whenever a rebuffer occurs, in order to proceed more cautiously.
This feature can be disabled by setting the value to 0.
* `minPossibleLiveOffsetSmoothingFactor`: An exponential smoothing factor that
is used to track the minimum possible live offset based on the currently
buffered media. A value very close to 1 means that the estimation is more
cautious and may take longer to adjust to improved network conditions, whereas
a lower value means the estimation will adjust faster at a higher risk of
running into rebuffers.
## BehindLiveWindowException and ERROR_CODE_BEHIND_LIVE_WINDOW ##
The playback position may fall behind the live window, for example if the player
is paused or buffering for a long enough period of time. If this happens then
playback will fail and an exception with error code
`ERROR_CODE_BEHIND_LIVE_WINDOW` will be reported via
`Player.Listener.onPlayerError`. Application code may wish to handle such
errors by resuming playback at the default position. The [PlayerActivity][] of
the demo app exemplifies this approach.
~~~
@Override
public void onPlayerError(PlaybackException error) {
if (eror.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) {
// Re-initialize player at the current live window default position.
player.seekToDefaultPosition();
player.prepare();
} else {
// Handle other errors.
}
}
~~~
{: .language-java}
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/live-streaming
[Supported Formats page]: {{ site.baseurl }}/supported-formats.html
[default UI components]: {{ site.baseurl }}/ui-components.html
[pending feature request (#2213)]: https://github.com/google/ExoPlayer/issues/2213
[PlayerActivity]: {{ site.release_v2 }}/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
---
title: Media items
permalink: /media-items.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/media-items
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
The [playlist API][] is based on `MediaItem`s, which can be conveniently built
using `MediaItem.Builder`. Inside the player, media items are converted into
playable `MediaSource`s by a `MediaSource.Factory`. Without
[custom configuration]({{ site.baseurl }}/media-sources.html#customizing-media-source-creation),
this conversion is carried out by a `DefaultMediaSourceFactory`, which is
capable of building complex media sources corresponding to the properties of the
media item. Some of the properties that can be set on media items are outlined
below.
## Simple media items ##
A media item consisting only of the stream URI can be built with the `fromUri`
convenience method:
~~~
MediaItem mediaItem = MediaItem.fromUri(videoUri);
~~~
{: .language-java}
For all other cases a `MediaItem.Builder` can be used. In the example below, a
media item is built with an ID and some attached metadata:
~~~
MediaItem mediaItem = new MediaItem.Builder()
.setUri(videoUri)
.setMediaId(mediaId)
.setTag(metadata)
.build();
~~~
{: .language-java}
Attaching metadata can be useful for
[updating your app's UI]({{ site.baseurl }}/playlists.html#detecting-when-playback-transitions-to-another-media-item)
when playlist transitions occur.
## Handling non-standard file extensions
The ExoPlayer library provides adaptive media sources for DASH, HLS and
SmoothStreaming. If the URI of such an adaptive media item ends with a standard
file extension, the corresponding media source is automatically created. If the
URI has a non-standard extension or no extension at all, then the MIME type can
be set explicitly to indicate the type of the media item:
~~~
// Use the explicit MIME type to build an HLS media item.
MediaItem mediaItem = new MediaItem.Builder()
.setUri(hlsUri)
.setMimeType(MimeTypes.APPLICATION_M3U8)
.build();
~~~
{: .language-java}
For progressive media streams a MIME type is not required.
## Protected content ##
For protected content, the media item's DRM properties should be set:
~~~
MediaItem mediaItem = new MediaItem.Builder()
.setUri(videoUri)
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri(licenseUri)
.setMultiSession(true)
.setLicenseRequestHeaders(httpRequestHeaders)
.build())
.build();
~~~
{: .language-java}
This example builds a media item for Widevine protected content. Inside the
player, `DefaultMediaSourceFactory` will pass these properties to a
`DrmSessionManagerProvider` to obtain a `DrmSessionManager`, which is then
injected into the created `MediaSource`. DRM behaviour can be
[further customized]({{ site.baseurl }}/drm.html#using-a-custom-drmsessionmanager)
to your needs.
## Sideloading subtitle tracks ##
To sideload subtitle tracks, `MediaItem.Subtitle` instances can be added when
when building a media item:
~~~
MediaItem.SubtitleConfiguration subtitle =
new MediaItem.SubtitleConfiguration.Builder(subtitleUri)
.setMimeType(mimeType) // The correct MIME type (required).
.setLanguage(language) // The subtitle language (optional).
.setSelectionFlags(selectionFlags) // Selection flags for the track (optional).
.build();
MediaItem mediaItem =
new MediaItem.Builder()
.setUri(videoUri)
.setSubtitleConfigurations(ImmutableList.of(subtitle))
.build();
~~~
{: .language-java}
Internally, `DefaultMediaSourceFactory` will use a `MergingMediaSource` to
combine the content media source with a `SingleSampleMediaSource` for each
subtitle track. `DefaultMediaSourceFactory` does not support sideloading
subtitles for multi-period DASH.
## Clipping a media stream ##
It's possible to clip the content referred to by a media item by setting custom
start and end positions:
~~~
MediaItem mediaItem =
new MediaItem.Builder()
.setUri(videoUri)
.setClippingConfiguration(
new ClippingConfiguration.Builder()
.setStartPositionMs(startPositionMs)
.setEndPositionMs(endPositionMs)
.build())
.build();
~~~
{: .language-java}
Internally, `DefaultMediaSourceFactory` will use a `ClippingMediaSource` to wrap
the content media source. There are additional clipping properties. See the
[`MediaItem.Builder` Javadoc][] for more details.
When clipping the start of a video file, try to align the start position with a
keyframe if possible. If the start position is not aligned with a keyframe then
the player will need to decode and discard data from the previous keyframe up to
the start position before playback can begin. This will introduce a short delay
at the start of playback, including when the player transitions to playing a
clipped media source as part of a playlist or due to looping.
{:.info}
## Ad insertion ##
To insert ads, a media item's ad tag URI property should be set:
~~~
MediaItem mediaItem = new MediaItem.Builder()
.setUri(videoUri)
.setAdsConfiguration(
new MediaItem.AdsConfiguration.Builder(adTagUri).build())
.build();
~~~
{: .language-java}
Internally, `DefaultMediaSourceFactory` will wrap the content media source in an
`AdsMediaSource` to insert ads as defined by the ad tag. For this to work, the
the player also needs to have its `DefaultMediaSourceFactory`
[configured accordingly]({{ site.baseurl }}/ad-insertion.html#declarative-ad-support).
{% include media3-known-issue-box.html issue-id="185" description="Subtitles, clipping and ad insertion are only supported if you use `DefaultMediaSourceFactory`." %}
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/media-items
[playlist API]: {{ site.baseurl }}/playlists.html
[`MediaItem.Builder` Javadoc]: {{ site.exo_sdk }}/MediaItem.Builder.html
---
title: Media sources
redirect_from:
- /mediasource.html
permalink: /media-sources.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/media-sources
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
In ExoPlayer every piece of media is represented by a `MediaItem`. However
internally, the player needs `MediaSource` instances to play the content. The
player creates these from media items using a `MediaSource.Factory`.
By default the player uses a `DefaultMediaSourceFactory`, which can create
instances of the following content `MediaSource` implementations:
* `DashMediaSource` for [DASH][].
* `SsMediaSource` for [SmoothStreaming][].
* `HlsMediaSource` for [HLS][].
* `ProgressiveMediaSource` for [regular media files][].
* `RtspMediaSource` for [RTSP][].
`DefaultMediaSourceFactory` can also create more complex media sources depending
on the properties of the corresponding media items. This is described in more
detail on the [Media items page]({{ site.baseurl }}/media-items.html).
For apps that need media source setups that are not supported by the
default configuration of the player, there are several options for
customization.
## Customizing media source creation ##
When building the player, a `MediaSource.Factory` can be injected. For example,
if an app wants to insert ads and use a `CacheDataSource.Factory` to support
caching, an instance of `DefaultMediaSourceFactory` can be configured to match
these requirements and injected during player construction:
~~~
MediaSource.Factory mediaSourceFactory =
new DefaultMediaSourceFactory(context)
.setDataSourceFactory(cacheDataSourceFactory)
.setLocalAdInsertionComponents(
adsLoaderProvider, /* adViewProvider= */ playerView);
ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(mediaSourceFactory)
.build();
~~~
{: .language-java}
The
[`DefaultMediaSourceFactory` JavaDoc]({{ site.exo_sdk }}/source/DefaultMediaSourceFactory.html)
describes the available options in more detail.
It's also possible to inject a custom `MediaSource.Factory` implementation, for
example to support creation of a custom media source type. The factory's
`createMediaSource(MediaItem)` will be called to create a media source for each
media item that is
[added to the playlist]({{ site.baseurl }}/playlists.html).
## Media source based playlist API ##
The [`ExoPlayer`] interface defines additional playlist methods that accept
media sources rather than media items. This makes it possible to bypass the
player's internal `MediaSource.Factory` and pass media source instances to the
player directly:
~~~
// Set a list of media sources as initial playlist.
exoPlayer.setMediaSources(listOfMediaSources);
// Add a single media source.
exoPlayer.addMediaSource(anotherMediaSource);
// Can be combined with the media item API.
exoPlayer.addMediaItem(/* index= */ 3, MediaItem.fromUri(videoUri));
exoPlayer.prepare();
exoPlayer.play();
~~~
{: .language-java}
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/media-sources
[DASH]: {{ site.baseurl }}/dash.html
[SmoothStreaming]: {{ site.baseurl }}/smoothstreaming.html
[HLS]: {{ site.baseurl }}/hls.html
[RTSP]: {{ site.baseurl }}/rtsp.html
[regular media files]: {{ site.baseurl }}/progressive.html
[`ExoPlayer`]: {{ site.exo_sdk }}/ExoPlayer.html
---
title: Network stacks
permalink: /network-stacks.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/network-stacks
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
ExoPlayer is commonly used for streaming media over the internet. It supports
multiple network stacks for making its underlying network requests. Your choice
of network stack can have a significant impact on streaming performance.
This page outlines how to configure ExoPlayer to use your network stack of
choice, lists the available options, and provides some guidance on how to choose
a network stack for your application.
## Configuring ExoPlayer to use a specific network stack ##
ExoPlayer loads data through `DataSource` components, which it obtains from
`DataSource.Factory` instances that are injected from application code.
If your application only needs to play http(s) content, selecting a network
stack is as simple as updating any `DataSource.Factory` instances that your
application injects to be instances of the `HttpDataSource.Factory`
that corresponds to the network stack you wish to use. If your application also
needs to play non-http(s) content such as local files, use
~~~
new DefaultDataSource.Factory(
...
/* baseDataSourceFactory= */ new PreferredHttpDataSource.Factory(...));
~~~
{: .language-java}
where `PreferredHttpDataSource.Factory` is the factory corresponding to your
preferred network stack. The `DefaultDataSource.Factory` layer adds in support
for non-http(s) sources such as local files.
The example below shows how to build an `ExoPlayer` that will use the Cronet
network stack and also support playback of non-http(s) content.
~~~
// Given a CronetEngine and Executor, build a CronetDataSource.Factory.
CronetDataSource.Factory cronetDataSourceFactory =
new CronetDataSource.Factory(cronetEngine, executor);
// Wrap the CronetDataSource.Factory in a DefaultDataSource.Factory, which adds
// in support for requesting data from other sources (e.g., files, resources,
// etc).
DefaultDataSource.Factory dataSourceFactory =
new DefaultDataSource.Factory(
context,
/* baseDataSourceFactory= */ cronetDataSourceFactory);
// Inject the DefaultDataSource.Factory when creating the player.
ExoPlayer player =
new ExoPlayer.Builder(context)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(context)
.setDataSourceFactory(dataSourceFactory))
.build();
~~~
{: .language-java}
## Supported network stacks ##
ExoPlayer provides direct support for Cronet, OkHttp and Android's built-in
network stack. It can also be extended to support any other network stack that
works on Android.
### Cronet ###
[Cronet](https://developer.android.com/guide/topics/connectivity/cronet) is the
Chromium network stack made available to Android apps as a library. It takes
advantage of multiple technologies that reduce the latency and increase the
throughput of the network requests that your app needs to work, including those
made by ExoPlayer. It natively supports the HTTP, HTTP/2, and HTTP/3 over QUIC
protocols. Cronet is used by some of the world's biggest streaming applications,
including YouTube.
ExoPlayer supports Cronet via its
[Cronet extension](https://github.com/google/ExoPlayer/tree/dev-v2/extensions/cronet).
Please see the extension's `README.md` for detailed instructions on how to use
it. Note that the Cronet extension is able to use three underlying Cronet
implementations:
1. **Google Play Services:** We recommend using this implementation in most
cases, and falling back to Android's built-in network stack
(i.e., `DefaultHttpDataSource`) if Google Play Services is not available.
1. **Cronet Embedded:** May be a good choice if a large percentage of your users
are in markets where Google Play Services is not widely available, or if you
want to control the exact version of the Cronet implementation being used. The
major disadvantage of Cronet Embedded is that it adds approximately 8MB to
your application.
1. **Cronet Fallback:** The fallback implementation of Cronet implements
Cronet's API as a wrapper around Android's built-in network stack. It should
not be used with ExoPlayer, since using Android's built-in network stack
directly (i.e., by using `DefaultHttpDataSource`) is more efficient.
### OkHttp ###
[OkHttp](https://square.github.io/okhttp/) is another modern network stack that
is widely used by many popular Android applications. It supports HTTP and
HTTP/2, but does not yet support HTTP/3 over QUIC.
ExoPlayer supports OkHttp via its
[OkHttp extension](https://github.com/google/ExoPlayer/tree/dev-v2/extensions/okhttp).
Please see the extension's `README.md` for detailed instructions on how to use
it. When using the OkHttp extension, the network stack is embedded within the
application. This is similar to Cronet Embedded, however OkHttp is significantly
smaller, adding under 1MB to your application.
### Android's built-in network stack ###
ExoPlayer supports use of Android's built-in network stack with
`DefaultHttpDataSource` and `DefaultHttpDataSource.Factory`, which are part of
the core ExoPlayer library.
The exact network stack implementation depends on the software running on the
underlying device. On most devices (as of 2021) only HTTP is supported (i.e.,
HTTP/2 and HTTP/3 over QUIC are not supported).
### Other network stacks ###
It's possible for applications to integrate other network stacks with ExoPlayer.
To do this, implement an `HttpDataSource` that wraps the network stack,
together with a corresponding `HttpDataSource.Factory`. ExoPlayer's Cronet and
OkHttp extensions are good examples of how to do this.
When integrating with a pure Java network stack, it's a good idea to implement a
`DataSourceContractTest` to check that your `HttpDataSource` implementation
behaves correctly. `OkHttpDataSourceContractTest` in the OkHttp extension is a
good example of how to do this.
## Choosing a network stack ##
The table below outlines the pros and cons of the network stacks supported by
ExoPlayer.
| Network stack | Protocols | APK size impact | Notes |
|:---|:--:|:--:|:---|
| Cronet (Google Play Services) | HTTP<br>HTTP/2<br>HTTP/3&nbsp;over&nbsp;QUIC | Small<br>(<100KB) | Requires Google Play Services. Cronet version updated automatically |
| Cronet (Embedded) | HTTP<br>HTTP/2<br>HTTP/3&nbsp;over&nbsp;QUIC | Large<br>(~8MB) | Cronet version controlled by app developer |
| Cronet (Fallback) | HTTP<br>(varies&nbsp;by&nbsp;device) | Small<br>(<100KB) | Not recommended for ExoPlayer |
| OkHttp | HTTP<br>HTTP/2 | Small<br>(<1MB) | Requires Kotlin runtime |
| Built-in network stack | HTTP<br>(varies&nbsp;by&nbsp;device) | None | Implementation varies by device |
The HTTP/2 and HTTP/3 over QUIC protocols can significantly improve media
streaming performance. In particular when streaming adaptive media distributed
via a content distribution network (CDN), there are cases for which use of these
protocols can allow CDNs to operate much more efficiently. For this reason,
Cronet's support for both HTTP/2 and HTTP/3 over QUIC (and OkHttp's support for
HTTP/2), is a major benefit compared to using Android's built-in network stack,
provided the servers on which the content is hosted also support these
protocols.
When considering media streaming in isolation, we recommend use of Cronet
provided by Google Play Services, falling back to `DefaultHttpDataSource` if
Google Play Services is unavailable. This recommendation strikes a good balance
between enabling use of HTTP/2 and HTTP/3 over QUIC on most devices, and
avoiding a significant increase in APK size. There are exceptions to this
recommendation. For cases where Google Play Services is likely to be unavailable
on a significant fraction of devices that will be running your application,
using Cronet Embedded or OkHttp may be more appropriate. Use of the built-in
network stack may be acceptable if APK size is a critical concern, or if media
streaming is only a minor part of your application's functionality.
Beyond just media, it's normally a good idea to choose a single network stack
for all of the networking performed by your application. This allows resources
(e.g., sockets) to be efficiently pooled and shared between ExoPlayer and other
application components.
To assist with resource sharing, it's recommended to use a single `CronetEngine`
or `OkHttpClient` instance throughout your application, when using Cronet or
OkHttp respectively.
{:.info}
Since your application will most likely need to perform networking not related
to media playback, your choice of network stack should ultimately factor in our
recommendations above for media streaming in isolation, the requirements of any
other components that perform networking, and their relative importance to your
application.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/network-stacks
---
title: OEM testing
permalink: /oems.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/oems
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
ExoPlayer is used by a large number of Android applications. As an OEM, it's
important to ensure that ExoPlayer works correctly both on new devices, and on
new platform builds for existing devices. This page describes compatibility
tests that we recommend running before shipping a device or platform OTA, and
some of the common failure modes encountered when running them.
## Running the tests ##
To run ExoPlayer's playback tests, first check out the latest release of
ExoPlayer from [GitHub][]. You can then run the tests from the command line or
Android Studio.
### Command line ###
From the root directory, build and install the playback tests:
~~~
./gradlew :playbacktests:installDebug
~~~
{: .language-shell}
Next, run the playback tests in the GTS package:
~~~
adb shell am instrument -w -r -e debug false \
-e package com.google.android.exoplayer2.playbacktests.gts \
com.google.android.exoplayer2.playbacktests.test/androidx.test.runner.AndroidJUnitRunner
~~~
{: .language-shell}
Test results appear in STDOUT.
### Android Studio ###
Open the ExoPlayer project, navigate to the `playbacktests` module, right click
on the `gts` folder and run the tests. Test results appear in Android Studio's
Run window.
## Common failure modes ##
Some of the common failure modes encountered when running ExoPlayer's playback
tests are described below, together with the likely root cause in each case. We
will add to this list as further failure modes are discovered.
### Unexpected video buffer presentation timestamp ###
Logcat will contain an error similar to:
~~~
Caused by: java.lang.IllegalStateException: Expected to dequeue video buffer
with presentation timestamp: 134766000. Instead got: 134733000 (Processed
buffers since last flush: 2242).
~~~
{: .language-shell}
This failure is most often caused by the video decoder under test incorrectly
discarding, inserting or re-ordering buffers. In the example above, the test
expected to dequeue a buffer with presentation timestamp `134766000` from
`MediaCodec.dequeueOutputBuffer`, but found that it dequeued a buffer with
presentation timestamp `134733000` instead. We recommend that you check the
decoder implementation when encountering this failure, in particular that it
correctly handles adaptive resolution switches without discarding any buffers.
### Too many dropped buffers ###
Logcat will contain an error similar to:
~~~
junit.framework.AssertionFailedError: Codec(DashTest:Video) was late decoding:
200 buffers. Limit: 25.
~~~
{: .language-shell}
This failure is a performance problem, where the video decoder under test was
late decoding a large number of buffers. In the example above, ExoPlayer dropped
200 buffers because they were late by the time they were dequeued, for a test
that imposes a limit of 25. The most obvious cause is that the video decoder
is too slow decoding buffers. If the failures only occur for the subset of tests
that play Widevine protected content, it's likely that the platform operations
for buffer decryption are too slow. We recommend checking the performance of
these components, and looking at whether any optimizations can be made to speed
them up.
### Native window could not be authenticated ###
Logcat will contain an error similar to:
~~~
SurfaceUtils: native window could not be authenticated
ExoPlayerImplInternal: Internal runtime error.
ExoPlayerImplInternal: android.media.MediaCodec$CodecException: Error 0xffffffff
~~~
{: .language-shell}
This failure is indicative of the platform failing to correctly set the secure
bit flag.
### Test timed out ###
Logcat will contain an error similar to:
~~~
AssertionFailedError: Test timed out after 300000 ms.
~~~
{: .language-shell}
This failure is most often caused by poor network connectivity during the test
run. If the device appears to have good network connectivity then it's possible
that the test is getting stuck calling into a platform component (e.g.
`MediaCodec`, `MediaDrm`, `AudioTrack` etc). Inspect the call stacks of the
threads in the test process to establish whether this is the case.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/oems
[GitHub]: https://github.com/google/ExoPlayer
---
title: Playlists
permalink: /playlists.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/playlists
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
The playlist API is defined by the `Player` interface, which is implemented by
all `ExoPlayer` implementations. It enables sequential playback of multiple
media items. The following example shows how to start playback of a playlist
containing two videos:
~~~
// Build the media items.
MediaItem firstItem = MediaItem.fromUri(firstVideoUri);
MediaItem secondItem = MediaItem.fromUri(secondVideoUri);
// Add the media items to be played.
player.addMediaItem(firstItem);
player.addMediaItem(secondItem);
// Prepare the player.
player.prepare();
// Start the playback.
player.play();
~~~
{: .language-java}
Transitions between items in a playlist are seamless. There's no requirement
that they're of the same format (e.g., it’s fine for a playlist to contain both
H264 and VP9 videos). They may even be of different types (e.g., it’s fine for a
playlist to contain both videos and audio only streams). It's allowed to use the
same `MediaItem` multiple times within a playlist.
## Modifying the playlist
It's possible to dynamically modify a playlist by adding, moving and removing
media items. This can be done both before and during playback by calling the
corresponding playlist API methods:
~~~
// Adds a media item at position 1 in the playlist.
player.addMediaItem(/* index= */ 1, MediaItem.fromUri(thirdUri));
// Moves the third media item from position 2 to the start of the playlist.
player.moveMediaItem(/* currentIndex= */ 2, /* newIndex= */ 0);
// Removes the first item from the playlist.
player.removeMediaItem(/* index= */ 0);
~~~
{: .language-java}
Replacing and clearing the entire playlist are also supported:
~~~
// Replaces the playlist with a new one.
List<MediaItem> newItems = ImmutableList.of(
MediaItem.fromUri(fourthUri),
MediaItem.fromUri(fifthUri));
player.setMediaItems(newItems, /* resetPosition= */ true);
// Clears the playlist. If prepared, the player transitions to the ended state.
player.clearMediaItems();
~~~
{: .language-java}
The player automatically handles modifications during playback in the correct
way. For example if the currently playing media item is moved, playback is not
interrupted and its new successor will be played upon completion. If the
currently playing `MediaItem` is removed, the player will automatically move to
playing the first remaining successor, or transition to the ended state if no
such successor exists.
## Querying the playlist
The playlist can be queried using `Player.getMediaItemCount` and
`Player.getMediaItemAt`. The currently playing media item can be queried
by calling `Player.getCurrentMediaItem`. There are also other convenience
methods like `Player.hasNextMediaItem` or `Player.getNextMediaItemIndex` to
simplify navigation in the playlist.
## Repeat modes
The player supports 3 repeat modes that can be set at any time with
`Player.setRepeatMode`:
* `Player.REPEAT_MODE_OFF`: The playlist isn't repeated and the player will
transition to `Player.STATE_ENDED` once the last item in the playlist has
been played.
* `Player.REPEAT_MODE_ONE`: The current item is repeated in an endless loop.
Methods like `Player.seekToNextMediaItem` will ignore this and seek to the
next item in the list, which will then be repeated in an endless loop.
* `Player.REPEAT_MODE_ALL`: The entire playlist is repeated in an endless loop.
## Shuffle mode
Shuffle mode can be enabled or disabled at any time with
`Player.setShuffleModeEnabled`. When in shuffle mode, the player will play the
playlist in a precomputed, randomized order. All items will be played once and
the shuffle mode can also be combined with `Player.REPEAT_MODE_ALL` to repeat
the same randomized order in an endless loop. When shuffle mode is turned off,
playback continues from the current item at its original position in the
playlist.
Note that the indices as returned by methods like
`Player.getCurrentMediaItemIndex` always refer to the original, unshuffled
order. Similarly, `Player.seekToNextMediaItem` will not play the item at
`player.getCurrentMediaItemIndex() + 1`, but the next item according to the
shuffle order. Inserting new items in the playlist or removing items will keep
the existing shuffled order unchanged as far as possible.
### Setting a custom shuffle order
By default the player supports shuffling by using the `DefaultShuffleOrder`.
This can be customized by providing a custom shuffle order implementation, or by
setting a custom order in the `DefaultShuffleOrder` constructor:
~~~
// Set a custom shuffle order for the 5 items currently in the playlist:
exoPlayer.setShuffleOrder(
new DefaultShuffleOrder(new int[] {3, 1, 0, 4, 2}, randomSeed));
// Enable shuffle mode.
exoPlayer.setShuffleModeEnabled(/* shuffleModeEnabled= */ true);
~~~
{: .language-java}
## Identifying playlist items
To identify playlist items, `MediaItem.mediaId` can be set when building the
item:
~~~
// Build a media item with a media ID.
MediaItem mediaItem =
new MediaItem.Builder().setUri(uri).setMediaId(mediaId).build();
~~~
{: .language-java}
If an app does not explicitly define a media ID for a media item, the string
representation of the URI is used.
## Associating app data with playlist items
In addition to an ID, each media item can also be configured with a custom tag,
which can be any app provided object. One use of custom tags is to attach
metadata to each media item:
~~~
// Build a media item with a custom tag.
MediaItem mediaItem =
new MediaItem.Builder().setUri(uri).setTag(metadata).build();
~~~
{: .language-java}
## Detecting when playback transitions to another media item
When playback transitions to another media item, or starts repeating the same
media item, `Listener.onMediaItemTransition(MediaItem,
@MediaItemTransitionReason)` is called. This callback receives the new media
item, along with a `@MediaItemTransitionReason` indicating why the transition
occurred. A common use case for `onMediaItemTransition` is to update the
application's UI for the new media item:
~~~
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {
updateUiForPlayingMediaItem(mediaItem);
}
~~~
{: .language-java}
If the metadata required to update the UI is attached to each media item using
custom tags, then an implementation might look like:
~~~
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {
@Nullable CustomMetadata metadata = null;
if (mediaItem != null && mediaItem.localConfiguration != null) {
metadata = (CustomMetadata) mediaItem.localConfiguration.tag;
}
updateUiForPlayingMediaItem(metadata);
}
~~~
{: .language-java}
## Detecting when the playlist changes
When a media item is added, removed or moved,
`Listener.onTimelineChanged(Timeline, @TimelineChangeReason)` is called
immediately with `TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED`. This callback is
called even when the player has not yet been prepared.
~~~
@Override
public void onTimelineChanged(
Timeline timeline, @TimelineChangeReason int reason) {
if (reason == TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) {
// Update the UI according to the modified playlist (add, move or remove).
updateUiForPlaylist(timeline);
}
}
~~~
{: .language-java}
When information such as the duration of a media item in the playlist becomes
available, the `Timeline` will be updated and `onTimelineChanged` will be called
with `TIMELINE_CHANGE_REASON_SOURCE_UPDATE`. Other reasons that can cause a
timeline update include:
* A manifest becoming available after preparing an adaptive media item.
* A manifest being updated periodically during playback of a live stream.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/playlists
---
title: Progressive
permalink: /progressive.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/progressive
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
{% include_relative _page_fragments/supported-formats-progressive.md %}
## Using MediaItem ##
To play a progressive stream, create a `MediaItem` with the media URI and pass
it to the player.
~~~
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media item to be played.
player.setMediaItem(MediaItem.fromUri(progressiveUri));
// Prepare the player.
player.prepare();
~~~
{: .language-java}
## Using ProgressiveMediaSource ##
For more customization options, you can create a `ProgressiveMediaSource` and
directly to the player instead of a `MediaItem`.
~~~
// Create a data source factory.
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
// Create a progressive media source pointing to a stream uri.
MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(progressiveUri));
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media source to be played.
player.setMediaSource(mediaSource);
// Prepare the player.
player.prepare();
~~~
{: .language-java}
## Customizing playback ##
ExoPlayer provides multiple ways for you to tailor playback experience to your
app's needs. See the [Customization page][] for examples.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/progressive
[Customization page]: {{ site.baseurl }}/customization.html
---
title: Pros and cons
permalink: /pros-and-cons.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
ExoPlayer has a number of advantages over Android's built in MediaPlayer:
* Fewer device specific issues and less variation in behavior across different
devices and versions of Android.
* The ability to update the player along with your application. Because
ExoPlayer is a library that you include in your application apk, you have
control over which version you use and you can easily update to a newer
version as part of a regular application update.
* The ability to [customize and extend the player][] to suit your use case.
ExoPlayer is designed specifically with this in mind, and allows many
components to be replaced with custom implementations.
* Support for [playlists][].
* Support for [DASH][] and [SmoothStreaming][], neither of which are supported
by MediaPlayer. Many other formats are also supported. See the [Supported
formats page][] for details.
* Support for advanced [HLS][] features, such as correct handling of
`#EXT-X-DISCONTINUITY` tags.
* Support for [Widevine common encryption][] on Android 4.4 (API level 19) and
higher.
* The ability to quickly integrate with a number of additional libraries using
official extensions. For example the [IMA extension][] makes it easy to
monetize your content using the [Interactive Media Ads SDK][].
It's important to note that there are also some disadvantages:
* For audio only playback on some devices, ExoPlayer may consume significantly
more battery than MediaPlayer. See the [Battery consumption page][] for
details.
* Including ExoPlayer in your app adds a few hundred kilobytes to the APK size.
This is likely only a concern for extremely lightweight apps. Guidance for
shrinking ExoPlayer can be found on the [APK shrinking page][].
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/pros-and-cons
[Supported formats page]: {{ site.baseurl }}/supported-formats.html
[IMA extension]: {{ site.release_v2 }}/extensions/ima
[Interactive Media Ads SDK]: https://developers.google.com/interactive-media-ads
[Battery consumption page]: {{ site.baseurl }}/battery-consumption.html
[customize and extend the player]: {{ site.baseurl }}/customization.html
[APK shrinking page]: {{ site.baseurl }}/shrinking.html
[playlists]: {{ site.baseurl }}/playlists.html
[DASH]: {{ site.baseurl }}/dash.html
[SmoothStreaming]: {{ site.baseurl }}/smoothstreaming.html
[HLS]: {{ site.baseurl }}/hls.html
[Widevine common encryption]: {{ site.baseurl }}/drm.html
---
title: Retrieving metadata
permalink: /retrieving-metadata.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/retrieving-metadata
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
## During playback ##
The metadata of the media can be retrieved during playback in multiple ways. The
most straightforward is to listen for the
`Player.Listener#onMediaMetadataChanged` event; this will provide a
[`MediaMetadata`][] object for use, which has fields such as `title` and
`albumArtist`. Alternatively, calling `Player#getMediaMetadata` returns the same
object.
~~~
public void onMediaMetadataChanged(MediaMetadata mediaMetadata) {
if (mediaMetadata.title != null) {
handleTitle(mediaMetadata.title);
}
}
~~~
{: .language-java}
If an application needs access to specific [`Metadata.Entry`][] objects, then it
should listen to `Player.Listener#onMetadata` (for dynamic metadata delivered
during playback). Alternatively, if there is a need to look at static metadata,
this can be accessed through the `TrackSelections#getFormat`.
`Player#getMediaMetadata` is populated from both of these sources.
## Without playback ##
If playback is not needed, it is more efficient to use the
[`MetadataRetriever`][] to extract the metadata because it avoids having to
create and prepare a player.
~~~
ListenableFuture<TrackGroupArray> trackGroupsFuture =
MetadataRetriever.retrieveMetadata(context, mediaItem);
Futures.addCallback(
trackGroupsFuture,
new FutureCallback<TrackGroupArray>() {
@Override
public void onSuccess(TrackGroupArray trackGroups) {
handleMetadata(trackGroups);
}
@Override
public void onFailure(Throwable t) {
handleFailure(t);
}
},
executor);
~~~
{: .language-java}
## Motion photos ##
It is also possible to extract the metadata of a motion photo, containing the
image and video offset and length for example. The supported formats are:
* JPEG motion photos recorded by Google Pixel and Samsung camera apps. This
format is playable by ExoPlayer and the associated metadata can therefore be
retrieved with a player or using the `MetadataRetriever`.
* HEIC motion photos recorded by Google Pixel and Samsung camera apps. This
format is currently not playable by ExoPlayer and the associated metadata
should therefore be retrieved using the `MetadataRetriever`.
For motion photos, the `TrackGroupArray` obtained with the `MetadataRetriever`
contains a `TrackGroup` with a single `Format` enclosing a
[`MotionPhotoMetadata`][] metadata entry.
~~~
for (int i = 0; i < trackGroups.length; i++) {
TrackGroup trackGroup = trackGroups.get(i);
Metadata metadata = trackGroup.getFormat(0).metadata;
if (metadata != null && metadata.length() == 1) {
Metadata.Entry metadataEntry = metadata.get(0);
if (metadataEntry instanceof MotionPhotoMetadata) {
MotionPhotoMetadata motionPhotoMetadata = (MotionPhotoMetadata) metadataEntry;
handleMotionPhotoMetadata(motionPhotoMetadata);
}
}
}
~~~
{: .language-java}
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/retrieving-metadata
[`MediaMetadata`]: {{ site.exo_sdk }}/MediaMetadata.html
[`Metadata.Entry`]: {{ site.exo_sdk }}/metadata/Metadata.Entry.html
[`MetadataRetriever`]: {{ site.exo_sdk }}/MetadataRetriever.html
[`MotionPhotoMetadata`]: {{ site.exo_sdk }}/metadata/mp4/MotionPhotoMetadata.html
---
title: RTSP
permalink: /rtsp.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/rtsp
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
{% include_relative _page_fragments/supported-formats-rtsp.md %}
## Using MediaItem ##
To play an RTSP stream, you need to depend on the RTSP module.
~~~
implementation 'com.google.android.exoplayer:exoplayer-rtsp:2.X.X'
~~~
{: .language-gradle}
You can then create a `MediaItem` for an RTSP URI and pass it to the player.
~~~
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media item to be played.
player.setMediaItem(MediaItem.fromUri(rtspUri));
// Prepare the player.
player.prepare();
~~~
{: .language-java}
### Authentication ###
ExoPlayer supports playback with RTSP BASIC and DIGEST authentication. To play
protected RTSP content, the `MediaItem`'s URI must be configured with the
authentication info. Specifically, the URI should be of the form
`rtsp://<username>:<password>@<host address>`.
## Using RtspMediaSource ##
For more customization options, you can create an `RtspMediaSource` and pass it
directly to the player instead of a `MediaItem`.
~~~
// Create an RTSP media source pointing to an RTSP uri.
MediaSource mediaSource =
new RtspMediaSource.Factory()
.createMediaSource(MediaItem.fromUri(rtspUri));
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media source to be played.
player.setMediaSource(mediaSource);
// Prepare the player.
player.prepare();
~~~
{: .language-java}
## Using RTSP behind a NAT (RTP/TCP support) ##
ExoPlayer uses UDP as the default protocol for RTP transport.
When streaming RTSP behind a NAT layer, the NAT might not be able to forward the
incoming RTP/UDP packets to the device. This occurs if the NAT lacks the
necessary UDP port mapping. If ExoPlayer detects there have not been incoming
RTP packets for a while and the playback has not started yet, ExoPlayer tears
down the current RTSP playback session, and retries playback using RTP-over-RTSP
(transmitting RTP packets using the TCP connection opened for RTSP).
The timeout for retrying with TCP can be customized by calling the method
`RtspMediaSource.Factory.setTimeoutMs()`. For example, if the timeout is set to
four seconds, the player will retry with TCP after four seconds of UDP
inactivity.
Setting the timeout also affects the end-of-stream detection logic. That is,
ExoPlayer will report the playback has ended if nothing is received for the
duration of the set timeout. Setting this value too small may lead to an early
end-of-stream signal under poor network conditions.
RTP/TCP offers better compatibility under some network setups. You can configure
ExoPlayer to use RTP/TCP by default with
`RtspMediaSource.Factory.setForceUseRtpTcp()`.
### Passing a custom SocketFactory
Custom `SocketFactory` instances can be useful when particular routing is
required (e.g. when RTSP traffic needs to pass a specific interface, or the
socket needs additional connectivity flags).
By default, `RtspMediaSource` will use Java's standard socket factory
(`SocketFactory.getDefault()`) to create connections to the remote endpoints.
This behavior can be overridden using
`RtspMediaSource.Factory.setSocketFactory()`.
~~~
// Create an RTSP media source pointing to an RTSP uri and override the socket
// factory.
MediaSource mediaSource =
new RtspMediaSource.Factory()
.setSocketFactory(...)
.createMediaSource(MediaItem.fromUri(rtspUri));
~~~
{: .language-java}
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/rtsp
---
title: APK shrinking
permalink: /shrinking.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/shrinking
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
Minimizing APK size is an important aspect of developing a good Android
application. This is particularly true when targeting developing markets, and
also when developing an Android Instant App. For such cases it may be desirable
to minimize the size of the ExoPlayer library that's included in the APK. This
page outlines some simple steps that can help to achieve this.
## Use modular dependencies ##
The most convenient way to use ExoPlayer is to add a dependency to the full
library:
~~~
implementation 'com.google.android.exoplayer:exoplayer:2.X.X'
~~~
{: .language-gradle}
However this may pull in more features than your app needs. Instead, depend only
on the library modules that you actually need. For example the following will
add dependencies on the Core, DASH and UI library modules, as might be required
for an app that only plays DASH content:
~~~
implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X'
~~~
{: .language-gradle}
## Enable code and resource shrinking ##
You should enable code and resource shrinking for your application's release
builds. ExoPlayer is structured in a way that allows code shrinking to
effectively remove unused functionality. For example, for an application that
plays DASH content, ExoPlayer's contribution to APK size can be reduced by
approximately 40% by enabling code shrinking.
Read [Shrink, obfuscate, and optimize your app][] on the Android Developer site
to learn how to enable code and resource shrinking.
## Specify which renderers your app needs ##
By default, the player's renderers will be created using
`DefaultRenderersFactory`. `DefaultRenderersFactory` depends on all of the
`Renderer` implementations provided in the ExoPlayer library, and as a result
none of them will be removed by code shrinking. If you know that your app only
needs a subset of renderers, you can specify your own `RenderersFactory`
instead. For example, an app that only plays audio can define a factory like
this when instantiating `ExoPlayer` instances:
~~~
RenderersFactory audioOnlyRenderersFactory =
(handler, videoListener, audioListener, textOutput, metadataOutput)
-> new Renderer[] {
new MediaCodecAudioRenderer(
context, MediaCodecSelector.DEFAULT, handler, audioListener)
};
ExoPlayer player =
new ExoPlayer.Builder(context, audioOnlyRenderersFactory).build();
~~~
{: .language-java}
This will allow other `Renderer` implementations to be removed by code
shrinking. In this particular example video, text and metadata renderers are
removed.
## Specify which extractors your app needs ##
By default, the player will create `Extractor`s to play progressive media using
`DefaultExtractorsFactory`. `DefaultExtractorsFactory` depends on all of the
`Extractor` implementations provided in the ExoPlayer library, and as a result
none of them will be removed by code shrinking. If you know that your app only
needs to play a small number of container formats, or doesn't play progressive
media at all, you can specify your own `ExtractorsFactory` instead. For example,
an app that only needs to play mp4 files can provide a factory like:
~~~
ExtractorsFactory mp4ExtractorFactory =
() -> new Extractor[] {new Mp4Extractor()};
ExoPlayer player =
new ExoPlayer.Builder(
context,
new DefaultMediaSourceFactory(context, mp4ExtractorFactory))
.build();
~~~
{: .language-java}
This will allow other `Extractor` implementations to be removed by code
shrinking, which can result in a significant reduction in size.
If your app is not playing progressive content at all, you should pass
`ExtractorsFactory.EMPTY` to the `DefaultMediaSourceFactory` constructor, then
pass that `mediaSourceFactory` to the `ExoPlayer.Builder` constructor.
~~~
ExoPlayer player =
new ExoPlayer.Builder(
context,
new DefaultMediaSourceFactory(context, ExtractorsFactory.EMPTY))
.build();
~~~
{: .language-java}
## Custom MediaSource instantiation ##
If your app is using a custom `MediaSource.Factory` and you want
`DefaultMediaSourceFactory` to be removed by code stripping, you should pass
your `MediaSource.Factory` directly to the `ExoPlayer.Builder` constructor.
~~~
ExoPlayer player =
new ExoPlayer.Builder(context, customMediaSourceFactory).build();
~~~
{: .language-java}
If your app is using `MediaSource`s directly instead of `MediaItem`s you should
pass `MediaSource.Factory.UNSUPPORTED` to the `ExoPlayer.Builder` constructor,
to ensure `DefaultMediaSourceFactory` and `DefaultExtractorsFactory` can be
stripped by code shrinking.
~~~
ExoPlayer player =
new ExoPlayer.Builder(context, MediaSource.Factory.UNSUPPORTED).build();
ProgressiveMediaSource mediaSource =
new ProgressiveMediaSource.Factory(
dataSourceFactory, customExtractorsFactory)
.createMediaSource(MediaItem.fromUri(uri));
~~~
{: .language-java}
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/shrinking
[Shrink, obfuscate, and optimize your app]: https://developer.android.com/studio/build/shrink-code
---
title: SmoothStreaming
permalink: /smoothstreaming.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/smoothstreaming
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
{% include_relative _page_fragments/supported-formats-smoothstreaming.md %}
## Using MediaItem ##
To play a SmoothStreaming stream, you need to depend on the SmoothStreaming
module.
~~~
implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.X.X'
~~~
{: .language-gradle}
You can then create a `MediaItem` for a SmoothStreaming manifest URI and pass it
to the player.
~~~
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media item to be played.
player.setMediaItem(MediaItem.fromUri(ssUri));
// Prepare the player.
player.prepare();
~~~
{: .language-java}
If your URI doesn't end with `.ism/Manifest`, you can pass
`MimeTypes.APPLICATION_SS` to `setMimeType` of `MediaItem.Builder` to explicitly
indicate the type of the content.
ExoPlayer will automatically adapt between representations defined in the
manifest, taking into account both available bandwidth and device capabilities.
## Using SsMediaSource ##
For more customization options, you can create a `SsMediaSource` and pass it
directly to the player instead of a `MediaItem`.
~~~
// Create a data source factory.
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
// Create a SmoothStreaming media source pointing to a manifest uri.
MediaSource mediaSource =
new SsMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(ssUri));
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media source to be played.
player.setMediaSource(mediaSource);
// Prepare the player.
player.prepare();
~~~
{: .language-java}
## Accessing the manifest ##
You can retrieve the current manifest by calling `Player.getCurrentManifest`.
For SmoothStreaming you should cast the returned object to `SsManifest`. The
`onTimelineChanged` callback of `Player.Listener` is also called whenever
the manifest is loaded. This will happen once for a on-demand content, and
possibly many times for live content. The code snippet below shows how an app
can do something whenever the manifest is loaded.
~~~
player.addListener(
new Player.Listener() {
@Override
public void onTimelineChanged(
Timeline timeline, @Player.TimelineChangeReason int reason) {
Object manifest = player.getCurrentManifest();
if (manifest != null) {
SsManifest ssManifest = (SsManifest) manifest;
// Do something with the manifest.
}
}
});
~~~
{: .language-java}
## Customizing playback ##
ExoPlayer provides multiple ways for you to tailor playback experience to your
app's needs. See the [Customization page][] for examples.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/smoothstreaming
[Customization page]: {{ site.baseurl }}/customization.html
---
title: Supported devices
permalink: /supported-devices.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/supported-devices
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
The minimum Android versions required for core ExoPlayer use cases are:
| Use case | Android version number | Android API level |
|----------|:------------:|:------------:|
| Audio playback | 4.1 | 16 |
| Video playback | 4.1 | 16 |
| DASH (no DRM) | 4.1 | 16 |
| DASH (Widevine CENC; "cenc" scheme) | 4.4 | 19 |
| DASH (Widevine CENC; "cbcs" scheme) | 7.1 | 25 |
| DASH (ClearKey; "cenc" scheme) | 5.0 | 21 |
| SmoothStreaming (no DRM) | 4.1 | 16 |
| SmoothStreaming (PlayReady SL2000; "cenc" scheme) | AndroidTV | AndroidTV |
| HLS (no DRM) | 4.1 | 16 |
| HLS (AES-128 encryption) | 4.1 | 16 |
| HLS (Widevine CENC; "cenc" scheme) | 4.4 | 19 |
| HLS (Widevine CENC; "cbcs" scheme) | 7.1 | 25 |
For a given use case, we aim to support ExoPlayer on all Android devices that
satisfy the minimum version requirement. Known device specific compatibility
issues are listed below. Device specific issues on our GitHub issue tracker can
be found
[here](https://github.com/google/ExoPlayer/labels/bug%3A%20device%20specific).
* **FireOS (version 4 and earlier)** - Whilst we endeavour to support FireOS
devices, FireOS is a fork of Android and as a result we are unable to
guarantee support. Device specific issues encountered on FireOS are normally
caused by incompatibilities in the support that FireOS provides for running
Android applications. Such issues should be reported to Amazon in the first
instance. We are aware of issues affecting FireOS version 4 and earlier. We
believe FireOS version 5 resolved these issues.
* **Nexus Player (only when using an HDMI to DVI cable)** - There is a known
issue affecting Nexus Player, only when the device is connected to a monitor
using a certain type of HDMI to DVI cable, which causes video being played too
quickly. Use of an HDMI to DVI cable is not realistic for an end user setup
because such cables cannot carry audio. Hence this issue can be safely
ignored. We suggest using a realistic end user setup (e.g., the device
connected to a TV using a standard HDMI cable) for development and testing.
* **Emulators** - Some Android emulators do not properly implement components of
Android's media stack, and as a result do not support ExoPlayer. This is an
issue with the emulator, not with ExoPlayer. Android's official emulator
("Virtual Devices" in Android Studio) supports ExoPlayer provided the system
image has an API level of at least 23. System images with earlier API levels
do not support ExoPlayer. The level of support provided by third party
emulators varies. Issues running ExoPlayer on third party emulators should be
reported to the developer of the emulator rather than to the ExoPlayer team.
Where possible, we recommend testing media applications on physical devices
rather than emulators.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/supported-devices
---
title: Supported formats
permalink: /supported-formats.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/supported-formats
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
When defining the formats that ExoPlayer supports, it's important to note that
"media formats" are defined at multiple levels. From the lowest level to the
highest, these are:
* The format of the individual media samples (e.g., a frame of video or a frame
of audio). These are *sample formats*. Note that a typical video file will
contain media in at least two sample formats; one for video (e.g., H.264) and
one for audio (e.g., AAC).
* The format of the container that houses the media samples and associated
metadata. These are *container formats*. A media file has a single container
format (e.g., MP4), which is commonly indicated by the file extension. Note
that for some audio only formats (e.g., MP3), the sample and container formats
may be the same.
* Adaptive streaming technologies such as DASH, SmoothStreaming and HLS. These
are not media formats as such, however it's still necessary to define what
level of support ExoPlayer provides.
The following sections define ExoPlayer's support at each level, from highest to
lowest. The last two sections describe support for standalone subtitle formats
and HDR video playback.
## Adaptive streaming ##
### DASH ###
{% include_relative _page_fragments/supported-formats-dash.md %}
### SmoothStreaming ###
{% include_relative _page_fragments/supported-formats-smoothstreaming.md %}
### HLS ###
{% include_relative _page_fragments/supported-formats-hls.md %}
## Progressive container formats ##
{% include_relative _page_fragments/supported-formats-progressive.md %}
## RTSP ##
{% include_relative _page_fragments/supported-formats-rtsp.md %}
## Sample formats ##
By default ExoPlayer uses Android's platform decoders. Hence the supported
sample formats depend on the underlying platform rather than on ExoPlayer.
Sample formats supported by Android devices are documented
[here](https://developer.android.com/guide/appendix/media-formats.html#core).
Note that individual devices may support additional formats beyond those listed.
In addition to Android's platform decoders, ExoPlayer can also make use of
software decoder extensions. These must be manually built and included in
projects that wish to make use of them. We currently provide software decoder
extensions for
[AV1]({{ site.release_v2 }}/extensions/av1),
[VP9]({{ site.release_v2 }}/extensions/vp9),
[FLAC]({{ site.release_v2 }}/extensions/flac),
[Opus]({{ site.release_v2 }}/extensions/opus) and
[FFmpeg]({{ site.release_v2 }}/extensions/ffmpeg).
### FFmpeg extension ###
The [FFmpeg extension]({{ site.release_v2 }}/extensions/ffmpeg) supports
decoding a variety of different audio sample formats. You can choose which
decoders to include when building the extension, as documented in the
extension's [README.md]({{ site.release_v2 }}/extensions/ffmpeg/README.md). The
following table provides a mapping from audio sample format to the corresponding
FFmpeg decoder name.
| Sample format | Decoder name(s) |
|---------------:|----------------------------|
| Vorbis | vorbis |
| Opus | opus |
| FLAC | flac |
| ALAC | alac |
| PCM μ-law | pcm_mulaw |
| PCM A-law | pcm_alaw |
| MP1, MP2, MP3 | mp3 |
| AMR-NB | amrnb |
| AMR-WB | amrwb |
| AAC | aac |
| AC-3 | ac3 |
| E-AC-3 | eac3 |
| DTS, DTS-HD | dca |
| TrueHD | mlp truehd |
## Standalone subtitle formats ##
ExoPlayer supports standalone subtitle files in a variety of formats. Subtitle
files can be side-loaded as described on the [media items page][].
| Container format | Supported | MIME type |
|---------------------------|:------------:|:----------|
| WebVTT | YES | MimeTypes.TEXT_VTT |
| TTML / SMPTE-TT | YES | MimeTypes.APPLICATION_TTML |
| SubRip | YES | MimeTypes.APPLICATION_SUBRIP |
| SubStationAlpha (SSA/ASS) | YES | MimeTypes.TEXT_SSA |
[media items page]: {{ site.baseurl }}/media-items.html#sideloading-subtitle-tracks
## HDR video playback ##
ExoPlayer handles extracting high dynamic range (HDR) video in various
containers, including Dolby Vision in MP4 and HDR10+ in Matroska/WebM. Decoding
and displaying HDR content depends on support from the Android platform and
device. See
[HDR Video Playback](https://source.android.com/devices/tech/display/hdr.html)
to learn about checking for HDR decoding/display capabilities and limitations of
HDR support across Android versions.
When playing an HDR stream that requires support for a particular codec profile,
ExoPlayer's default `MediaCodec` selector will pick a decoder that supports that
profile (if available), even if another decoder for the same MIME type that
doesn't support that profile appears higher up the codec list. This can result
in selecting a software decoder in cases where the stream exceeds the
capabilities of a hardware decoder for the same MIME type.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/supported-formats
---
title: Track selection
permalink: /track-selection.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/track-selection
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
When a media item contains multiple tracks, track selection is the process that
determines which of them are chosen for playback. The track selection process is
configured by [`TrackSelectionParameters`][], which allows many different
constraints and overrides influencing track selection to be specified.
## Querying the available tracks
You can listen to `Player.Listener.onTracksChanged` to be notified about changes
to tracks, including:
* The available tracks becoming known when preparation of the media item being
played completes. Note that the player needs to prepare a media item to know
what tracks it contains.
* The available tracks changing due to playback transitioning from one media
item to another.
* Changes to the selected tracks.
~~~
player.addListener(new Player.Listener() {
@Override
public void onTracksChanged(Tracks tracks) {
// Update UI using current tracks.
}
});
~~~
{: .language-java}
You can also query the current tracks by calling `player.getCurrentTracks()`.
The returned `Tracks` contains a list of `Track.Group`s, where tracks within a
single `Group` present the same content but in different formats.
As an example of how tracks can be grouped, consider an adaptive playback where
a main video feed is provided in five bitrates, and an alternative video feed
(e.g., a different camera angle in a sports match) is provided in two bitrates.
In this case there will be two video track groups, one corresponding to the main
video feed containing five tracks, and a second for the alternative video feed
containing two tracks.
Audio tracks whose languages differ are not grouped, because content in
different languages is not considered to be the same. Conversely, audio tracks
in the same language that only differ in properties such as bitrate, sampling
rate, channel count and so on can be grouped. This also applies to text tracks.
Each `Group` can be queried to determine which tracks are supported for
playback, which are currently selected, and what `Format` each track uses:
~~~
for (Tracks.Group trackGroup : tracks.getGroups()) {
// Group level information.
@C.TrackType int trackType = trackGroup.getTrackType();
boolean trackInGroupIsSelected = trackGroup.isSelected();
boolean trackInGroupIsSupported = trackGroup.isSupported();
for (int i = 0; i < trackGroup.length; i++) {
// Individual track information.
boolean isSupported = trackGroup.isTrackSupported(i);
boolean isSelected = trackGroup.isTrackSelected(i);
Format trackFormat = trackGroup.getTrackFormat(i);
}
}
~~~
{: .language-java}
* A track is 'supported' if the `Player` is able to decode and render its
samples. Note that even if multiple track groups of the same type (for example
multiple audio track groups) are supported, it only means that they are
supported individually and the player is not necessarily able to play them at
the same time.
* A track is 'selected' if it has been chosen for playback given the current
`TrackSelectionParameters`. If multiple tracks within one track group are
selected, the player uses these tracks for adaptive playback (for example,
multiple video tracks with different bitrates). Note that only one of these
tracks will be played at any one time.
## Modifying track selection parameters
The track selection process can be configured using
`Player.setTrackSelectionParameters`. This can be done both before and during
playback. The example below demonstrates how to obtain the current
`TrackSelectionParameters` from the player, modify them, and update the `Player`
with the modified result:
~~~
player.setTrackSelectionParameters(
player.getTrackSelectionParameters()
.buildUpon()
.setMaxVideoSizeSd()
.setPreferredAudioLanguage("hu")
.build());
~~~
{: .language-java}
### Constraint based track selection
Most options in `TrackSelectionParameters` allow you to specify constraints,
which are independent of the tracks that are actually available. Available
constraints include:
* Maximum and minimum video width, height, frame rate, and bitrate.
* Maximum audio channel count and bitrate.
* Preferred MIME types for video and audio.
* Preferred audio languages and role flags.
* Preferred text languages and role flags.
ExoPlayer uses sensible defaults for these constraints, for example restricting
video resolution to the display size and preferring the audio language that
matches the user's system Locale setting.
There are several benefits to using constraint based track selection rather than
selecting specific tracks from those that are available:
* You can specify constraints before knowing what tracks a media item provides.
This means that constraints can be specified before the player has prepared a
media item, whereas selecting specific tracks requires application code to
wait until the available tracks become known.
* Constraints are applied for all media items in a playlist, even when those
items have different available tracks. For example, a preferred audio language
constraint will be automatically applied for all media items, even if the
`Format` of the track in that language varies from one media item to the next.
This is not the case when selecting specific tracks, as described below.
### Selecting specific tracks
It's possible to select specific tracks using `TrackSelectionParameters`. First,
the player's currently available tracks should be queried using
`Player.getCurrentTracks`. Second, having identified which tracks to select,
they can be set on `TrackSelectionParameters` using a `TrackSelectionOverride`.
For example, to select the first track from a specific `audioTrackGroup`:
~~~
player.setTrackSelectionParameters(
player.getTrackSelectionParameters()
.buildUpon()
.setOverrideForType(
new TrackSelectionOverride(
audioTrackGroup.getMediaTrackGroup(),
/* trackIndex= */ 0))
.build());
~~~
{: .language-java}
A `TrackSelectionOverride` will only apply to media items that contain a
`TrackGroup` exactly matching the one specified in the override. Hence an
override may not apply to a subsequent media item if that item contains
different tracks.
### Disabling track types or groups
Track types like video, audio or text, can be disabled completely using
`TrackSelectionParameters.Builder.setTrackTypeDisabled`. A disabled track type
will be disabled for all media items:
~~~
player.setTrackSelectionParameters(
player.getTrackSelectionParameters()
.buildUpon()
.setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, /* disabled= */ true)
.build());
~~~
{: .language-java}
Alternatively, it's possible to prevent the selection of tracks from a specific
`TrackGroup` by specifying an empty override for that group:
~~~
player.setTrackSelectionParameters(
player.getTrackSelectionParameters()
.buildUpon()
.addOverride(
new TrackSelectionOverride(
disabledTrackGroup.getMediaTrackGroup(),
/* trackIndices= */ ImmutableList.of()))
.build());
~~~
{: .language-java}
## Customizing the track selector
Track selection is the responsibility of a `TrackSelector`, an instance
of which can be provided whenever an `ExoPlayer` is built and later obtained
with `ExoPlayer.getTrackSelector()`.
~~~
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
ExoPlayer player =
new ExoPlayer.Builder(context)
.setTrackSelector(trackSelector)
.build();
~~~
{: .language-java}
`DefaultTrackSelector` is a flexible `TrackSelector` suitable for most use
cases. It uses the `TrackSelectionParameters` set in the `Player`, but also
provides some advanced customization options that can be specified in the
`DefaultTrackSelector.ParametersBuilder`:
~~~
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setAllowVideoMixedMimeTypeAdaptiveness(true));
~~~
{: .language-java}
### Tunneling
Tunneled playback can be enabled in cases where the combination of renderers and
selected tracks supports it. This can be done by using
`DefaultTrackSelector.ParametersBuilder.setTunnelingEnabled(true)`.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/track-selection
[`TrackSelectionParameters`]: {{ site.exo_sdk }}/trackselection/TrackSelectionParameters.html
---
title: Transforming media
permalink: /transforming-media.html
redirect_to:
- https://developer.android.com/media/media3/transformer
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
The [Transformer API][] can be used to convert media streams. It takes an input
media stream, applies changes to it as configured by the app, and produces the
corresponding output file. The available transformations are:
* Track removal.
* Flattening of slow motion videos or, in other words, their conversion into
normal videos that retain the desired slow motion effects, but can be played
with a player that is not aware of slow motion video formats. The purpose of
this transformation is to make slow motion videos suitable for sharing with
other apps or uploading to a server.
## Starting a transformation ##
To transform media, you need to add the following dependency to your app’s
`build.gradle` file:
~~~
implementation 'com.google.android.exoplayer:exoplayer-transformer:2.X.X'
~~~
{: .language-gradle}
where `2.X.X` is your preferred ExoPlayer version.
You can then start a transformation by building a `Transformer` instance and
calling `startTransformation` on it. The code sample below starts a
transformation that removes the audio track from the input:
~~~
// Configure and create a Transformer instance.
Transformer transformer =
new Transformer.Builder(context)
.setRemoveAudio(true)
.addListener(transformerListener)
.build();
// Start the transformation.
transformer.startTransformation(inputMediaItem, outputPath);
~~~
{: .language-java}
Other parameters, such as the `MediaSource.Factory`, can be passed to the
builder.
`startTransformation` receives a `MediaItem` describing the input, and a path or
a `ParcelFileDescriptor` indicating where the output should be written. The
input can be a progressive or an adaptive stream, but the output is always a
progressive stream. For adaptive inputs, the highest resolution tracks are
always selected for the transformation. The input can be of any container format
supported by ExoPlayer (see the [Supported formats page][] for details), but the
output is always an MP4 file.
Multiple transformations can be executed sequentially with the same
`Transformer` instance, but concurrent transformations with the same instance
are not supported.
## Listening to events ##
The `startTransformation` method is asynchronous. It returns immediately and the
app is notified of events via the listener passed to the `Transformer` builder.
~~~
Transformer.Listener transformerListener =
new Transformer.Listener() {
@Override
public void onTransformationCompleted(MediaItem inputMediaItem, TransformationResult result) {
playOutput();
}
@Override
public void onTransformationError(MediaItem inputMediaItem, TransformationException exception) {
displayError(exception);
}
};
~~~
{: .language-java}
## Displaying progress updates ##
`Transformer.getProgress` can be called to query the current progress of a
transformation. The returned value indicates the progress state. If the progress
state is `PROGRESS_STATE_AVAILABLE` then the passed `ProgressHolder` will have
been updated with the current progress percentage. The snippet below
demonstrates how to periodically query the progress of a transformation, where
the `updateProgressInUi` method could be implemented to update a progress bar
displayed to the user.
~~~
transformer.startTransformation(inputMediaItem, outputPath);
ProgressHolder progressHolder = new ProgressHolder();
mainHandler.post(
new Runnable() {
@Override
public void run() {
@ProgressState int progressState = transformer.getProgress(progressHolder);
updateProgressInUi(progressState, progressHolder);
if (progressState != PROGRESS_STATE_NO_TRANSFORMATION) {
mainHandler.postDelayed(/* r= */ this, /* delayMillis= */ 500);
}
}
});
~~~
{: .language-java}
## Flattening slow motion videos ##
We define a slow motion video as a media stream whose metadata points to
sections of the stream that should be slowed during playback. Flattening is the
process of converting a slow motion video to a regular media format (for example
MP4) where the slow motion sections are played at the requested speed. The slow
motion metadata is removed, and the video and audio streams are modified so as
to produce the desired effect when the output is played with a standard player
(that is, a player that is not aware of slow motion formats).
To flatten slow motion streams, use the `setFlattenForSlowMotion` builder
method.
~~~
Transformer transformer =
new Transformer.Builder(context)
.setFlattenForSlowMotion(true)
.addListener(transformerListener)
.build();
transformer.startTransformation(inputMediaItem, outputPath);
~~~
{: .language-java}
This allows apps to support slow motion videos without having to worry about
handling these special formats. All they need to do is to store and play the
flattened version of the video instead of the original one.
Currently, Samsung's slow motion format is the only one supported.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/transforming-media
[Transformer API]: {{ site.exo_sdk }}/transformer/Transformer.html
[Supported formats page]: {{ site.baseurl }}/supported-formats.html
---
title: Troubleshooting
redirect_from:
- /faqs.html
- /debugging-playback-issues.html
permalink: /troubleshooting.html
redirect_to:
- https://developer.android.com/media/media3/exoplayer/troubleshooting
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
* [Fixing "Cleartext HTTP traffic not permitted" errors][]
* [Fixing "SSLHandshakeException", "CertPathValidatorException" and "ERR_CERT_AUTHORITY_INVALID" errors][]
* [Why are some media files not seekable?][]
* [Why is seeking inaccurate in some MP3 files?][]
* [Why is seeking in my video slow?][]
* [Why do some MPEG-TS files fail to play?][]
* [Why are subtitles not found in some MPEG-TS files?][]
* [Why do some MP4/FMP4 files play incorrectly?][]
* [Why do some streams fail with HTTP response code 301 or 302?][]
* [Why do some streams fail with UnrecognizedInputFormatException?][]
* [Why doesn't setPlaybackParameters work properly on some devices?][]
* [What do "Player is accessed on the wrong thread" errors mean?][]
* [How can I fix "Unexpected status line: ICY 200 OK"?][]
* [How can I query whether the stream being played is a live stream?][]
* [How do I keep audio playing when my app is backgrounded?][]
* [Why does ExoPlayer support my content but the Cast extension doesn't?][]
* [Why does content fail to play, but no error is surfaced?][]
* [How can I get a decoding extension to load and be used for playback?][]
* [Can I play YouTube videos directly with ExoPlayer?][]
* [Video playback is stuttering][]
---
#### Fixing "Cleartext HTTP traffic not permitted" errors ####
This error will occur if your app requests cleartext HTTP traffic (i.e.,
`http://` rather than `https://`) when its Network Security Configuration does
not permit it. If your app targets Android 9 (API level 28) or later, cleartext
HTTP traffic is disabled by the default configuration.
If your app needs to work with cleartext HTTP traffic then you need to use a
Network Security Configuration that permits it. Please see Android's
[network security documentation](https://developer.android.com/training/articles/security-config.html)
for details. To enable all cleartext HTTP traffic, you can simply add
`android:usesCleartextTraffic="true"` to the `application` element of your app's
`AndroidManifest.xml`.
The ExoPlayer demo app uses the default Network Security Configuration, and so
does not allow cleartext HTTP traffic. You can enable it using the instructions
above.
#### Fixing "SSLHandshakeException", "CertPathValidatorException" and "ERR_CERT_AUTHORITY_INVALID" errors ####
`SSLHandshakeException`, `CertPathValidatorException` and
`ERR_CERT_AUTHORITY_INVALID` all indicate a problem with the server's SSL
certificate. These errors are not ExoPlayer specific. Please see
[Android's SSL documentation](https://developer.android.com/training/articles/security-ssl#CommonProblems)
for more details.
#### Why are some media files not seekable? ####
By default ExoPlayer does not support seeking in media where the only method for
performing accurate seek operations is for the player to scan and index the
entire file. ExoPlayer considers such files as unseekable. Most modern media
container formats include metadata for seeking (e.g., a sample index), have a
well defined seek algorithm (e.g., interpolated bisection search for Ogg), or
indicate that their content is constant bitrate. Efficient seek operations are
possible and supported by ExoPlayer in these cases.
If you require seeking but have unseekable media, we suggest converting your
content to use a more appropriate container format. For MP3, ADTS and AMR files,
you can also enable seeking under the assumption that the files have a constant
bitrate, as described
[here](customization.html#enabling-constant-bitrate-seeking).
#### Why is seeking inaccurate in some MP3 files? ####
Variable bitrate (VBR) MP3 files are fundamentally unsuitable for use cases that
require exact seeking. There are two reasons for this:
1. For exact seeking, a container format will ideally provide a precise
time-to-byte mapping in a header. This mapping allows a player to map a
requested seek time to the corresponding byte offset, and start requesting,
parsing and playing media from that offset. The headers available for
specifying this mapping in MP3 (e.g., XING headers) are, unfortunately, often
imprecise.
1. For container formats that don't provide a precise time-to-byte mapping (or
any time-to-byte mapping at all), it's still possible to perform an exact
seek if the container includes absolute sample timestamps in the stream. In
this case a player can map the seek time to a best guess of the corresponding
byte offset, start requesting media from that offset, parse the first
absolute sample timestamp, and effectively perform a guided binary search
into the media until it finds the right sample. Unfortunately MP3 does not
include absolute sample timestamps in the stream, so this approach is not
possible.
For these reasons, the only way to perform an exact seek into a VBR MP3 file is
to scan the entire file and manually build up a time-to-byte mapping in the
player. This strategy can be enabled by using [`FLAG_ENABLE_INDEX_SEEKING`][],
which can be [set on a `DefaultExtractorsFactory`][] using
[`setMp3ExtractorFlags`][]. Note that it doesn't scale well to large MP3 files,
particularly if the user tries to seek to near the end of the stream shortly
after starting playback, which requires the player to wait until it's downloaded
and indexed the entire stream before performing the seek. In ExoPlayer, we
decided to optimize for speed over accuracy in this case and
[`FLAG_ENABLE_INDEX_SEEKING`][] is therefore disabled by default.
If you control the media you're playing, we strongly advise that you use a more
appropriate container format, such as MP4. There are no use cases we're aware of
where MP3 is the best choice of media format.
#### Why is seeking in my video slow? ####
When the player seeks to a new playback position in a video it needs to do two
things:
1. Load the data corresponding to the new playback position into the buffer
(this may not be necessary if this data is already buffered).
2. Flush the video decoder and start decoding from the I-frame (keyframe) before
the new playback position, due to the [intra-frame coding] used by most video
compression formats. In order to ensure the seek is 'accurate' (i.e.
playback starts exactly at the seek position), all frames between the
preceding I-frame and the seek position need to be decoded and immediately
discarded (without being shown on the screen).
The latency introduced by (1) can be mitigated by either increasing the amount
of data buffered in memory by the player, or [pre-caching the data to disk].
The latency introduced by (2) can be mitigated by either reducing the accuracy
of the seek using [`ExoPlayer.setSeekParameters`], or re-encoding the video
to have more frequent I-frames (which will result in a larger output file).
#### Why do some MPEG-TS files fail to play? ####
Some MPEG-TS files do not contain access unit delimiters (AUDs). By default
ExoPlayer relies on AUDs to cheaply detect frame boundaries. Similarly, some
MPEG-TS files do not contain IDR keyframes. By default these are the only type
of keyframes considered by ExoPlayer.
ExoPlayer will appear to be stuck in the buffering state when asked to play an
MPEG-TS file that lacks AUDs or IDR keyframes. If you need to play such files,
you can do so using [`FLAG_DETECT_ACCESS_UNITS`][] and
[`FLAG_ALLOW_NON_IDR_KEYFRAMES`][] respectively. These flags can be [set on a
`DefaultExtractorsFactory`][] using [`setTsExtractorFlags`][] or on a
`DefaultHlsExtractorFactory` using the
[constructor]({{ site.exo_sdk }}/source/hls/DefaultHlsExtractorFactory.html#DefaultHlsExtractorFactory-int-boolean-).
Use of `FLAG_DETECT_ACCESS_UNITS` has no side effects other than being
computationally expensive relative to AUD based frame boundary detection. Use of
`FLAG_ALLOW_NON_IDR_KEYFRAMES` may result in temporary visual corruption at the
start of playback and immediately after seeks when playing some MPEG-TS files.
#### Why are subtitles not found in some MPEG-TS files? ####
Some MPEG-TS files include CEA-608 tracks but don't declare them in the
container metadata, so ExoPlayer is unable to detect them. You can manually
specify that the subtitle track(s) exist by providing a list of expected
subtitle formats to the `DefaultExtractorsFactory`, including the accessibility
channels that can be used to identify them in the MPEG-TS stream:
```java
DefaultExtractorsFactory extractorsFactory =
new DefaultExtractorsFactory()
.setTsSubtitleFormats(
ImmutableList.of(
new Format.Builder()
.setSampleMimeType(MimeTypes.APPLICATION_CEA608)
.setAccessibilityChannel(accessibilityChannel)
// Set other subtitle format info, e.g. language.
.build()));
Player player =
new ExoPlayer.Builder(
context,
new DefaultMediaSourceFactory(context, extractorsFactory))
.build();
```
#### Why do some MP4/FMP4 files play incorrectly? ####
Some MP4/FMP4 files contain edit lists that rewrite the media timeline by
skipping, moving or repeating lists of samples. ExoPlayer has partial support
for applying edit lists. For example, it can delay or repeat groups of samples
starting on a synchronization sample, but it does not truncate audio samples or
preroll media for edits that don't start on a synchronization sample.
If you are seeing that part of the media is unexpectedly missing or repeated,
try setting [`Mp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS`][] or
[`FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS`][], which will cause
the extractor to ignore edit lists entirely. These can be [set on a
`DefaultExtractorsFactory`][] using [`setMp4ExtractorFlags`][] or
[`setFragmentedMp4ExtractorFlags`][].
#### Why do some streams fail with HTTP response code 301 or 302? ####
HTTP response codes 301 and 302 both indicate redirection. Brief descriptions
can be found on [Wikipedia][]. When ExoPlayer makes a request and receives a
response with status code 301 or 302, it will normally follow the redirect
and start playback as normal. The one case where this does not happen by default
is for cross-protocol redirects. A cross-protocol redirect is one that redirects
from HTTPS to HTTP or vice-versa (or less commonly, between another pair of
protocols). You can test whether a URL causes a cross-protocol redirect using
the [wget][] command line tool as follows:
```
wget "https://yourserver.com/test.mp3" 2>&1 | grep Location
```
The output should look something like this:
```
$ wget "https://yourserver.com/test.mp3" 2>&1 | grep Location
Location: https://second.com/test.mp3 [following]
Location: http://third.com/test.mp3 [following]
```
In this example there are two redirects. The first redirect is from
`https://yourserver.com/test.mp3` to `https://second.com/test.mp3`. Both are
HTTPS, and so this is not a cross-protocol redirect. The second redirect is from
`https://second.com/test.mp3` to `http://third.com/test.mp3`. This redirects
from HTTPS to HTTP and so is a cross-protocol redirect. ExoPlayer will not
follow this redirect in its default configuration, meaning playback will fail.
If you need to, you can configure ExoPlayer to follow cross-protocol redirects
when instantiating [`DefaultHttpDataSource.Factory`][] instances used in your
application. Learn about selecting and configuring the network stack
[here]({{ site.base_url }}/customization.html#configuring-the-network-stack).
#### Why do some streams fail with UnrecognizedInputFormatException? ####
This question relates to playback failures of the form:
```
UnrecognizedInputFormatException: None of the available extractors
(MatroskaExtractor, FragmentedMp4Extractor, ...) could read the stream.
```
There are two possible causes of this failure. The most common cause is that
you're trying to play DASH (mpd), HLS (m3u8) or SmoothStreaming (ism, isml)
content, but the player tries to play it as a progressive stream. To play such
streams, you must depend on the respective [ExoPlayer module][]. In cases where
the stream URI doesn't end with the standard file extension, you can also pass
`MimeTypes.APPLICATION_MPD`, `MimeTypes.APPLICATION_M3U8` or
`MimeTypes.APPLICATION_SS` to `setMimeType` of `MediaItem.Builder` to explicitly
specify the type of stream.
The second, less common cause, is that ExoPlayer does not support the container
format of the media that you're trying to play. In this case the failure is
working as intended, however feel free to submit a feature request to our
[issue tracker][], including details of the container format and a test stream.
Please search for an existing feature request before submitting a new one.
#### Why doesn't setPlaybackParameters work properly on some devices? ####
When running a debug build of your app on Android M and earlier, you may
experience choppy performance, audible artifacts and high CPU utilization when
using the [`setPlaybackParameters`][] API. This is because an optimization
that's important to this API is disabled for debug builds running on these
versions of Android.
It's important to note that this issue affects debug builds only. It does *not*
affect release builds, for which the optimization is always enabled. Hence the
releases you provide to end users should not be affected by this issue.
#### What do "Player is accessed on the wrong thread" errors mean? ####
See [A note on threading][] on the getting started page.
#### How can I fix "Unexpected status line: ICY 200 OK"? ####
This problem can occur if the server response includes an ICY status line,
rather than one that's HTTP compliant. ICY status lines are deprecated and
should not be used, so if you control the server you should update it to provide
an HTTP compliant response. If you're unable to do this then using the
[OkHttp extension][] will resolve the problem, since it's able to handle ICY
status lines correctly.
#### How can I query whether the stream being played is a live stream? ####
You can query the player's [`isCurrentWindowLive`][] method. In addition, you
can check [`isCurrentWindowDynamic`][] to find out whether the window is dynamic
(i.e., still updating over time).
#### How do I keep audio playing when my app is backgrounded? ####
There are a few steps that you need to take to ensure continued playback of
audio when your app is in the background:
1. You need to have a running [foreground service][]. This prevents the system
from killing your process to free up resources.
1. You need to hold a [`WifiLock`][] and a [`WakeLock`][]. These ensure that the
system keeps the WiFi radio and CPU awake. This can be easily done if using
[`ExoPlayer`][] by calling [`setWakeMode`][], which will automatically
acquire and release the required locks at the correct times.
It's important that you release the locks (if not using `setWakeMode`) and stop
the service as soon as audio is no longer being played.
#### Why does ExoPlayer support my content but the Cast extension doesn't? ####
It's possible that the content that you are trying to play is not
[CORS enabled][]. The [Cast framework][] requires content to be CORS enabled in
order to play it.
#### Why does content fail to play, but no error is surfaced? ####
It's possible that the device on which you are playing the content does not
support a specific media sample format. This can be easily confirmed by adding
an [`EventLogger`][] as a listener to your player, and looking for a line
similar to this one in Logcat:
```
[ ] Track:x, id=x, mimeType=mime/type, ... , supported=NO_UNSUPPORTED_TYPE
```
`NO_UNSUPPORTED_TYPE` means that the device is not able to decode the media
sample format specified by the `mimeType`. See the [Android media formats
documentation][] for information about supported sample formats. [How can I get
a decoding extension to load and be used for playback?] may also be useful.
#### How can I get a decoding extension to load and be used for playback? ####
* Most extensions have manual steps to check out and build the dependencies, so
make sure you've followed the steps in the README for the relevant extension.
For example, for the FFmpeg extension it's necessary to follow the
instructions in [extensions/ffmpeg/README.md][], including passing
configuration flags to [enable decoders][] for the format(s) you want to play.
* For extensions that have native code, make sure you're using the correct
version of the Android NDK as specified in the README, and look out for any
errors that appear during configuration and building. You should see `.so`
files appear in the `libs` subdirectory of the extension's path for each
supported architecture after following the steps in the README.
* To try out playback using the extension in the [demo application][], see
[enabling extension decoders][]. See the README for the extension for
instructions on using the extension from your own app.
* If you're using [`DefaultRenderersFactory`][], you should see an info-level
log line like "Loaded FfmpegAudioRenderer" in Logcat when the extension loads.
If that's missing, make sure the application has a dependency on the
extension.
* If you see warning-level logs from [`LibraryLoader`][] in Logcat, this
indicates that loading the native component of the extension failed. If this
happens, check you've followed the steps in the extension's README correctly
and that no errors were output while following the instructions.
If you're still experiencing problems using extensions, please check the
ExoPlayer [issue tracker][] for any relevant recent issues. If you need to file
a new issue and it relates to building the native part of the extension, please
include full command line output from running README instructions, to help us
diagnose the issue.
#### Can I play YouTube videos directly with ExoPlayer? ####
No, ExoPlayer cannot play videos from YouTube, i.e., urls of the form
`https://www.youtube.com/watch?v=...`. Instead, you should use the [YouTube
Android Player API](https://developers.google.com/youtube/android/player/) which
is the official way to play YouTube videos on Android.
#### Video playback is stuttering ###
The device may not be able to decode the content fast enough if, for example,
the content bitrate or resolution exceeds the device capabilities. You may need
to use lower quality content to obtain good performance on such devices.
If you're experiencing video stuttering on a device running Android 6 to 11,
particularly when playing DRM protected or high frame rate content, you can try
[enabling asynchronous buffer queueing].
[Fixing "Cleartext HTTP traffic not permitted" errors]: #fixing-cleartext-http-traffic-not-permitted-errors
[Fixing "SSLHandshakeException", "CertPathValidatorException" and "ERR_CERT_AUTHORITY_INVALID" errors]: #fixing-sslhandshakeexception-certpathvalidatorexception-and-err_cert_authority_invalid-errors
[What formats does ExoPlayer support?]: #what-formats-does-exoplayer-support
[Why are some media files not seekable?]: #why-are-some-media-files-not-seekable
[Why is seeking inaccurate in some MP3 files?]: #why-is-seeking-inaccurate-in-some-mp3-files
[Why is seeking in my video slow?]: #why-is-seeking-in-my-video-slow
[Why do some MPEG-TS files fail to play?]: #why-do-some-mpeg-ts-files-fail-to-play
[Why are subtitles not found in some MPEG-TS files?]: #why-are-subtitles-not-found-in-some-mpeg-ts-files
[Why do some MP4/FMP4 files play incorrectly?]: #why-do-some-mp4fmp4-files-play-incorrectly
[Why do some streams fail with HTTP response code 301 or 302?]: #why-do-some-streams-fail-with-http-response-code-301-or-302
[Why do some streams fail with UnrecognizedInputFormatException?]: #why-do-some-streams-fail-with-unrecognizedinputformatexception
[Why doesn't setPlaybackParameters work properly on some devices?]: #why-doesnt-setplaybackparameters-work-properly-on-some-devices
[What do "Player is accessed on the wrong thread" errors mean?]: #what-do-player-is-accessed-on-the-wrong-thread-errors-mean
[How can I fix "Unexpected status line: ICY 200 OK"?]: #how-can-i-fix-unexpected-status-line-icy-200-ok
[How can I query whether the stream being played is a live stream?]: #how-can-i-query-whether-the-stream-being-played-is-a-live-stream
[How do I keep audio playing when my app is backgrounded?]: #how-do-i-keep-audio-playing-when-my-app-is-backgrounded
[Why does ExoPlayer support my content but the Cast extension doesn't?]: #why-does-exoplayer-support-my-content-but-the-cast-extension-doesnt
[Why does content fail to play, but no error is surfaced?]: #why-does-content-fail-to-play-but-no-error-is-surfaced
[How can I get a decoding extension to load and be used for playback?]: #how-can-i-get-a-decoding-extension-to-load-and-be-used-for-playback
[Can I play YouTube videos directly with ExoPlayer?]: #can-i-play-youtube-videos-directly-with-exoplayer
[Video playback is stuttering]: #video-playback-is-stuttering
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/troubleshooting
[Supported formats]: {{ site.baseurl }}/supported-formats.html
[set on a `DefaultExtractorsFactory`]: {{ site.base_url }}/customization.html#customizing-extractor-flags
[`setMp3ExtractorFlags`]: {{ site.exo_sdk }}/extractor/DefaultExtractorsFactory#setMp3ExtractorFlags(@com.google.android.exoplayer2.extractor.mp4.Mp4Extractor.Flagsint)
[`FLAG_ENABLE_INDEX_SEEKING`]: {{ site.exo_sdk }}/extractor/mp3/Mp3Extractor.html#FLAG_ENABLE_INDEX_SEEKING
[intra-frame coding]: https://en.wikipedia.org/wiki/Intra-frame_coding
[pre-caching the data to disk]: https://exoplayer.dev/downloading-media.html
[`ExoPlayer.setSeekParameters]: {{ site.exo_sdk }}/ExoPlayer.html#setSeekParameters(com.google.android.exoplayer2.SeekParameters)
[`FLAG_DETECT_ACCESS_UNITS`]: {{ site.exo_sdk }}/extractor/ts/DefaultTsPayloadReaderFactory.html#FLAG_DETECT_ACCESS_UNITS
[`FLAG_ALLOW_NON_IDR_KEYFRAMES`]: {{ site.exo_sdk }}/extractor/ts/DefaultTsPayloadReaderFactory.html#FLAG_ALLOW_NON_IDR_KEYFRAMES
[`setTsExtractorFlags`]: {{ site.exo_sdk }}/extractor/DefaultExtractorsFactory#setTsExtractorFlags(@com.google.android.exoplayer2.extractor.mp4.Mp4Extractor.Flagsint)
[`Mp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS`]: {{ site.exo_sdk }}/extractor/mp4/Mp4Extractor.html#FLAG_WORKAROUND_IGNORE_EDIT_LISTS
[`FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS`]: {{ site.exo_sdk }}/extractor/mp4/FragmentedMp4Extractor.html#FLAG_WORKAROUND_IGNORE_EDIT_LISTS
[`setMp4ExtractorFlags`]: {{ site.exo_sdk }}/extractor/DefaultExtractorsFactory#setMp4ExtractorFlags(@com.google.android.exoplayer2.extractor.mp4.Mp4Extractor.Flagsint)
[`setFragmentedMp4ExtractorFlags`]: {{ site.exo_sdk }}/extractor/DefaultExtractorsFactory#setFragmentedMp4ExtractorFlags(@com.google.android.exoplayer2.extractor.mp4.Mp4Extractor.Flagsint)
[Wikipedia]: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
[wget]: https://www.gnu.org/software/wget/manual/wget.html
[`DefaultHttpDataSource.Factory`]: {{ site.exo_sdk }}/upstream/DefaultHttpDataSource.Factory.html
[ExoPlayer module]: {{ site.base_url }}/hello-world.html#add-exoplayer-modules
[issue tracker]: https://github.com/google/ExoPlayer/issues
[`isCurrentWindowLive`]: {{ site.exo_sdk }}/Player.html#isCurrentWindowLive()
[`isCurrentWindowDynamic`]: {{ site.exo_sdk }}/Player.html#isCurrentWindowDynamic()
[`setPlaybackParameters`]: {{ site.exo_sdk }}/Player.html#setPlaybackParameters(com.google.android.exoplayer2.PlaybackParameters)
[foreground service]: https://developer.android.com/guide/components/services.html#Foreground
[`WifiLock`]: {{ site.android_sdk }}/android/net/wifi/WifiManager.WifiLock.html
[`WakeLock`]: {{ site.android_sdk }}/android/os/PowerManager.WakeLock.html
[`ExoPlayer`]: {{ site.exo_sdk }}/ExoPlayer.html
[`setWakeMode`]: {{ site.exo_sdk }}/ExoPlayer.html#setWakeMode(int)
[A note on threading]: {{ site.base_url }}/hello-world.html#a-note-on-threading
[OkHttp extension]: {{ site.release_v2 }}/extensions/okhttp
[CORS enabled]: https://www.w3.org/wiki/CORS_Enabled
[Cast framework]: {{ site.google_sdk }}/cast/docs/chrome_sender/advanced#cors_requirements
[Android media formats documentation]: https://developer.android.com/guide/topics/media/media-formats#core
[extensions/ffmpeg/README.md]: {{ site.release_v2 }}/extensions/ffmpeg/README.md
[enable decoders]: {{ site.base_url }}/supported-formats.html#ffmpeg-extension
[demo application]: {{ site.base_url }}/demo-application.html
[enabling extension decoders]: {{ site.base_url }}/demo-application.html#enabling-extension-decoders
[`DefaultRenderersFactory`]: {{ site.exo_sdk }}/DefaultRenderersFactory.html
[`LibraryLoader`]: {{ site.exo_sdk }}/util/LibraryLoader.html
[`EventLogger`]: {{ site.baseurl }}/debug-logging.html
[enabling asynchronous buffer queueing]: {{ site.baseurl }}/customization.html#enabling-asynchronous-buffer-queueing
---
title: UI components
permalink: /ui-components.html
redirect_to:
- https://developer.android.com/media/media3/ui/playerview
---
This documentation may be out-of-date. Please refer to the
[documentation for the latest ExoPlayer release][] on developer.android.com.
{:.info}
An app playing media requires user interface components for displaying media and
controlling playback. The ExoPlayer library includes a UI module that contains
a number of UI components. To depend on the UI module add a dependency as shown
below.
~~~
implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X'
~~~
{: .language-gradle}
The most important component is `StyledPlayerView`, a view for media
playbacks. It displays video, subtitles and album art during playback, as
well as playback controls.
`StyledPlayerView` has a `setPlayer` method for attaching and detaching (by
passing `null`) player instances.
## StyledPlayerView ##
`StyledPlayerView` can be used for both video and audio playbacks. It renders
video and subtitles in the case of video playback, and can display artwork
included as metadata in audio files. You can include it in your layout files
like any other UI component. For example, a `StyledPlayerView` can be included
with the following XML:
~~~
<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:show_buffering="when_playing"
app:show_shuffle_button="true"/>
~~~
{: .language-xml}
The snippet above illustrates that `StyledPlayerView` provides several
attributes. These attributes can be used to customize the view's behavior, as
well as its look and feel. Most of these attributes have corresponding setter
methods, which can be used to customize the view at runtime. The
[`StyledPlayerView`][] Javadoc lists these attributes and setter methods in
more detail.
Once the view is declared in the layout file, it can be looked up in the
`onCreate` method of the activity:
~~~
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
playerView = findViewById(R.id.player_view);
}
~~~
{: .language-java}
When a player has been initialized, it can be attached to the view by calling
`setPlayer`:
~~~
// Instantiate the player.
player = new ExoPlayer.Builder(context).build();
// Attach player to the view.
playerView.setPlayer(player);
// Set the media item to be played.
player.setMediaItem(mediaItem);
// Prepare the player.
player.prepare();
~~~
{: .language-java}
### Choosing a surface type ###
The `surface_type` attribute of `StyledPlayerView` lets you set the type of
surface used for video playback. Besides the values `spherical_gl_surface_view`
(which is a special value for spherical video playback) and
`video_decoder_gl_surface_view` (which is for video rendering using extension
renderers), the allowed values are `surface_view`, `texture_view` and `none`. If
the view is for audio playback only, `none` should be used to avoid having to
create a surface, since doing so can be expensive.
If the view is for regular video playback then `surface_view` or `texture_view`
should be used. `SurfaceView` has a number of benefits over `TextureView` for
video playback:
* Significantly lower power consumption on many devices.
* More accurate frame timing, resulting in smoother video playback.
* Support for secure output when playing DRM protected content.
* The ability to render video content at the full resolution of the display on
Android TV devices that upscale the UI layer.
`SurfaceView` should therefore be preferred over `TextureView` where possible.
`TextureView` should be used only if `SurfaceView` does not meet your needs. One
example is where smooth animations or scrolling of the video surface is required
prior to Android N, as described below. For this case, it's preferable to use
`TextureView` only when [`SDK_INT`][] is less than 24 (Android N) and
`SurfaceView` otherwise.
`SurfaceView` rendering wasn't properly synchronized with view animations until
Android N. On earlier releases this could result in unwanted effects when a
`SurfaceView` was placed into scrolling container, or when it was subjected to
animation. Such effects included the view's contents appearing to lag slightly
behind where it should be displayed, and the view turning black when subjected
to animation. To achieve smooth animation or scrolling of video prior to Android
N, it's therefore necessary to use `TextureView` rather than `SurfaceView`.
{:.info}
Some Android TV devices run their UI layer at a resolution that's lower than the
full resolution of the display, upscaling it for presentation to the user. For
example, the UI layer may be run at 1080p on an Android TV that has a 4K
display. On such devices, `SurfaceView` must be used to render content at the
full resolution of the display. The full resolution of the display (in its
current display mode) can be queried using [`Util.getCurrentDisplayModeSize`][].
The UI layer resolution can be queried using Android's [`Display.getSize`] API.
{:.info}
### Overriding drawables ###
We don't guarantee that the customizations described in the following section
will continue to work in future versions of the library. The resource IDs may
change name, or some may be deleted entirely. This is indicated by the
[resource IDs being marked 'private'][].
{:.info}
`StyledPlayerView` uses `StyledPlayerControlView` to display the playback
controls and progress bar. The drawables used by `StyledPlayerControlView` can
be overridden by drawables with the same names defined in your application. See
the [`StyledPlayerControlView`][] Javadoc for a list of control drawables that
can be overridden.
## Further customization ##
Where customization beyond that described above is required, we expect that app
developers will implement their own UI components rather than use those provided
by ExoPlayer's UI module.
[documentation for the latest ExoPlayer release]: https://developer.android.com/guide/topics/media/exoplayer/ui-components
[`StyledPlayerView`]: {{ site.exo_sdk }}/ui/StyledPlayerView.html
[`StyledPlayerControlView`]: {{ site.exo_sdk }}/ui/StyledPlayerControlView.html
[resource IDs being marked 'private']: https://developer.android.com/studio/projects/android-library#PrivateResources
[`SDK_INT`]: {{ site.android_sdk }}/android/os/Build.VERSION.html#SDK_INT
[`Util.getCurrentDisplayModeSize`]: {{ site.exo_sdk }}/util/Util.html#getCurrentDisplayModeSize(android.content.Context)
[`Display.getSize`]: {{ site.android_sdk }}/android/view/Display.html#getSize(android.graphics.Point)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment