diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdLog.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdHeadersFactory.java
similarity index 89%
rename from libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdLog.java
rename to libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdHeadersFactory.java
index eef715f0fa..95216e41ee 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdLog.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdHeadersFactory.java
@@ -35,15 +35,15 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Represents the data for CMCD (Common Media Client Data) in adaptive streaming formats DASH, HLS,
- * and SmoothStreaming.
+ * This class serves as a factory for generating Common Media Client Data (CMCD) HTTP request
+ * headers in adaptive streaming formats, DASH, HLS, and SmoothStreaming.
*
- *
It holds various attributes related to the playback of media content according to the
- * specifications outlined in the CMCD standard document It encapsulates the necessary attributes and information relevant to media content playback,
+ * following the guidelines specified in the CMCD standard document CTA-5004.
*/
@UnstableApi
-public final class CmcdLog {
+public final class CmcdHeadersFactory {
/** Indicates the streaming format used for media content. */
@Retention(RetentionPolicy.SOURCE)
@@ -74,34 +74,62 @@ public final class CmcdLog {
/** Represents the Live Streaming stream type. */
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 trackSelection The {@linkplain ExoTrackSelection track selection}.
* @param bufferedDurationUs The duration of media currently buffered from the current playback
* 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
* streaming formats specified by the {@link StreamingFormat} annotation.
* @param isLive {@code true} if the media content is being streamed live, {@code false}
* otherwise.
+ * @throws IllegalArgumentException If {@code bufferedDurationUs} is negative.
*/
- public static CmcdLog createInstance(
+ public CmcdHeadersFactory(
CmcdConfiguration cmcdConfiguration,
ExoTrackSelection trackSelection,
long bufferedDurationUs,
- long chunkDurationUs,
@StreamingFormat String streamingFormat,
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 =
cmcdConfiguration.requestConfig.getCustomData();
- int bitrateKbps = trackSelection.getSelectedFormat().bitrate / 1000;
+ int bitrateKbps = Util.ceilDivide(trackSelection.getSelectedFormat().bitrate, 1000);
- CmcdLog.CmcdObject.Builder cmcdObject =
- new CmcdLog.CmcdObject.Builder()
- .setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_OBJECT));
+ CmcdObject.Builder cmcdObject =
+ new CmcdObject.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_OBJECT));
if (cmcdConfiguration.isBitrateLoggingAllowed()) {
cmcdObject.setBitrateKbps(bitrateKbps);
}
@@ -111,26 +139,25 @@ public final class CmcdLog {
for (int i = 0; i < trackGroup.length; i++) {
topBitrate = max(topBitrate, trackGroup.getFormat(i).bitrate);
}
- cmcdObject.setTopBitrateKbps(topBitrate / 1000);
+ cmcdObject.setTopBitrateKbps(Util.ceilDivide(topBitrate, 1000));
}
if (cmcdConfiguration.isObjectDurationLoggingAllowed() && chunkDurationUs != C.TIME_UNSET) {
cmcdObject.setObjectDurationMs(chunkDurationUs / 1000);
}
- CmcdLog.CmcdRequest.Builder cmcdRequest =
- new CmcdLog.CmcdRequest.Builder()
- .setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_REQUEST));
+ CmcdRequest.Builder cmcdRequest =
+ new CmcdRequest.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_REQUEST));
if (cmcdConfiguration.isBufferLengthLoggingAllowed()) {
cmcdRequest.setBufferLengthMs(bufferedDurationUs / 1000);
}
if (cmcdConfiguration.isMeasuredThroughputLoggingAllowed()
&& trackSelection.getLatestBitrateEstimate() != Long.MIN_VALUE) {
- cmcdRequest.setMeasuredThroughputInKbps(trackSelection.getLatestBitrateEstimate() / 1000);
+ cmcdRequest.setMeasuredThroughputInKbps(
+ Util.ceilDivide(trackSelection.getLatestBitrateEstimate(), 1000));
}
- CmcdLog.CmcdSession.Builder cmcdSession =
- new CmcdLog.CmcdSession.Builder()
- .setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_SESSION));
+ CmcdSession.Builder cmcdSession =
+ new CmcdSession.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_SESSION));
if (cmcdConfiguration.isContentIdLoggingAllowed()) {
cmcdSession.setContentId(cmcdConfiguration.contentId);
}
@@ -144,40 +171,18 @@ public final class CmcdLog {
cmcdSession.setStreamType(isLive ? STREAM_TYPE_LIVE : STREAM_TYPE_VOD);
}
- CmcdLog.CmcdStatus.Builder cmcdStatus =
- new CmcdLog.CmcdStatus.Builder()
- .setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_STATUS));
+ CmcdStatus.Builder cmcdStatus =
+ new CmcdStatus.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_STATUS));
if (cmcdConfiguration.isMaximumRequestThroughputLoggingAllowed()) {
cmcdStatus.setMaximumRequestedThroughputKbps(
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 httpRequestHeaders = ImmutableMap.builder();
- this.cmcdObject.populateHttpRequestHeaders(httpRequestHeaders);
- this.cmcdRequest.populateHttpRequestHeaders(httpRequestHeaders);
- this.cmcdSession.populateHttpRequestHeaders(httpRequestHeaders);
- this.cmcdStatus.populateHttpRequestHeaders(httpRequestHeaders);
+ cmcdObject.build().populateHttpRequestHeaders(httpRequestHeaders);
+ cmcdRequest.build().populateHttpRequestHeaders(httpRequestHeaders);
+ cmcdSession.build().populateHttpRequestHeaders(httpRequestHeaders);
+ cmcdStatus.build().populateHttpRequestHeaders(httpRequestHeaders);
return httpRequestHeaders.buildOrThrow();
}
@@ -492,7 +497,7 @@ public final class CmcdLog {
/** Sets the {@link CmcdSession#customData}. The default value is {@code null}. */
@CanIgnoreReturnValue
- public CmcdSession.Builder setCustomData(@Nullable String customData) {
+ public Builder setCustomData(@Nullable String customData) {
this.customData = customData;
return this;
}
@@ -635,7 +640,7 @@ public final class CmcdLog {
/** Sets the {@link CmcdStatus#customData}. The default value is {@code null}. */
@CanIgnoreReturnValue
- public CmcdStatus.Builder setCustomData(@Nullable String customData) {
+ public Builder setCustomData(@Nullable String customData) {
this.customData = customData;
return this;
}
diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdLogTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdHeadersFactoryTest.java
similarity index 87%
rename from libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdLogTest.java
rename to libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdHeadersFactoryTest.java
index fc7fbd1fda..1379f85348 100644
--- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdLogTest.java
+++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdHeadersFactoryTest.java
@@ -28,9 +28,9 @@ import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.junit.runner.RunWith;
-/** Tests for {@link CmcdLog}. */
+/** Tests for {@link CmcdHeadersFactory}. */
@RunWith(AndroidJUnit4.class)
-public class CmcdLogTest {
+public class CmcdHeadersFactoryTest {
@Test
public void createInstance_populatesCmcdHeaders() {
@@ -62,17 +62,16 @@ public class CmcdLogTest {
when(trackSelection.getTrackGroup())
.thenReturn(new TrackGroup(format, new Format.Builder().setPeakBitrate(1_000_000).build()));
when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L);
- CmcdLog cmcdLog =
- CmcdLog.createInstance(
- cmcdConfiguration,
- trackSelection,
- /* bufferedDurationUs= */ 1_760_000,
- /* chunkDurationUs= */ 3_000_000,
- CmcdLog.STREAMING_FORMAT_DASH,
- true);
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> requestHeaders =
- cmcdLog.getHttpRequestHeaders();
+ new CmcdHeadersFactory(
+ cmcdConfiguration,
+ trackSelection,
+ /* bufferedDurationUs= */ 1_760_000,
+ /* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_DASH,
+ /* isLive= */ true)
+ .setChunkDurationUs(3_000_000)
+ .createHttpRequestHeaders();
assertThat(requestHeaders)
.containsExactly(
diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java
index 9194e1ca7d..aece24f704 100644
--- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java
+++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java
@@ -51,7 +51,7 @@ import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
import androidx.media3.exoplayer.source.chunk.SingleSampleMediaChunk;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
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.LoaderErrorThrower;
import androidx.media3.extractor.ChunkIndex;
@@ -371,24 +371,17 @@ public class DefaultDashChunkSource implements DashChunkSource {
playbackPositionUs, bufferedDurationUs, availableLiveDurationUs, queue, chunkIterators);
int selectedTrackIndex = trackSelection.getSelectedIndex();
- long chunkDurationUs = C.TIME_UNSET;
- if (selectedTrackIndex < chunkIterators.length && chunkIterators[selectedTrackIndex].next()) {
- chunkDurationUs =
- chunkIterators[selectedTrackIndex].getChunkEndTimeUs()
- - chunkIterators[selectedTrackIndex].getChunkStartTimeUs();
- }
+
@Nullable
- CmcdLog cmcdLog =
+ CmcdHeadersFactory cmcdHeadersFactory =
cmcdConfiguration == null
? null
- : CmcdLog.createInstance(
+ : new CmcdHeadersFactory(
cmcdConfiguration,
trackSelection,
bufferedDurationUs,
- chunkDurationUs,
- CmcdLog.STREAMING_FORMAT_DASH,
- manifest.dynamic);
-
+ /* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_DASH,
+ /* isLive= */ manifest.dynamic);
RepresentationHolder representationHolder = updateSelectedBaseUrl(selectedTrackIndex);
if (representationHolder.chunkExtractor != null) {
Representation selectedRepresentation = representationHolder.representation;
@@ -411,7 +404,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
trackSelection.getSelectionData(),
pendingInitializationUri,
pendingIndexUri,
- cmcdLog);
+ cmcdHeadersFactory);
return;
}
}
@@ -477,7 +470,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
maxSegmentCount,
seekTimeUs,
nowPeriodTimeUs,
- cmcdLog);
+ cmcdHeadersFactory);
}
@Override
@@ -652,7 +645,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
@Nullable Object trackSelectionData,
@Nullable RangedUri initializationUri,
@Nullable RangedUri indexUri,
- @Nullable CmcdLog cmcdLog) {
+ @Nullable CmcdHeadersFactory cmcdHeadersFactory) {
Representation representation = representationHolder.representation;
@Nullable RangedUri requestUri;
if (initializationUri != null) {
@@ -667,7 +660,9 @@ public class DefaultDashChunkSource implements DashChunkSource {
requestUri = indexUri;
}
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
- cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
+ cmcdHeadersFactory == null
+ ? ImmutableMap.of()
+ : cmcdHeadersFactory.createHttpRequestHeaders();
DataSpec dataSpec =
DashUtil.buildDataSpec(
representation,
@@ -695,12 +690,10 @@ public class DefaultDashChunkSource implements DashChunkSource {
int maxSegmentCount,
long seekTimeUs,
long nowPeriodTimeUs,
- @Nullable CmcdLog cmcdLog) {
+ @Nullable CmcdHeadersFactory cmcdHeadersFactory) {
Representation representation = representationHolder.representation;
long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum);
RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum);
- ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
- cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
if (representationHolder.chunkExtractor == null) {
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum);
int flags =
@@ -708,6 +701,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
firstSegmentNum, nowPeriodTimeUs)
? 0
: 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 =
DashUtil.buildDataSpec(
representation,
@@ -751,6 +750,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
representationHolder.isSegmentAvailableAtFullNetworkSpeed(segmentNum, nowPeriodTimeUs)
? 0
: 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 =
DashUtil.buildDataSpec(
representation,
diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsChunkSource.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsChunkSource.java
index a5f294db0d..0e39945182 100644
--- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsChunkSource.java
+++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsChunkSource.java
@@ -48,7 +48,7 @@ import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
import androidx.media3.exoplayer.trackselection.BaseTrackSelection;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
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.ImmutableMap;
import com.google.common.collect.Iterables;
@@ -481,36 +481,30 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
seenExpectedPlaylistError = false;
expectedPlaylistUrl = null;
- long chunkDurationUs = C.TIME_UNSET;
- if (selectedTrackIndex < mediaChunkIterators.length
- && mediaChunkIterators[selectedTrackIndex].next()) {
- chunkDurationUs =
- mediaChunkIterators[selectedTrackIndex].getChunkEndTimeUs()
- - mediaChunkIterators[selectedTrackIndex].getChunkStartTimeUs();
- }
@Nullable
- CmcdLog cmcdLog =
+ CmcdHeadersFactory cmcdHeadersFactory =
cmcdConfiguration == null
? null
- : CmcdLog.createInstance(
+ : new CmcdHeadersFactory(
cmcdConfiguration,
trackSelection,
bufferedDurationUs,
- chunkDurationUs,
- CmcdLog.STREAMING_FORMAT_HLS,
- !playlist.hasEndTag);
+ /* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_HLS,
+ /* isLive= */ !playlist.hasEndTag);
// Check if the media segment or its initialization segment are fully encrypted.
@Nullable
Uri initSegmentKeyUri =
getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase.initializationSegment);
- out.chunk = maybeCreateEncryptionChunkFor(initSegmentKeyUri, selectedTrackIndex, cmcdLog);
+ out.chunk =
+ maybeCreateEncryptionChunkFor(initSegmentKeyUri, selectedTrackIndex, cmcdHeadersFactory);
if (out.chunk != null) {
return;
}
@Nullable
Uri mediaSegmentKeyUri = getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase);
- out.chunk = maybeCreateEncryptionChunkFor(mediaSegmentKeyUri, selectedTrackIndex, cmcdLog);
+ out.chunk =
+ maybeCreateEncryptionChunkFor(mediaSegmentKeyUri, selectedTrackIndex, cmcdHeadersFactory);
if (out.chunk != null) {
return;
}
@@ -546,7 +540,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* initSegmentKey= */ keyCache.get(initSegmentKeyUri),
shouldSpliceIn,
playerId,
- cmcdLog);
+ cmcdHeadersFactory);
}
@Nullable
@@ -854,7 +848,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable
private Chunk maybeCreateEncryptionChunkFor(
- @Nullable Uri keyUri, int selectedTrackIndex, @Nullable CmcdLog cmcdLog) {
+ @Nullable Uri keyUri,
+ int selectedTrackIndex,
+ @Nullable CmcdHeadersFactory cmcdHeadersFactory) {
if (keyUri == null) {
return null;
}
@@ -868,7 +864,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return null;
}
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
- cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
+ cmcdHeadersFactory == null
+ ? ImmutableMap.of()
+ : cmcdHeadersFactory.createHttpRequestHeaders();
DataSpec dataSpec =
new DataSpec.Builder()
.setUri(keyUri)
diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaChunk.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaChunk.java
index 7ce06f0b00..d10c3014fc 100644
--- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaChunk.java
+++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaChunk.java
@@ -34,7 +34,7 @@ import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist;
import androidx.media3.exoplayer.source.chunk.MediaChunk;
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.ExtractorInput;
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
* otherwise.
* @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(
HlsExtractorFactory extractorFactory,
@@ -102,11 +103,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Nullable byte[] initSegmentKey,
boolean shouldSpliceIn,
PlayerId playerId,
- @Nullable CmcdLog cmcdLog) {
+ @Nullable CmcdHeadersFactory cmcdHeadersFactory) {
// Media segment.
HlsMediaPlaylist.SegmentBase mediaSegment = segmentBaseHolder.segmentBase;
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
- cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
+ cmcdHeadersFactory == null
+ ? ImmutableMap.of()
+ : cmcdHeadersFactory
+ .setChunkDurationUs(mediaSegment.durationUs)
+ .createHttpRequestHeaders();
DataSpec dataSpec =
new DataSpec.Builder()
.setUri(UriUtil.resolveToUri(mediaPlaylist.baseUri, mediaSegment.url))
diff --git a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsChunkSourceTest.java b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsChunkSourceTest.java
index 41d47fc41d..aad5789ff2 100644
--- a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsChunkSourceTest.java
+++ b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsChunkSourceTest.java
@@ -210,7 +210,7 @@ public class HlsChunkSourceTest {
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
- "br=800,tb=800",
+ "br=800,tb=800,d=4000",
"CMCD-Request",
"bl=0",
"CMCD-Session",
@@ -256,7 +256,7 @@ public class HlsChunkSourceTest {
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
- "br=800,tb=800",
+ "br=800,tb=800,d=4000",
"CMCD-Request",
"bl=0",
"CMCD-Session",
@@ -303,7 +303,7 @@ public class HlsChunkSourceTest {
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
- "br=800,tb=800,key1=value1",
+ "br=800,tb=800,d=4000,key1=value1",
"CMCD-Request",
"bl=0,key2=\"stringValue\"",
"CMCD-Session",
diff --git a/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSource.java b/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSource.java
index 886589a88b..e508f2ecf4 100644
--- a/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSource.java
+++ b/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSource.java
@@ -40,7 +40,7 @@ import androidx.media3.exoplayer.source.chunk.MediaChunk;
import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
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.FallbackSelection;
import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
@@ -280,23 +280,17 @@ public class DefaultSsChunkSource implements SsChunkSource {
int manifestTrackIndex = trackSelection.getIndexInTrackGroup(trackSelectionIndex);
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
- CmcdLog cmcdLog =
+ CmcdHeadersFactory cmcdHeadersFactory =
cmcdConfiguration == null
? null
- : CmcdLog.createInstance(
- cmcdConfiguration,
- trackSelection,
- bufferedDurationUs,
- chunkDurationUs,
- CmcdLog.STREAMING_FORMAT_SS,
- manifest.isLive);
+ : new CmcdHeadersFactory(
+ cmcdConfiguration,
+ trackSelection,
+ bufferedDurationUs,
+ /* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_SS,
+ /* isLive= */ manifest.isLive)
+ .setChunkDurationUs(chunkEndTimeUs - chunkStartTimeUs);
out.chunk =
newMediaChunk(
@@ -310,7 +304,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
trackSelection.getSelectionReason(),
trackSelection.getSelectionData(),
chunkExtractor,
- cmcdLog);
+ cmcdHeadersFactory);
}
@Override
@@ -355,9 +349,11 @@ public class DefaultSsChunkSource implements SsChunkSource {
@C.SelectionReason int trackSelectionReason,
@Nullable Object trackSelectionData,
ChunkExtractor chunkExtractor,
- @Nullable CmcdLog cmcdLog) {
+ @Nullable CmcdHeadersFactory cmcdHeadersFactory) {
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
- cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
+ cmcdHeadersFactory == null
+ ? ImmutableMap.of()
+ : cmcdHeadersFactory.createHttpRequestHeaders();
DataSpec dataSpec =
new DataSpec.Builder().setUri(uri).setHttpRequestHeaders(httpRequestHeaders).build();
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
diff --git a/libraries/exoplayer_smoothstreaming/src/test/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSourceTest.java b/libraries/exoplayer_smoothstreaming/src/test/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSourceTest.java
index 7a22a9d435..82d2594975 100644
--- a/libraries/exoplayer_smoothstreaming/src/test/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSourceTest.java
+++ b/libraries/exoplayer_smoothstreaming/src/test/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSourceTest.java
@@ -64,7 +64,7 @@ public class DefaultSsChunkSourceTest {
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
- "br=307,tb=1536,d=1968",
+ "br=308,tb=1536,d=1968",
"CMCD-Request",
"bl=0,mtp=1000",
"CMCD-Session",
@@ -109,7 +109,7 @@ public class DefaultSsChunkSourceTest {
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
- "br=307,tb=1536,d=1968",
+ "br=308,tb=1536,d=1968",
"CMCD-Request",
"bl=0,mtp=1000",
"CMCD-Session",
@@ -155,7 +155,7 @@ public class DefaultSsChunkSourceTest {
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
- "br=307,tb=1536,d=1968,key1=value1",
+ "br=308,tb=1536,d=1968,key1=value1",
"CMCD-Request",
"bl=0,mtp=1000,key2=\"stringValue\"",
"CMCD-Session",