diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 49c7112baa..f97db1cef6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -39,8 +39,11 @@ ([#9024](https://github.com/google/ExoPlayer/issues/9024)). * Fix accessibility focus in `PlayerControlView` ([#9111](https://github.com/google/ExoPlayer/issues/9111)). - * Fix issue that `StyledPlayerView` and `PlayerView` don't update UI - when available player commands change. + * Fix issue that `StyledPlayerView` and `PlayerView` don't update UI when + available player commands change. +* DASH + * Use identical cache keys for downloading and playing DASH segments + ([#9370](https://github.com/google/ExoPlayer/issues/9370)). * Remove deprecated symbols: * Remove `Renderer.VIDEO_SCALING_MODE_*` constants. Use identically named constants in `C` instead. diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java index 9ca1c176fe..78eec4ec69 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java @@ -46,20 +46,20 @@ public final class DashUtil { /** * Builds a {@link DataSpec} for a given {@link RangedUri} belonging to {@link Representation}. * + * @param representation The {@link Representation} to which the request belongs. * @param baseUrl The base url with which to resolve the request URI. * @param requestUri The {@link RangedUri} of the data to request. - * @param cacheKey An optional cache key. * @param flags Flags to be set on the returned {@link DataSpec}. See {@link * DataSpec.Builder#setFlags(int)}. * @return The {@link DataSpec}. */ public static DataSpec buildDataSpec( - String baseUrl, RangedUri requestUri, @Nullable String cacheKey, int flags) { + Representation representation, String baseUrl, RangedUri requestUri, int flags) { return new DataSpec.Builder() .setUri(requestUri.resolveUri(baseUrl)) .setPosition(requestUri.start) .setLength(requestUri.length) - .setKey(cacheKey) + .setKey(resolveCacheKey(representation, requestUri)) .setFlags(flags) .build(); } @@ -77,8 +77,7 @@ public final class DashUtil { */ public static DataSpec buildDataSpec( Representation representation, RangedUri requestUri, int flags) { - return buildDataSpec( - representation.baseUrls.get(0).url, requestUri, representation.getCacheKey(), flags); + return buildDataSpec(representation, representation.baseUrls.get(0).url, requestUri, flags); } /** @@ -289,9 +288,9 @@ public final class DashUtil { throws IOException { DataSpec dataSpec = DashUtil.buildDataSpec( + representation, representation.baseUrls.get(baseUrlIndex).url, requestUri, - representation.getCacheKey(), /* flags= */ 0); InitializationChunk initializationChunk = new InitializationChunk( @@ -304,6 +303,21 @@ public final class DashUtil { initializationChunk.load(); } + /** + * Resolves the cache key to be used when requesting the given ranged URI for the given {@link + * Representation}. + * + * @param representation The {@link Representation} to which the URI belongs to. + * @param rangedUri The URI for which to resolve the cache key. + * @return The cache key. + */ + public static String resolveCacheKey(Representation representation, RangedUri rangedUri) { + @Nullable String cacheKey = representation.getCacheKey(); + return cacheKey != null + ? cacheKey + : rangedUri.resolveUri(representation.baseUrls.get(0).url).toString(); + } + private static ChunkExtractor newChunkExtractor(int trackType, Format format) { String mimeType = format.containerMimeType; boolean isWebm = diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index b147867854..d878430747 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -628,10 +628,7 @@ public class DefaultDashChunkSource implements DashChunkSource { } DataSpec dataSpec = DashUtil.buildDataSpec( - representationHolder.selectedBaseUrl.url, - requestUri, - representation.getCacheKey(), - /* flags= */ 0); + representation, representationHolder.selectedBaseUrl.url, requestUri, /* flags= */ 0); return new InitializationChunk( dataSource, dataSpec, @@ -664,10 +661,7 @@ public class DefaultDashChunkSource implements DashChunkSource { : DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED; DataSpec dataSpec = DashUtil.buildDataSpec( - representationHolder.selectedBaseUrl.url, - segmentUri, - representation.getCacheKey(), - flags); + representation, representationHolder.selectedBaseUrl.url, segmentUri, flags); return new SingleSampleMediaChunk( dataSource, dataSpec, @@ -706,10 +700,7 @@ public class DefaultDashChunkSource implements DashChunkSource { : DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED; DataSpec dataSpec = DashUtil.buildDataSpec( - representationHolder.selectedBaseUrl.url, - segmentUri, - representation.getCacheKey(), - flags); + representation, representationHolder.selectedBaseUrl.url, segmentUri, flags); long sampleOffsetUs = -representation.presentationTimeOffsetUs; return new ContainerMediaChunk( dataSource, @@ -765,9 +756,9 @@ public class DefaultDashChunkSource implements DashChunkSource { ? 0 : DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED; return DashUtil.buildDataSpec( + representationHolder.representation, representationHolder.selectedBaseUrl.url, segmentUri, - representationHolder.representation.getCacheKey(), flags); } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java index 513ab6403c..5bc557761d 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java @@ -15,12 +15,15 @@ */ package com.google.android.exoplayer2.source.dash.offline; +import static com.google.android.exoplayer2.util.Util.castNonNull; + import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.offline.DownloadException; import com.google.android.exoplayer2.offline.SegmentDownloader; +import com.google.android.exoplayer2.source.dash.BaseUrlExclusionList; import com.google.android.exoplayer2.source.dash.DashSegmentIndex; import com.google.android.exoplayer2.source.dash.DashUtil; import com.google.android.exoplayer2.source.dash.DashWrappingSegmentIndex; @@ -70,6 +73,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; */ public final class DashDownloader extends SegmentDownloader { + private final BaseUrlExclusionList baseUrlExclusionList; + /** * Creates a new instance. * @@ -113,6 +118,7 @@ public final class DashDownloader extends SegmentDownloader { CacheDataSource.Factory cacheDataSourceFactory, Executor executor) { super(mediaItem, manifestParser, cacheDataSourceFactory, executor); + baseUrlExclusionList = new BaseUrlExclusionList(); } @Override @@ -163,28 +169,32 @@ public final class DashDownloader extends SegmentDownloader { throw new DownloadException("Unbounded segment index"); } - String baseUrl = representation.baseUrls.get(0).url; - RangedUri initializationUri = representation.getInitializationUri(); + String baseUrl = castNonNull(baseUrlExclusionList.selectBaseUrl(representation.baseUrls)).url; + @Nullable RangedUri initializationUri = representation.getInitializationUri(); if (initializationUri != null) { - addSegment(periodStartUs, baseUrl, initializationUri, out); + out.add(createSegment(representation, baseUrl, periodStartUs, initializationUri)); } - RangedUri indexUri = representation.getIndexUri(); + @Nullable RangedUri indexUri = representation.getIndexUri(); if (indexUri != null) { - addSegment(periodStartUs, baseUrl, indexUri, out); + out.add(createSegment(representation, baseUrl, periodStartUs, indexUri)); } long firstSegmentNum = index.getFirstSegmentNum(); long lastSegmentNum = firstSegmentNum + segmentCount - 1; for (long j = firstSegmentNum; j <= lastSegmentNum; j++) { - addSegment(periodStartUs + index.getTimeUs(j), baseUrl, index.getSegmentUrl(j), out); + out.add( + createSegment( + representation, + baseUrl, + periodStartUs + index.getTimeUs(j), + index.getSegmentUrl(j))); } } } - private static void addSegment( - long startTimeUs, String baseUrl, RangedUri rangedUri, ArrayList out) { - DataSpec dataSpec = - new DataSpec(rangedUri.resolveUri(baseUrl), rangedUri.start, rangedUri.length); - out.add(new Segment(startTimeUs, dataSpec)); + private Segment createSegment( + Representation representation, String baseUrl, long startTimeUs, RangedUri rangedUri) { + DataSpec dataSpec = DashUtil.buildDataSpec(representation, baseUrl, rangedUri, /* flags= */ 0); + return new Segment(startTimeUs, dataSpec); } @Nullable diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java index 655baea575..d0cb9dabdd 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; import com.google.android.exoplayer2.source.dash.manifest.BaseUrl; import com.google.android.exoplayer2.source.dash.manifest.Period; +import com.google.android.exoplayer2.source.dash.manifest.RangedUri; import com.google.android.exoplayer2.source.dash.manifest.Representation; import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase; import com.google.android.exoplayer2.upstream.DummyDataSource; @@ -67,6 +68,46 @@ public final class DashUtilTest { assertThat(format).isNull(); } + @Test + public void resolveCacheKey_representationCacheKeyIsNull_resolvesRangedUriWithFirstBaseUrl() { + ImmutableList baseUrls = + ImmutableList.of(new BaseUrl("http://www.google.com"), new BaseUrl("http://www.foo.com")); + Representation.SingleSegmentRepresentation representation = + new Representation.SingleSegmentRepresentation( + /* revisionId= */ 1L, + new Format.Builder().build(), + baseUrls, + new SingleSegmentBase(), + /* inbandEventStreams= */ null, + /* cacheKey= */ null, + /* contentLength= */ 1); + RangedUri rangedUri = new RangedUri("path/to/resource", /* start= */ 0, /* length= */ 1); + + String cacheKey = DashUtil.resolveCacheKey(representation, rangedUri); + + assertThat(cacheKey).isEqualTo("http://www.google.com/path/to/resource"); + } + + @Test + public void resolveCacheKey_representationCacheKeyDefined_usesRepresentationCacheKey() { + ImmutableList baseUrls = + ImmutableList.of(new BaseUrl("http://www.google.com"), new BaseUrl("http://www.foo.com")); + Representation.SingleSegmentRepresentation representation = + new Representation.SingleSegmentRepresentation( + /* revisionId= */ 1L, + new Format.Builder().build(), + baseUrls, + new SingleSegmentBase(), + /* inbandEventStreams= */ null, + "cacheKey", + /* contentLength= */ 1); + RangedUri rangedUri = new RangedUri("path/to/resource", /* start= */ 0, /* length= */ 1); + + String cacheKey = DashUtil.resolveCacheKey(representation, rangedUri); + + assertThat(cacheKey).isEqualTo("cacheKey"); + } + private static Period newPeriod(AdaptationSet... adaptationSets) { return new Period("", 0, Arrays.asList(adaptationSets)); }