From 78ecb10ac0ee8d36c127dce2dbc5569c516e2379 Mon Sep 17 00:00:00 2001 From: bachinger Date: Thu, 15 Jul 2021 19:14:59 +0100 Subject: [PATCH] Add RepresentationHolder.selectedBaseUrl and use it for new chunks PiperOrigin-RevId: 384968532 --- .../exoplayer2/source/dash/DashUtil.java | 142 +++++++++++++++--- .../source/dash/DefaultDashChunkSource.java | 79 ++++++++-- 2 files changed, 184 insertions(+), 37 deletions(-) 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 cf5be4f1be..9ca1c176fe 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,6 +46,29 @@ public final class DashUtil { /** * Builds a {@link DataSpec} for a given {@link RangedUri} belonging to {@link Representation}. * + * @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) { + return new DataSpec.Builder() + .setUri(requestUri.resolveUri(baseUrl)) + .setPosition(requestUri.start) + .setLength(requestUri.length) + .setKey(cacheKey) + .setFlags(flags) + .build(); + } + + /** + * Builds a {@link DataSpec} for a given {@link RangedUri} belonging to {@link Representation}. + * + *

Uses the first base URL of the representation to build the data spec. + * * @param representation The {@link Representation} to which the request belongs. * @param requestUri The {@link RangedUri} of the data to request. * @param flags Flags to be set on the returned {@link DataSpec}. See {@link @@ -54,13 +77,8 @@ public final class DashUtil { */ public static DataSpec buildDataSpec( Representation representation, RangedUri requestUri, int flags) { - return new DataSpec.Builder() - .setUri(requestUri.resolveUri(representation.baseUrls.get(0).url)) - .setPosition(requestUri.start) - .setLength(requestUri.length) - .setKey(representation.getCacheKey()) - .setFlags(flags) - .build(); + return buildDataSpec( + representation.baseUrls.get(0).url, requestUri, representation.getCacheKey(), flags); } /** @@ -96,6 +114,7 @@ public final class DashUtil { } } Format manifestFormat = representation.format; + @Nullable Format sampleFormat = DashUtil.loadSampleFormat(dataSource, primaryTrackType, representation); return sampleFormat == null ? manifestFormat @@ -109,24 +128,46 @@ public final class DashUtil { * @param trackType The type of the representation. Typically one of the {@link * com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants. * @param representation The representation which initialization chunk belongs to. + * @param baseUrlIndex The index of the base URL to be picked from the {@link + * Representation#baseUrls list of base URLs}. * @return the sample {@link Format} of the given representation. * @throws IOException Thrown when there is an error while loading. */ @Nullable public static Format loadSampleFormat( - DataSource dataSource, int trackType, Representation representation) throws IOException { + DataSource dataSource, int trackType, Representation representation, int baseUrlIndex) + throws IOException { if (representation.getInitializationUri() == null) { return null; } ChunkExtractor chunkExtractor = newChunkExtractor(trackType, representation.format); try { - loadInitializationData(chunkExtractor, dataSource, representation, /* loadIndex= */ false); + loadInitializationData( + chunkExtractor, dataSource, representation, baseUrlIndex, /* loadIndex= */ false); } finally { chunkExtractor.release(); } return Assertions.checkStateNotNull(chunkExtractor.getSampleFormats())[0]; } + /** + * Loads initialization data for the {@code representation} and returns the sample {@link Format}. + * + *

Uses the first base URL for loading the format. + * + * @param dataSource The source from which the data should be loaded. + * @param trackType The type of the representation. Typically one of the {@link + * com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants. + * @param representation The representation which initialization chunk belongs to. + * @return the sample {@link Format} of the given representation. + * @throws IOException Thrown when there is an error while loading. + */ + @Nullable + public static Format loadSampleFormat( + DataSource dataSource, int trackType, Representation representation) throws IOException { + return loadSampleFormat(dataSource, trackType, representation, /* baseUrlIndex= */ 0); + } + /** * Loads initialization and index data for the {@code representation} and returns the {@link * ChunkIndex}. @@ -135,6 +176,38 @@ public final class DashUtil { * @param trackType The type of the representation. Typically one of the {@link * com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants. * @param representation The representation which initialization chunk belongs to. + * @param baseUrlIndex The index of the base URL with which to resolve the request URI. + * @return The {@link ChunkIndex} of the given representation, or null if no initialization or + * index data exists. + * @throws IOException Thrown when there is an error while loading. + */ + @Nullable + public static ChunkIndex loadChunkIndex( + DataSource dataSource, int trackType, Representation representation, int baseUrlIndex) + throws IOException { + if (representation.getInitializationUri() == null) { + return null; + } + ChunkExtractor chunkExtractor = newChunkExtractor(trackType, representation.format); + try { + loadInitializationData( + chunkExtractor, dataSource, representation, baseUrlIndex, /* loadIndex= */ true); + } finally { + chunkExtractor.release(); + } + return chunkExtractor.getChunkIndex(); + } + + /** + * Loads initialization and index data for the {@code representation} and returns the {@link + * ChunkIndex}. + * + *

Uses the first base URL for loading the index. + * + * @param dataSource The source from which the data should be loaded. + * @param trackType The type of the representation. Typically one of the {@link + * com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants. + * @param representation The representation which initialization chunk belongs to. * @return The {@link ChunkIndex} of the given representation, or null if no initialization or * index data exists. * @throws IOException Thrown when there is an error while loading. @@ -142,16 +215,7 @@ public final class DashUtil { @Nullable public static ChunkIndex loadChunkIndex( DataSource dataSource, int trackType, Representation representation) throws IOException { - if (representation.getInitializationUri() == null) { - return null; - } - ChunkExtractor chunkExtractor = newChunkExtractor(trackType, representation.format); - try { - loadInitializationData(chunkExtractor, dataSource, representation, /* loadIndex= */ true); - } finally { - chunkExtractor.release(); - } - return chunkExtractor.getChunkIndex(); + return loadChunkIndex(dataSource, trackType, representation, /* baseUrlIndex= */ 0); } /** @@ -161,6 +225,7 @@ public final class DashUtil { * @param chunkExtractor The {@link ChunkExtractor} to use. * @param dataSource The source from which the data should be loaded. * @param representation The representation which initialization chunk belongs to. + * @param baseUrlIndex The index of the base URL with which to resolve the request URI. * @param loadIndex Whether to load index data too. * @throws IOException Thrown when there is an error while loading. */ @@ -168,6 +233,7 @@ public final class DashUtil { ChunkExtractor chunkExtractor, DataSource dataSource, Representation representation, + int baseUrlIndex, boolean loadIndex) throws IOException { RangedUri initializationUri = Assertions.checkNotNull(representation.getInitializationUri()); @@ -179,24 +245,54 @@ public final class DashUtil { } // It's common for initialization and index data to be stored adjacently. Attempt to merge // the two requests together to request both at once. - requestUri = initializationUri.attemptMerge(indexUri, representation.baseUrls.get(0).url); + requestUri = + initializationUri.attemptMerge(indexUri, representation.baseUrls.get(baseUrlIndex).url); if (requestUri == null) { - loadInitializationData(dataSource, representation, chunkExtractor, initializationUri); + loadInitializationData( + dataSource, representation, baseUrlIndex, chunkExtractor, initializationUri); requestUri = indexUri; } } else { requestUri = initializationUri; } - loadInitializationData(dataSource, representation, chunkExtractor, requestUri); + loadInitializationData(dataSource, representation, baseUrlIndex, chunkExtractor, requestUri); + } + + /** + * Loads initialization data for the {@code representation} and optionally index data then returns + * a {@link BundledChunkExtractor} which contains the output. + * + *

Uses the first base URL for loading the initialization data. + * + * @param chunkExtractor The {@link ChunkExtractor} to use. + * @param dataSource The source from which the data should be loaded. + * @param representation The representation which initialization chunk belongs to. + * @param loadIndex Whether to load index data too. + * @throws IOException Thrown when there is an error while loading. + */ + public static void loadInitializationData( + ChunkExtractor chunkExtractor, + DataSource dataSource, + Representation representation, + boolean loadIndex) + throws IOException { + loadInitializationData( + chunkExtractor, dataSource, representation, /* baseUrlIndex= */ 0, loadIndex); } private static void loadInitializationData( DataSource dataSource, Representation representation, + int baseUrlIndex, ChunkExtractor chunkExtractor, RangedUri requestUri) throws IOException { - DataSpec dataSpec = DashUtil.buildDataSpec(representation, requestUri, /* flags= */ 0); + DataSpec dataSpec = + DashUtil.buildDataSpec( + representation.baseUrls.get(baseUrlIndex).url, + requestUri, + representation.getCacheKey(), + /* flags= */ 0); InitializationChunk initializationChunk = new InitializationChunk( dataSource, 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 5cf7e8836f..3757a5b123 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 @@ -40,6 +40,7 @@ import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import com.google.android.exoplayer2.source.chunk.SingleSampleMediaChunk; import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerTrackEmsgHandler; 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.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.RangedUri; import com.google.android.exoplayer2.source.dash.manifest.Representation; @@ -203,6 +204,7 @@ public class DefaultDashChunkSource implements DashChunkSource { new RepresentationHolder( periodDurationUs, representation, + representation.baseUrls.get(0), BundledChunkExtractor.FACTORY.createProgressiveMediaExtractor( trackType, representation.format, @@ -562,14 +564,20 @@ public class DefaultDashChunkSource implements DashChunkSource { if (initializationUri != null) { // It's common for initialization and index data to be stored adjacently. Attempt to merge // the two requests together to request both at once. - requestUri = initializationUri.attemptMerge(indexUri, representation.baseUrls.get(0).url); + requestUri = + initializationUri.attemptMerge(indexUri, representationHolder.selectedBaseUrl.url); if (requestUri == null) { requestUri = initializationUri; } } else { requestUri = indexUri; } - DataSpec dataSpec = DashUtil.buildDataSpec(representation, requestUri, /* flags= */ 0); + DataSpec dataSpec = + DashUtil.buildDataSpec( + representationHolder.selectedBaseUrl.url, + requestUri, + representation.getCacheKey(), + /* flags= */ 0); return new InitializationChunk( dataSource, dataSpec, @@ -593,7 +601,6 @@ public class DefaultDashChunkSource implements DashChunkSource { Representation representation = representationHolder.representation; long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum); RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum); - String baseUrl = representation.baseUrls.get(0).url; if (representationHolder.chunkExtractor == null) { long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum); int flags = @@ -601,7 +608,12 @@ public class DefaultDashChunkSource implements DashChunkSource { firstSegmentNum, nowPeriodTimeUs) ? 0 : DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED; - DataSpec dataSpec = DashUtil.buildDataSpec(representation, segmentUri, flags); + DataSpec dataSpec = + DashUtil.buildDataSpec( + representationHolder.selectedBaseUrl.url, + segmentUri, + representation.getCacheKey(), + flags); return new SingleSampleMediaChunk( dataSource, dataSpec, @@ -617,7 +629,9 @@ public class DefaultDashChunkSource implements DashChunkSource { int segmentCount = 1; for (int i = 1; i < maxSegmentCount; i++) { RangedUri nextSegmentUri = representationHolder.getSegmentUrl(firstSegmentNum + i); - @Nullable RangedUri mergedSegmentUri = segmentUri.attemptMerge(nextSegmentUri, baseUrl); + @Nullable + RangedUri mergedSegmentUri = + segmentUri.attemptMerge(nextSegmentUri, representationHolder.selectedBaseUrl.url); if (mergedSegmentUri == null) { // Unable to merge segment fetches because the URIs do not merge. break; @@ -636,7 +650,12 @@ public class DefaultDashChunkSource implements DashChunkSource { representationHolder.isSegmentAvailableAtFullNetworkSpeed(segmentNum, nowPeriodTimeUs) ? 0 : DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED; - DataSpec dataSpec = DashUtil.buildDataSpec(representation, segmentUri, flags); + DataSpec dataSpec = + DashUtil.buildDataSpec( + representationHolder.selectedBaseUrl.url, + segmentUri, + representation.getCacheKey(), + flags); long sampleOffsetUs = -representation.presentationTimeOffsetUs; return new ContainerMediaChunk( dataSource, @@ -691,7 +710,11 @@ public class DefaultDashChunkSource implements DashChunkSource { representationHolder.isSegmentAvailableAtFullNetworkSpeed(currentIndex, nowPeriodTimeUs) ? 0 : DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED; - return DashUtil.buildDataSpec(representationHolder.representation, segmentUri, flags); + return DashUtil.buildDataSpec( + representationHolder.selectedBaseUrl.url, + segmentUri, + representationHolder.representation.getCacheKey(), + flags); } @Override @@ -713,6 +736,7 @@ public class DefaultDashChunkSource implements DashChunkSource { @Nullable /* package */ final ChunkExtractor chunkExtractor; public final Representation representation; + public final BaseUrl selectedBaseUrl; @Nullable public final DashSegmentIndex segmentIndex; private final long periodDurationUs; @@ -721,11 +745,13 @@ public class DefaultDashChunkSource implements DashChunkSource { /* package */ RepresentationHolder( long periodDurationUs, Representation representation, + BaseUrl selectedBaseUrl, @Nullable ChunkExtractor chunkExtractor, long segmentNumShift, @Nullable DashSegmentIndex segmentIndex) { this.periodDurationUs = periodDurationUs; this.representation = representation; + this.selectedBaseUrl = selectedBaseUrl; this.segmentNumShift = segmentNumShift; this.chunkExtractor = chunkExtractor; this.segmentIndex = segmentIndex; @@ -735,26 +761,41 @@ public class DefaultDashChunkSource implements DashChunkSource { /* package */ RepresentationHolder copyWithNewRepresentation( long newPeriodDurationUs, Representation newRepresentation) throws BehindLiveWindowException { - DashSegmentIndex oldIndex = representation.getIndex(); - DashSegmentIndex newIndex = newRepresentation.getIndex(); + @Nullable DashSegmentIndex oldIndex = representation.getIndex(); + @Nullable DashSegmentIndex newIndex = newRepresentation.getIndex(); if (oldIndex == null) { // Segment numbers cannot shift if the index isn't defined by the manifest. return new RepresentationHolder( - newPeriodDurationUs, newRepresentation, chunkExtractor, segmentNumShift, oldIndex); + newPeriodDurationUs, + newRepresentation, + selectedBaseUrl, + chunkExtractor, + segmentNumShift, + oldIndex); } if (!oldIndex.isExplicit()) { // Segment numbers cannot shift if the index isn't explicit. return new RepresentationHolder( - newPeriodDurationUs, newRepresentation, chunkExtractor, segmentNumShift, newIndex); + newPeriodDurationUs, + newRepresentation, + selectedBaseUrl, + chunkExtractor, + segmentNumShift, + newIndex); } long oldIndexSegmentCount = oldIndex.getSegmentCount(newPeriodDurationUs); if (oldIndexSegmentCount == 0) { // Segment numbers cannot shift if the old index was empty. return new RepresentationHolder( - newPeriodDurationUs, newRepresentation, chunkExtractor, segmentNumShift, newIndex); + newPeriodDurationUs, + newRepresentation, + selectedBaseUrl, + chunkExtractor, + segmentNumShift, + newIndex); } long oldIndexFirstSegmentNum = oldIndex.getFirstSegmentNum(); @@ -786,13 +827,23 @@ public class DefaultDashChunkSource implements DashChunkSource { - newIndexFirstSegmentNum; } return new RepresentationHolder( - newPeriodDurationUs, newRepresentation, chunkExtractor, newSegmentNumShift, newIndex); + newPeriodDurationUs, + newRepresentation, + selectedBaseUrl, + chunkExtractor, + newSegmentNumShift, + newIndex); } @CheckResult /* package */ RepresentationHolder copyWithNewSegmentIndex(DashSegmentIndex segmentIndex) { return new RepresentationHolder( - periodDurationUs, representation, chunkExtractor, segmentNumShift, segmentIndex); + periodDurationUs, + representation, + selectedBaseUrl, + chunkExtractor, + segmentNumShift, + segmentIndex); } public long getFirstSegmentNum() {