public final class

HlsDownloader

extends SegmentDownloader<HlsPlaylist>

 java.lang.Object

androidx.media3.exoplayer.offline.SegmentDownloader<HlsPlaylist>

↳androidx.media3.exoplayer.hls.offline.HlsDownloader

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-exoplayer-hls', version: '1.5.0-alpha01'

  • groupId: androidx.media3
  • artifactId: media3-exoplayer-hls
  • version: 1.5.0-alpha01

Artifact androidx.media3:media3-exoplayer-hls:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

A downloader for HLS streams.

Example usage:

 SimpleCache cache = new SimpleCache(downloadFolder, new NoOpCacheEvictor(), databaseProvider);
 CacheDataSource.Factory cacheDataSourceFactory =
     new CacheDataSource.Factory()
         .setCache(cache)
         .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory());
 // Create a downloader for the first variant in a multivariant playlist.
 HlsDownloader hlsDownloader =
     new HlsDownloader(
         new MediaItem.Builder()
             .setUri(playlistUri)
             .setStreamKeys(
                 Collections.singletonList(
                     new StreamKey(HlsMultivariantPlaylist.GROUP_INDEX_VARIANT, 0)))
             .build(),
         Collections.singletonList();
 // Perform the download.
 hlsDownloader.download(progressListener);
 // Use the downloaded data for playback.
 HlsMediaSource mediaSource =
     new HlsMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaItem);
 

Summary

Fields
from SegmentDownloader<M>DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_MS
Constructors
publicHlsDownloader(MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory)

Creates a new instance.

publicHlsDownloader(MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory, java.util.concurrent.Executor executor)

Creates a new instance.

publicHlsDownloader(MediaItem mediaItem, ParsingLoadable.Parser<HlsPlaylist> manifestParser, CacheDataSource.Factory cacheDataSourceFactory, java.util.concurrent.Executor executor)

publicHlsDownloader(MediaItem mediaItem, ParsingLoadable.Parser<HlsPlaylist> manifestParser, CacheDataSource.Factory cacheDataSourceFactory, java.util.concurrent.Executor executor, long maxMergedSegmentStartTimeDiffMs)

Creates a new instance.

Methods
protected abstract java.util.List<SegmentDownloader.Segment>getSegments(DataSource dataSource, FilterableManifest<T> manifest, boolean removing)

Returns a list of all downloadable SegmentDownloader.Segments for a given manifest.

from SegmentDownloader<M>cancel, download, execute, getCompressibleDataSpec, getManifest, remove
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public HlsDownloader(MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory)

Creates a new instance.

Parameters:

mediaItem: The MediaItem to be downloaded.
cacheDataSourceFactory: A for the cache into which the download will be written.

public HlsDownloader(MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory, java.util.concurrent.Executor executor)

Creates a new instance.

Parameters:

mediaItem: The MediaItem to be downloaded.
cacheDataSourceFactory: A for the cache into which the download will be written.
executor: An java.util.concurrent.Executor used to make requests for the media being downloaded. Providing an java.util.concurrent.Executor that uses multiple threads will speed up the download by allowing parts of it to be executed in parallel.

public HlsDownloader(MediaItem mediaItem, ParsingLoadable.Parser<HlsPlaylist> manifestParser, CacheDataSource.Factory cacheDataSourceFactory, java.util.concurrent.Executor executor)

Deprecated: Use HlsDownloader instead.

public HlsDownloader(MediaItem mediaItem, ParsingLoadable.Parser<HlsPlaylist> manifestParser, CacheDataSource.Factory cacheDataSourceFactory, java.util.concurrent.Executor executor, long maxMergedSegmentStartTimeDiffMs)

Creates a new instance.

Parameters:

mediaItem: The MediaItem to be downloaded.
manifestParser: A parser for HLS playlists.
cacheDataSourceFactory: A for the cache into which the download will be written.
executor: An java.util.concurrent.Executor used to make requests for the media being downloaded. Providing an java.util.concurrent.Executor that uses multiple threads will speed up the download by allowing parts of it to be executed in parallel.
maxMergedSegmentStartTimeDiffMs: The maximum difference of the start time of two segments, up to which the segments (of the same URI) should be merged into a single download segment, in milliseconds.

