Home | History | Annotate | Download | only in media
      1 page.title=ExoPlayer
      2 page.tags="audio","video","adaptive","streaming","DASH","smoothstreaming"
      3 @jd:body
      4 
      5 <div id="qv-wrapper">
      6   <div id="qv">
      7     <h2>In this document</h2>
      8     <ol>
      9       <li><a href="#overview">Overview</a></li>
     10       <li><a href="#trackrenderer">TrackRenderer</a></li>
     11       <li><a href="#samplesource">SampleSource</a>
     12         <ol>
     13           <li><a href="#mediaextractor">Providing media using MediaExtractor</a></li>
     14           <li><a href="#adaptive-playback">Providing media for adaptive playback</a>
     15             <ol>
     16               <li><a href="#format-selection">Format selection for adaptive playback</a></li>
     17             </ol>
     18           </li>
     19         </ol>
     20       <li><a href="#events">Player Events</a>
     21         <ol>
     22           <li><a href="#high-events">High level events</a></li>
     23           <li><a href="#low-events">Low level events</a></li>
     24         </ol>
     25       </li>
     26       <li><a href="#sending-messages">Sending messages to components</a></li>
     27       <li><a href="#customizing">Customizing ExoPlayer</a>
     28         <ol>
     29           <li><a href="#custom-guidelines">Custom component guidelines</a></li>
     30         </ol>
     31       </li>
     32       <li><a href="#drm">Digital Rights Management</a></li>
     33     </ol>
     34     <h2>Key Classes</h2>
     35     <ol>
     36       <li>{@link android.media.MediaCodec}</li>
     37       <li>{@link android.media.MediaExtractor}</li>
     38       <li>{@link android.media.AudioTrack}</li>
     39     </ol>
     40     <h2>Related Samples</h2>
     41     <ol>
     42       <li><a class="external-link" href="https://github.com/google/ExoPlayer">
     43         ExoPlayer Project</a></li>
     44       <li><a class="external-link" href="http://google.github.io/ExoPlayer/doc/reference/packages.html">
     45         Class Reference</a></li>
     46     </ol>
     47   </div>
     48 </div>
     49 
     50 
     51 <p>Playing videos and music is a popular activity on Android devices. The Android framework
     52   provides {@link android.media.MediaPlayer} as a quick solution for playing media with minimal
     53   code, and the {@link android.media.MediaCodec} and {@link android.media.MediaExtractor} classes
     54   are provided for building custom media players. The open source project, ExoPlayer, is a
     55   solution between these two options, providing a pre-built player that you can extend.</p>
     56 
     57 <p>ExoPlayer supports features not currently provided by
     58   {@link android.media.MediaPlayer}, including Dynamic adaptive streaming
     59   over HTTP (DASH), SmoothStreaming, and persistent caching. ExoPlayer can be extended
     60   to handle additional media formats, and because you include it as part of your app code,
     61   you can update it along with your app.</p>
     62 
     63 <p>This guide describes how to use ExoPlayer for playing Android supported media formats, as well as
     64   DASH and SmoothStreaming playback. This guide also discusses ExoPlayer events, messages, DRM
     65   support and guidelines for customizing the player.</p>
     66 
     67 <p class="note">
     68   <strong>Note:</strong> ExoPlayer is an open source project that is not part of the Android
     69   framework and is distributed separately from the Android SDK. The project contains a library and
     70   a demo app that shows both simple and more advanced use of ExoPlayer:</p>
     71 
     72 <ul>
     73     <li><a class="external-link" href="https://github.com/google/ExoPlayer/tree/master/library">
     74       ExoPlayer Library</a> &mdash; This part of the project contains the core library classes.</li>
     75     <li><a class="external-link" href="https://github.com/google/ExoPlayer/tree/master/demo/src/main/java/com/google/android/exoplayer/demo/simple">
     76       Simple Demo</a> &mdash; This part of the app demonstrates a basic use of ExoPlayer.</li>
     77     <li><a class="external-link" href="https://github.com/google/ExoPlayer/tree/master/demo/src/main/java/com/google/android/exoplayer/demo/full">
     78       Full Demo</a> &mdash; This part of the app demonstrates more advanced features,
     79       including the ability to select between multiple audio tracks, a background audio mode,
     80       event logging and DRM protected playback. </li>
     81 </ul>
     82 
     83 
     84 <h2 id="overview">Overview</h2>
     85 
     86 <p>ExoPlayer is a media player built on top of the {@link android.media.MediaExtractor} and
     87   {@link android.media.MediaCodec} APIs released in Android 4.1 (API level 16). At the core of this
     88   library is the {@code ExoPlayer} class. This class maintains the players global state, but makes few
     89   assumptions about the nature of the media being played, such as how the media data is obtained,
     90   how it is buffered or its format. You inject this functionality through ExoPlayers {@code
     91   prepare()} method in the form of {@code TrackRenderer} objects.</p>
     92 
     93 <p>ExoPlayer provides default {@code TrackRenderer} implementations for audio and
     94   video, which make use of the {@link android.media.MediaCodec} and {@link android.media.AudioTrack}
     95   classes in the Android framework. Both renderers require a {@code SampleSource} object, from which
     96   they obtain individual media samples for playback. Figure 1 shows the high level object model for
     97   an ExoPlayer implementation configured to play audio and video using these components.</p>
     98 
     99 <img src="{@docRoot}images/exoplayer/object-model.png" alt="" id="figure1" />
    100 <p class="img-caption">
    101   <strong>Figure 1.</strong> High level object model for an ExoPlayer configured to play audio
    102   and video using {@code TrackRenderer} objects
    103 </p>
    104 
    105 
    106 <h2 id="trackrenderer">TrackRenderer</h2>
    107 
    108 <p>A {@code TrackRenderer} processes a component of media for playback, such as
    109   video, audio or text. The ExoPlayer class invokes methods on its {@code TrackRenderer} instances from a
    110   single playback thread, and by doing so causes each media component to be rendered as the global
    111   playback position is advanced. The ExoPlayer library provides {@code MediaCodecVideoTrackRenderer} as
    112   the default implementations rendering video and {@code MediaCodecAudioTrackRenderer} for audio.
    113   Both implementations make use of {@link android.media.MediaCodec} to decode individual media
    114   samples. They can handle all audio and video formats supported by a given Android device
    115   (see <a href="http://developer.android.com/guide/appendix/media-formats.html">Supported Media
    116   Formats</a> for details). The ExoPlayer library also provides an implementation for rendering
    117   text called {@code TextTrackRenderer}.
    118 </p>
    119 
    120 <p>The code example below outlines the main steps required to instantiate an ExoPlayer to play video
    121   and audio using the standard {@code TrackRenderer} implementations.</p>
    122 
    123 <pre>
    124 // 1. Instantiate the player.
    125 player = ExoPlayer.Factory.newInstance(RENDERER_COUNT);
    126 // 2. Construct renderers.
    127 MediaCodecVideoTrackRenderer videoRenderer = 
    128 MediaCodecAudioTrackRenderer audioRenderer = ...
    129 // 3. Inject the renderers through prepare.
    130 player.prepare(videoRenderer, audioRenderer);
    131 // 4. Pass the surface to the video renderer.
    132 player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE,
    133         surface);
    134 // 5. Start playback.
    135 player.setPlayWhenReady(true);
    136 ...
    137 player.release(); // Dont forget to release when done!
    138 </pre>
    139 
    140 <p>For a complete example, see the {@code SimplePlayerActivity} in the ExoPlayer demo app, which
    141   correctly manages an ExoPlayer instance with respect to both the {@link android.app.Activity} and
    142   {@link android.view.Surface} lifecycles.</p>
    143 
    144 
    145 <h2 id="samplesource">SampleSource</h2>
    146 
    147 <p>A standard {@code TrackRenderer} implementation requires a {@code SampleSource} to
    148   be provided in its constructor. A {@code SampleSource} object provides format information and
    149   media samples to be rendered. The ExoPlayer library provides {@code FrameworkSampleSource} and
    150   {@code ChunkSampleSource}. The {@code FrameworkSampleSource} class uses {@link
    151   android.media.MediaExtractor} to request, buffer and extract the media samples. The {@code
    152   ChunkSampleSource} class provides adaptive playback using DASH or SmoothStreaming, and
    153   implements networking, buffering and media extraction within the ExoPlayer library.</p>
    154 
    155 
    156 <h3 id="mediaextractor">Providing media using MediaExtractor</h3>
    157 
    158 <p>
    159   In order to render media formats supported by the Android framework, the {@code
    160   FrameworkSampleSource} class uses {@link android.media.MediaExtractor} for networking,
    161   buffering and sample extraction functionality. By doing so, it supports any media container format
    162   supported by the version of Android where it is running. For more information about media formats
    163   supported by Android, see <a href="{@docRoot}guide/appendix/media-formats.html">Supported
    164   Media Formats</a>.
    165 </p>
    166 
    167 <p>The diagram in Figure 2 shows the object model for an ExoPlayer implementation using
    168   {@code FrameworkSampleSource}.</p>
    169 
    170 <img src="{@docRoot}images/exoplayer/frameworksamplesource.png" alt="" id="figure2" />
    171 <p class="img-caption">
    172   <strong>Figure 2.</strong> Object model for an implementation of ExoPlayer that renders
    173   media formats supported by Android using {@code FrameworkSampleSource}
    174 </p>
    175 
    176 <p>The following code example outlines how the video and audio renderers are constructed to
    177   load the video from a specified URI.</p>
    178 
    179 <pre>
    180 FrameworkSampleSource sampleSource = new FrameworkSampleSource(
    181         activity, uri, null, 2);
    182 MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(
    183         sampleSource, null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0,
    184         mainHandler, playerActivity, 50);
    185 MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
    186         sampleSource, null, true);
    187 </pre>
    188 
    189 <p>The ExoPlayer demo app provides a complete implementation of this code in
    190   {@code DefaultRendererBuilder}. The {@code SimplePlaybackActivity} class uses it to play one
    191   of the videos available in the demo app. Note that in the example, video and audio
    192   are muxed, meaning they are streamed together from a single URI. The {@code FrameworkSampleSource}
    193   instance provides video samples to the {@code videoRenderer} object and audio samples to the
    194   {@code audioRenderer} object as they are extracted from the media container format. It is also
    195   possible to play demuxed media, where video and audio are streamed separately from different URIs.
    196   This functionality can be achieved by having two {@code FrameworkSampleSource} instances instead
    197   of one.</p>
    198 
    199 
    200 <h3 id="adaptive-playback">Providing media for adaptive playback</h3>
    201 
    202 <p>ExoPlayer supports adaptive streaming, which allows the quality of the
    203   media data to be adjusted during playback based on the network conditions. DASH
    204   and SmoothStreaming are examples of adaptive streaming technologies. Both these approaches
    205   load media in small chunks (typically 2 to 10 seconds in duration). Whenever a chunk of media
    206   is requested, the client selects from a number of possible formats. For example, a client may
    207   select a high quality format if network conditions are good, or a low quality format if network
    208   conditions are bad. In both techniques, video and audio are streamed separately.</p>
    209 
    210 <p>ExoPlayer supports adaptive playback through use of the {@code ChunkSampleSource} class,
    211   which loads chunks of media data from which individual samples can be extracted. Each {@code
    212   ChunkSampleSource} requires a {@code ChunkSource} object to be injected through its constructor,
    213   which is responsible for providing media chunks from which to load and read samples. The {@code
    214   DashMp4ChunkSource} and {@code SmoothStreamingChunkSource} classes provide DASH and SmoothStreaming
    215   playback using the FMP4 container format. The {@code DashWebMChunkSource} class uses the WebM
    216   container format to provide DASH playback.</p>
    217 
    218 <p>All of the standard {@code ChunkSource} implementations require a {@code FormatEvaluator} and
    219   a {@code DataSource} to be injected through their constructors. The {@code FormatEvaluator}
    220   objects select from the available formats before each chunk is loaded. The {@code DataSource}
    221   objects are responsible for actually loading the data. Finally, the {@code ChunkSampleSources}
    222   require a {@code LoadControl} object that controls the chunk buffering policy.</p>
    223 
    224 <p>The object model of an ExoPlayer configured for a DASH adaptive playback is shown in the
    225   diagram below. This example uses an {@code HttpDataSource} object to stream the media over the
    226   network. The video quality is varied at runtime using the adaptive implementation of {@code
    227   FormatEvaluator}, while audio is played at a fixed quality level.</p>
    228 
    229 <img src="{@docRoot}images/exoplayer/adaptive-streaming.png" alt="" id="figure3" />
    230 <p class="img-caption">
    231   <strong>Figure 3.</strong> Object model for a DASH adaptive playback using ExoPlayer
    232 </p>
    233 
    234 <p>The following code example outlines how the video and audio renderers are constructed.</p>
    235 
    236 <pre>
    237 Handler mainHandler = playerActivity.getMainHandler();
    238 LoadControl loadControl = new DefaultLoadControl(
    239         new BufferPool(BUFFER_SEGMENT_SIZE));
    240 BandwidthMeter bandwidthMeter = new BandwidthMeter();
    241 
    242 // Build the video renderer.
    243 DataSource videoDataSource = new HttpDataSource(userAgent,
    244         HttpDataSource.REJECT_PAYWALL_TYPES, bandwidthMeter);
    245 ChunkSource videoChunkSource = new DashMp4ChunkSource(videoDataSource,
    246         new AdaptiveEvaluator(bandwidthMeter), videoRepresentations);
    247 ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource,
    248         loadControl, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
    249 MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(
    250         videoSampleSource, null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
    251         0, mainHandler, playerActivity, 50);
    252 
    253 // Build the audio renderer.
    254 DataSource audioDataSource = new HttpDataSource(userAgent,
    255         HttpDataSource.REJECT_PAYWALL_TYPES, bandwidthMeter);
    256 ChunkSource audioChunkSource = new DashMp4ChunkSource(audioDataSource,
    257         new FormatEvaluator.FixedEvaluator(), audioRepresentation);
    258 SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource,
    259         loadControl, AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
    260 MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
    261         audioSampleSource, null, true);
    262 </pre>
    263 
    264 <p>In this code, {@code videoRepresentations} and {@code audioRepresentation} are {@code
    265   Representation} objects, each of which describes one of the available media streams. In the DASH
    266   model, these streams are parsed from a media presentation description (MPD) file. The ExoPlayer
    267   library provides a {@code MediaPresentationDescriptionParser} class to obtain {@code
    268   Representation} objects from MPD files.</p>
    269 
    270 <p class="note">
    271   <strong>Note:</strong> Building Representation objects from MPD files is not required. You can
    272   build Representation objects from other data sources if necessary.
    273 </p>
    274 
    275 <p>The ExoPlayer demo app provides complete implementation of this code in
    276   {@code DashVodRendererBuilder}. The {@code SimplePlaybackActivity} class uses this builder to
    277   construct renderers for playing DASH sample videos in the demo app. It asynchronously fetches a
    278   specified MPD file in order to construct the required {@code Representation} objects. For an
    279   equivalent SmoothStreaming example, see the {@code SmoothStreamingRendererBuilder} class in the
    280   demo app.</p>
    281 
    282 
    283 <h4 id="format-selection">Format selection for adaptive playback</h4>
    284 
    285 <p>For DASH and SmoothStreaming playback, consider both static format selection at the
    286   start of playback and dynamic format selection during playback. Static format selection should be
    287   used to filter out formats that should not be used throughout the playback, for example formats
    288   with resolutions higher than the maximum supported by the playback device. Dynamic selection varies
    289   the selected format during playback, typically to adapt video quality in response to changes in
    290   network conditions.</p>
    291 
    292 <h5 id="static-selection">Static format selection</h5>
    293 
    294 <p>When preparing a player, you should consider filtering out some of the available formats if
    295   they are not useable for playback. Static format selection allows you to filter out
    296   formats that cannot be used on a particular device or are not compatible with your player.
    297   For audio playback, this often means picking a single format to play and discarding the others.</p>
    298 
    299 <p>For video playback, filtering formats can be more complicated. Apps should first
    300   eliminate any streams that whose resolution is too high to be played by the device. For H.264,
    301   which is normally used for DASH and SmoothStreaming playback, ExoPlayers {@code MediaCodecUtil}
    302   class provides a {@code maxH264DecodableFrameSize()} method that can be used to determine what
    303   resolution streams the device is able to handle, as shown in the following code example:</p>
    304 
    305 <pre>
    306 int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
    307 Format format = representation.format;
    308 if (format.width * format.height &lt;= maxDecodableFrameSize) {
    309   // The device can play this stream.
    310   videoRepresentations.add(representation);
    311 } else {
    312   // The device isn't capable of playing this stream.
    313 }
    314 </pre>
    315 
    316 <p>This approach is used to filter {@code Representations} in the {@code DashVodRendererBuilder}
    317   class of the ExoPlayer demo app, and similarly to filter track indices in {@code
    318   SmoothStreamingRendererBuilder}.</p>
    319 
    320 <p>In addition to eliminating unsupported formats, it should be noted that the ability to
    321   seamlessly switch between H.264 streams of different resolution is an optional decoder feature
    322   available in Android 4.3 (API level 16) and higher, and so is not supported by all devices. The
    323   availability of an adaptive H.264 decoder can be queried using {@code MediaCodecUtil}, as shown in
    324   the following code example:</p>
    325 
    326 <pre>
    327 boolean isAdaptive = MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264).adaptive;
    328 </pre>
    329 
    330 <p>The {@code MediaCodecVideoTrackRenderer} class is still able to handle resolution changes on
    331   devices that do not have adaptive decoders, however the switch is not seamless. Typically, the
    332   switch creates a small discontinuity in visual output lasting around 50-100ms. For devices that
    333   do not provide an adaptive decoder, app developers may choose to adapt between formats at
    334   a single fixed resolution so as to avoid discontinuities. The ExoPlayer demo app
    335   implementation does not pick a fixed resolution.</p>
    336 
    337 
    338 <h5 id="dynamic-selection">Dynamic format selection</h5>
    339 
    340 <p>During playback, you can use a {@code FormatEvaluator} to dynamically select from the
    341   available video formats. The ExoPlayer library provides a {@code FormatEvaluator.Adaptive}
    342   implementation for dynamically selecting between video formats based on the current network
    343   conditions.</p>
    344 
    345 <p>This class provides a simple, general purpose reference implementation, however you are
    346   encouraged to write your own {@code FormatEvaluator} implementation to best suit your particular
    347   needs.</p>
    348 
    349 
    350 <h2 id="events">Player Events</h2>
    351 
    352 <p>During playback, your app can listen for events generated by the ExoPlayer that indicate the
    353   overall state of the player. These events are useful as triggers for updating the app user
    354   interface such as playback controls. Many ExoPlayer components also report their own component
    355   specific low level events, which can be useful for performance monitoring.</p>
    356 
    357 
    358 <h3 id="high-events">High level events</h3>
    359 
    360 <p>ExoPlayer allows instances of {@code ExoPlayer.Listener} to be added and removed using its
    361   {@code addListener()} and {@code removeListener()} methods. Registered listeners are notified of
    362   changes in playback state, as well as when errors occur that cause playback to fail. For more
    363   information about the valid playback states and the possible transitions between them, see the
    364   ExoPlayer source code.</p>
    365 
    366 <p>Developers who implement custom playback controls should register a listener and use it to
    367   update their controls as the players state changes. An app should also show an
    368   appropriate error to the user if playback fails.</p>
    369 
    370 <h3 id="low-events">Low level events</h3>
    371 
    372 <p>In addition to high level listeners, many of the individual components provided by the
    373   ExoPlayer library allow their own event listeners. For example, {@code
    374   MediaCodecVideoTrackRenderer} has constructors that take a {@code
    375   MediaCodecVideoTrackRenderer.EventListener}. In the ExoPlayer demo app, {@code SimplePlayerActivity}
    376   acts as a listener so that it can adjust the dimensions of the target surface to have the correct
    377   height and width ratio for the video being played:</p>
    378 
    379 <pre>
    380 &#64;Override
    381 public void onVideoSizeChanged(int width, int height) {
    382   surfaceView.setVideoWidthHeightRatio(height == 0 ? 1 : (float) width / height);
    383 }
    384 </pre>
    385 
    386 <p>The {@code RendererBuilder} classes in the ExoPlayer demo app inject the activity as the
    387   listener, for example in the {@code DashVodRendererBuilder} class:</p>
    388 
    389 <pre>
    390 MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(
    391         videoSampleSource, null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
    392         0, <strong>mainHandler, playerActivity</strong>, 50);
    393 </pre>
    394 
    395 <p>Note that you must pass a {@link android.os.Handler} object to the renderer, which determines
    396   the thread on which the listeners methods are invoked. In most cases, you should use a
    397   {@link android.os.Handler} associated with the apps main thread, as is the case in this example.
    398   </p>
    399 
    400 <p>Listening to individual components can be useful for adjusting UI based on player events, as
    401   in the example above. Listening to component events can also be helpful for logging performance
    402   metrics. For example, {@code MediaCodecVideoTrackRenderer} notifies its listener of dropped video
    403   frames. A developer may wish to log such metrics to track playback performance in their
    404   app.</p>
    405 
    406 <p>Many components also notify their listeners when errors occur. Such errors may or may not
    407   cause playback to fail. If an error does not cause playback to fail, it may still result in
    408   degraded performance, and so you may wish to log all errors in order to track playback
    409   performance. Note that an ExoPlayer instance always notifies its high level listeners of errors that
    410   cause playback to fail, in addition to the listener of the individual component from which the error
    411   originated. Hence, you should display error messages to users only from high level listeners.
    412   Within individual component listeners, you should use error notifications only for informational
    413   purposes.</p>
    414 
    415 
    416 <h2 id="sending-messages">Sending messages to components</h2>
    417 
    418 <p>Some ExoPlayer components allow changes in configuration during playback. By convention, you make
    419   these changes by passing asynchronous messages through the ExoPlayer to the component.
    420   This approach ensures both thread safety and that the configuration change is
    421   executed in order with any other operations being performed on the player.</p>
    422 
    423 <p>The most common use of messaging is passing a target surface to
    424   {@code MediaCodecVideoTrackRenderer}:</p>
    425 
    426 <pre>
    427 player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE,
    428         surface);
    429 </pre>
    430 
    431 <p>Note that if the surface needs to be cleared because
    432   {@link android.view.SurfaceHolder.Callback#surfaceDestroyed
    433   SurfaceHolder.Callback.surfaceDestroyed()} has been invoked, then you must send this
    434   message using the blocking variant of {@code sendMessage()}:</p>
    435 <p>
    436 
    437 <pre>
    438 player.blockingSendMessage(videoRenderer,
    439         MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, null);
    440 </pre>
    441 
    442 <p>You must use a blocking message because the contract of {@link
    443   android.view.SurfaceHolder.Callback#surfaceDestroyed surfaceDestroyed()} requires that the
    444   app does not attempt to access the surface after the method returns. The {@code
    445   SimplePlayerActivity} class in the demo app demonstrates how the surface should be set and
    446   cleared.</p>
    447 
    448 
    449 <h2 id="customizing">Customizing ExoPlayer</h2>
    450 
    451 <p>One of the main benefits of ExoPlayer over {@link android.media.MediaPlayer} is the ability to
    452   customize and extend the player to better suit the developers use case. The ExoPlayer library
    453   is designed specifically with this in mind, defining a number of abstract base classes and
    454   interfaces that make it possible for app developers to easily replace the default implementations
    455   provided by the library. Here are some use cases for building custom components:</p>
    456 
    457 <ul>
    458   <li><strong>{@code TrackRenderer}</strong> - You may want to implement a custom
    459     {@code TrackRenderer} to handle media types other than audio and video. The {@code
    460     TextTrackRenderer} class within the ExoPlayer library is an example of how to implement a
    461     custom renderer. You could use the approach it demonstrates to render custom
    462     overlays or annotations. Implementing this kind of functionality as a {@code TrackRenderer}
    463     makes it easy to keep the overlays or annotations in sync with the other media being played.</li>
    464   <li><strong>{@code SampleSource}</strong> - If you need to support a container format not
    465     already handled by {@link android.media.MediaExtractor} or ExoPlayer, consider implementing a
    466     custom {@code SampleSource} class.</li>
    467   <li><strong>{@code FormatEvaluator}</strong> - The ExoPlayer library provides {@code
    468     FormatEvaluator.Adaptive} as a simple reference implementation that switches between different
    469     quality video formats based on the available bandwidth. App developers are encouraged to
    470     develop their own adaptive {@code FormatEvaluator} implementations, which can be designed to
    471     suit their use specific needs.</li>
    472   <li><strong>{@code DataSource}</strong> - ExoPlayers upstream package already contains a
    473     number of {@code DataSource} implementations for different use cases, such as writing and
    474     reading to and from a persistent media cache. You may want to implement you own
    475     {@code DataSource} class to load data in another way, such as a custom
    476     protocol or HTTP stack for data input.</li>
    477 </ul>
    478 
    479 
    480 <h3 id="custom-guidelines">Custom component guidelines</h3>
    481 
    482 <p>If a custom component needs to report events back to the app, we recommend that you
    483   do so using the same model as existing ExoPlayer components, where an event listener is passed
    484   together with a {@link android.os.Handler} to the constructor of the component.</p>
    485 
    486 <p>We recommended that custom components use the same model as existing ExoPlayer components to
    487   allow reconfiguration by the app during playback, as described in
    488   <a href="#sending-messages">Sending messages to components</a>.
    489   To do this, you should implement a {@code ExoPlayerComponent} and receive
    490   configuration changes in its {@code handleMessage()} method. Your app should pass
    491   configuration changes by calling ExoPlayers {@code sendMessage()} and {@code
    492   blockingSendMessage()} methods.</p>
    493 
    494 
    495 <h2 id="drm">Digital Rights Management</h2>
    496 
    497 <p>On Android 4.3 (API level 18) and higher, ExoPlayer supports Digital Rights Managment (DRM)
    498   protected playback. In order to play DRM protected content with ExoPlayer, your app must
    499   inject a {@code DrmSessionManager} into the {@code MediaCodecVideoTrackRenderer} and {@code
    500   MediaCodecAudioTrackRenderer} constructors. A {@code DrmSessionManager} object is responsible for
    501   providing the {@code MediaCrypto} object required for decryption, as well as ensuring that the
    502   required decryption keys are available to the underlying DRM module being used.</p>
    503 
    504 <p>The ExoPlayer library provides a default implementation of {@code DrmSessionManager}, called
    505   {@code StreamingDrmSessionManager}, which uses {@link android.media.MediaDrm}. The session
    506   manager supports any DRM scheme for which a modular DRM component exists on the device. All
    507   Android devices are required to support Widevine modular DRM (with L3 security, although many
    508   devices also support L1). Some devices may support additional schemes such as PlayReady.</p>
    509 
    510 <p>The {@code StreamingDrmSessionManager} class requires a {@code MediaDrmCallback} to be
    511   injected into its constructor, which is responsible for actually making provisioning and key
    512   requests. You should implement this interface to make network requests to your license
    513   server and obtain the required keys. The {@code WidevineTestMediaDrmCallback} class in the
    514   ExoPlayer demo app sends requests to a Widevine test server.</p>
    515