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}.
*
* @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 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}.
*
* <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
* 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}.
*
* <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
* 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.
*
* <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(
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,

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.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() {