androidvideo-streamingexoplayer2.xvideoquality

Android Exoplayer - Different URLs for different video quality and change manually


I am using exoplayer in my application. I am using it to play video from a url. What i am trying to do is that i have three different urls for high,medium and low quality of same the video, and i would like to let the user to be able to change the video quality manually.

{
     "lowQualityUrl":"string url",
     "mediumQualityUrl":"string url",
     "highQualityUrl":"string url"
}

In JWplayer there is an option to add different sources/url for different qualities. Is there something similar that can be done in exoplayer...?

Edit : I don't want to play videos one after another. I just want to switch to a different quality of the same video, like in youtube. But instead of using a single url for the source, what i have are 3 different urls for 3 qualities(low,medium,high) of the same video.


Solution

  • I found a solution or rather a workaround for the issue. I am using the exoplayer inside a recylcerview. This code might need some optimization.

    I got this idea from another answer which was under this question,which had this github link, but i think the author deleted it. What i did was create a class for keeping all the urls for a particular video. Then showing a Spinner above the exoplayer, and when user selects a particular quality then i prepare the exoplayer with the new URL, and then seekto to the previously playing position. You can ignore the StringUtils methods.

    VideoPlayerConfig.kt

    object VideoPlayerConfig {
    //Minimum Video you want to buffer while Playing
    val MIN_BUFFER_DURATION = 3000
    //Max Video you want to buffer during PlayBack
    val MAX_BUFFER_DURATION = 5000
    //Min Video you want to buffer before start Playing it
    val MIN_PLAYBACK_START_BUFFER = 1500
    //Min video You want to buffer when user resumes video
    val MIN_PLAYBACK_RESUME_BUFFER = 5000
    } 
    

    VideoQuality.kt

    You can change this class according to your need. I need to store exactly 3 urls for low,medium and high quality urls. And i needed to show them in that order as well in the spinner.

    class VideoQuality {
    private val videoQualityUrls = HashMap<String, String>()
    
    companion object {
        val LOW = getStringResource(R.string.low)
        val MEDIUM = getStringResource(R.string.medium)
        val HIGH = getStringResource(R.string.high)
    }
    
    val qualityArray
        get() = arrayListOf<String>().apply {
            if (hasQuality(LOW)) add(LOW)
            if (hasQuality(MEDIUM)) add(MEDIUM)
            if (hasQuality(HIGH)) add(HIGH)
        }
    var defaultVideoQuality: String? = HIGH
    
    var lowQuality: String?
        set(value) {
            setVideoQualityUrl(LOW, value)
        }
        get() = videoQualityUrls[LOW] ?: ""
    var mediumQuality: String?
        set(value) {
            setVideoQualityUrl(MEDIUM, value)
        }
        get() = videoQualityUrls[MEDIUM] ?: ""
    var highQuality: String?
        set(value) {
            setVideoQualityUrl(HIGH, value)
        }
        get() = videoQualityUrls[HIGH] ?: ""
    
    private fun setVideoQualityUrl(quality: String?, url: String?) {
        if (url != null && quality != null) {
            videoQualityUrls[quality] = url
        }
    }
    
    private fun hasQuality(quality: String?): Boolean {
        if (videoQualityUrls[quality] == null) {
            return false
        }
        return true
    }
    
    fun getVideoQualityUrl(quality: String?): String? {
        return videoQualityUrls[quality]
    }
    }
    

    Methods to implement for the exoplayer

    private fun initializePlayer() {
        if (exoPlayer == null) {
    
            val loadControl = DefaultLoadControl.Builder()
                .setBufferDurationsMs(2 * VideoPlayerConfig.MIN_BUFFER_DURATION, 2 * VideoPlayerConfig.MAX_BUFFER_DURATION, VideoPlayerConfig.MIN_PLAYBACK_START_BUFFER, VideoPlayerConfig.MIN_PLAYBACK_RESUME_BUFFER)
                .createDefaultLoadControl()
            //Create a default TrackSelector
            val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory()
            val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
            exoPlayer = ExoPlayerFactory.newSimpleInstance(itemView.context, DefaultRenderersFactory(itemView.context), trackSelector, loadControl)
            exoPlayer!!.addListener(PlayEventListener())
            val videoQualityInfo:VideoQuality = videoListVideoDataHolderData!!.videoQualityUrls //Just an object that i created and stored in a dataHolder for this view.
            val url = videoQualityInfo.getVideoQualityUrl(videoQualityInfo.defaultVideoQuality) ?: ""
            preparePlayer(url)
        }
    }
    
    private fun preparePlayer(url: String) {
    
        if (url.isNotEmpty()) {
            val mediaSource = buildMediaSource(StringUtils.makeHttpUrl(url))
            exoPlayer?.prepare(mediaSource)
            videoView.player = exoPlayer
        } else {
            Log.d(APPTAG, "NO DEFAULT URL")
        }
    }
    private fun buildMediaSource(url: String): ProgressiveMediaSource {
        val mUri: Uri = Uri.parse(url)
        val dataSourceFactory = DefaultDataSourceFactory(
            itemView.context,
            Util.getUserAgent(itemView.context, getStringResource(R.string.app_name))
        )
        val videoSource = ProgressiveMediaSource.Factory(dataSourceFactory)
            .createMediaSource(mUri)
        return videoSource
    }
    

    And then in the Spinner/QualitySelector's OnItemSelectedListener

    videoQualitySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onNothingSelected(parent: AdapterView<*>?) {
    
            }
    
            override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
                val currentTime = exoPlayer?.currentPosition
                val isReadyToPlay = exoPlayer?.playWhenReady
                val urlToBuild = when (videoQualityUrls.qualityArray[position]) {
                    VideoQuality.LOW -> videoQualityUrls.lowQuality
                    VideoQuality.MEDIUM -> videoQualityUrls.mediumQuality
                    else -> videoQualityUrls.highQuality
                }
                Log.d(APPTAG, "VIDEO DETAILS :::: ${currentTime} ${isReadyToPlay} ${urlToBuild}")
                if (!urlToBuild.isNullOrEmpty()) {
                    val mediaSource = buildMediaSource(StringUtils.makeHttpUrl(urlToBuild))
                    exoPlayer?.prepare(mediaSource)
                    exoPlayer?.playWhenReady = isReadyToPlay ?: false
                    exoPlayer?.seekTo(currentTime ?: 0)
                }
    
            }
        }