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 99## Adding a download ## 100 101To add a download you need to create a `DownloadRequest` and send it to your 102`DownloadService`. For adaptive streams `DownloadHelper` can be used to help 103build a `DownloadRequest`, as described [further down this page][]. The example 104below shows how to create a download request: 105 106~~~ 107DownloadRequest downloadRequest = 108 new DownloadRequest.Builder(contentId, contentUri).build(); 109~~~ 110{: .language-java} 111 112where `contentId` is a unique identifier for the content. In simple cases, the 113`contentUri` can often be used as the `contentId`, however apps are free to use 114whatever ID scheme best suits their use case. `DownloadRequest.Builder` also has 115some optional setters. For example, `setKeySetId` and `setData` can be used to 116set DRM and custom data that the app wishes to associate with the download, 117respectively. The content's MIME type can also be specified using `setMimeType`, 118as a hint for cases where the content type cannot be inferred from `contentUri`. 119 120Once created, the request can be sent to the `DownloadService` to add the 121download: 122 123~~~ 124DownloadService.sendAddDownload( 125 context, 126 MyDownloadService.class, 127 downloadRequest, 128 /* foreground= */ false) 129~~~ 130{: .language-java} 131 132where `MyDownloadService` is the app's `DownloadService` subclass, and the 133`foreground` parameter controls whether the service will be started in the 134foreground. If your app is already in the foreground then the `foreground` 135parameter should normally be set to `false`, since the `DownloadService` will 136put itself in the foreground if it determines that it has work to do. 137 138## Removing downloads ## 139 140A download can be removed by sending a remove command to the `DownloadService`, 141where `contentId` identifies the download to be removed: 142 143~~~ 144DownloadService.sendRemoveDownload( 145 context, 146 MyDownloadService.class, 147 contentId, 148 /* foreground= */ false) 149~~~ 150{: .language-java} 151 152You can also remove all downloaded data with 153`DownloadService.sendRemoveAllDownloads`. 154 155## Starting and stopping downloads ## 156 157A download will only progress if four conditions are met: 158 159* The download doesn't have a stop reason. 160* Downloads aren't paused. 161* The requirements for downloads to progress are met. Requirements can specify 162 constraints on the allowed network types, as well as whether the device should 163 be idle or connected to a charger. 164* The maximum number of parallel downloads is not exceeded. 165 166All of these conditions can be controlled by sending commands to your 167`DownloadService`. 168 169#### Setting and clearing download stop reasons #### 170 171It's possible to set a reason for one or all downloads being stopped: 172 173~~~ 174// Set the stop reason for a single download. 175DownloadService.sendSetStopReason( 176 context, 177 MyDownloadService.class, 178 contentId, 179 stopReason, 180 /* foreground= */ false); 181 182// Clear the stop reason for a single download. 183DownloadService.sendSetStopReason( 184 context, 185 MyDownloadService.class, 186 contentId, 187 Download.STOP_REASON_NONE, 188 /* foreground= */ false); 189~~~ 190{: .language-java} 191 192where `stopReason` can be any non-zero value (`Download.STOP_REASON_NONE = 0` is 193a special value meaning that the download is not stopped). Apps that have 194multiple reasons for stopping downloads can use different values to keep track 195of why each download is stopped. Setting and clearing the stop reason for all 196downloads works the same way as setting and clearing the stop reason for a 197single download, except that `contentId` should be set to `null`. 198 199Setting a stop reason does not remove a download. The partial download will be 200retained, and clearing the stop reason will cause the download to continue. 201{:.info} 202 203When a download has a non-zero stop reason, it will be in the 204`Download.STATE_STOPPED` state. Stop reasons are persisted in the 205`DownloadIndex`, and so are retained if the application process is killed and 206later restarted. 207 208#### Pausing and resuming all downloads #### 209 210All downloads can be paused and resumed as follows: 211 212~~~ 213// Pause all downloads. 214DownloadService.sendPauseDownloads( 215 context, 216 MyDownloadService.class, 217 /* foreground= */ false); 218 219// Resume all downloads. 220DownloadService.sendResumeDownloads( 221 context, 222 MyDownloadService.class, 223 /* foreground= */ false); 224~~~ 225{: .language-java} 226 227When downloads are paused, they will be in the `Download.STATE_QUEUED` state. 228Unlike [setting stop reasons][], this approach does not persist any state 229changes. It only affects the runtime state of the `DownloadManager`. 230 231#### Setting the requirements for downloads to progress #### 232 233[`Requirements`][] can be used to specify constraints that must be met for 234downloads to proceed. The requirements can be set by calling 235`DownloadManager.setRequirements()` when creating the `DownloadManager`, as in 236the example [above][]. They can also be changed dynamically by sending a command 237to the `DownloadService`: 238 239~~~ 240// Set the download requirements. 241DownloadService.sendSetRequirements( 242 context, 243 MyDownloadService.class, 244 requirements, 245 /* foreground= */ false); 246~~~ 247{: .language-java} 248 249When a download cannot proceed because the requirements are not met, it 250will be in the `Download.STATE_QUEUED` state. You can query the not met 251requirements with `DownloadManager.getNotMetRequirements()`. 252 253#### Setting the maximum number of parallel downloads #### 254 255The maximum number of parallel downloads can be set by calling 256`DownloadManager.setMaxParallelDownloads()`. This would normally be done when 257creating the `DownloadManager`, as in the example [above][]. 258 259When a download cannot proceed because the maximum number of parallel downloads 260are already in progress, it will be in the `Download.STATE_QUEUED` state. 261 262## Querying downloads ## 263 264The `DownloadIndex` of a `DownloadManager` can be queried for the state of all 265downloads, including those that have completed or failed. The `DownloadIndex` 266can be obtained by calling `DownloadManager.getDownloadIndex()`. A cursor that 267iterates over all downloads can then be obtained by calling 268`DownloadIndex.getDownloads()`. Alternatively, the state of a single download 269can be queried by calling `DownloadIndex.getDownload()`. 270 271`DownloadManager` also provides `DownloadManager.getCurrentDownloads()`, which 272returns the state of current (i.e. not completed or failed) downloads only. This 273method is useful for updating notifications and other UI components that display 274the progress and status of current downloads. 275 276## Listening to downloads ## 277 278You can add a listener to `DownloadManager` to be informed when current 279downloads change state: 280 281~~~ 282downloadManager.addListener( 283 new DownloadManager.Listener() { 284 // Override methods of interest here. 285 }); 286~~~ 287{: .language-java} 288 289See `DownloadManagerListener` in the demo app's [`DownloadTracker`][] class for 290a concrete example. 291 292Download progress updates do not trigger calls on `DownloadManager.Listener`. To 293update a UI component that shows download progress, you should periodically 294query the `DownloadManager` at your desired update rate. [`DownloadService`][] 295contains an example of this, which periodically updates the service foreground 296notification. 297{:.info} 298 299## Playing downloaded content ## 300 301Playing downloaded content is similar to playing online content, except that 302data is read from the download `Cache` instead of over the network. 303 304It's important that you do not try and read files directly from the download 305directory. Instead, use ExoPlayer library classes as described below. 306{:.info} 307 308To play downloaded content, create a `CacheDataSource.Factory` using the same 309`Cache` instance that was used for downloading, and inject it into 310`DefaultMediaSourceFactory` when building the player: 311 312~~~ 313// Create a read-only cache data source factory using the download cache. 314DataSource.Factory cacheDataSourceFactory = 315 new CacheDataSource.Factory() 316 .setCache(downloadCache) 317 .setUpstreamDataSourceFactory(httpDataSourceFactory) 318 .setCacheWriteDataSinkFactory(null); // Disable writing. 319 320ExoPlayer player = new ExoPlayer.Builder(context) 321 .setMediaSourceFactory( 322 new DefaultMediaSourceFactory(cacheDataSourceFactory)) 323 .build(); 324~~~ 325{: .language-java} 326 327If the same player instance will also be used to play non-downloaded content 328then the `CacheDataSource.Factory` should be configured as read-only to avoid 329downloading that content as well during playback. 330 331Once the player has been configured with the `CacheDataSource.Factory`, it will 332have access to the downloaded content for playback. Playing a download is then 333as simple as passing the corresponding `MediaItem` to the player. A `MediaItem` 334can be obtained from a `Download` using `Download.request.toMediaItem`, or 335directly from a `DownloadRequest` using `DownloadRequest.toMediaItem`. 336 337### MediaSource configuration ### 338 339The example above makes the download cache available for playback of all 340`MediaItem`s. It's also possible to make the download cache available for 341individual `MediaSource` instances, which can be passed directly to the player: 342 343~~~ 344ProgressiveMediaSource mediaSource = 345 new ProgressiveMediaSource.Factory(cacheDataSourceFactory) 346 .createMediaSource(MediaItem.fromUri(contentUri)); 347player.setMediaSource(mediaSource); 348player.prepare(); 349~~~ 350{: .language-java} 351 352## Downloading and playing adaptive streams ## 353 354Adaptive streams (e.g. DASH, SmoothStreaming and HLS) normally contain multiple 355media tracks. There are often multiple tracks that contain the same content in 356different qualities (e.g. SD, HD and 4K video tracks). There may also be 357multiple tracks of the same type containing different content (e.g. multiple 358audio tracks in different languages). 359 360For streaming playbacks, a track selector can be used to choose which of the 361tracks are played. Similarly, for downloading, a `DownloadHelper` can be used to 362choose which of the tracks are downloaded. Typical usage of a `DownloadHelper` 363follows these steps: 364 3651. Build a `DownloadHelper` using one of the `DownloadHelper.forMediaItem` 366 methods. Prepare the helper and wait for the callback. 367 ~~~ 368 DownloadHelper downloadHelper = 369 DownloadHelper.forMediaItem( 370 context, 371 MediaItem.fromUri(contentUri), 372 new DefaultRenderersFactory(context), 373 dataSourceFactory); 374 downloadHelper.prepare(myCallback); 375 ~~~ 376 {: .language-java} 3771. Optionally, inspect the default selected tracks using `getMappedTrackInfo` 378 and `getTrackSelections`, and make adjustments using `clearTrackSelections`, 379 `replaceTrackSelections` and `addTrackSelection`. 3801. Create a `DownloadRequest` for the selected tracks by calling 381 `getDownloadRequest`. The request can be passed to your `DownloadService` to 382 add the download, as described above. 3831. Release the helper using `release()`. 384 385Playback of downloaded adaptive content requires configuring the player and 386passing the corresponding `MediaItem`, as described above. 387 388When building the `MediaItem`, `MediaItem.playbackProperties.streamKeys` must be 389set to match those in the `DownloadRequest` so that the player only tries to 390play the subset of tracks that have been downloaded. Using 391`Download.request.toMediaItem` and `DownloadRequest.toMediaItem` to build the 392`MediaItem` will take care of this for you. 393 394If you see data being requested from the network when trying to play downloaded 395adaptive content, the most likely cause is that the player is trying to adapt to 396a track that was not downloaded. Ensure you've set the stream keys correctly. 397{:.info} 398 399[JobScheduler]: {{ site.android_sdk }}/android/app/job/JobScheduler 400[PlatformScheduler]: {{ site.exo_sdk }}/scheduler/PlatformScheduler.html 401[WorkManager]: https://developer.android.com/topic/libraries/architecture/workmanager/ 402[`DemoDownloadService`]: {{ site.release_v2 }}/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java 403[`AndroidManifest.xml`]: {{ site.release_v2 }}/demos/main/src/main/AndroidManifest.xml 404[`DemoUtil`]: {{ site.release_v2 }}/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.java 405[`DownloadTracker`]: {{ site.release_v2 }}/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java 406[`DownloadService`]: {{ site.release_v2 }}/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java 407[`Requirements`]: {{ site.exo_sdk }}/scheduler/Requirements.html 408[further down this page]: #downloading-and-playing-adaptive-streams 409[above]: #creating-a-downloadmanager 410[setting stop reasons]: #setting-and-clearing-download-stop-reasons 411