Simplify and accurately compute chunk duration
Refactored `CmcdLog` to `CmcdHeadersFactory` for improved representation of its purpose and updated implementations. #minor-change PiperOrigin-RevId: 552831995
This commit is contained in:
parent
6633b05c0a
commit
11648e6c8e
@ -35,15 +35,15 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the data for CMCD (Common Media Client Data) in adaptive streaming formats DASH, HLS,
|
* This class serves as a factory for generating Common Media Client Data (CMCD) HTTP request
|
||||||
* and SmoothStreaming.
|
* headers in adaptive streaming formats, DASH, HLS, and SmoothStreaming.
|
||||||
*
|
*
|
||||||
* <p>It holds various attributes related to the playback of media content according to the
|
* <p>It encapsulates the necessary attributes and information relevant to media content playback,
|
||||||
* specifications outlined in the CMCD standard document <a
|
* following the guidelines specified in the CMCD standard document <a
|
||||||
* href="https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf">CTA-5004</a>.
|
* href="https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf">CTA-5004</a>.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class CmcdLog {
|
public final class CmcdHeadersFactory {
|
||||||
|
|
||||||
/** Indicates the streaming format used for media content. */
|
/** Indicates the streaming format used for media content. */
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@ -74,34 +74,62 @@ public final class CmcdLog {
|
|||||||
/** Represents the Live Streaming stream type. */
|
/** Represents the Live Streaming stream type. */
|
||||||
public static final String STREAM_TYPE_LIVE = "l";
|
public static final String STREAM_TYPE_LIVE = "l";
|
||||||
|
|
||||||
|
private final CmcdConfiguration cmcdConfiguration;
|
||||||
|
private final ExoTrackSelection trackSelection;
|
||||||
|
private final long bufferedDurationUs;
|
||||||
|
private final @StreamingFormat String streamingFormat;
|
||||||
|
private final boolean isLive;
|
||||||
|
private long chunkDurationUs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates an instance.
|
||||||
*
|
*
|
||||||
* @param cmcdConfiguration The {@link CmcdConfiguration} for this chunk source.
|
* @param cmcdConfiguration The {@link CmcdConfiguration} for this chunk source.
|
||||||
* @param trackSelection The {@linkplain ExoTrackSelection track selection}.
|
* @param trackSelection The {@linkplain ExoTrackSelection track selection}.
|
||||||
* @param bufferedDurationUs The duration of media currently buffered from the current playback
|
* @param bufferedDurationUs The duration of media currently buffered from the current playback
|
||||||
* position, in microseconds.
|
* position, in microseconds.
|
||||||
* @param chunkDurationUs The duration of current media chunk being requested, in microseconds. If
|
|
||||||
* the duration is not known, it can be set to {@link C#TIME_UNSET}.
|
|
||||||
* @param streamingFormat The streaming format of the media content. Must be one of the allowed
|
* @param streamingFormat The streaming format of the media content. Must be one of the allowed
|
||||||
* streaming formats specified by the {@link StreamingFormat} annotation.
|
* streaming formats specified by the {@link StreamingFormat} annotation.
|
||||||
* @param isLive {@code true} if the media content is being streamed live, {@code false}
|
* @param isLive {@code true} if the media content is being streamed live, {@code false}
|
||||||
* otherwise.
|
* otherwise.
|
||||||
|
* @throws IllegalArgumentException If {@code bufferedDurationUs} is negative.
|
||||||
*/
|
*/
|
||||||
public static CmcdLog createInstance(
|
public CmcdHeadersFactory(
|
||||||
CmcdConfiguration cmcdConfiguration,
|
CmcdConfiguration cmcdConfiguration,
|
||||||
ExoTrackSelection trackSelection,
|
ExoTrackSelection trackSelection,
|
||||||
long bufferedDurationUs,
|
long bufferedDurationUs,
|
||||||
long chunkDurationUs,
|
|
||||||
@StreamingFormat String streamingFormat,
|
@StreamingFormat String streamingFormat,
|
||||||
boolean isLive) {
|
boolean isLive) {
|
||||||
|
checkArgument(bufferedDurationUs >= 0);
|
||||||
|
this.cmcdConfiguration = cmcdConfiguration;
|
||||||
|
this.trackSelection = trackSelection;
|
||||||
|
this.bufferedDurationUs = bufferedDurationUs;
|
||||||
|
this.streamingFormat = streamingFormat;
|
||||||
|
this.isLive = isLive;
|
||||||
|
this.chunkDurationUs = C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration of current media chunk being requested, in microseconds. The default value is
|
||||||
|
* {@link C#TIME_UNSET}.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException If {@code chunkDurationUs} is negative.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public CmcdHeadersFactory setChunkDurationUs(long chunkDurationUs) {
|
||||||
|
checkArgument(chunkDurationUs >= 0);
|
||||||
|
this.chunkDurationUs = chunkDurationUs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates and returns a new {@link ImmutableMap} containing the CMCD HTTP request headers. */
|
||||||
|
public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> createHttpRequestHeaders() {
|
||||||
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> customData =
|
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> customData =
|
||||||
cmcdConfiguration.requestConfig.getCustomData();
|
cmcdConfiguration.requestConfig.getCustomData();
|
||||||
int bitrateKbps = trackSelection.getSelectedFormat().bitrate / 1000;
|
int bitrateKbps = Util.ceilDivide(trackSelection.getSelectedFormat().bitrate, 1000);
|
||||||
|
|
||||||
CmcdLog.CmcdObject.Builder cmcdObject =
|
CmcdObject.Builder cmcdObject =
|
||||||
new CmcdLog.CmcdObject.Builder()
|
new CmcdObject.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_OBJECT));
|
||||||
.setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_OBJECT));
|
|
||||||
if (cmcdConfiguration.isBitrateLoggingAllowed()) {
|
if (cmcdConfiguration.isBitrateLoggingAllowed()) {
|
||||||
cmcdObject.setBitrateKbps(bitrateKbps);
|
cmcdObject.setBitrateKbps(bitrateKbps);
|
||||||
}
|
}
|
||||||
@ -111,26 +139,25 @@ public final class CmcdLog {
|
|||||||
for (int i = 0; i < trackGroup.length; i++) {
|
for (int i = 0; i < trackGroup.length; i++) {
|
||||||
topBitrate = max(topBitrate, trackGroup.getFormat(i).bitrate);
|
topBitrate = max(topBitrate, trackGroup.getFormat(i).bitrate);
|
||||||
}
|
}
|
||||||
cmcdObject.setTopBitrateKbps(topBitrate / 1000);
|
cmcdObject.setTopBitrateKbps(Util.ceilDivide(topBitrate, 1000));
|
||||||
}
|
}
|
||||||
if (cmcdConfiguration.isObjectDurationLoggingAllowed() && chunkDurationUs != C.TIME_UNSET) {
|
if (cmcdConfiguration.isObjectDurationLoggingAllowed() && chunkDurationUs != C.TIME_UNSET) {
|
||||||
cmcdObject.setObjectDurationMs(chunkDurationUs / 1000);
|
cmcdObject.setObjectDurationMs(chunkDurationUs / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
CmcdLog.CmcdRequest.Builder cmcdRequest =
|
CmcdRequest.Builder cmcdRequest =
|
||||||
new CmcdLog.CmcdRequest.Builder()
|
new CmcdRequest.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_REQUEST));
|
||||||
.setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_REQUEST));
|
|
||||||
if (cmcdConfiguration.isBufferLengthLoggingAllowed()) {
|
if (cmcdConfiguration.isBufferLengthLoggingAllowed()) {
|
||||||
cmcdRequest.setBufferLengthMs(bufferedDurationUs / 1000);
|
cmcdRequest.setBufferLengthMs(bufferedDurationUs / 1000);
|
||||||
}
|
}
|
||||||
if (cmcdConfiguration.isMeasuredThroughputLoggingAllowed()
|
if (cmcdConfiguration.isMeasuredThroughputLoggingAllowed()
|
||||||
&& trackSelection.getLatestBitrateEstimate() != Long.MIN_VALUE) {
|
&& trackSelection.getLatestBitrateEstimate() != Long.MIN_VALUE) {
|
||||||
cmcdRequest.setMeasuredThroughputInKbps(trackSelection.getLatestBitrateEstimate() / 1000);
|
cmcdRequest.setMeasuredThroughputInKbps(
|
||||||
|
Util.ceilDivide(trackSelection.getLatestBitrateEstimate(), 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
CmcdLog.CmcdSession.Builder cmcdSession =
|
CmcdSession.Builder cmcdSession =
|
||||||
new CmcdLog.CmcdSession.Builder()
|
new CmcdSession.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_SESSION));
|
||||||
.setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_SESSION));
|
|
||||||
if (cmcdConfiguration.isContentIdLoggingAllowed()) {
|
if (cmcdConfiguration.isContentIdLoggingAllowed()) {
|
||||||
cmcdSession.setContentId(cmcdConfiguration.contentId);
|
cmcdSession.setContentId(cmcdConfiguration.contentId);
|
||||||
}
|
}
|
||||||
@ -144,40 +171,18 @@ public final class CmcdLog {
|
|||||||
cmcdSession.setStreamType(isLive ? STREAM_TYPE_LIVE : STREAM_TYPE_VOD);
|
cmcdSession.setStreamType(isLive ? STREAM_TYPE_LIVE : STREAM_TYPE_VOD);
|
||||||
}
|
}
|
||||||
|
|
||||||
CmcdLog.CmcdStatus.Builder cmcdStatus =
|
CmcdStatus.Builder cmcdStatus =
|
||||||
new CmcdLog.CmcdStatus.Builder()
|
new CmcdStatus.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_STATUS));
|
||||||
.setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_STATUS));
|
|
||||||
if (cmcdConfiguration.isMaximumRequestThroughputLoggingAllowed()) {
|
if (cmcdConfiguration.isMaximumRequestThroughputLoggingAllowed()) {
|
||||||
cmcdStatus.setMaximumRequestedThroughputKbps(
|
cmcdStatus.setMaximumRequestedThroughputKbps(
|
||||||
cmcdConfiguration.requestConfig.getRequestedMaximumThroughputKbps(bitrateKbps));
|
cmcdConfiguration.requestConfig.getRequestedMaximumThroughputKbps(bitrateKbps));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CmcdLog(
|
|
||||||
cmcdObject.build(), cmcdRequest.build(), cmcdSession.build(), cmcdStatus.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private final CmcdObject cmcdObject;
|
|
||||||
private final CmcdRequest cmcdRequest;
|
|
||||||
private final CmcdSession cmcdSession;
|
|
||||||
private final CmcdStatus cmcdStatus;
|
|
||||||
|
|
||||||
private CmcdLog(
|
|
||||||
CmcdObject cmcdObject,
|
|
||||||
CmcdRequest cmcdRequest,
|
|
||||||
CmcdSession cmcdSession,
|
|
||||||
CmcdStatus cmcdStatus) {
|
|
||||||
this.cmcdObject = cmcdObject;
|
|
||||||
this.cmcdRequest = cmcdRequest;
|
|
||||||
this.cmcdSession = cmcdSession;
|
|
||||||
this.cmcdStatus = cmcdStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> getHttpRequestHeaders() {
|
|
||||||
ImmutableMap.Builder<String, String> httpRequestHeaders = ImmutableMap.builder();
|
ImmutableMap.Builder<String, String> httpRequestHeaders = ImmutableMap.builder();
|
||||||
this.cmcdObject.populateHttpRequestHeaders(httpRequestHeaders);
|
cmcdObject.build().populateHttpRequestHeaders(httpRequestHeaders);
|
||||||
this.cmcdRequest.populateHttpRequestHeaders(httpRequestHeaders);
|
cmcdRequest.build().populateHttpRequestHeaders(httpRequestHeaders);
|
||||||
this.cmcdSession.populateHttpRequestHeaders(httpRequestHeaders);
|
cmcdSession.build().populateHttpRequestHeaders(httpRequestHeaders);
|
||||||
this.cmcdStatus.populateHttpRequestHeaders(httpRequestHeaders);
|
cmcdStatus.build().populateHttpRequestHeaders(httpRequestHeaders);
|
||||||
return httpRequestHeaders.buildOrThrow();
|
return httpRequestHeaders.buildOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,7 +497,7 @@ public final class CmcdLog {
|
|||||||
|
|
||||||
/** Sets the {@link CmcdSession#customData}. The default value is {@code null}. */
|
/** Sets the {@link CmcdSession#customData}. The default value is {@code null}. */
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public CmcdSession.Builder setCustomData(@Nullable String customData) {
|
public Builder setCustomData(@Nullable String customData) {
|
||||||
this.customData = customData;
|
this.customData = customData;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -635,7 +640,7 @@ public final class CmcdLog {
|
|||||||
|
|
||||||
/** Sets the {@link CmcdStatus#customData}. The default value is {@code null}. */
|
/** Sets the {@link CmcdStatus#customData}. The default value is {@code null}. */
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public CmcdStatus.Builder setCustomData(@Nullable String customData) {
|
public Builder setCustomData(@Nullable String customData) {
|
||||||
this.customData = customData;
|
this.customData = customData;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
@ -28,9 +28,9 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
/** Tests for {@link CmcdLog}. */
|
/** Tests for {@link CmcdHeadersFactory}. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class CmcdLogTest {
|
public class CmcdHeadersFactoryTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createInstance_populatesCmcdHeaders() {
|
public void createInstance_populatesCmcdHeaders() {
|
||||||
@ -62,17 +62,16 @@ public class CmcdLogTest {
|
|||||||
when(trackSelection.getTrackGroup())
|
when(trackSelection.getTrackGroup())
|
||||||
.thenReturn(new TrackGroup(format, new Format.Builder().setPeakBitrate(1_000_000).build()));
|
.thenReturn(new TrackGroup(format, new Format.Builder().setPeakBitrate(1_000_000).build()));
|
||||||
when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L);
|
when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L);
|
||||||
CmcdLog cmcdLog =
|
|
||||||
CmcdLog.createInstance(
|
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> requestHeaders =
|
||||||
|
new CmcdHeadersFactory(
|
||||||
cmcdConfiguration,
|
cmcdConfiguration,
|
||||||
trackSelection,
|
trackSelection,
|
||||||
/* bufferedDurationUs= */ 1_760_000,
|
/* bufferedDurationUs= */ 1_760_000,
|
||||||
/* chunkDurationUs= */ 3_000_000,
|
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_DASH,
|
||||||
CmcdLog.STREAMING_FORMAT_DASH,
|
/* isLive= */ true)
|
||||||
true);
|
.setChunkDurationUs(3_000_000)
|
||||||
|
.createHttpRequestHeaders();
|
||||||
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> requestHeaders =
|
|
||||||
cmcdLog.getHttpRequestHeaders();
|
|
||||||
|
|
||||||
assertThat(requestHeaders)
|
assertThat(requestHeaders)
|
||||||
.containsExactly(
|
.containsExactly(
|
@ -51,7 +51,7 @@ import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
|
|||||||
import androidx.media3.exoplayer.source.chunk.SingleSampleMediaChunk;
|
import androidx.media3.exoplayer.source.chunk.SingleSampleMediaChunk;
|
||||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||||
import androidx.media3.exoplayer.upstream.CmcdLog;
|
import androidx.media3.exoplayer.upstream.CmcdHeadersFactory;
|
||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||||
import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
|
import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
|
||||||
import androidx.media3.extractor.ChunkIndex;
|
import androidx.media3.extractor.ChunkIndex;
|
||||||
@ -371,24 +371,17 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
playbackPositionUs, bufferedDurationUs, availableLiveDurationUs, queue, chunkIterators);
|
playbackPositionUs, bufferedDurationUs, availableLiveDurationUs, queue, chunkIterators);
|
||||||
|
|
||||||
int selectedTrackIndex = trackSelection.getSelectedIndex();
|
int selectedTrackIndex = trackSelection.getSelectedIndex();
|
||||||
long chunkDurationUs = C.TIME_UNSET;
|
|
||||||
if (selectedTrackIndex < chunkIterators.length && chunkIterators[selectedTrackIndex].next()) {
|
|
||||||
chunkDurationUs =
|
|
||||||
chunkIterators[selectedTrackIndex].getChunkEndTimeUs()
|
|
||||||
- chunkIterators[selectedTrackIndex].getChunkStartTimeUs();
|
|
||||||
}
|
|
||||||
@Nullable
|
@Nullable
|
||||||
CmcdLog cmcdLog =
|
CmcdHeadersFactory cmcdHeadersFactory =
|
||||||
cmcdConfiguration == null
|
cmcdConfiguration == null
|
||||||
? null
|
? null
|
||||||
: CmcdLog.createInstance(
|
: new CmcdHeadersFactory(
|
||||||
cmcdConfiguration,
|
cmcdConfiguration,
|
||||||
trackSelection,
|
trackSelection,
|
||||||
bufferedDurationUs,
|
bufferedDurationUs,
|
||||||
chunkDurationUs,
|
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_DASH,
|
||||||
CmcdLog.STREAMING_FORMAT_DASH,
|
/* isLive= */ manifest.dynamic);
|
||||||
manifest.dynamic);
|
|
||||||
|
|
||||||
RepresentationHolder representationHolder = updateSelectedBaseUrl(selectedTrackIndex);
|
RepresentationHolder representationHolder = updateSelectedBaseUrl(selectedTrackIndex);
|
||||||
if (representationHolder.chunkExtractor != null) {
|
if (representationHolder.chunkExtractor != null) {
|
||||||
Representation selectedRepresentation = representationHolder.representation;
|
Representation selectedRepresentation = representationHolder.representation;
|
||||||
@ -411,7 +404,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
trackSelection.getSelectionData(),
|
trackSelection.getSelectionData(),
|
||||||
pendingInitializationUri,
|
pendingInitializationUri,
|
||||||
pendingIndexUri,
|
pendingIndexUri,
|
||||||
cmcdLog);
|
cmcdHeadersFactory);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -477,7 +470,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
maxSegmentCount,
|
maxSegmentCount,
|
||||||
seekTimeUs,
|
seekTimeUs,
|
||||||
nowPeriodTimeUs,
|
nowPeriodTimeUs,
|
||||||
cmcdLog);
|
cmcdHeadersFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -652,7 +645,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
@Nullable Object trackSelectionData,
|
@Nullable Object trackSelectionData,
|
||||||
@Nullable RangedUri initializationUri,
|
@Nullable RangedUri initializationUri,
|
||||||
@Nullable RangedUri indexUri,
|
@Nullable RangedUri indexUri,
|
||||||
@Nullable CmcdLog cmcdLog) {
|
@Nullable CmcdHeadersFactory cmcdHeadersFactory) {
|
||||||
Representation representation = representationHolder.representation;
|
Representation representation = representationHolder.representation;
|
||||||
@Nullable RangedUri requestUri;
|
@Nullable RangedUri requestUri;
|
||||||
if (initializationUri != null) {
|
if (initializationUri != null) {
|
||||||
@ -667,7 +660,9 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
requestUri = indexUri;
|
requestUri = indexUri;
|
||||||
}
|
}
|
||||||
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
|
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
|
||||||
cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
|
cmcdHeadersFactory == null
|
||||||
|
? ImmutableMap.of()
|
||||||
|
: cmcdHeadersFactory.createHttpRequestHeaders();
|
||||||
DataSpec dataSpec =
|
DataSpec dataSpec =
|
||||||
DashUtil.buildDataSpec(
|
DashUtil.buildDataSpec(
|
||||||
representation,
|
representation,
|
||||||
@ -695,12 +690,10 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
int maxSegmentCount,
|
int maxSegmentCount,
|
||||||
long seekTimeUs,
|
long seekTimeUs,
|
||||||
long nowPeriodTimeUs,
|
long nowPeriodTimeUs,
|
||||||
@Nullable CmcdLog cmcdLog) {
|
@Nullable CmcdHeadersFactory cmcdHeadersFactory) {
|
||||||
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);
|
||||||
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
|
|
||||||
cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
|
|
||||||
if (representationHolder.chunkExtractor == null) {
|
if (representationHolder.chunkExtractor == null) {
|
||||||
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum);
|
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum);
|
||||||
int flags =
|
int flags =
|
||||||
@ -708,6 +701,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;
|
||||||
|
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
|
||||||
|
cmcdHeadersFactory == null
|
||||||
|
? ImmutableMap.of()
|
||||||
|
: cmcdHeadersFactory
|
||||||
|
.setChunkDurationUs(endTimeUs - startTimeUs)
|
||||||
|
.createHttpRequestHeaders();
|
||||||
DataSpec dataSpec =
|
DataSpec dataSpec =
|
||||||
DashUtil.buildDataSpec(
|
DashUtil.buildDataSpec(
|
||||||
representation,
|
representation,
|
||||||
@ -751,6 +750,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;
|
||||||
|
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
|
||||||
|
cmcdHeadersFactory == null
|
||||||
|
? ImmutableMap.of()
|
||||||
|
: cmcdHeadersFactory
|
||||||
|
.setChunkDurationUs(endTimeUs - startTimeUs)
|
||||||
|
.createHttpRequestHeaders();
|
||||||
DataSpec dataSpec =
|
DataSpec dataSpec =
|
||||||
DashUtil.buildDataSpec(
|
DashUtil.buildDataSpec(
|
||||||
representation,
|
representation,
|
||||||
|
@ -48,7 +48,7 @@ import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
|
|||||||
import androidx.media3.exoplayer.trackselection.BaseTrackSelection;
|
import androidx.media3.exoplayer.trackselection.BaseTrackSelection;
|
||||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||||
import androidx.media3.exoplayer.upstream.CmcdLog;
|
import androidx.media3.exoplayer.upstream.CmcdHeadersFactory;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
@ -481,36 +481,30 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
seenExpectedPlaylistError = false;
|
seenExpectedPlaylistError = false;
|
||||||
expectedPlaylistUrl = null;
|
expectedPlaylistUrl = null;
|
||||||
|
|
||||||
long chunkDurationUs = C.TIME_UNSET;
|
|
||||||
if (selectedTrackIndex < mediaChunkIterators.length
|
|
||||||
&& mediaChunkIterators[selectedTrackIndex].next()) {
|
|
||||||
chunkDurationUs =
|
|
||||||
mediaChunkIterators[selectedTrackIndex].getChunkEndTimeUs()
|
|
||||||
- mediaChunkIterators[selectedTrackIndex].getChunkStartTimeUs();
|
|
||||||
}
|
|
||||||
@Nullable
|
@Nullable
|
||||||
CmcdLog cmcdLog =
|
CmcdHeadersFactory cmcdHeadersFactory =
|
||||||
cmcdConfiguration == null
|
cmcdConfiguration == null
|
||||||
? null
|
? null
|
||||||
: CmcdLog.createInstance(
|
: new CmcdHeadersFactory(
|
||||||
cmcdConfiguration,
|
cmcdConfiguration,
|
||||||
trackSelection,
|
trackSelection,
|
||||||
bufferedDurationUs,
|
bufferedDurationUs,
|
||||||
chunkDurationUs,
|
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_HLS,
|
||||||
CmcdLog.STREAMING_FORMAT_HLS,
|
/* isLive= */ !playlist.hasEndTag);
|
||||||
!playlist.hasEndTag);
|
|
||||||
|
|
||||||
// Check if the media segment or its initialization segment are fully encrypted.
|
// Check if the media segment or its initialization segment are fully encrypted.
|
||||||
@Nullable
|
@Nullable
|
||||||
Uri initSegmentKeyUri =
|
Uri initSegmentKeyUri =
|
||||||
getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase.initializationSegment);
|
getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase.initializationSegment);
|
||||||
out.chunk = maybeCreateEncryptionChunkFor(initSegmentKeyUri, selectedTrackIndex, cmcdLog);
|
out.chunk =
|
||||||
|
maybeCreateEncryptionChunkFor(initSegmentKeyUri, selectedTrackIndex, cmcdHeadersFactory);
|
||||||
if (out.chunk != null) {
|
if (out.chunk != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@Nullable
|
@Nullable
|
||||||
Uri mediaSegmentKeyUri = getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase);
|
Uri mediaSegmentKeyUri = getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase);
|
||||||
out.chunk = maybeCreateEncryptionChunkFor(mediaSegmentKeyUri, selectedTrackIndex, cmcdLog);
|
out.chunk =
|
||||||
|
maybeCreateEncryptionChunkFor(mediaSegmentKeyUri, selectedTrackIndex, cmcdHeadersFactory);
|
||||||
if (out.chunk != null) {
|
if (out.chunk != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -546,7 +540,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
/* initSegmentKey= */ keyCache.get(initSegmentKeyUri),
|
/* initSegmentKey= */ keyCache.get(initSegmentKeyUri),
|
||||||
shouldSpliceIn,
|
shouldSpliceIn,
|
||||||
playerId,
|
playerId,
|
||||||
cmcdLog);
|
cmcdHeadersFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -854,7 +848,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Chunk maybeCreateEncryptionChunkFor(
|
private Chunk maybeCreateEncryptionChunkFor(
|
||||||
@Nullable Uri keyUri, int selectedTrackIndex, @Nullable CmcdLog cmcdLog) {
|
@Nullable Uri keyUri,
|
||||||
|
int selectedTrackIndex,
|
||||||
|
@Nullable CmcdHeadersFactory cmcdHeadersFactory) {
|
||||||
if (keyUri == null) {
|
if (keyUri == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -868,7 +864,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
|
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
|
||||||
cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
|
cmcdHeadersFactory == null
|
||||||
|
? ImmutableMap.of()
|
||||||
|
: cmcdHeadersFactory.createHttpRequestHeaders();
|
||||||
DataSpec dataSpec =
|
DataSpec dataSpec =
|
||||||
new DataSpec.Builder()
|
new DataSpec.Builder()
|
||||||
.setUri(keyUri)
|
.setUri(keyUri)
|
||||||
|
@ -34,7 +34,7 @@ import androidx.media3.exoplayer.analytics.PlayerId;
|
|||||||
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist;
|
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist;
|
||||||
import androidx.media3.exoplayer.source.chunk.MediaChunk;
|
import androidx.media3.exoplayer.source.chunk.MediaChunk;
|
||||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||||
import androidx.media3.exoplayer.upstream.CmcdLog;
|
import androidx.media3.exoplayer.upstream.CmcdHeadersFactory;
|
||||||
import androidx.media3.extractor.DefaultExtractorInput;
|
import androidx.media3.extractor.DefaultExtractorInput;
|
||||||
import androidx.media3.extractor.ExtractorInput;
|
import androidx.media3.extractor.ExtractorInput;
|
||||||
import androidx.media3.extractor.metadata.id3.Id3Decoder;
|
import androidx.media3.extractor.metadata.id3.Id3Decoder;
|
||||||
@ -82,6 +82,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
* @param initSegmentKey The initialization segment decryption key, if fully encrypted. Null
|
* @param initSegmentKey The initialization segment decryption key, if fully encrypted. Null
|
||||||
* otherwise.
|
* otherwise.
|
||||||
* @param shouldSpliceIn Whether samples for this chunk should be spliced into existing samples.
|
* @param shouldSpliceIn Whether samples for this chunk should be spliced into existing samples.
|
||||||
|
* @param cmcdHeadersFactory The {@link CmcdHeadersFactory} for generating CMCD request headers.
|
||||||
*/
|
*/
|
||||||
public static HlsMediaChunk createInstance(
|
public static HlsMediaChunk createInstance(
|
||||||
HlsExtractorFactory extractorFactory,
|
HlsExtractorFactory extractorFactory,
|
||||||
@ -102,11 +103,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
@Nullable byte[] initSegmentKey,
|
@Nullable byte[] initSegmentKey,
|
||||||
boolean shouldSpliceIn,
|
boolean shouldSpliceIn,
|
||||||
PlayerId playerId,
|
PlayerId playerId,
|
||||||
@Nullable CmcdLog cmcdLog) {
|
@Nullable CmcdHeadersFactory cmcdHeadersFactory) {
|
||||||
// Media segment.
|
// Media segment.
|
||||||
HlsMediaPlaylist.SegmentBase mediaSegment = segmentBaseHolder.segmentBase;
|
HlsMediaPlaylist.SegmentBase mediaSegment = segmentBaseHolder.segmentBase;
|
||||||
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
|
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
|
||||||
cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
|
cmcdHeadersFactory == null
|
||||||
|
? ImmutableMap.of()
|
||||||
|
: cmcdHeadersFactory
|
||||||
|
.setChunkDurationUs(mediaSegment.durationUs)
|
||||||
|
.createHttpRequestHeaders();
|
||||||
DataSpec dataSpec =
|
DataSpec dataSpec =
|
||||||
new DataSpec.Builder()
|
new DataSpec.Builder()
|
||||||
.setUri(UriUtil.resolveToUri(mediaPlaylist.baseUri, mediaSegment.url))
|
.setUri(UriUtil.resolveToUri(mediaPlaylist.baseUri, mediaSegment.url))
|
||||||
|
@ -210,7 +210,7 @@ public class HlsChunkSourceTest {
|
|||||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
"CMCD-Object",
|
"CMCD-Object",
|
||||||
"br=800,tb=800",
|
"br=800,tb=800,d=4000",
|
||||||
"CMCD-Request",
|
"CMCD-Request",
|
||||||
"bl=0",
|
"bl=0",
|
||||||
"CMCD-Session",
|
"CMCD-Session",
|
||||||
@ -256,7 +256,7 @@ public class HlsChunkSourceTest {
|
|||||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
"CMCD-Object",
|
"CMCD-Object",
|
||||||
"br=800,tb=800",
|
"br=800,tb=800,d=4000",
|
||||||
"CMCD-Request",
|
"CMCD-Request",
|
||||||
"bl=0",
|
"bl=0",
|
||||||
"CMCD-Session",
|
"CMCD-Session",
|
||||||
@ -303,7 +303,7 @@ public class HlsChunkSourceTest {
|
|||||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
"CMCD-Object",
|
"CMCD-Object",
|
||||||
"br=800,tb=800,key1=value1",
|
"br=800,tb=800,d=4000,key1=value1",
|
||||||
"CMCD-Request",
|
"CMCD-Request",
|
||||||
"bl=0,key2=\"stringValue\"",
|
"bl=0,key2=\"stringValue\"",
|
||||||
"CMCD-Session",
|
"CMCD-Session",
|
||||||
|
@ -40,7 +40,7 @@ import androidx.media3.exoplayer.source.chunk.MediaChunk;
|
|||||||
import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
|
import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
|
||||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||||
import androidx.media3.exoplayer.upstream.CmcdLog;
|
import androidx.media3.exoplayer.upstream.CmcdHeadersFactory;
|
||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.FallbackSelection;
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.FallbackSelection;
|
||||||
import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
|
import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
|
||||||
@ -280,23 +280,17 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
|||||||
int manifestTrackIndex = trackSelection.getIndexInTrackGroup(trackSelectionIndex);
|
int manifestTrackIndex = trackSelection.getIndexInTrackGroup(trackSelectionIndex);
|
||||||
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
|
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
|
||||||
|
|
||||||
long chunkDurationUs = C.TIME_UNSET;
|
|
||||||
if (trackSelectionIndex < chunkIterators.length && chunkIterators[trackSelectionIndex].next()) {
|
|
||||||
chunkDurationUs =
|
|
||||||
chunkIterators[trackSelectionIndex].getChunkEndTimeUs()
|
|
||||||
- chunkIterators[trackSelectionIndex].getChunkStartTimeUs();
|
|
||||||
}
|
|
||||||
@Nullable
|
@Nullable
|
||||||
CmcdLog cmcdLog =
|
CmcdHeadersFactory cmcdHeadersFactory =
|
||||||
cmcdConfiguration == null
|
cmcdConfiguration == null
|
||||||
? null
|
? null
|
||||||
: CmcdLog.createInstance(
|
: new CmcdHeadersFactory(
|
||||||
cmcdConfiguration,
|
cmcdConfiguration,
|
||||||
trackSelection,
|
trackSelection,
|
||||||
bufferedDurationUs,
|
bufferedDurationUs,
|
||||||
chunkDurationUs,
|
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_SS,
|
||||||
CmcdLog.STREAMING_FORMAT_SS,
|
/* isLive= */ manifest.isLive)
|
||||||
manifest.isLive);
|
.setChunkDurationUs(chunkEndTimeUs - chunkStartTimeUs);
|
||||||
|
|
||||||
out.chunk =
|
out.chunk =
|
||||||
newMediaChunk(
|
newMediaChunk(
|
||||||
@ -310,7 +304,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
|||||||
trackSelection.getSelectionReason(),
|
trackSelection.getSelectionReason(),
|
||||||
trackSelection.getSelectionData(),
|
trackSelection.getSelectionData(),
|
||||||
chunkExtractor,
|
chunkExtractor,
|
||||||
cmcdLog);
|
cmcdHeadersFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -355,9 +349,11 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
|||||||
@C.SelectionReason int trackSelectionReason,
|
@C.SelectionReason int trackSelectionReason,
|
||||||
@Nullable Object trackSelectionData,
|
@Nullable Object trackSelectionData,
|
||||||
ChunkExtractor chunkExtractor,
|
ChunkExtractor chunkExtractor,
|
||||||
@Nullable CmcdLog cmcdLog) {
|
@Nullable CmcdHeadersFactory cmcdHeadersFactory) {
|
||||||
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
|
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
|
||||||
cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
|
cmcdHeadersFactory == null
|
||||||
|
? ImmutableMap.of()
|
||||||
|
: cmcdHeadersFactory.createHttpRequestHeaders();
|
||||||
DataSpec dataSpec =
|
DataSpec dataSpec =
|
||||||
new DataSpec.Builder().setUri(uri).setHttpRequestHeaders(httpRequestHeaders).build();
|
new DataSpec.Builder().setUri(uri).setHttpRequestHeaders(httpRequestHeaders).build();
|
||||||
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
|
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
|
||||||
|
@ -64,7 +64,7 @@ public class DefaultSsChunkSourceTest {
|
|||||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
"CMCD-Object",
|
"CMCD-Object",
|
||||||
"br=307,tb=1536,d=1968",
|
"br=308,tb=1536,d=1968",
|
||||||
"CMCD-Request",
|
"CMCD-Request",
|
||||||
"bl=0,mtp=1000",
|
"bl=0,mtp=1000",
|
||||||
"CMCD-Session",
|
"CMCD-Session",
|
||||||
@ -109,7 +109,7 @@ public class DefaultSsChunkSourceTest {
|
|||||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
"CMCD-Object",
|
"CMCD-Object",
|
||||||
"br=307,tb=1536,d=1968",
|
"br=308,tb=1536,d=1968",
|
||||||
"CMCD-Request",
|
"CMCD-Request",
|
||||||
"bl=0,mtp=1000",
|
"bl=0,mtp=1000",
|
||||||
"CMCD-Session",
|
"CMCD-Session",
|
||||||
@ -155,7 +155,7 @@ public class DefaultSsChunkSourceTest {
|
|||||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
"CMCD-Object",
|
"CMCD-Object",
|
||||||
"br=307,tb=1536,d=1968,key1=value1",
|
"br=308,tb=1536,d=1968,key1=value1",
|
||||||
"CMCD-Request",
|
"CMCD-Request",
|
||||||
"bl=0,mtp=1000,key2=\"stringValue\"",
|
"bl=0,mtp=1000,key2=\"stringValue\"",
|
||||||
"CMCD-Session",
|
"CMCD-Session",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user