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