1--- 2title: Downloading media 3--- 4 5ExoPlayer provides functionality to download media for offline playback. In most 6use cases it's desirable for downloads to continue even when your app is in the 7background. For these use cases your app should subclass `DownloadService`, and 8send commands to the service to add, remove and control the downloads. The 9diagram below shows the main classes that are involved. 10 11{% include figure.html url="/images/downloading.svg" index="1" caption="Classes 12for downloading media. The arrow directions indicate the flow of data." 13width="85%" %} 14 15* `DownloadService`: Wraps a `DownloadManager` and forwards commands to it. The 16 service allows the `DownloadManager` to keep running even when the app is in 17 the background. 18* `DownloadManager`: Manages multiple downloads, loading (and storing) their 19 states from (and to) a `DownloadIndex`, starting and stopping downloads based 20 on requirements such as network connectivity, and so on. To download the 21 content, the manager will typically read the data being downloaded from a 22 `HttpDataSource`, and write it into a `Cache`. 23* `DownloadIndex`: Persists the states of the downloads. 24 25## Creating a DownloadService ## 26 27To create a `DownloadService`, you need to subclass it and implement its 28abstract methods: 29 30* `getDownloadManager()`: Returns the `DownloadManager` to be used. 31* `getScheduler()`: Returns an optional `Scheduler`, which can restart the 32 service when requirements needed for pending downloads to progress are met. 33 ExoPlayer provides these implementations: 34 * `PlatformScheduler`, which uses [JobScheduler][] (Minimum API is 21). See 35 the [PlatformScheduler][] javadocs for app permission requirements. 36 * `WorkManagerScheduler`, which uses [WorkManager][]. 37* `getForegroundNotification()`: Returns a notification to be displayed when the 38 service is running in the foreground. You can use 39 `DownloadNotificationHelper.buildProgressNotification` to create a 40 notification in default style. 41 42Finally, you need to define the service in your `AndroidManifest.xml` file: 43 44~~~ 45<service android:name="com.myapp.MyDownloadService" 46 android:exported="false"> 47 <!-- This is needed for Scheduler --> 48 <intent-filter> 49 <action android:name="com.google.android.exoplayer.downloadService.action.RESTART"/> 50 <category android:name="android.intent.category.DEFAULT"/> 51 </intent-filter> 52</service> 53~~~ 54{: .language-xml} 55 56See [`DemoDownloadService`][] and [`AndroidManifest.xml`][] in the ExoPlayer 57demo app for a concrete example. 58 59## Creating a DownloadManager ## 60 61The following code snippet demonstrates how to instantiate a `DownloadManager`, 62which can be returned by `getDownloadManager()` in your `DownloadService`: 63 64~~~ 65// Note: This should be a singleton in your app. 66databaseProvider = new StandaloneDatabaseProvider(context); 67 68// A download cache should not evict media, so should use a NoopCacheEvictor. 69downloadCache = new SimpleCache( 70 downloadDirectory, 71 new NoOpCacheEvictor(), 72 databaseProvider); 73 74// Create a factory for reading the data from the network. 75dataSourceFactory = new DefaultHttpDataSource.Factory(); 76 77// Choose an executor for downloading data. Using Runnable::run will cause each download task to 78// download data on its own thread. Passing an executor that uses multiple threads will speed up 79// download tasks that can be split into smaller parts for parallel execution. Applications that 80// already have an executor for background downloads may wish to reuse their existing executor. 81Executor downloadExecutor = Runnable::run; 82 83// Create the download manager. 84downloadManager = new DownloadManager( 85 context, 86 databaseProvider, 87 downloadCache, 88 dataSourceFactory, 89 downloadExecutor); 90 91// Optionally, setters can be called to configure the download manager. 92downloadManager.setRequirements(requirements); 93downloadManager.setMaxParallelDownloads(3); 94~~~ 95{: .language-java} 96 97See [`DemoUtil`][] in the demo app for a concrete example. 98 99The example in the demo app also imports download state from legacy `ActionFile` 100instances. This is only necessary if your app used `ActionFile` prior to 101ExoPlayer 2.10.0. 102{:.info} 103 104## Adding a download ## 105 106To add a download you need to create a `DownloadRequest` and send it to your 107`DownloadService`. For adaptive streams `DownloadHelper` can be used to help 108build a `DownloadRequest`, as described [further down this page][]. The example 109below shows how to create a download request: 110 111~~~ 112DownloadRequest downloadRequest = 113 new DownloadRequest.Builder(contentId, contentUri).build(); 114~~~ 115{: .language-java} 116 117where `contentId` is a unique identifier for the content. In simple cases, the 118`contentUri` can often be used as the `contentId`, however apps are free to use 119whatever ID scheme best suits their use case. `DownloadRequest.Builder` also has 120some optional setters. For example, `setKeySetId` and `setData` can be used to 121set DRM and custom data that the app wishes to associate with the download, 122respectively. The content's MIME type can also be specified using `setMimeType`, 123as a hint for cases where the content type cannot be inferred from `contentUri`. 124 125Once created, the request can be sent to the `DownloadService` to add the 126download: 127 128~~~ 129DownloadService.sendAddDownload( 130 context, 131 MyDownloadService.class, 132 downloadRequest, 133 /* foreground= */ false) 134~~~ 135{: .language-java} 136 137where `MyDownloadService` is the app's `DownloadService` subclass, and the 138`foreground` parameter controls whether the service will be started in the 139foreground. If your app is already in the foreground then the `foreground` 140parameter should normally be set to `false`, since the `DownloadService` will 141put itself in the foreground if it determines that it has work to do. 142 143## Removing downloads ## 144 145A download can be removed by sending a remove command to the `DownloadService`, 146where `contentId` identifies the download to be removed: 147 148~~~ 149DownloadService.sendRemoveDownload( 150 context, 151 MyDownloadService.class, 152 contentId, 153 /* foreground= */ false) 154~~~ 155{: .language-java} 156 157You can also remove all downloaded data with 158`DownloadService.sendRemoveAllDownloads`. 159 160## Starting and stopping downloads ## 161 162A download will only progress if four conditions are met: 163 164* The download doesn't have a stop reason. 165* Downloads aren't paused. 166* The requirements for downloads to progress are met. Requirements can specify 167 constraints on the allowed network types, as well as whether the device should 168 be idle or connected to a charger. 169* The maximum number of parallel downloads is not exceeded. 170 171All of these conditions can be controlled by sending commands to your 172`DownloadService`. 173 174#### Setting and clearing download stop reasons #### 175 176It's possible to set a reason for one or all downloads being stopped: 177 178~~~ 179// Set the stop reason for a single download. 180DownloadService.sendSetStopReason( 181 context, 182 MyDownloadService.class, 183 contentId, 184 stopReason, 185 /* foreground= */ false); 186 187// Clear the stop reason for a single download. 188DownloadService.sendSetStopReason( 189 context, 190 MyDownloadService.class, 191 contentId, 192 Download.STOP_REASON_NONE, 193 /* foreground= */ false); 194~~~ 195{: .language-java} 196 197where `stopReason` can be any non-zero value (`Download.STOP_REASON_NONE = 0` is 198a special value meaning that the download is not stopped). Apps that have 199multiple reasons for stopping downloads can use different values to keep track 200of why each download is stopped. Setting and clearing the stop reason for all 201downloads works the same way as setting and clearing the stop reason for a 202single download, except that `contentId` should be set to `null`. 203 204Setting a stop reason does not remove a download. The partial download will be 205retained, and clearing the stop reason will cause the download to continue. 206{:.info} 207 208When a download has a non-zero stop reason, it will be in the 209`Download.STATE_STOPPED` state. Stop reasons are persisted in the 210`DownloadIndex`, and so are retained if the application process is killed and 211later restarted. 212 213#### Pausing and resuming all downloads #### 214 215All downloads can be paused and resumed as follows: 216 217~~~ 218// Pause all downloads. 219DownloadService.sendPauseDownloads( 220 context, 221 MyDownloadService.class, 222 /* foreground= */ false); 223 224// Resume all downloads. 225DownloadService.sendResumeDownloads( 226 context, 227 MyDownloadService.class, 228 /* foreground= */ false); 229~~~ 230{: .language-java} 231 232When downloads are paused, they will be in the `Download.STATE_QUEUED` state. 233Unlike [setting stop reasons][], this approach does not persist any state 234changes. It only affects the runtime state of the `DownloadManager`. 235 236#### Setting the requirements for downloads to progress #### 237 238[`Requirements`][] can be used to specify constraints that must be met for 239downloads to proceed. The requirements can be set by calling 240`DownloadManager.setRequirements()` when creating the `DownloadManager`, as in 241the example [above][]. They can also be changed dynamically by sending a command 242to the `DownloadService`: 243 244~~~ 245// Set the download requirements. 246DownloadService.sendSetRequirements( 247 context, 248 MyDownloadService.class, 249 requirements, 250 /* foreground= */ false); 251~~~ 252{: .language-java} 253 254When a download cannot proceed because the requirements are not met, it 255will be in the `Download.STATE_QUEUED` state. You can query the not met 256requirements with `DownloadManager.getNotMetRequirements()`. 257 258#### Setting the maximum number of parallel downloads #### 259 260The maximum number of parallel downloads can be set by calling 261`DownloadManager.setMaxParallelDownloads()`. This would normally be done when 262creating the `DownloadManager`, as in the example [above][]. 263 264When a download cannot proceed because the maximum number of parallel downloads 265are already in progress, it will be in the `Download.STATE_QUEUED` state. 266 267## Querying downloads ## 268 269The `DownloadIndex` of a `DownloadManager` can be queried for the state of all 270downloads, including those that have completed or failed. The `DownloadIndex` 271can be obtained by calling `DownloadManager.getDownloadIndex()`. A cursor that 272iterates over all downloads can then be obtained by calling 273`DownloadIndex.getDownloads()`. Alternatively, the state of a single download 274can be queried by calling `DownloadIndex.getDownload()`. 275 276`DownloadManager` also provides `DownloadManager.getCurrentDownloads()`, which 277returns the state of current (i.e. not completed or failed) downloads only. This 278method is useful for updating notifications and other UI components that display 279the progress and status of current downloads. 280 281## Listening to downloads ## 282 283You can add a listener to `DownloadManager` to be informed when current 284downloads change state: 285 286~~~ 287downloadManager.addListener( 288 new DownloadManager.Listener() { 289 // Override methods of interest here. 290 }); 291~~~ 292{: .language-java} 293 294See `DownloadManagerListener` in the demo app's [`DownloadTracker`][] class for 295a concrete example. 296 297Download progress updates do not trigger calls on `DownloadManager.Listener`. To 298update a UI component that shows download progress, you should periodically 299query the `DownloadManager` at your desired update rate. [`DownloadService`][] 300contains an example of this, which periodically updates the service foreground 301notification. 302{:.info} 303 304## Playing downloaded content ## 305 306Playing downloaded content is similar to playing online content, except that 307data is read from the download `Cache` instead of over the network. 308 309It's important that you do not try and read files directly from the download 310directory. Instead, use ExoPlayer library classes as described below. 311{:.info} 312 313To play downloaded content, create a `CacheDataSource.Factory` using the same 314`Cache` instance that was used for downloading, and inject it into 315`DefaultMediaSourceFactory` when building the player: 316 317~~~ 318// Create a read-only cache data source factory using the download cache. 319DataSource.Factory cacheDataSourceFactory = 320 new CacheDataSource.Factory() 321 .setCache(downloadCache) 322 .setUpstreamDataSourceFactory(httpDataSourceFactory) 323 .setCacheWriteDataSinkFactory(null); // Disable writing. 324 325ExoPlayer player = new ExoPlayer.Builder(context) 326 .setMediaSourceFactory( 327 new DefaultMediaSourceFactory(cacheDataSourceFactory)) 328 .build(); 329~~~ 330{: .language-java} 331 332If the same player instance will also be used to play non-downloaded content 333then the `CacheDataSource.Factory` should be configured as read-only to avoid 334downloading that content as well during playback. 335 336Once the player has been configured with the `CacheDataSource.Factory`, it will 337have access to the downloaded content for playback. Playing a download is then 338as simple as passing the corresponding `MediaItem` to the player. A `MediaItem` 339can be obtained from a `Download` using `Download.request.toMediaItem`, or 340directly from a `DownloadRequest` using `DownloadRequest.toMediaItem`. 341 342### MediaSource configuration ### 343 344The example above makes the download cache available for playback of all 345`MediaItem`s. It's also possible to make the download cache available for 346individual `MediaSource` instances, which can be passed directly to the player: 347 348~~~ 349ProgressiveMediaSource mediaSource = 350 new ProgressiveMediaSource.Factory(cacheDataSourceFactory) 351 .createMediaSource(MediaItem.fromUri(contentUri)); 352player.setMediaSource(mediaSource); 353player.prepare(); 354~~~ 355{: .language-java} 356 357## Downloading and playing adaptive streams ## 358 359Adaptive streams (e.g. DASH, SmoothStreaming and HLS) normally contain multiple 360media tracks. There are often multiple tracks that contain the same content in 361different qualities (e.g. SD, HD and 4K video tracks). There may also be 362multiple tracks of the same type containing different content (e.g. multiple 363audio tracks in different languages). 364 365For streaming playbacks, a track selector can be used to choose which of the 366tracks are played. Similarly, for downloading, a `DownloadHelper` can be used to 367choose which of the tracks are downloaded. Typical usage of a `DownloadHelper` 368follows these steps: 369 3701. Build a `DownloadHelper` using one of the `DownloadHelper.forMediaItem` 371 methods. Prepare the helper and wait for the callback. 372 ~~~ 373 DownloadHelper downloadHelper = 374 DownloadHelper.forMediaItem( 375 context, 376 MediaItem.fromUri(contentUri), 377 new DefaultRenderersFactory(context), 378 dataSourceFactory); 379 downloadHelper.prepare(myCallback); 380 ~~~ 381 {: .language-java} 3821. Optionally, inspect the default selected tracks using `getMappedTrackInfo` 383 and `getTrackSelections`, and make adjustments using `clearTrackSelections`, 384 `replaceTrackSelections` and `addTrackSelection`. 3851. Create a `DownloadRequest` for the selected tracks by calling 386 `getDownloadRequest`. The request can be passed to your `DownloadService` to 387 add the download, as described above. 3881. Release the helper using `release()`. 389 390Playback of downloaded adaptive content requires configuring the player and 391passing the corresponding `MediaItem`, as described above. 392 393When building the `MediaItem`, `MediaItem.playbackProperties.streamKeys` must be 394set to match those in the `DownloadRequest` so that the player only tries to 395play the subset of tracks that have been downloaded. Using 396`Download.request.toMediaItem` and `DownloadRequest.toMediaItem` to build the 397`MediaItem` will take care of this for you. 398 399If you see data being requested from the network when trying to play downloaded 400adaptive content, the most likely cause is that the player is trying to adapt to 401a track that was not downloaded. Ensure you've set the stream keys correctly. 402{:.info} 403 404[JobScheduler]: {{ site.android_sdk }}/android/app/job/JobScheduler 405[PlatformScheduler]: {{ site.exo_sdk }}/scheduler/PlatformScheduler.html 406[WorkManager]: https://developer.android.com/topic/libraries/architecture/workmanager/ 407[`DemoDownloadService`]: {{ site.release_v2 }}/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java 408[`AndroidManifest.xml`]: {{ site.release_v2 }}/demos/main/src/main/AndroidManifest.xml 409[`DemoUtil`]: {{ site.release_v2 }}/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.java 410[`DownloadTracker`]: {{ site.release_v2 }}/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java 411[`DownloadService`]: {{ site.release_v2 }}/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java 412[`Requirements`]: {{ site.exo_sdk }}/scheduler/Requirements.html 413[further down this page]: #downloading-and-playing-adaptive-streams 414[above]: #creating-a-downloadmanager 415[setting stop reasons]: #setting-and-clearing-download-stop-reasons 416