From 11648e6c8e145fa7a5ca5b2d09ad64a0fb683b13 Mon Sep 17 00:00:00 2001 From: rohks Date: Tue, 1 Aug 2023 16:48:19 +0000 Subject: [PATCH] Simplify and accurately compute chunk duration Refactored `CmcdLog` to `CmcdHeadersFactory` for improved representation of its purpose and updated implementations. #minor-change PiperOrigin-RevId: 552831995 --- .../{CmcdLog.java => CmcdHeadersFactory.java} | 109 +++++++++--------- ...gTest.java => CmcdHeadersFactoryTest.java} | 21 ++-- .../dash/DefaultDashChunkSource.java | 45 ++++---- .../media3/exoplayer/hls/HlsChunkSource.java | 34 +++--- .../media3/exoplayer/hls/HlsMediaChunk.java | 11 +- .../exoplayer/hls/HlsChunkSourceTest.java | 6 +- .../smoothstreaming/DefaultSsChunkSource.java | 32 +++-- .../DefaultSsChunkSourceTest.java | 6 +- 8 files changed, 136 insertions(+), 128 deletions(-) rename libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/{CmcdLog.java => CmcdHeadersFactory.java} (89%) rename libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/{CmcdLogTest.java => CmcdHeadersFactoryTest.java} (87%) 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",