Basic Use of ExoPlayer and Code Behind the Scenes

When first using ExoPlayer by following the sample code, seeing it convert a simple URL to a playing movie is exciting. Seems some magic happened. But what happened in the whole process, how does ExoPlayer made it? Now let’s try to find the answer.

Ignore the advanced feature of ExoPlayer like DRM stuff, first, let’s use the simplest code to play a video on your mobile device and see what happened behind the scenes.

Here is the basic code of playing a movie, given a URL, ExoPlayer can make it play on your device:

Declare a PlayerView in a layout file to display the movie:

1
<com.google.android.exoplayer2.ui.PlayerView
2
  xmlns:android="http://schemas.android.com/apk/res/android"
3
  android:id="@+id/player_view"
4
  android:layout_width="match_parent"
5
  android:layout_height="match_parent"
6
  android:keepScreenOn="true" />

Set up ExoPlayer in the code:

1
private val contentUrl = "https://tungsten.aaplimg.com/VOD/bipbop_adv_fmp4_example/master.m3u8"
2
3
private val mBandwidthMeter = DefaultBandwidthMeter()
4
5
private var mediaDataSourceFactory = DefaultDataSourceFactory(context, Util.getUserAgent(context, context.getString(R.string.app_name)), mBandwidthMeter)
6
7
private var player: SimpleExoPlayer? = null 
8
9
fun initPlayer(context: Context, playerView: PlayerView) {
10
    // Create a default track selector.
11
    val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(mBandwidthMeter)
12
    val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
13
14
    // Create a player instance.
15
    player = ExoPlayerFactory.newSimpleInstance(context, trackSelector)
16
17
    // Bind the player to the view.
18
    playerView.player = player    
19
    // This is the MediaSource representing the content media (i.e. not the ad).
20
    val contentMediaSource = buildMediaSource(Uri.parse(contentUrl))
21
    
22
    // Prepare the player with the source.
23
    player?.seekTo(contentPosition)
24
    player?.prepare(contentMediaSource)
25
26
    // play the movie if player is ready to play
27
    player?.playWhenReady = true 
28
    }
29
30
private fun buildMediaSource(uri: Uri): MediaSource {
31
    val type = Util.inferContentType(uri)
32
    return when (type) {
33
        C.TYPE_DASH -> DashMediaSource.Factory(
34
                DefaultDashChunkSource.Factory(mediaDataSourceFactory),
35
                manifestDataSourceFactory)
36
                .createMediaSource(uri)
37
        C.TYPE_SS -> SsMediaSource.Factory(
38
                DefaultSsChunkSource.Factory(mediaDataSourceFactory), manifestDataSourceFactory)
39
                .createMediaSource(uri)
40
        C.TYPE_HLS -> HlsMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri)
41
        C.TYPE_OTHER -> ExtractorMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri)
42
        else -> throw IllegalStateException("Unsupported type: $type")
43
    }
44
}
45
46
fun release() {
47
    player?.release()
48
    player = null
49
}

Done, this can be said the least amount of code to play a movie. Here below is a general impression of classes mentioned in the code above, they work together to present a movie on the screen:

1.PlayerView

A high-level view for Player media playbacks extends FrameLayout, It displays video, subtitles and album art during playback, and displays playback controls using a PlayerControlView.

It includes another class named AspectRatioFrameLayout which can resize itself to match a specified aspect ratio, eg 16:9 or 4:3.

A PlayerView holds an instance of View to display a content stream, this view can be three types, SurfaceView, TextureView or SphericalSurfaceView. By default, it’s set to SurfaceView.

2.DefaultBandwidthmeter

We can treat this class as an estimator of network speed, it estimates bandwidth by listening to data transfer. The bandwidth estimate is calculated using SlidingPercentile and is updated each time a transfer ends.

The initial estimate is based on the current operator’s network country code or the locale of the user, as well as the network connection type.

3.AdaptiveTrackSelection

A bandwidth based adaptive TrackSelection, whose selected track is updated to be the one of highest quality given the current network conditions and the state of the buffer, this class contains the strategy of track selection, the strategy will change according to the BandwidthMeter passed to it.

4.DefaultTrackSelector

TrackSelector is the component of an ExoPlayer responsible for selecting tracks to be consumed by each of the player’s renderers. DefaultTrackSelector is default implementation of TrackSelector, track selections are made according to configurable Parameters.

TubiPlayer don’t have manually selection of different video sources now, if we want to add this option for users, set parameter to this class.

5.DefaultDataSourceFactory

DataSource is a component from which streams of data can be read, it is defined as an interface in the ExoPlayer project,

DefaultDataSource is a DataSource that supports multiple URI schemes,

Seen from the name, DefaultDataSoueceFactory is a factory that produces DefaultDataSource instances that delegate to DefaultHttpDataSources for non-file/asset/content URIs.

Notice when creating an instance of DefaultDataSourceFactory, an instance of BandwidthMeter should be passed to it and the BandwidthMeter should be the same as the parameter passed to AdaptiveTrackSelectior.

6.MediaSource

