Android Exoplayer v2 simple example
The dependencies
implementation 'com.google.android.exoplayer:exoplayer-core:2.10.6' implementation 'com.google.android.exoplayer:exoplayer-dash:2.10.6' implementation 'com.google.android.exoplayer:exoplayer-ui:2.10.6' implementation 'com.google.android.exoplayer:exoplayer-hls:2.10.6'
The layout files
custom_playback_controller.xml
activity_player.xml
The Kotlin class files
ComponentListener.kt
import android.util.Log
import com.google.android.exoplayer2.Format
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.analytics.AnalyticsListener
import com.google.android.exoplayer2.audio.AudioRendererEventListener
import com.google.android.exoplayer2.decoder.DecoderCounters
import com.google.android.exoplayer2.video.VideoRendererEventListener
class ComponentListener : Player.EventListener, VideoRendererEventListener, AudioRendererEventListener, AnalyticsListener {
private val TAG = "ComponentListener"
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
val stateString: String
when (playbackState) {
Player.STATE_IDLE -> stateString = "ExoPlayer.STATE_IDLE -"
Player.STATE_BUFFERING -> stateString = "ExoPlayer.STATE_BUFFERING -"
Player.STATE_READY -> stateString = "ExoPlayer.STATE_READY -"
Player.STATE_ENDED -> stateString = "ExoPlayer.STATE_ENDED -"
else -> stateString = "UNKNOWN_STATE -"
}
Log.d(TAG, "changed state to $stateString playWhenReady: $playWhenReady")
}
// Implementing VideoRendererEventListener.
override fun onVideoEnabled(counters: DecoderCounters) {
// Do nothing.
}
override fun onVideoDecoderInitialized(decoderName: String, initializedTimestampMs: Long, initializationDurationMs: Long) {
// Do nothing.
}
override fun onVideoInputFormatChanged(format: Format) {
// Do nothing.
}
override fun onDroppedFrames(count: Int, elapsedMs: Long) {
// Do nothing.
}
override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
// Do nothing.
}
override fun onVideoDisabled(counters: DecoderCounters) {
// Do nothing.
}
// Implementing AudioRendererEventListener.
override fun onAudioEnabled(counters: DecoderCounters) {
// Do nothing.
}
override fun onAudioSessionId(audioSessionId: Int) {
// Do nothing.
}
override fun onAudioDecoderInitialized(decoderName: String, initializedTimestampMs: Long, initializationDurationMs: Long) {
// Do nothing.
}
override fun onAudioInputFormatChanged(format: Format) {
// Do nothing.
}
override fun onAudioSinkUnderrun(bufferSize: Int, bufferSizeMs: Long, elapsedSinceLastFeedMs: Long) {
// Do nothing.
}
override fun onAudioDisabled(counters: DecoderCounters) {
// Do nothing.
}
}
PlayerActivity.kt
import android.annotation.SuppressLint
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import android.view.View
import com.example.exoplayer.MainActivity.Companion.MEDIA_URI
import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.DefaultRenderersFactory
import com.google.android.exoplayer2.ExoPlayerFactory
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.audio.AudioRendererEventListener
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.source.dash.DashMediaSource
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
import com.google.android.exoplayer2.util.Util
import com.google.android.exoplayer2.video.VideoRendererEventListener
/**
* A fullscreen activity to play audio or video streams.
*/
class PlayerActivity : AppCompatActivity() {
companion object {
private val BANDWIDTH_METER = DefaultBandwidthMeter()
}
private var playerView: PlayerView? = null
private lateinit var player: SimpleExoPlayer
private var mediaUri = ""
private var playbackPosition: Long = 0
private var currentWindow: Int = 0
private var playWhenReady = true
private var componentListener: ComponentListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
mediaUri = intent.getStringExtra(MEDIA_URI)
if (mediaUri.isEmpty()) {
AlertDialog.Builder(this)
.setTitle("Invalid media uri")
.setMessage("The uri is empty, please provide a valid media uri")
.setPositiveButton("OK") { _, _ ->
finish()
}
.show()
}
playerView = findViewById(R.id.video_view)
componentListener = ComponentListener()
}
public override fun onStart() {
super.onStart()
if (Util.SDK_INT > 23) {
initializePlayer(mediaUri)
}
}
public override fun onResume() {
super.onResume()
hideSystemUi()
if (Util.SDK_INT <= 23 || !::player.isInitialized) {
initializePlayer(mediaUri)
}
}
public override fun onPause() {
super.onPause()
if (Util.SDK_INT <= 23 && ::player.isInitialized) {
releasePlayer()
}
}
public override fun onStop() {
super.onStop()
if (Util.SDK_INT > 23 && ::player.isInitialized) {
releasePlayer()
}
}
private fun initializePlayer(uri: String) {
if (uri.isEmpty()) {
return
}
if (!::player.isInitialized) {
// a factory to create an AdaptiveVideoTrackSelection
val adaptiveTrackSelectionFactory = AdaptiveTrackSelection.Factory(BANDWIDTH_METER)
player = ExoPlayerFactory.newSimpleInstance(
this,
DefaultRenderersFactory(this),
DefaultTrackSelector(adaptiveTrackSelectionFactory),
DefaultLoadControl())
player.addListener(componentListener)
player.addAnalyticsListener(componentListener)
player.addAnalyticsListener(componentListener)
}
playerView?.player = player
player.playWhenReady = playWhenReady
player.seekTo(currentWindow, playbackPosition)
val mediaSource = buildMediaSource(Uri.parse(uri))
player.prepare(mediaSource, true, false)
}
private fun releasePlayer() {
playbackPosition = player.currentPosition
currentWindow = player.currentWindowIndex
playWhenReady = player.playWhenReady
player.removeListener(componentListener)
player.setVideoListener(null)
player.removeAnalyticsListener(componentListener)
player.removeAnalyticsListener(componentListener)
player.release()
}
private fun buildMediaSource(uri: Uri): MediaSource {
val userAgent = "exoplayer-codelab"
if (uri.getLastPathSegment().contains("mp3") || uri.getLastPathSegment().contains("mp4")) {
return ProgressiveMediaSource.Factory(DefaultHttpDataSourceFactory(userAgent))
.createMediaSource(uri)
} else if (uri.getLastPathSegment().contains("m3u8")) {
return HlsMediaSource.Factory(DefaultHttpDataSourceFactory(userAgent))
.createMediaSource(uri)
} else {
val dashChunkSourceFactory = DefaultDashChunkSource.Factory(
DefaultHttpDataSourceFactory("ua", BANDWIDTH_METER))
val manifestDataSourceFactory = DefaultHttpDataSourceFactory(userAgent)
return DashMediaSource.Factory(dashChunkSourceFactory, manifestDataSourceFactory).createMediaSource(uri)
}
}
@SuppressLint("InlinedApi")
private fun hideSystemUi() {
playerView!!.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
or View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
}
}
The resource files
colors.xml
#FFFFFF #000000 #FF4081
strings.xml
ExoPlayer code lab Media player http://storage.googleapis.com/exoplayer-test-media-0/play.mp3 http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4 Google logo
styles.xml
Player activity class field variables, these class field variables are used within the player class.
private var playerView: PlayerView? = null private var player: SimpleExoPlayer? = null private var playbackPosition: Long = 0 private var currentWindow: Int = 0 private var playWhenReady = true private var componentListener: ComponentListener? = null
Initialize the playerView and componentListener in the onCreate function.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
playerView = findViewById(R.id.video_view)
componentListener = ComponentListener()
}
The function for initializing the Exoplayer
private fun initializePlayer(uri: String) {
if (uri.isEmpty()) {
return
}
if (!::player.isInitialized) {
// a factory to create an AdaptiveVideoTrackSelection
val adaptiveTrackSelectionFactory = AdaptiveTrackSelection.Factory(BANDWIDTH_METER)
player = ExoPlayerFactory.newSimpleInstance(
this,
DefaultRenderersFactory(this),
DefaultTrackSelector(adaptiveTrackSelectionFactory),
DefaultLoadControl())
player.addListener(componentListener)
player.addAnalyticsListener(componentListener)
player.addAnalyticsListener(componentListener)
}
playerView?.player = player
player.playWhenReady = playWhenReady
player.seekTo(currentWindow, playbackPosition)
val mediaSource = buildMediaSource(Uri.parse(uri))
player.prepare(mediaSource, true, false)
}
The function for releasing the Exoplayer.
private fun releasePlayer() {
playbackPosition = player.currentPosition
currentWindow = player.currentWindowIndex
playWhenReady = player.playWhenReady
player.removeListener(componentListener)
player.setVideoListener(null)
player.removeAnalyticsListener(componentListener)
player.removeAnalyticsListener(componentListener)
player.release()
}
The function for building the media source to be played in the Exoplayer.
private fun buildMediaSource(uri: Uri): MediaSource {
val userAgent = "exoplayer-codelab"
if (uri.getLastPathSegment().contains("mp3") || uri.getLastPathSegment().contains("mp4")) {
return ProgressiveMediaSource.Factory(DefaultHttpDataSourceFactory(userAgent))
.createMediaSource(uri)
} else if (uri.getLastPathSegment().contains("m3u8")) {
return HlsMediaSource.Factory(DefaultHttpDataSourceFactory(userAgent))
.createMediaSource(uri)
} else {
val dashChunkSourceFactory = DefaultDashChunkSource.Factory(
DefaultHttpDataSourceFactory("ua", BANDWIDTH_METER))
val manifestDataSourceFactory = DefaultHttpDataSourceFactory(userAgent)
return DashMediaSource.Factory(dashChunkSourceFactory, manifestDataSourceFactory).createMediaSource(uri)
}
}
The activity life cycle functions (onStart and onResume) that calls the function for initializing the exoplayer.
public override fun onStart() {
super.onStart()
if (Util.SDK_INT > 23) {
initializePlayer(mediaUri)
}
}
public override fun onResume() {
super.onResume()
hideSystemUi()
if (Util.SDK_INT <= 23 || !::player.isInitialized) {
initializePlayer(mediaUri)
}
}
The activity life cycle functions (onPause and onStop) that calls the function for releasing the exoplayer.
public override fun onPause() {
super.onPause()
if (Util.SDK_INT <= 23 && ::player.isInitialized) {
releasePlayer()
}
}
public override fun onStop() {
super.onStop()
if (Util.SDK_INT > 23 && ::player.isInitialized) {
releasePlayer()
}
}
Complete example in Github
References:
https://codelabs.developers.google.com/codelabs/exoplayer-intro/#0
https://github.com/google/ExoPlayer
Search within Codexpedia
Search the entire web