This recipe shows how to create a video player using ExoPlayer. ExoPlayer is the de-facto video player for Android, and is capable of playing pretty much anything. You can use it to play on or offline files, or streaming formats.
1. Include ExoPlayer
In the root build.gradle
:
repositories {
google()
jcenter()
}
Then in the module build.gradle
:
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
}
dependencies {
...
implementation 'com.google.android.exoplayer:exoplayer:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
// And for each specific format you want to use:
implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.X.X'
}
2. Creating a video player
Creating a video player using a media source is easy if you have a MediaSource
:
fun createVideoPlayer(context: Context, uri: String) {
val player = SimpleExoPlayer.Builder(requireContext())
.setUseLazyPreparation(true)
.build()
// Magic... read on below
val mediaSource = ....?
player.prepare(mediaSource)
return player
}
How a MediaSource should be created depends on your usecase, and is described a few sections below.
3. Using the video player
In you layout, use something like the snippet below. There are smaller versions (e.g. without the AspectRatioFrameLayout
) but this one covers a common usecase where the video must appear in a specifically sized view, similar to the centerCrop
scale type for ImageViews.
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:hide_on_touch="false"
app:show_timeout="0"
app:use_artwork="false"
app:use_controller="false">
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout
android:id="@+id/video_framelayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
</com.google.android.exoplayer2.ui.SimpleExoPlayerView>
Alternative to a SurfaceView
you may also use a TextureView
if you need more control over the view.
Next, create a video player using the createVideoPlayer
method above, and attach it to the view:
val player = createVideoPlayer(context, uri)
// Configure it, for example:
player_view.useController = false
player_view.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
player_view.player = player
player.setVideoSurfaceView(surface_view)
And start it:
player.playWhenReady = true
You may also use a listener to detect the state of the video, like so:
player.addListener(object : Player.EventListener {
override fun onLoadingChanged(isLoading: Boolean) {
// Note: this method may be called many times, every time the buffer
// is loading
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
if (playbackState == Player.STATE_ENDED) {
// the video has ended
} else if (playbackState == Player.STATE_READY) {
thumbnail_placeholder.visibility = View.GONE
}
}
})
4a. Creating a media source from difference sources
The snippets below create a MediaSource
from different video file sources.
These can be used for either online video files or local ones.
From a URL
private fun getOnlineMediaSource(context: Context, uri: String): ProgressiveMediaSource {
val factory = DefaultDataSourceFactory(
context,
Util.getUserAgent(context, context.getString(R.string.user_agent))
)
return ProgressiveMediaSource
.Factory(factory)
.createMediaSource(Uri.parse(uri))
}
From the /res/raw
folder
private fun createProgressiveMediaSource(
dataSource: DataSource,
dataSpec: DataSpec
): ProgressiveMediaSource {
dataSource.open(dataSpec)
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
val dataFactory = DataSource.Factory { dataSource }
return ProgressiveMediaSource
.Factory(dataFactory)
.setExtractorsFactory(extractorsFactory)
.createMediaSource(dataSource.uri)
}
fun createRawMediaSource(
context: Context,
@RawRes rawRes: Int
) : ProgressiveMediaSource {
val dataSpec = DataSpec(RawResourceDataSource.buildRawResourceUri(R.raw.sample_video))
val dataSource = RawResourceDataSource(context)
return createProgressiveMediaSource(dataSource, dataSpec)
}
From a local file
This method requries the createProgressiveMediaSource
method from the snippets above.
fun createFileMediaSource(
context: Context,
@RawRes rawRes: Int
) : ProgressiveMediaSource {
val dataSpec = DataSpec(localUri)
val dataSource = FileDataSourceFactory().createDataSource()
return createProgressiveMediaSource(dataSource, dataSpec)
}
4b. Creating a media source for streaming video
When loading a streaming video instead of a file, you’ll need to create a media source fit for the stream type.
HLS
HLS is a common streaming format. Depending on its configuration it has a 3 to 30 second buffer, which makes it suitable for live streaming videos but less suitable for video calling.
An HLS stream URL typically sends with .m3u8
.
private fun getHlsMediaSource(
context: Context,
uri: String
) : MediaSource {
val dataSourceFactory = DefaultHttpDataSourceFactory(
Util.getUserAgent(
context,
context.getString(R.string.user_agent)
)
)
return HlsMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(uri))
}
RTMP
An RTMP stream typically starts with rtmp://
.
private fun getRtmpMediaSource(uri: String): MediaSource {
val rtmpDataSourceFactory = RtmpDataSourceFactory()
return ProgressiveMediaSource
.Factory(rtmpDataSourceFactory)
.createMediaSource(Uri.parse(uri))
}