Methods

protected abstract java.util.List<SegmentDownloader.Segment> getSegments(DataSource dataSource, FilterableManifest<T> manifest, boolean removing)

Returns a list of all downloadable SegmentDownloader.Segments for a given manifest. Any required data should be loaded using SegmentDownloader.getManifest(DataSource, DataSpec, boolean) or SegmentDownloader.execute(RunnableFutureTask, boolean).

Parameters:

dataSource: The DataSource through which to load any required data.
manifest: The manifest containing the segments.
removing: Whether the segments are being obtained as part of a removal. If true then a partial segment list is returned in the case that a load error prevents all segments from being listed. If false then an java.io.IOException will be thrown in this case.

Returns:

The list of downloadable SegmentDownloader.Segments.

Source

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.media3.exoplayer.hls.offline;

import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.UriUtil;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.cache.CacheDataSource;
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist;
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist;
import androidx.media3.exoplayer.hls.playlist.HlsPlaylist;
import androidx.media3.exoplayer.hls.playlist.HlsPlaylistParser;
import androidx.media3.exoplayer.offline.SegmentDownloader;
import androidx.media3.exoplayer.upstream.ParsingLoadable.Parser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.Executor;

// LINT.IfChange(javadoc)
/**
 * A downloader for HLS streams.
 *
 * <p>Example usage:
 *
 * <pre>{@code
 * SimpleCache cache = new SimpleCache(downloadFolder, new NoOpCacheEvictor(), databaseProvider);
 * CacheDataSource.Factory cacheDataSourceFactory =
 *     new CacheDataSource.Factory()
 *         .setCache(cache)
 *         .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory());
 * // Create a downloader for the first variant in a multivariant playlist.
 * HlsDownloader hlsDownloader =
 *     new HlsDownloader(
 *         new MediaItem.Builder()
 *             .setUri(playlistUri)
 *             .setStreamKeys(
 *                 Collections.singletonList(
 *                     new StreamKey(HlsMultivariantPlaylist.GROUP_INDEX_VARIANT, 0)))
 *             .build(),
 *         Collections.singletonList();
 * // Perform the download.
 * hlsDownloader.download(progressListener);
 * // Use the downloaded data for playback.
 * HlsMediaSource mediaSource =
 *     new HlsMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaItem);
 * }</pre>
 */