MediaSource defines and provides media to be played by an ExoPlayer. A MediaSource has two main responsibilities:

​ a. To provide the player with a Timeline defining the structure of its media, and to provide a new timeline whenever the structure of the media changes.

​ b. To provide MediaPeriod instances for the periods in its timeline.

The ExoPlayer library modules provide default implementations for regular media files like ExtractorMediaSource, DASH (DashMediaSource), SmoothStreaming (SsMediaSource), HLS (HlsMediaSource), and an implementation for loading single media samples (singleSampleMediaSource), that’s most often used for side-loaded subtitle files, and implementations for building more complex MediaSources from simpler ones MergingMediaSource, ConcatenatingMediaSource, LoopingMediaSource and ClippingMediaSource.

7.SimpleExoPlayer

To better understand SimpleExoPlayer, it’s better to start with two interfaces related to it:

Player is a media player interface defining traditional high-level functionality, such as the ability to play, pause, seek and query properties of the currently playing media.

ExoPlayer is a subclass of Interface Player, it’s an extensible media player that plays MediaSources. ExoPlayer is designed to make few assumptions about (and hence impose few restrictions on) the type of the 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 it’s prepared for playback.

Components common to all ExoPlayer implementations are:

MediaSource

Defines the media to be played, loads the media, and from which the loaded media can be read. A MediaSource is injected via prepare(MediaSource)} at the start of playback.

Renderer

Renders individual components of the media. The library provides default implementations for common media types MediaCodecVideoRenderer , MediaCodecAudioRenderer, TextRenderer and MetadataRenderer. A Renderer consumes media from the MediaSource being played.

TrackSelector

Selects tracks provided by the MediaSource to be consumed by each of the available Renderers. The library provides a default implementation DefaultTrackSelector suitable for most use cases.

LoadControl

Controls when the MediaSource buffers more media, and how much media is buffered. The library provides a default implementation DefaultLoadControl suitable for most use cases.

BasePlayer

An abstract class which implements common implementation independent methods.

SimpleExoPlayer

A subclass of BasePlayer and implements interface ExoPlayer. In other words, it’s an implementation that uses default Render components.

Among those classes, the leading actor is absolutely SimpleExoPlayer, it has key control to play a movie, you may ask: How does ExoPlayer make a movie present on the screen?

Here below are three important steps during the whole process of play a movie:

1.SimpleExoPlayer.prepare(contentMediaSource)

For SimpleExiPlayer, many player related work is done by another class ExoPlayerImpl, and ExoPlayerImpl will use class ExoPlayerImplInternl to implements internal behavior,

Taking HlsmediaSource as an example, let’s see what happeened after SimpleExoPlayer.prepare(contentMediaSource) was called,main methods are executed in the following order:

1
SimpleExoPlayer.prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) 
2
→ ExoPlayerImpl.prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) 
3
→ ExoPlayerImplInternal.prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) 
4
→ ExoPlayerImpllInternal.handleMessage(Message msg)
5
→ ExoPlayerImplInternal.prepareInternal(MediaSource mediaSource, boolean resetPosition, boolean resetState) 
6
→ HlsMediaSource.prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource, TransferListener mediaTransferListener) 
7
→ DefaultHlsPlayListTracker.start(Uri inirialPlaylistUri, EventDispatcher eventDispatcher, PrimaryplaylistListener )
8
→ Loader.srtaetLoading(T loadable, Callback<T> callback, int defaultMinRetryCount) 
9
→ LoadTask.start(long delayMillis) 
10
→ Loadtask.run() 
11
→ ParsingLoadable.load() 
12
→ HlsPlaylistparser.parse(Uri uri, InputStream inputStream)

Finally, the handler at ExoPlayerimplInternal will receive message MSG_DO_SOME_WORK. Function doSomeWork() will be called.

If playbackInfo.playbackState == Player.STATE_BUFFERING and shouldTransitionToReadyState, state of playbackInfo will be set to Player.STATE_READY.

2.SimExoPlayer.playWhenReady = true

This is the action to tell player start playback if ready to go, methods are executed in the following order:

1
SimplyExoPlayer.setPlayWhenReady(boolean playWhenReady) 
2
→ ExoPlayerImpl.setPlayWhenReady(boolean playWhenReady, boolean suppressPlayback) 
3
→ ExoPlayerImpllnternal.setPlayWhenReady(boolean playWhenReady) 
4
→ ExoPlayerImpllnternal.setPlayWhenReadyInternal(boolean playWhenReady)

if playbackInfo.playbackstate == READY, function startRenders() and doSomeWork() will be called, you should be able to see the movie already playing now.

if playbackInfo.playbackstate == Buffering, function doSomeWork() will be called and continuing buffer video contents.

3.SimExoPlayer.release

After the movie is played, the resources should be released in time. Methods are executed in the following order:

1
ExoPlayerImpl.release() 
2
→ExoPlayerImpl.release() 
3
→ExoPlayerImplInternal.releaseInternal() 
4
→ExoPlayerImplInternal.disableRender(Render render), MediaSource.releaseSource(), loadControl.onReleased()