title: How to download a video while playing it, using ExoPlayer?
body: <h2>Background</h2>
  
  <p>I'm working on an app that can play some short videos.</p>
  
  <p>I want to avoid accessing the Internet every time the user plays them, to make it faster and to lower the data usage.</p>
  
  <h2>The problem</h2>
  
  <p>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). </p>
  
  <p>Here's the code of playing a video file from URL (sample available <strong>here</strong>):</p>
  
  <p><strong>gradle</strong></p>
  
  <pre><code>...
  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'
  ...
  </code></pre>
  
  <p><strong>manifest</strong></p>
  
  <pre><code>&lt;manifest package="com.example.user.myapplication" xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"&gt;
  
      &lt;uses-permission android:name="android.permission.INTERNET"/&gt;
      &lt;uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/&gt;
  
      &lt;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"&gt;
          &lt;activity
              android:name=".MainActivity" android:screenOrientation="portrait"&gt;
              &lt;intent-filter&gt;
                  &lt;action android:name="android.intent.action.MAIN"/&gt;
  
                  &lt;category android:name="android.intent.category.LAUNCHER"/&gt;
              &lt;/intent-filter&gt;
          &lt;/activity&gt;
      &lt;/application&gt;
  
  &lt;/manifest&gt;
  </code></pre>
  
  <p><strong>activity_main.xml</strong></p>
  
  <pre><code>&lt;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"&gt;
  
      &lt;com.google.android.exoplayer2.ui.PlayerView
          android:id="@+id/playerView" android:layout_width="match_parent" android:layout_height="match_parent"
          app:resize_mode="zoom"/&gt;
  &lt;/FrameLayout&gt;
  </code></pre>
  
  <p><strong>MainActivity.kt</strong></p>
  
  <pre><code>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 -&gt; Log.d("appLog", "STATE_READY")
                      Player.STATE_BUFFERING -&gt; Log.d("appLog", "STATE_BUFFERING")
                      Player.STATE_IDLE -&gt; Log.d("appLog", "STATE_IDLE")
                      Player.STATE_ENDED -&gt; 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))
  }
  </code></pre>
  
  <h2>What I've tried</h2>
  
  <p>I've tried reading on the docs, and got those links (by asking about it <a href="https://github.com/google/ExoPlayer/issues/4650" rel="noreferrer"><strong>here</strong></a> ) :</p>
  
  <p><a href="https://medium.com/google-exoplayer/downloading-streams-6d259eec7f95" rel="noreferrer">https://medium.com/google-exoplayer/downloading-streams-6d259eec7f95</a>
  <a href="https://medium.com/google-exoplayer/downloading-adaptive-streams-37191f9776e" rel="noreferrer">https://medium.com/google-exoplayer/downloading-adaptive-streams-37191f9776e</a></p>
  
  <p>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.</p>
  
  <h2>The questions</h2>
  
  <ol>
  <li>How can I use ExoPlayer to play a video file, while also downloading it to some filepath ?</li>
  <li>Is there a way to enable a caching mechanism (which uses the disk) on ExoPlayer to be activated for the exact same purpose?</li>
  </ol>
  
  <p>Note: To make it clear. I do not want to download the file and only then play it. </p>
  
  <hr>
  
  <p>EDIT: I've found a way to get&amp;use the file from the API's cache (wrote about it <a href="https://stackoverflow.com/a/53704530/878126"><strong>here</strong></a>), but it appears that this is considered as unsafe (written <a href="https://github.com/google/ExoPlayer/issues/5569#issuecomment-470081053" rel="noreferrer"><strong>here</strong></a>). </p>
  
  <p>So, given the simple cache mechanism that the API of ExoPlayer supports, my current questions are: </p>
  
  <ol>
  <li>If a file was cached, how can I use it in a safe manner?</li>
  <li>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) ? </li>
  </ol>
  
  <p>I've made a Github repository for this <a href="https://github.com/AndroidDeveloperLB/ExoPlayerCacheSample" rel="noreferrer"><strong>here</strong></a>. You can try it out.</p>