Wednesday, 20 March 2019

How to download a video while playing it, using ExoPlayer?

Background

I'm working on an app that can play some short videos.

I want to avoid accessing the Internet every time the user plays them, to make it faster and to lower the data usage.

The problem

Currently I've only found how to either play or download (it's just a file so I could download it like any other file).

Here's the code of playing a video file from URL (sample available here):

gradle

...
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.exoplayer:exoplayer-core:2.8.4'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.8.4'
...

manifest

<manifest package="com.example.user.myapplication" xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <application
        android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"
        tools:ignore="AllowBackup,GoogleAppIndexingWarning">
        <activity
            android:name=".MainActivity" android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

activity_main.xml

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" tools:context=".MainActivity">

    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/playerView" android:layout_width="match_parent" android:layout_height="match_parent"
        app:resize_mode="zoom"/>
</FrameLayout>

MainActivity.kt

class MainActivity : AppCompatActivity() {
    private var player: SimpleExoPlayer? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onStart() {
        super.onStart()
        playVideo()
    }

    private fun playVideo() {
        player = ExoPlayerFactory.newSimpleInstance(this@MainActivity, DefaultTrackSelector())
        playerView.player = player
        player!!.addVideoListener(object : VideoListener {
            override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
            }

            override fun onRenderedFirstFrame() {
                Log.d("appLog", "onRenderedFirstFrame")
            }
        })
        player!!.addListener(object : PlayerEventListener() {
            override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
                super.onPlayerStateChanged(playWhenReady, playbackState)
                when (playbackState) {
                    Player.STATE_READY -> Log.d("appLog", "STATE_READY")
                    Player.STATE_BUFFERING -> Log.d("appLog", "STATE_BUFFERING")
                    Player.STATE_IDLE -> Log.d("appLog", "STATE_IDLE")
                    Player.STATE_ENDED -> Log.d("appLog", "STATE_ENDED")
                }
            }
        })
        player!!.volume = 0f
        player!!.playWhenReady = true
        player!!.repeatMode = Player.REPEAT_MODE_ALL
        player!!.playVideoFromUrl(this@MainActivity, "https://sample-videos.com/video123/mkv/720/big_buck_bunny_720p_1mb.mkv")
    }

    override fun onStop() {
        super.onStop()
        playerView.player = null
        player!!.release()
        player = null
    }


    abstract class PlayerEventListener : Player.EventListener {
        override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {}
        override fun onSeekProcessed() {}
        override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {}
        override fun onPlayerError(error: ExoPlaybackException?) {}
        override fun onLoadingChanged(isLoading: Boolean) {}
        override fun onPositionDiscontinuity(reason: Int) {}
        override fun onRepeatModeChanged(repeatMode: Int) {}
        override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {}
        override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {}
        override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {}
    }

    companion object {
        @JvmStatic
        fun getUserAgent(context: Context): String {
            val packageManager = context.packageManager
            val info = packageManager.getPackageInfo(context.packageName, 0)
            val appName = info.applicationInfo.loadLabel(packageManager).toString()
            return Util.getUserAgent(context, appName)
        }
    }

    fun SimpleExoPlayer.playVideoFromUri(context: Context, uri: Uri) {
        val dataSourceFactory = DefaultDataSourceFactory(context, MainActivity.getUserAgent(context))
        val mediaSource = ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri)
        prepare(mediaSource)
    }


    fun SimpleExoPlayer.playVideoFromUrl(context: Context, url: String) = playVideoFromUri(context, Uri.parse(url))

    fun SimpleExoPlayer.playVideoFile(context: Context, file: File) = playVideoFromUri(context, Uri.fromFile(file))
}

What I've tried

I've tried reading on the docs, and got those links (by asking about it here ) :

https://medium.com/google-exoplayer/downloading-streams-6d259eec7f95 https://medium.com/google-exoplayer/downloading-adaptive-streams-37191f9776e

So sadly, currently the only solution I can come up with, is to download the file on another thread, which will cause the device to have 2 connections to it, thus using twice the bandwidth.

The questions

  1. How can I use ExoPlayer to play a video file, while also downloading it to some filepath ?
  2. Is there a way to enable a caching mechanism (which uses the disk) on ExoPlayer to be activated for the exact same purpose?

Note: To make it clear. I do not want to download the file and only then play it.


EDIT: I've found a way to get&use the file from the API's cache (wrote about it here), but it appears that this is considered as unsafe (written here).

So, given the simple cache mechanism that the API of ExoPlayer supports, my current questions are:

  1. If a file was cached, how can I use it in a safe manner?
  2. If a file was partially cached (meaning we've downloaded a part of it), how can I continue preparing it (without actually playing it or waiting for the whole playback to finish) till I can use it (in a safe manner of course) ?

I've made a Github repository for this here. You can try it out.



from How to download a video while playing it, using ExoPlayer?

No comments:

Post a Comment