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