or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

analytics.mdaudio-rendering.mdcore-player.mddrm-support.mdindex.mdmedia-sources.mdoffline-support.mdtrack-selection.mdvideo-rendering.md

offline-support.mddocs/

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

```