Add RepresentationHolder.selectedBaseUrl and use it for new chunks

PiperOrigin-RevId: 384968532
This commit is contained in:
bachinger 2021-07-15 19:14:59 +01:00 committed by Ian Baker
parent 99abb4e1e9
commit 78ecb10ac0
2 changed files with 184 additions and 37 deletions

View File

@ -46,6 +46,29 @@ public final class DashUtil {
/** /**
* Builds a {@link DataSpec} for a given {@link RangedUri} belonging to {@link Representation}. * 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}.
*
* <p>Uses the first base URL of the representation to build the data spec.
*
* @param representation The {@link Representation} to which the request belongs. * @param representation The {@link Representation} to which the request belongs.
* @param requestUri The {@link RangedUri} of the data to request. * @param requestUri The {@link RangedUri} of the data to request.
* @param flags Flags to be set on the returned {@link DataSpec}. See {@link * @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( public static DataSpec buildDataSpec(
Representation representation, RangedUri requestUri, int flags) { Representation representation, RangedUri requestUri, int flags) {
return new DataSpec.Builder() return buildDataSpec(
.setUri(requestUri.resolveUri(representation.baseUrls.get(0).url)) representation.baseUrls.get(0).url, requestUri, representation.getCacheKey(), flags);
.setPosition(requestUri.start)
.setLength(requestUri.length)
.setKey(representation.getCacheKey())
.setFlags(flags)
.build();
} }
/** /**
@ -96,6 +114,7 @@ public final class DashUtil {
} }
} }
Format manifestFormat = representation.format; Format manifestFormat = representation.format;
@Nullable
Format sampleFormat = DashUtil.loadSampleFormat(dataSource, primaryTrackType, representation); Format sampleFormat = DashUtil.loadSampleFormat(dataSource, primaryTrackType, representation);
return sampleFormat == null return sampleFormat == null
? manifestFormat ? manifestFormat
@ -109,24 +128,46 @@ public final class DashUtil {
* @param trackType The type of the representation. Typically one of the {@link * @param trackType The type of the representation. Typically one of the {@link
* com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants. * com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants.
* @param representation The representation which initialization chunk belongs to. * @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. * @return the sample {@link Format} of the given representation.
* @throws IOException Thrown when there is an error while loading. * @throws IOException Thrown when there is an error while loading.
*/ */
@Nullable @Nullable
public static Format loadSampleFormat( 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) { if (representation.getInitializationUri() == null) {
return null; return null;
} }
ChunkExtractor chunkExtractor = newChunkExtractor(trackType, representation.format); ChunkExtractor chunkExtractor = newChunkExtractor(trackType, representation.format);
try { try {
loadInitializationData(chunkExtractor, dataSource, representation, /* loadIndex= */ false); loadInitializationData(
chunkExtractor, dataSource, representation, baseUrlIndex, /* loadIndex= */ false);
} finally { } finally {
chunkExtractor.release(); chunkExtractor.release();
} }
return Assertions.checkStateNotNull(chunkExtractor.getSampleFormats())[0]; return Assertions.checkStateNotNull(chunkExtractor.getSampleFormats())[0];
} }
/**
* Loads initialization data for the {@code representation} and returns the sample {@link Format}.
*
* <p>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 * Loads initialization and index data for the {@code representation} and returns the {@link
* ChunkIndex}. * ChunkIndex}.
@ -135,6 +176,38 @@ public final class DashUtil {
* @param trackType The type of the representation. Typically one of the {@link * @param trackType The type of the representation. Typically one of the {@link
* com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants. * com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants.
* @param representation The representation which initialization chunk belongs to. * @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}.
*
* <p>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 * @return The {@link ChunkIndex} of the given representation, or null if no initialization or
* index data exists. * index data exists.
* @throws IOException Thrown when there is an error while loading. * @throws IOException Thrown when there is an error while loading.
@ -142,16 +215,7 @@ public final class DashUtil {
@Nullable @Nullable
public static ChunkIndex loadChunkIndex( public static ChunkIndex loadChunkIndex(
DataSource dataSource, int trackType, Representation representation) throws IOException { DataSource dataSource, int trackType, Representation representation) throws IOException {
if (representation.getInitializationUri() == null) { return loadChunkIndex(dataSource, trackType, representation, /* baseUrlIndex= */ 0);
return null;
}
ChunkExtractor chunkExtractor = newChunkExtractor(trackType, representation.format);
try {
loadInitializationData(chunkExtractor, dataSource, representation, /* loadIndex= */ true);
} finally {
chunkExtractor.release();
}
return chunkExtractor.getChunkIndex();
} }
/** /**
@ -161,6 +225,7 @@ public final class DashUtil {
* @param chunkExtractor The {@link ChunkExtractor} to use. * @param chunkExtractor The {@link ChunkExtractor} to use.
* @param dataSource The source from which the data should be loaded. * @param dataSource The source from which the data should be loaded.
* @param representation The representation which initialization chunk belongs to. * @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. * @param loadIndex Whether to load index data too.
* @throws IOException Thrown when there is an error while loading. * @throws IOException Thrown when there is an error while loading.
*/ */
@ -168,6 +233,7 @@ public final class DashUtil {
ChunkExtractor chunkExtractor, ChunkExtractor chunkExtractor,
DataSource dataSource, DataSource dataSource,
Representation representation, Representation representation,
int baseUrlIndex,
boolean loadIndex) boolean loadIndex)
throws IOException { throws IOException {
RangedUri initializationUri = Assertions.checkNotNull(representation.getInitializationUri()); 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 // It's common for initialization and index data to be stored adjacently. Attempt to merge
// the two requests together to request both at once. // 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) { if (requestUri == null) {
loadInitializationData(dataSource, representation, chunkExtractor, initializationUri); loadInitializationData(
dataSource, representation, baseUrlIndex, chunkExtractor, initializationUri);
requestUri = indexUri; requestUri = indexUri;
} }
} else { } else {
requestUri = initializationUri; 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.
*
* <p>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( private static void loadInitializationData(
DataSource dataSource, DataSource dataSource,
Representation representation, Representation representation,
int baseUrlIndex,
ChunkExtractor chunkExtractor, ChunkExtractor chunkExtractor,
RangedUri requestUri) RangedUri requestUri)
throws IOException { 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 = InitializationChunk initializationChunk =
new InitializationChunk( new InitializationChunk(
dataSource, dataSource,

View File

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