diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/SegmentDownloader.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/SegmentDownloader.java index d4b8ece7262..c49ce693688 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/SegmentDownloader.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/SegmentDownloader.java @@ -76,7 +76,7 @@ public int compareTo(Segment other) { } private static final int BUFFER_SIZE_BYTES = 128 * 1024; - private static final long MAX_MERGED_SEGMENT_START_TIME_DIFF_US = 20 * C.MICROS_PER_SECOND; + public static final long DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_US = 20 * C.MICROS_PER_SECOND; private final DataSpec manifestDataSpec; private final Parser manifestParser; @@ -86,6 +86,7 @@ public int compareTo(Segment other) { private final CacheKeyFactory cacheKeyFactory; @Nullable private final PriorityTaskManager priorityTaskManager; private final Executor executor; + private final long maxMergedSegmentStartTimeDiffUs; /** * The currently active runnables. @@ -99,6 +100,30 @@ public int compareTo(Segment other) { private volatile boolean isCanceled; + /** + * @deprecated Use {@link SegmentDownloader#SegmentDownloader(MediaItem, Parser, + * CacheDataSource.Factory, Executor, long)} instead. + */ + @Deprecated + public SegmentDownloader( + MediaItem mediaItem, + Parser manifestParser, + CacheDataSource.Factory cacheDataSourceFactory, + Executor executor + ) { + checkNotNull(mediaItem.localConfiguration); + this.manifestDataSpec = getCompressibleDataSpec(mediaItem.localConfiguration.uri); + this.manifestParser = manifestParser; + this.streamKeys = new ArrayList<>(mediaItem.localConfiguration.streamKeys); + this.cacheDataSourceFactory = cacheDataSourceFactory; + this.executor = executor; + cache = Assertions.checkNotNull(cacheDataSourceFactory.getCache()); + cacheKeyFactory = cacheDataSourceFactory.getCacheKeyFactory(); + priorityTaskManager = cacheDataSourceFactory.getUpstreamPriorityTaskManager(); + activeRunnables = new ArrayList<>(); + maxMergedSegmentStartTimeDiffUs = DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_US; + } + /** * @param mediaItem The {@link MediaItem} to be downloaded. * @param manifestParser A parser for manifests belonging to the media to be downloaded. @@ -107,12 +132,17 @@ public int compareTo(Segment other) { * @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 SegmentDownloader( MediaItem mediaItem, Parser manifestParser, CacheDataSource.Factory cacheDataSourceFactory, - Executor executor) { + Executor executor, + long maxMergedSegmentStartTimeDiffMs + ) { checkNotNull(mediaItem.localConfiguration); this.manifestDataSpec = getCompressibleDataSpec(mediaItem.localConfiguration.uri); this.manifestParser = manifestParser; @@ -123,6 +153,7 @@ public SegmentDownloader( cacheKeyFactory = cacheDataSourceFactory.getCacheKeyFactory(); priorityTaskManager = cacheDataSourceFactory.getUpstreamPriorityTaskManager(); activeRunnables = new ArrayList<>(); + maxMergedSegmentStartTimeDiffUs = Util.msToUs(maxMergedSegmentStartTimeDiffMs); } @Override @@ -145,7 +176,7 @@ public final void download(@Nullable ProgressListener progressListener) // Sort the segments so that we download media in the right order from the start of the // content, and merge segments where possible to minimize the number of server round trips. Collections.sort(segments); - mergeSegments(segments, cacheKeyFactory); + mergeSegments(segments, cacheKeyFactory, maxMergedSegmentStartTimeDiffUs); // Scan the segments, removing any that are fully downloaded. int totalSegments = segments.size(); @@ -416,7 +447,11 @@ private void removeActiveRunnable(int index) { } } - private static void mergeSegments(List segments, CacheKeyFactory keyFactory) { + private static void mergeSegments( + List segments, + CacheKeyFactory keyFactory, + long maxMergedSegmentStartTimeDiffUs + ) { HashMap lastIndexByCacheKey = new HashMap<>(); int nextOutIndex = 0; for (int i = 0; i < segments.size(); i++) { @@ -425,7 +460,7 @@ private static void mergeSegments(List segments, CacheKeyFactory keyFac @Nullable Integer lastIndex = lastIndexByCacheKey.get(cacheKey); @Nullable Segment lastSegment = lastIndex == null ? null : segments.get(lastIndex); if (lastSegment == null - || segment.startTimeUs > lastSegment.startTimeUs + MAX_MERGED_SEGMENT_START_TIME_DIFF_US + || segment.startTimeUs > lastSegment.startTimeUs + maxMergedSegmentStartTimeDiffUs || !canMergeSegments(lastSegment.dataSpec, segment.dataSpec)) { lastIndexByCacheKey.put(cacheKey, nextOutIndex); segments.set(nextOutIndex, segment); diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/offline/DashDownloader.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/offline/DashDownloader.java index e3c41727288..ee496ff2083 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/offline/DashDownloader.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/offline/DashDownloader.java @@ -100,7 +100,28 @@ public DashDownloader(MediaItem mediaItem, CacheDataSource.Factory cacheDataSour */ public DashDownloader( MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory, Executor executor) { - this(mediaItem, new DashManifestParser(), cacheDataSourceFactory, executor); + this(mediaItem, new DashManifestParser(), cacheDataSourceFactory, executor, DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_US); + } + + /** + * @deprecated Use {@link DashDownloader#DashDownloader(MediaItem, Parser, + * CacheDataSource.Factory, Executor, long)} instead. + */ + @Deprecated + public DashDownloader( + MediaItem mediaItem, + Parser manifestParser, + CacheDataSource.Factory cacheDataSourceFactory, + Executor executor + ) { + super( + mediaItem, + manifestParser, + cacheDataSourceFactory, + executor, + DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_US + ); + baseUrlExclusionList = new BaseUrlExclusionList(); } /** @@ -113,13 +134,24 @@ public DashDownloader( * @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 DashDownloader( MediaItem mediaItem, Parser manifestParser, CacheDataSource.Factory cacheDataSourceFactory, - Executor executor) { - super(mediaItem, manifestParser, cacheDataSourceFactory, executor); + Executor executor, + long maxMergedSegmentStartTimeDiffMs + ) { + super( + mediaItem, + manifestParser, + cacheDataSourceFactory, + executor, + maxMergedSegmentStartTimeDiffMs + ); baseUrlExclusionList = new BaseUrlExclusionList(); } diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/offline/HlsDownloader.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/offline/HlsDownloader.java index 777694e9f63..380ca9634bc 100644 --- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/offline/HlsDownloader.java +++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/offline/HlsDownloader.java @@ -89,7 +89,33 @@ public HlsDownloader(MediaItem mediaItem, CacheDataSource.Factory cacheDataSourc */ public HlsDownloader( MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory, Executor executor) { - this(mediaItem, new HlsPlaylistParser(), cacheDataSourceFactory, executor); + this( + mediaItem, + new HlsPlaylistParser(), + cacheDataSourceFactory, + executor, + DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_US + ); + } + + /** + * @deprecated Use {@link HlsDownloader#HlsDownloader(MediaItem, Parser, + * CacheDataSource.Factory, Executor, long)} instead. + */ + @Deprecated + public HlsDownloader( + MediaItem mediaItem, + Parser manifestParser, + CacheDataSource.Factory cacheDataSourceFactory, + Executor executor + ) { + super( + mediaItem, + manifestParser, + cacheDataSourceFactory, + executor, + DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_US + ); } /** @@ -102,13 +128,24 @@ public HlsDownloader( * @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 manifestParser, CacheDataSource.Factory cacheDataSourceFactory, - Executor executor) { - super(mediaItem, manifestParser, cacheDataSourceFactory, executor); + Executor executor, + long maxMergedSegmentStartTimeDiffMs + ) { + super( + mediaItem, + manifestParser, + cacheDataSourceFactory, + executor, + maxMergedSegmentStartTimeDiffMs + ); } @Override diff --git a/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/offline/SsDownloader.java b/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/offline/SsDownloader.java index 4ca632271bb..5a95d966e7a 100644 --- a/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/offline/SsDownloader.java +++ b/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/offline/SsDownloader.java @@ -93,7 +93,29 @@ public SsDownloader( .build(), new SsManifestParser(), cacheDataSourceFactory, - executor); + executor, + DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_US + ); + } + + /** + * @deprecated Use {@link SsDownloader#SsDownloader(MediaItem, Parser, + * CacheDataSource.Factory, Executor, long)} instead. + */ + @Deprecated + public SsDownloader( + MediaItem mediaItem, + Parser manifestParser, + CacheDataSource.Factory cacheDataSourceFactory, + Executor executor + ) { + super( + mediaItem, + manifestParser, + cacheDataSourceFactory, + executor, + DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_US + ); } /** @@ -106,13 +128,24 @@ public SsDownloader( * @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 SsDownloader( MediaItem mediaItem, Parser manifestParser, CacheDataSource.Factory cacheDataSourceFactory, - Executor executor) { - super(mediaItem, manifestParser, cacheDataSourceFactory, executor); + Executor executor, + long maxMergedSegmentStartTimeDiffMs + ) { + super( + mediaItem, + manifestParser, + cacheDataSourceFactory, + executor, + maxMergedSegmentStartTimeDiffMs + ); } @Override