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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:layoutDirection="ltr"
    android:background="#CC000000"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:paddingTop="4dp"
        android:orientation="horizontal">

        <!--<ImageButton android:id="@id/exo_prev"-->
            <!--style="@style/ExoMediaButton.Previous"/>-->

        <ImageButton android:id="@id/exo_rew"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton.Rewind"/>

        <ImageButton android:id="@id/exo_shuffle"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton.Shuffle"/>

        <ImageButton android:id="@id/exo_repeat_toggle"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton"/>

        <ImageButton android:id="@id/exo_play"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton.Play"/>

        <ImageButton android:id="@id/exo_pause"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton.Pause"/>

        <ImageButton android:id="@id/exo_ffwd"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton.FastForward"/>

        <!--<ImageButton android:id="@id/exo_next"-->
            <!--style="@style/ExoMediaButton.Next"/>-->

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <TextView android:id="@id/exo_position"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="14sp"
            android:textStyle="bold"
            android:paddingLeft="4dp"
            android:paddingRight="4dp"
            android:includeFontPadding="false"
            android:textColor="#FF00A6FF"/>

        <com.google.android.exoplayer2.ui.DefaultTimeBar
            android:id="@id/exo_progress"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="26dp"/>

        <TextView android:id="@id/exo_duration"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="14sp"
            android:textStyle="bold"
            android:paddingLeft="4dp"
            android:paddingRight="4dp"
            android:includeFontPadding="false"
            android:textColor="#FF00A6FF"/>

    </LinearLayout>

</LinearLayout>

activity_player.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="#000000">
    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/video_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:controller_layout_id="@layout/custom_playback_control"/>
</FrameLayout>

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

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <color name="colorPrimary">#FFFFFF</color>
  <color name="colorPrimaryDark">#000000</color>
  <color name="colorAccent">#FF4081</color>
</resources>

strings.xml

<resources>
  <string name="app_name">ExoPlayer code lab</string>
  <string name="player_activity_name">Media player</string>
  <string name="media_url_mp3">http://storage.googleapis.com/exoplayer-test-media-0/play.mp3</string>
  <string name="media_url_mp4">http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4</string>
  <string name="media_url_dash"><![CDATA[http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0]]></string>
  <string name="logo">Google logo</string>
</resources>

styles.xml

<resources>

  <style name="PlayerTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="android:windowBackground">@null</item>
  </style>

</resources>

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()
    }
}

No comments:

Post a Comment

Note: only a member of this blog may post a comment.

How to extract filename from Uri?

Now, we can extract filename with and without extension :) You will convert your bitmap to uri and get the real path of your file. Now w...