278 lines
12 KiB
Markdown
278 lines
12 KiB
Markdown
---
|
||
title: Customization
|
||
---
|
||
|
||
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 ###
|
||
|
||
ExoPlayer supports Android's default network stack, as well as Cronet and
|
||
OkHttp. In each case it's possible to customize the network stack for your use
|
||
case. The following example shows how to customize the player to use Android's
|
||
default network stack with cross-protocol redirects enabled:
|
||
|
||
~~~
|
||
// Build a HttpDataSource.Factory with cross-protocol redirects enabled.
|
||
HttpDataSource.Factory httpDataSourceFactory =
|
||
new DefaultHttpDataSource.Factory().setAllowCrossProtocolRedirects(true);
|
||
|
||
// Wrap the HttpDataSource.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, httpDataSourceFactory);
|
||
|
||
// Inject the DefaultDataSourceFactory when creating the player.
|
||
ExoPlayer player =
|
||
new ExoPlayer.Builder(context)
|
||
.setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory))
|
||
.build();
|
||
~~~
|
||
{: .language-java}
|
||
|
||
The same approach can be used to configure and inject `HttpDataSource.Factory`
|
||
implementations provided by the [Cronet extension] and the [OkHttp extension],
|
||
depending on your preferred choice of network stack.
|
||
|
||
### 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(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(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.
|
||
|
||
## 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.
|
||
|
||
[Cronet extension]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/cronet
|
||
[OkHttp extension]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/okhttp
|
||
[LoadErrorHandlingPolicy]: {{ site.exo_sdk }}/upstream/LoadErrorHandlingPolicy.html
|
||
[media source based playlist API]: {{ site.baseurl }}/media-sources.html#media-source-based-playlist-api
|
||
|