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

Custom Search

Search the entire web

Custom Search