@UnstableApi
public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {

  /**
   * Creates a new instance.
   *
   * @param mediaItem The {@link MediaItem} to be downloaded.
   * @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the
   *     download will be written.
   */
  public HlsDownloader(MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory) {
    this(mediaItem, cacheDataSourceFactory, Runnable::run);
  }

  /**
   * Creates a new instance.
   *
   * @param mediaItem The {@link MediaItem} to be downloaded.
   * @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the
   *     download will be written.
   * @param executor An {@link Executor} used to make requests for the media being downloaded.
   *     Providing an {@link Executor} that uses multiple threads will speed up the download by
   *     allowing parts of it to be executed in parallel.
   */
  public HlsDownloader(
      MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory, Executor executor) {
    this(
        mediaItem,
        new HlsPlaylistParser(),
        cacheDataSourceFactory,
        executor,
        DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_MS);
  }

  /**
   * @deprecated Use {@link HlsDownloader#HlsDownloader(MediaItem, Parser, CacheDataSource.Factory,
   *     Executor, long)} instead.
   */
  @Deprecated
  public HlsDownloader(
      MediaItem mediaItem,
      Parser<HlsPlaylist> manifestParser,
      CacheDataSource.Factory cacheDataSourceFactory,
      Executor executor) {
    this(
        mediaItem,
        manifestParser,
        cacheDataSourceFactory,
        executor,
        DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_MS);
  }

  /**
   * Creates a new instance.
   *
   * @param mediaItem The {@link MediaItem} to be downloaded.
   * @param manifestParser A parser for HLS playlists.
   * @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the
   *     download will be written.
   * @param executor An {@link Executor} used to make requests for the media being downloaded.
   *     Providing an {@link Executor} that uses multiple threads will speed up the download by
   *     allowing parts of it to be executed in parallel.
   * @param maxMergedSegmentStartTimeDiffMs The maximum difference of the start time of two
   *     segments, up to which the segments (of the same URI) should be merged into a single
   *     download segment, in milliseconds.
   */
  public HlsDownloader(
      MediaItem mediaItem,
      Parser<HlsPlaylist> manifestParser,
      CacheDataSource.Factory cacheDataSourceFactory,
      Executor executor,
      long maxMergedSegmentStartTimeDiffMs) {
    super(
        mediaItem,
        manifestParser,
        cacheDataSourceFactory,
        executor,
        maxMergedSegmentStartTimeDiffMs);
  }

  @Override
  protected List<Segment> getSegments(DataSource dataSource, HlsPlaylist manifest, boolean removing)
      throws IOException, InterruptedException {
    ArrayList<DataSpec> mediaPlaylistDataSpecs = new ArrayList<>();
    if (manifest instanceof HlsMultivariantPlaylist) {
      HlsMultivariantPlaylist multivariantPlaylist = (HlsMultivariantPlaylist) manifest;
      addMediaPlaylistDataSpecs(multivariantPlaylist.mediaPlaylistUrls, mediaPlaylistDataSpecs);
    } else {
      mediaPlaylistDataSpecs.add(
          SegmentDownloader.getCompressibleDataSpec(Uri.parse(manifest.baseUri)));
    }

    ArrayList<Segment> segments = new ArrayList<>();
    HashSet<Uri> seenEncryptionKeyUris = new HashSet<>();
    for (DataSpec mediaPlaylistDataSpec : mediaPlaylistDataSpecs) {
      segments.add(new Segment(/* startTimeUs= */ 0, mediaPlaylistDataSpec));
      HlsMediaPlaylist mediaPlaylist;
      try {
        mediaPlaylist = (HlsMediaPlaylist) getManifest(dataSource, mediaPlaylistDataSpec, removing);
      } catch (IOException e) {
        if (!removing) {
          throw e;
        }
        // Generating an incomplete segment list is allowed. Advance to the next media playlist.
        continue;
      }
      @Nullable HlsMediaPlaylist.Segment lastInitSegment = null;
      List<HlsMediaPlaylist.Segment> hlsSegments = mediaPlaylist.segments;
      for (int i = 0; i < hlsSegments.size(); i++) {
        HlsMediaPlaylist.Segment segment = hlsSegments.get(i);
        HlsMediaPlaylist.Segment initSegment = segment.initializationSegment;
        if (initSegment != null && initSegment != lastInitSegment) {
          lastInitSegment = initSegment;
          addSegment(mediaPlaylist, initSegment, seenEncryptionKeyUris, segments);
        }
        addSegment(mediaPlaylist, segment, seenEncryptionKeyUris, segments);
      }
    }
    return segments;
  }

  private void addMediaPlaylistDataSpecs(List<Uri> mediaPlaylistUrls, List<DataSpec> out) {
    for (int i = 0; i < mediaPlaylistUrls.size(); i++) {
      out.add(SegmentDownloader.getCompressibleDataSpec(mediaPlaylistUrls.get(i)));
    }
  }

  private void addSegment(
      HlsMediaPlaylist mediaPlaylist,
      HlsMediaPlaylist.Segment segment,
      HashSet<Uri> seenEncryptionKeyUris,
      ArrayList<Segment> out) {
    String baseUri = mediaPlaylist.baseUri;
    long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs;
    if (segment.fullSegmentEncryptionKeyUri != null) {
      Uri keyUri = UriUtil.resolveToUri(baseUri, segment.fullSegmentEncryptionKeyUri);
      if (seenEncryptionKeyUris.add(keyUri)) {
        out.add(new Segment(startTimeUs, SegmentDownloader.getCompressibleDataSpec(keyUri)));
      }
    }
    Uri segmentUri = UriUtil.resolveToUri(baseUri, segment.url);
    DataSpec dataSpec = new DataSpec(segmentUri, segment.byteRangeOffset, segment.byteRangeLength);
    out.add(new Segment(startTimeUs, dataSpec));
  }
}