0
# Offline Support
1
2
ExoPlayer provides comprehensive offline playback capabilities through download management, enabling users to download media content for offline viewing. The offline system handles adaptive stream downloads, progress tracking, and resume functionality.
3
4
## DownloadManager
5
6
The central component that manages media downloads for offline playback.
7
8
```java { .api }
9
public final class DownloadManager {
10
/**
11
* Listener for download manager events.
12
*/
13
public interface Listener {
14
/**
15
* Called when the download state changes.
16
*
17
* @param downloadManager The download manager
18
* @param download The download
19
* @param finalException The final exception, if the download failed
20
*/
21
default void onDownloadChanged(DownloadManager downloadManager, Download download, @Nullable Exception finalException) {}
22
23
/**
24
* Called when downloads are removed.
25
*
26
* @param downloadManager The download manager
27
* @param downloads The removed downloads
28
*/
29
default void onDownloadRemoved(DownloadManager downloadManager, Download download) {}
30
31
/**
32
* Called when the requirements state changes.
33
*
34
* @param downloadManager The download manager
35
* @param requirements The requirements
36
* @param notMetRequirements The requirements that are not met
37
*/
38
default void onRequirementsStateChanged(DownloadManager downloadManager, Requirements requirements, @RequirementsWatcher.RequirementFlags int notMetRequirements) {}
39
40
/**
41
* Called when waiting for requirements changes.
42
*
43
* @param downloadManager The download manager
44
* @param waitingForRequirements Whether waiting for requirements
45
*/
46
default void onWaitingForRequirementsChanged(DownloadManager downloadManager, boolean waitingForRequirements) {}
47
48
/**
49
* Called when the idle state changes.
50
*
51
* @param downloadManager The download manager
52
* @param idle Whether the download manager is idle
53
*/
54
default void onIdle(DownloadManager downloadManager, boolean idle) {}
55
}
56
57
/**
58
* Creates a DownloadManager.
59
*
60
* @param context The context
61
* @param downloadIndex The download index
62
* @param downloaderFactory The downloader factory
63
*/
64
public DownloadManager(Context context, DownloadIndex downloadIndex, DownloaderFactory downloaderFactory);
65
66
/**
67
* Creates a DownloadManager with requirements.
68
*
69
* @param context The context
70
* @param downloadIndex The download index
71
* @param downloaderFactory The downloader factory
72
* @param requirements The requirements for downloads
73
* @param executor The executor for download operations
74
*/
75
public DownloadManager(Context context, DownloadIndex downloadIndex, DownloaderFactory downloaderFactory,
76
Requirements requirements, Executor executor);
77
78
/**
79
* Adds a listener.
80
*
81
* @param listener The listener to add
82
*/
83
public void addListener(Listener listener);
84
85
/**
86
* Removes a listener.
87
*
88
* @param listener The listener to remove
89
*/
90
public void removeListener(Listener listener);
91
92
/**
93
* Starts the download manager.
94
*/
95
public void start();
96
97
/**
98
* Stops the download manager.
99
*/
100
public void stop();
101
102
/**
103
* Pauses downloads.
104
*/
105
public void pause();
106
107
/**
108
* Resumes downloads.
109
*/
110
public void resume();
111
112
/**
113
* Returns the download index.
114
*
115
* @return The download index
116
*/
117
public DownloadIndex getDownloadIndex();
118
119
/**
120
* Returns whether the download manager is initialized.
121
*
122
* @return Whether initialized
123
*/
124
public boolean isInitialized();
125
126
/**
127
* Returns whether the download manager is idle.
128
*
129
* @return Whether idle
130
*/
131
public boolean isIdle();
132
133
/**
134
* Returns whether waiting for requirements.
135
*
136
* @return Whether waiting for requirements
137
*/
138
public boolean isWaitingForRequirements();
139
140
/**
141
* Returns the current downloads.
142
*
143
* @return List of current downloads
144
*/
145
public List<Download> getCurrentDownloads();
146
147
/**
148
* Adds a download.
149
*
150
* @param request The download request
151
*/
152
public void addDownload(DownloadRequest request);
153
154
/**
155
* Adds a download with stop reason.
156
*
157
* @param request The download request
158
* @param stopReason The stop reason
159
*/
160
public void addDownload(DownloadRequest request, int stopReason);
161
162
/**
163
* Removes a download.
164
*
165
* @param id The download ID
166
*/
167
public void removeDownload(String id);
168
169
/**
170
* Removes all downloads.
171
*/
172
public void removeAllDownloads();
173
174
/**
175
* Sets the stop reason for a download.
176
*
177
* @param id The download ID
178
* @param stopReason The stop reason
179
*/
180
public void setStopReason(@Nullable String id, int stopReason);
181
182
/**
183
* Sets the maximum parallel downloads.
184
*
185
* @param maxParallelDownloads The maximum parallel downloads
186
*/
187
public void setMaxParallelDownloads(int maxParallelDownloads);
188
189
/**
190
* Sets the minimum retry count.
191
*
192
* @param minRetryCount The minimum retry count
193
*/
194
public void setMinRetryCount(int minRetryCount);
195
196
/**
197
* Sets the requirements.
198
*
199
* @param requirements The requirements
200
*/
201
public void setRequirements(Requirements requirements);
202
203
/**
204
* Releases the download manager.
205
*/
206
public void release();
207
}
208
```
209
210
## DownloadRequest
211
212
Represents a request to download media content.
213
214
```java { .api }
215
public final class DownloadRequest implements Parcelable {
216
/**
217
* The download ID.
218
*/
219
public final String id;
220
221
/**
222
* The media URI.
223
*/
224
public final Uri uri;
225
226
/**
227
* The MIME type, or null if unknown.
228
*/
229
@Nullable public final String mimeType;
230
231
/**
232
* The stream keys for adaptive downloads.
233
*/
234
public final List<StreamKey> streamKeys;
235
236
/**
237
* Custom cache key, or null to use default.
238
*/
239
@Nullable public final String customCacheKey;
240
241
/**
242
* Application data associated with the request.
243
*/
244
public final byte[] data;
245
246
/**
247
* Creates a DownloadRequest.
248
*
249
* @param id The download ID
250
* @param uri The media URI
251
* @param mimeType The MIME type
252
* @param streamKeys The stream keys
253
* @param customCacheKey The custom cache key
254
* @param data The application data
255
*/
256
public DownloadRequest(String id, Uri uri, @Nullable String mimeType, List<StreamKey> streamKeys,
257
@Nullable String customCacheKey, @Nullable byte[] data);
258
259
/**
260
* Creates a DownloadRequest with generated ID.
261
*
262
* @param uri The media URI
263
* @return A DownloadRequest with generated ID
264
*/
265
public static DownloadRequest fromUri(Uri uri);
266
267
/**
268
* Creates a DownloadRequest with generated ID and MIME type.
269
*
270
* @param uri The media URI
271
* @param mimeType The MIME type
272
* @return A DownloadRequest
273
*/
274
public static DownloadRequest fromUri(Uri uri, @Nullable String mimeType);
275
276
/**
277
* Returns a copy with the specified stream keys.
278
*
279
* @param streamKeys The stream keys
280
* @return A copy with the specified stream keys
281
*/
282
public DownloadRequest copyWithStreamKeys(List<StreamKey> streamKeys);
283
284
/**
285
* Returns a copy with the specified ID.
286
*
287
* @param id The ID
288
* @return A copy with the specified ID
289
*/
290
public DownloadRequest copyWithId(String id);
291
}
292
```
293
294
## Download
295
296
Represents the state of a download.
297
298
```java { .api }
299
public final class Download {
300
/**
301
* Download states.
302
*/
303
@IntDef({STATE_QUEUED, STATE_STOPPED, STATE_DOWNLOADING, STATE_COMPLETED, STATE_FAILED, STATE_REMOVING, STATE_RESTARTING})
304
@interface State {}
305
306
public static final int STATE_QUEUED = 0;
307
public static final int STATE_STOPPED = 1;
308
public static final int STATE_DOWNLOADING = 2;
309
public static final int STATE_COMPLETED = 3;
310
public static final int STATE_FAILED = 4;
311
public static final int STATE_REMOVING = 5;
312
public static final int STATE_RESTARTING = 6;
313
314
/**
315
* Failure reasons.
316
*/
317
@IntDef({FAILURE_REASON_NONE, FAILURE_REASON_UNKNOWN})
318
@interface FailureReason {}
319
320
public static final int FAILURE_REASON_NONE = C.INDEX_UNSET;
321
public static final int FAILURE_REASON_UNKNOWN = 0;
322
323
/**
324
* Stop reasons.
325
*/
326
public static final int STOP_REASON_NONE = 0;
327
328
/**
329
* The download request.
330
*/
331
public final DownloadRequest request;
332
333
/**
334
* The download state.
335
*/
336
@State public final int state;
337
338
/**
339
* The start time in milliseconds since epoch.
340
*/
341
public final long startTimeMs;
342
343
/**
344
* The update time in milliseconds since epoch.
345
*/
346
public final long updateTimeMs;
347
348
/**
349
* The content length in bytes, or C.LENGTH_UNSET if unknown.
350
*/
351
public final long contentLength;
352
353
/**
354
* The stop reason.
355
*/
356
public final int stopReason;
357
358
/**
359
* The failure reason.
360
*/
361
@FailureReason public final int failureReason;
362
363
/**
364
* The download progress.
365
*/
366
public final DownloadProgress progress;
367
368
/**
369
* Creates a Download.
370
*/
371
public Download(DownloadRequest request, @State int state, long startTimeMs, long updateTimeMs,
372
long contentLength, int stopReason, @FailureReason int failureReason,
373
DownloadProgress progress);
374
375
/**
376
* Returns the percentage downloaded.
377
*
378
* @return The percentage downloaded (0-100)
379
*/
380
public float getPercentDownloaded();
381
382
/**
383
* Returns the downloaded bytes.
384
*
385
* @return The downloaded bytes
386
*/
387
public long getBytesDownloaded();
388
}
389
```
390
391
## DownloadProgress
392
393
Tracks the progress of a download.
394
395
```java { .api }
396
public final class DownloadProgress {
397
/**
398
* The number of bytes downloaded.
399
*/
400
public final long bytesDownloaded;
401
402
/**
403
* The content length in bytes, or C.LENGTH_UNSET if unknown.
404
*/
405
public final long contentLength;
406
407
/**
408
* The percentage downloaded (0-100).
409
*/
410
public final float percentDownloaded;
411
412
/**
413
* Creates a DownloadProgress.
414
*
415
* @param bytesDownloaded The bytes downloaded
416
* @param contentLength The content length
417
* @param percentDownloaded The percentage downloaded
418
*/
419
public DownloadProgress(long bytesDownloaded, long contentLength, float percentDownloaded);
420
}
421
```
422
423
## Downloader Interface
424
425
Interface for downloading media content.
426
427
```java { .api }
428
public interface Downloader {
429
/**
430
* Progress listener for download operations.
431
*/
432
interface ProgressListener {
433
/**
434
* Called when download progress changes.
435
*
436
* @param contentLength The content length
437
* @param bytesDownloaded The bytes downloaded
438
* @param percentDownloaded The percentage downloaded
439
*/
440
void onProgress(long contentLength, long bytesDownloaded, float percentDownloaded);
441
}
442
443
/**
444
* Downloads the content.
445
*
446
* @param progressListener The progress listener
447
* @throws IOException If an IO error occurs
448
* @throws InterruptedException If the download is interrupted
449
*/
450
void download(@Nullable ProgressListener progressListener) throws IOException, InterruptedException;
451
452
/**
453
* Cancels the download.
454
*/
455
void cancel();
456
457
/**
458
* Removes the downloaded content.
459
*
460
* @throws IOException If an IO error occurs
461
*/
462
void remove() throws IOException;
463
}
464
```
465
466
## DownloadHelper
467
468
Helper class for creating download requests with track selection.
469
470
```java { .api }
471
public abstract class DownloadHelper {
472
/**
473
* Callback for DownloadHelper operations.
474
*/
475
public interface Callback {
476
/**
477
* Called when the helper is prepared.
478
*
479
* @param helper The download helper
480
*/
481
void onPrepared(DownloadHelper helper);
482
483
/**
484
* Called when preparation fails.
485
*
486
* @param helper The download helper
487
* @param e The error
488
*/
489
void onPrepareError(DownloadHelper helper, IOException e);
490
}
491
492
/**
493
* Creates a DownloadHelper for the given media item.
494
*
495
* @param mediaItem The media item
496
* @param renderersFactory The renderers factory
497
* @param dataSourceFactory The data source factory
498
* @return The download helper
499
*/
500
public static DownloadHelper forMediaItem(MediaItem mediaItem, RenderersFactory renderersFactory,
501
@Nullable DataSource.Factory dataSourceFactory);
502
503
/**
504
* Creates a DownloadHelper for the given media item with track selector.
505
*
506
* @param mediaItem The media item
507
* @param renderersFactory The renderers factory
508
* @param trackSelector The track selector
509
* @param dataSourceFactory The data source factory
510
* @return The download helper
511
*/
512
public static DownloadHelper forMediaItem(MediaItem mediaItem, RenderersFactory renderersFactory,
513
@Nullable TrackSelector trackSelector,
514
@Nullable DataSource.Factory dataSourceFactory);
515
516
/**
517
* Prepares the helper asynchronously.
518
*
519
* @param callback The callback
520
*/
521
public void prepare(Callback callback);
522
523
/**
524
* Releases the helper.
525
*/
526
public void release();
527
528
/**
529
* Returns the number of periods.
530
*
531
* @return The number of periods
532
*/
533
public int getPeriodCount();
534
535
/**
536
* Returns the mapped track info for a period.
537
*
538
* @param periodIndex The period index
539
* @return The mapped track info
540
*/
541
public MappedTrackInfo getMappedTrackInfo(int periodIndex);
542
543
/**
544
* Returns the track selection for a period.
545
*
546
* @param periodIndex The period index
547
* @return The track selection
548
*/
549
public List<ExoTrackSelection> getTrackSelections(int periodIndex);
550
551
/**
552
* Adds track selections for a period.
553
*
554
* @param periodIndex The period index
555
* @param overrides The track selection overrides
556
*/
557
public void addTrackSelections(int periodIndex, TrackSelectionOverride... overrides);
558
559
/**
560
* Replaces track selections for a period.
561
*
562
* @param periodIndex The period index
563
* @param overrides The track selection overrides
564
*/
565
public void replaceTrackSelections(int periodIndex, TrackSelectionOverride... overrides);
566
567
/**
568
* Adds track selections for all periods.
569
*
570
* @param overrides The track selection overrides
571
*/
572
public void addTrackSelections(TrackSelectionOverride... overrides);
573
574
/**
575
* Clears track selections for a period.
576
*
577
* @param periodIndex The period index
578
*/
579
public void clearTrackSelections(int periodIndex);
580
581
/**
582
* Gets download requests for the selected tracks.
583
*
584
* @param data Application data for the requests
585
* @return List of download requests
586
*/
587
public List<DownloadRequest> getDownloadRequests(@Nullable byte[] data);
588
589
/**
590
* Gets a download request for the selected tracks.
591
*
592
* @param data Application data for the request
593
* @return The download request
594
*/
595
public DownloadRequest getDownloadRequest(@Nullable byte[] data);
596
}
597
```
598
599
## Requirements
600
601
Defines requirements for downloads (network, charging, etc.).
602
603
```java { .api }
604
public final class Requirements implements Parcelable {
605
/**
606
* Requirement flags.
607
*/
608
@IntDef(flag = true, value = {NETWORK, NETWORK_UNMETERED, DEVICE_CHARGING, DEVICE_IDLE})
609
@interface RequirementFlags {}
610
611
public static final int NETWORK = 1;
612
public static final int NETWORK_UNMETERED = 1 << 1;
613
public static final int DEVICE_CHARGING = 1 << 2;
614
public static final int DEVICE_IDLE = 1 << 3;
615
616
/**
617
* The requirement flags.
618
*/
619
@RequirementFlags public final int requirements;
620
621
/**
622
* Creates Requirements.
623
*
624
* @param requirements The requirement flags
625
*/
626
public Requirements(@RequirementFlags int requirements);
627
628
/**
629
* Returns the requirements that are not met.
630
*
631
* @param context The context
632
* @return The unmet requirements
633
*/
634
@RequirementFlags public int getNotMetRequirements(Context context);
635
636
/**
637
* Returns whether the requirements are met.
638
*
639
* @param context The context
640
* @return Whether the requirements are met
641
*/
642
public boolean areRequirementsMet(Context context);
643
}
644
```
645
646
## Usage Examples
647
648
### Basic Download Setup
649
650
```java
651
// Create download database
652
DatabaseProvider databaseProvider = new StandaloneDatabaseProvider(context);
653
DownloadIndex downloadIndex = new DefaultDownloadIndex(databaseProvider);
654
655
// Create downloader factory
656
DownloaderFactory downloaderFactory = new DefaultDownloaderFactory(
657
new CacheDataSource.Factory()
658
.setCache(downloadCache)
659
.setUpstreamDataSource(httpDataSourceFactory),
660
/* executor= */ Runnable::run
661
);
662
663
// Create requirements (Wi-Fi and charging)
664
Requirements requirements = new Requirements(
665
Requirements.NETWORK_UNMETERED | Requirements.DEVICE_CHARGING
666
);
667
668
// Create download manager
669
DownloadManager downloadManager = new DownloadManager(
670
context, downloadIndex, downloaderFactory, requirements,
671
Executors.newFixedThreadPool(2)
672
);
673
674
// Add listener
675
downloadManager.addListener(new DownloadProgressListener());
676
677
// Start download manager
678
downloadManager.start();
679
```
680
681
### Creating Download Requests
682
683
```java
684
// Simple download request
685
Uri mediaUri = Uri.parse("https://example.com/video.mp4");
686
DownloadRequest simpleRequest = DownloadRequest.fromUri(mediaUri);
687
688
// Add to download manager
689
downloadManager.addDownload(simpleRequest);
690
691
// Download with specific tracks using DownloadHelper
692
MediaItem mediaItem = MediaItem.fromUri("https://example.com/manifest.mpd");
693
RenderersFactory renderersFactory = new DefaultRenderersFactory(context);
694
695
DownloadHelper downloadHelper = DownloadHelper.forMediaItem(
696
mediaItem, renderersFactory, httpDataSourceFactory
697
);
698
699
downloadHelper.prepare(new DownloadHelper.Callback() {
700
@Override
701
public void onPrepared(DownloadHelper helper) {
702
// Select specific tracks
703
for (int periodIndex = 0; periodIndex < helper.getPeriodCount(); periodIndex++) {
704
MappedTrackInfo mappedTrackInfo = helper.getMappedTrackInfo(periodIndex);
705
706
// Add video track selection (720p max)
707
TrackSelectionOverride videoOverride = new TrackSelectionOverride(
708
selectVideoTrack(mappedTrackInfo, 1280, 720)
709
);
710
helper.addTrackSelections(periodIndex, videoOverride);
711
712
// Add audio track selection (English)
713
TrackSelectionOverride audioOverride = new TrackSelectionOverride(
714
selectAudioTrack(mappedTrackInfo, "en")
715
);
716
helper.addTrackSelections(periodIndex, audioOverride);
717
}
718
719
// Create download request
720
DownloadRequest request = helper.getDownloadRequest("my_download".getBytes());
721
downloadManager.addDownload(request);
722
723
helper.release();
724
}
725
726
@Override
727
public void onPrepareError(DownloadHelper helper, IOException e) {
728
Log.e(TAG, "Failed to prepare download", e);
729
helper.release();
730
}
731
});
732
```
733
734
### Download Progress Monitoring
735
736
```java
737
public class DownloadProgressListener implements DownloadManager.Listener {
738
private static final String TAG = "DownloadProgress";
739
740
@Override
741
public void onDownloadChanged(DownloadManager downloadManager, Download download,
742
@Nullable Exception finalException) {
743
switch (download.state) {
744
case Download.STATE_QUEUED:
745
Log.d(TAG, "Download queued: " + download.request.id);
746
updateDownloadUI(download.request.id, "Queued", 0);
747
break;
748
749
case Download.STATE_DOWNLOADING:
750
float progress = download.getPercentDownloaded();
751
long downloaded = download.getBytesDownloaded();
752
Log.d(TAG, String.format("Downloading %s: %.1f%% (%d bytes)",
753
download.request.id, progress, downloaded));
754
updateDownloadUI(download.request.id, "Downloading", (int) progress);
755
break;
756
757
case Download.STATE_COMPLETED:
758
Log.d(TAG, "Download completed: " + download.request.id);
759
updateDownloadUI(download.request.id, "Completed", 100);
760
break;
761
762
case Download.STATE_FAILED:
763
Log.e(TAG, "Download failed: " + download.request.id, finalException);
764
updateDownloadUI(download.request.id, "Failed", 0);
765
break;
766
767
case Download.STATE_STOPPED:
768
Log.d(TAG, "Download stopped: " + download.request.id);
769
updateDownloadUI(download.request.id, "Stopped", (int) download.getPercentDownloaded());
770
break;
771
}
772
}
773
774
@Override
775
public void onDownloadRemoved(DownloadManager downloadManager, Download download) {
776
Log.d(TAG, "Download removed: " + download.request.id);
777
removeDownloadUI(download.request.id);
778
}
779
780
@Override
781
public void onRequirementsStateChanged(DownloadManager downloadManager, Requirements requirements,
782
@RequirementsWatcher.RequirementFlags int notMetRequirements) {
783
if (notMetRequirements == 0) {
784
Log.d(TAG, "All requirements met, downloads can proceed");
785
showRequirementsStatus("Ready to download");
786
} else {
787
List<String> unmetRequirements = new ArrayList<>();
788
if ((notMetRequirements & Requirements.NETWORK) != 0) {
789
unmetRequirements.add("Network");
790
}
791
if ((notMetRequirements & Requirements.NETWORK_UNMETERED) != 0) {
792
unmetRequirements.add("Wi-Fi");
793
}
794
if ((notMetRequirements & Requirements.DEVICE_CHARGING) != 0) {
795
unmetRequirements.add("Charging");
796
}
797
798
String message = "Waiting for: " + String.join(", ", unmetRequirements);
799
Log.d(TAG, message);
800
showRequirementsStatus(message);
801
}
802
}
803
804
@Override
805
public void onIdle(DownloadManager downloadManager, boolean idle) {
806
Log.d(TAG, "Download manager idle: " + idle);
807
if (idle) {
808
showDownloadStatus("All downloads completed");
809
}
810
}
811
812
private void updateDownloadUI(String downloadId, String status, int progress) {
813
// Update UI with download progress
814
}
815
816
private void removeDownloadUI(String downloadId) {
817
// Remove download from UI
818
}
819
820
private void showRequirementsStatus(String message) {
821
// Show requirements status in UI
822
}
823
824
private void showDownloadStatus(String message) {
825
// Show general download status
826
}
827
}
828
```
829
830
### Playing Downloaded Content
831
832
```java
833
public class OfflinePlaybackManager {
834
private final DownloadManager downloadManager;
835
private final Cache downloadCache;
836
837
public OfflinePlaybackManager(DownloadManager downloadManager, Cache downloadCache) {
838
this.downloadManager = downloadManager;
839
this.downloadCache = downloadCache;
840
}
841
842
/**
843
* Checks if content is available offline.
844
*
845
* @param mediaUri The media URI
846
* @return Whether the content is available offline
847
*/
848
public boolean isAvailableOffline(Uri mediaUri) {
849
try {
850
Download download = downloadManager.getDownloadIndex().getDownload(mediaUri.toString());
851
return download != null && download.state == Download.STATE_COMPLETED;
852
} catch (DatabaseIOException e) {
853
Log.e(TAG, "Failed to check offline availability", e);
854
return false;
855
}
856
}
857
858
/**
859
* Creates a media source for offline playback.
860
*
861
* @param mediaItem The media item
862
* @return The media source configured for offline playback
863
*/
864
public MediaSource createOfflineMediaSource(MediaItem mediaItem) {
865
// Create data source factory that uses cache
866
DataSource.Factory dataSourceFactory = new CacheDataSource.Factory()
867
.setCache(downloadCache)
868
.setUpstreamDataSource(new FileDataSource.Factory())
869
.setCacheWriteDataSinkFactory(null); // Read-only for offline playback
870
871
// Create media source factory
872
return new DefaultMediaSourceFactory(dataSourceFactory)
873
.createMediaSource(mediaItem);
874
}
875
876
/**
877
* Gets download progress for a media item.
878
*
879
* @param mediaUri The media URI
880
* @return The download progress, or null if not found
881
*/
882
@Nullable
883
public DownloadProgress getDownloadProgress(Uri mediaUri) {
884
try {
885
Download download = downloadManager.getDownloadIndex().getDownload(mediaUri.toString());
886
return download != null ? download.progress : null;
887
} catch (DatabaseIOException e) {
888
Log.e(TAG, "Failed to get download progress", e);
889
return null;
890
}
891
}
892
}
893
```
894
895
### Advanced Download Management
896
897
```java
898
public class AdvancedDownloadManager {
899
private final DownloadManager downloadManager;
900
private final Map<String, DownloadRequest> pendingDownloads = new HashMap<>();
901
902
public AdvancedDownloadManager(DownloadManager downloadManager) {
903
this.downloadManager = downloadManager;
904
}
905
906
/**
907
* Downloads with quality selection.
908
*
909
* @param mediaItem The media item
910
* @param maxVideoHeight Maximum video height (e.g., 720 for 720p)
911
* @param preferredLanguage Preferred audio language
912
*/
913
public void downloadWithQuality(MediaItem mediaItem, int maxVideoHeight, String preferredLanguage) {
914
DownloadHelper helper = DownloadHelper.forMediaItem(
915
mediaItem,
916
new DefaultRenderersFactory(context),
917
httpDataSourceFactory
918
);
919
920
helper.prepare(new DownloadHelper.Callback() {
921
@Override
922
public void onPrepared(DownloadHelper helper) {
923
try {
924
selectTracksForDownload(helper, maxVideoHeight, preferredLanguage);
925
926
DownloadRequest request = helper.getDownloadRequest(
927
createDownloadMetadata(mediaItem, maxVideoHeight, preferredLanguage)
928
);
929
930
downloadManager.addDownload(request);
931
932
} finally {
933
helper.release();
934
}
935
}
936
937
@Override
938
public void onPrepareError(DownloadHelper helper, IOException e) {
939
Log.e(TAG, "Failed to prepare download for quality selection", e);
940
helper.release();
941
}
942
});
943
}
944
945
/**
946
* Pauses a specific download.
947
*
948
* @param downloadId The download ID
949
*/
950
public void pauseDownload(String downloadId) {
951
downloadManager.setStopReason(downloadId, 1); // Custom stop reason
952
}
953
954
/**
955
* Resumes a paused download.
956
*
957
* @param downloadId The download ID
958
*/
959
public void resumeDownload(String downloadId) {
960
downloadManager.setStopReason(downloadId, Download.STOP_REASON_NONE);
961
}
962
963
/**
964
* Removes a download and deletes the content.
965
*
966
* @param downloadId The download ID
967
*/
968
public void removeDownload(String downloadId) {
969
downloadManager.removeDownload(downloadId);
970
}
971
972
/**
973
* Gets all completed downloads.
974
*
975
* @return List of completed downloads
976
*/
977
public List<Download> getCompletedDownloads() {
978
return downloadManager.getCurrentDownloads().stream()
979
.filter(download -> download.state == Download.STATE_COMPLETED)
980
.collect(Collectors.toList());
981
}
982
983
/**
984
* Gets total storage used by downloads.
985
*
986
* @return Total bytes used by downloads
987
*/
988
public long getTotalDownloadSize() {
989
return downloadManager.getCurrentDownloads().stream()
990
.filter(download -> download.state == Download.STATE_COMPLETED)
991
.mapToLong(Download::getBytesDownloaded)
992
.sum();
993
}
994
995
private void selectTracksForDownload(DownloadHelper helper, int maxVideoHeight, String preferredLanguage) {
996
for (int periodIndex = 0; periodIndex < helper.getPeriodCount(); periodIndex++) {
997
MappedTrackInfo mappedTrackInfo = helper.getMappedTrackInfo(periodIndex);
998
999
// Select video track with quality limit
1000
TrackGroup videoTrackGroup = selectBestVideoTrack(mappedTrackInfo, maxVideoHeight);
1001
if (videoTrackGroup != null) {
1002
helper.addTrackSelections(periodIndex,
1003
new TrackSelectionOverride(videoTrackGroup, Arrays.asList(0)));
1004
}
1005
1006
// Select audio track with language preference
1007
TrackGroup audioTrackGroup = selectAudioTrackByLanguage(mappedTrackInfo, preferredLanguage);
1008
if (audioTrackGroup != null) {
1009
helper.addTrackSelections(periodIndex,
1010
new TrackSelectionOverride(audioTrackGroup, Arrays.asList(0)));
1011
}
1012
}
1013
}
1014
1015
private byte[] createDownloadMetadata(MediaItem mediaItem, int maxVideoHeight, String preferredLanguage) {
1016
// Create metadata for the download
1017
return String.format("quality=%dp,lang=%s", maxVideoHeight, preferredLanguage).getBytes();
1018
}
1019
1020
private TrackGroup selectBestVideoTrack(MappedTrackInfo mappedTrackInfo, int maxVideoHeight) {
1021
// Implementation to select best video track within height limit
1022
return null; // Simplified for example
1023
}
1024
1025
private TrackGroup selectAudioTrackByLanguage(MappedTrackInfo mappedTrackInfo, String preferredLanguage) {
1026
// Implementation to select audio track by language
1027
return null; // Simplified for example
1028
}
1029
}
1030
```