From 4997ea82fa6c5b7eb152404a9d1963052d9bc514 Mon Sep 17 00:00:00 2001 From: rohks Date: Thu, 12 Dec 2024 04:33:03 -0800 Subject: [PATCH] Refactor `CmcdData` to handle object type determination internally Moved the `getObjectType` method from `CmcdData.Factory` to `CmcdData` and updated the logic to derive the object type directly within `CmcdData`. This change eliminates the need for chunk source classes to set this value explicitly. PiperOrigin-RevId: 705457755 --- .../media3/exoplayer/upstream/CmcdData.java | 63 ++++++----- .../exoplayer/upstream/CmcdDataTest.java | 106 ++++++++++++++++-- .../dash/DefaultDashChunkSource.java | 8 +- .../media3/exoplayer/hls/HlsChunkSource.java | 14 +-- .../smoothstreaming/DefaultSsChunkSource.java | 3 +- 5 files changed, 138 insertions(+), 56 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdData.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdData.java index 38f43a12b9..4c61b991e6 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdData.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdData.java @@ -28,6 +28,7 @@ import androidx.annotation.Nullable; import androidx.annotation.StringDef; import androidx.media3.common.C; import androidx.media3.common.C.TrackType; +import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.common.TrackGroup; import androidx.media3.common.util.UnstableApi; @@ -99,32 +100,6 @@ public final class CmcdData { this.chunkDurationUs = C.TIME_UNSET; } - /** - * Retrieves the object type value from the given {@link ExoTrackSelection}. - * - * @param trackSelection The {@link ExoTrackSelection} from which to retrieve the object type. - * @return The object type value as a String if {@link TrackType} can be mapped to one of the - * object types specified by {@link CmcdData.ObjectType} annotation, or {@code null}. - * @throws IllegalArgumentException if the provided {@link ExoTrackSelection} is {@code null}. - */ - @Nullable - public static @CmcdData.ObjectType String getObjectType(ExoTrackSelection trackSelection) { - @TrackType - int trackType = MimeTypes.getTrackType(trackSelection.getSelectedFormat().sampleMimeType); - if (trackType == C.TRACK_TYPE_UNKNOWN) { - trackType = MimeTypes.getTrackType(trackSelection.getSelectedFormat().containerMimeType); - } - - if (trackType == C.TRACK_TYPE_AUDIO) { - return OBJECT_TYPE_AUDIO_ONLY; - } else if (trackType == C.TRACK_TYPE_VIDEO) { - return OBJECT_TYPE_VIDEO_ONLY; - } else { - // Track type cannot be mapped to a known object type. - return null; - } - } - /** * Sets the duration of current media chunk being requested, in microseconds. * @@ -146,6 +121,10 @@ public final class CmcdData { /** * Sets the object type of the current object being requested. * + *

Must be set if {@linkplain #setTrackSelection track selection} is not provided. If unset + * and a {@linkplain #setTrackSelection track selection} is provided, the object type is derived + * from it. + * *

Default is {@code null}. */ @CanIgnoreReturnValue @@ -266,11 +245,15 @@ public final class CmcdData { */ public CmcdData createCmcdData() { boolean isManifestObjectType = isManifestObjectType(objectType); - boolean isMediaObjectType = isMediaObjectType(objectType); - if (!isManifestObjectType) { checkStateNotNull(trackSelection, "Track selection must be set"); } + + if (objectType == null) { + objectType = getObjectTypeFromFormat(checkNotNull(trackSelection).getSelectedFormat()); + } + + boolean isMediaObjectType = isMediaObjectType(objectType); if (isMediaObjectType) { checkState(bufferedDurationUs != C.TIME_UNSET, "Buffered duration must be set"); checkState(chunkDurationUs != C.TIME_UNSET, "Chunk duration must be set"); @@ -389,6 +372,30 @@ public final class CmcdData { cmcdConfiguration.dataTransmissionMode); } + @Nullable + private static @ObjectType String getObjectTypeFromFormat(Format format) { + String audioMimeType = MimeTypes.getAudioMediaMimeType(format.codecs); + String videoMimeType = MimeTypes.getVideoMediaMimeType(format.codecs); + + if (audioMimeType != null && videoMimeType != null) { + return OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO; + } + + @TrackType int trackType = MimeTypes.getTrackType(format.sampleMimeType); + if (trackType == C.TRACK_TYPE_UNKNOWN) { + trackType = MimeTypes.getTrackType(format.containerMimeType); + } + + if (trackType == C.TRACK_TYPE_AUDIO) { + return OBJECT_TYPE_AUDIO_ONLY; + } else if (trackType == C.TRACK_TYPE_VIDEO) { + return OBJECT_TYPE_VIDEO_ONLY; + } else { + // Track type cannot be mapped to a known media object type. + return null; + } + } + private static boolean isManifestObjectType(@Nullable @ObjectType String objectType) { return Objects.equals(objectType, OBJECT_TYPE_MANIFEST); } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdDataTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdDataTest.java index 292b2fc4e8..dd976595b7 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdDataTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdDataTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import android.net.Uri; import androidx.media3.common.Format; import androidx.media3.common.MediaItem; +import androidx.media3.common.MimeTypes; import androidx.media3.common.TrackGroup; import androidx.media3.datasource.DataSpec; import androidx.media3.exoplayer.trackselection.ExoTrackSelection; @@ -71,7 +72,7 @@ public class CmcdDataTest { } @Test - public void createInstance_audioObjectType_setsCorrectHttpHeaders() { + public void createInstance_audioSampleMimeType_setsCorrectHttpHeaders() { CmcdConfiguration.Factory cmcdConfigurationFactory = mediaItem -> new CmcdConfiguration( @@ -87,7 +88,8 @@ public class CmcdDataTest { CmcdConfiguration cmcdConfiguration = cmcdConfigurationFactory.createCmcdConfiguration(mediaItem); ExoTrackSelection trackSelection = mock(ExoTrackSelection.class); - Format format = new Format.Builder().setPeakBitrate(840_000).build(); + Format format = + new Format.Builder().setPeakBitrate(840_000).setSampleMimeType(MimeTypes.AUDIO_AC4).build(); when(trackSelection.getSelectedFormat()).thenReturn(format); when(trackSelection.getTrackGroup()) .thenReturn(new TrackGroup(format, new Format.Builder().setPeakBitrate(1_000_000).build())); @@ -96,7 +98,6 @@ public class CmcdDataTest { CmcdData cmcdData = new CmcdData.Factory(cmcdConfiguration, CmcdData.STREAMING_FORMAT_DASH) .setTrackSelection(trackSelection) - .setObjectType(CmcdData.OBJECT_TYPE_AUDIO_ONLY) .setBufferedDurationUs(1_760_000) .setPlaybackRate(2.0f) .setIsLive(true) @@ -120,7 +121,7 @@ public class CmcdDataTest { } @Test - public void createInstance_audioObjectType_setsCorrectQueryParameters() { + public void createInstance_audioSampleMimeType_setsCorrectQueryParameters() { CmcdConfiguration.Factory cmcdConfigurationFactory = mediaItem -> new CmcdConfiguration( @@ -137,7 +138,8 @@ public class CmcdDataTest { CmcdConfiguration cmcdConfiguration = cmcdConfigurationFactory.createCmcdConfiguration(mediaItem); ExoTrackSelection trackSelection = mock(ExoTrackSelection.class); - Format format = new Format.Builder().setPeakBitrate(840_000).build(); + Format format = + new Format.Builder().setPeakBitrate(840_000).setSampleMimeType(MimeTypes.AUDIO_AC4).build(); when(trackSelection.getSelectedFormat()).thenReturn(format); when(trackSelection.getTrackGroup()) .thenReturn(new TrackGroup(format, new Format.Builder().setPeakBitrate(1_000_000).build())); @@ -163,6 +165,96 @@ public class CmcdDataTest { + "rtp=1700,sf=d,sid=\"sessionId\",st=l,su,tb=1000"); } + @Test + public void createInstance_videoContainerMimeType_setsCorrectHttpHeaders() { + CmcdConfiguration.Factory cmcdConfigurationFactory = + mediaItem -> + new CmcdConfiguration( + "sessionId", mediaItem.mediaId, new CmcdConfiguration.RequestConfig() {}); + MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build(); + CmcdConfiguration cmcdConfiguration = + cmcdConfigurationFactory.createCmcdConfiguration(mediaItem); + ExoTrackSelection trackSelection = mock(ExoTrackSelection.class); + Format format = + new Format.Builder() + .setPeakBitrate(840_000) + .setContainerMimeType(MimeTypes.VIDEO_MP4) + .build(); + when(trackSelection.getSelectedFormat()).thenReturn(format); + when(trackSelection.getTrackGroup()) + .thenReturn(new TrackGroup(format, new Format.Builder().setPeakBitrate(1_000_000).build())); + when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L); + DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build(); + CmcdData cmcdData = + new CmcdData.Factory(cmcdConfiguration, CmcdData.STREAMING_FORMAT_DASH) + .setTrackSelection(trackSelection) + .setBufferedDurationUs(1_760_000) + .setPlaybackRate(2.0f) + .setIsLive(true) + .setDidRebuffer(true) + .setIsBufferEmpty(false) + .setChunkDurationUs(3_000_000) + .createCmcdData(); + + dataSpec = cmcdData.addToDataSpec(dataSpec); + + assertThat(dataSpec.httpRequestHeaders) + .containsExactly( + "CMCD-Object", + "br=840,d=3000,ot=v,tb=1000", + "CMCD-Request", + "bl=1800,dl=900,mtp=500,su", + "CMCD-Session", + "cid=\"mediaId\",pr=2.00,sf=d,sid=\"sessionId\",st=l", + "CMCD-Status", + "bs"); + } + + @Test + public void createInstance_muxedAudioAndVideoCodecs_setsCorrectHttpHeaders() { + CmcdConfiguration.Factory cmcdConfigurationFactory = + mediaItem -> + new CmcdConfiguration( + "sessionId", mediaItem.mediaId, new CmcdConfiguration.RequestConfig() {}); + MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build(); + CmcdConfiguration cmcdConfiguration = + cmcdConfigurationFactory.createCmcdConfiguration(mediaItem); + ExoTrackSelection trackSelection = mock(ExoTrackSelection.class); + Format format = + new Format.Builder() + .setPeakBitrate(840_000) + .setCodecs("avc1.4D5015,ac-3,mp4a.40.2") + .build(); + when(trackSelection.getSelectedFormat()).thenReturn(format); + when(trackSelection.getTrackGroup()) + .thenReturn(new TrackGroup(format, new Format.Builder().setPeakBitrate(1_000_000).build())); + when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L); + DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build(); + CmcdData cmcdData = + new CmcdData.Factory(cmcdConfiguration, CmcdData.STREAMING_FORMAT_DASH) + .setTrackSelection(trackSelection) + .setBufferedDurationUs(1_760_000) + .setPlaybackRate(2.0f) + .setIsLive(true) + .setDidRebuffer(true) + .setIsBufferEmpty(false) + .setChunkDurationUs(3_000_000) + .createCmcdData(); + + dataSpec = cmcdData.addToDataSpec(dataSpec); + + assertThat(dataSpec.httpRequestHeaders) + .containsExactly( + "CMCD-Object", + "br=840,d=3000,ot=av,tb=1000", + "CMCD-Request", + "bl=1800,dl=900,mtp=500,su", + "CMCD-Session", + "cid=\"mediaId\",pr=2.00,sf=d,sid=\"sessionId\",st=l", + "CMCD-Status", + "bs"); + } + @Test public void createInstance_manifestObjectType_setsCorrectHttpHeaders() { CmcdConfiguration.Factory cmcdConfigurationFactory = @@ -210,7 +302,7 @@ public class CmcdDataTest { } @Test - public void createInstance_unsetObjectType_setsCorrectHttpHeaders() { + public void createInstance_nullInferredObjectType_setsCorrectHttpHeaders() { CmcdConfiguration.Factory cmcdConfigurationFactory = mediaItem -> new CmcdConfiguration( @@ -249,7 +341,7 @@ public class CmcdDataTest { } @Test - public void createInstance_unsetObjectType_setsCorrectQueryParameters() { + public void createInstance_nullInferredObjectType_setsCorrectQueryParameters() { CmcdConfiguration.Factory cmcdConfigurationFactory = mediaItem -> new CmcdConfiguration( 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 5e06a4ebe8..73fb74701a 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 @@ -793,9 +793,7 @@ public class DefaultDashChunkSource implements DashChunkSource { flags, /* httpRequestHeaders= */ ImmutableMap.of()); if (cmcdDataFactory != null) { - cmcdDataFactory - .setChunkDurationUs(endTimeUs - startTimeUs) - .setObjectType(CmcdData.Factory.getObjectType(trackSelection)); + cmcdDataFactory.setChunkDurationUs(endTimeUs - startTimeUs); @Nullable Pair nextObjectAndRangeRequest = getNextObjectAndRangeRequest(firstSegmentNum, segmentUri, representationHolder); @@ -852,9 +850,7 @@ public class DefaultDashChunkSource implements DashChunkSource { flags, /* httpRequestHeaders= */ ImmutableMap.of()); if (cmcdDataFactory != null) { - cmcdDataFactory - .setChunkDurationUs(endTimeUs - startTimeUs) - .setObjectType(CmcdData.Factory.getObjectType(trackSelection)); + cmcdDataFactory.setChunkDurationUs(endTimeUs - startTimeUs); @Nullable Pair nextObjectAndRangeRequest = getNextObjectAndRangeRequest(firstSegmentNum, segmentUri, representationHolder); 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 4d3e51fe83..b9b9f999eb 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 @@ -27,7 +27,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.media3.common.C; import androidx.media3.common.Format; -import androidx.media3.common.MimeTypes; import androidx.media3.common.TrackGroup; import androidx.media3.common.util.TimestampAdjuster; import androidx.media3.common.util.UriUtil; @@ -513,11 +512,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; .setPlaybackRate(loadingInfo.playbackSpeed) .setIsLive(!playlist.hasEndTag) .setDidRebuffer(loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs)) - .setIsBufferEmpty(queue.isEmpty()) - .setObjectType( - getIsMuxedAudioAndVideo() - ? CmcdData.OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO - : CmcdData.Factory.getObjectType(trackSelection)); + .setIsBufferEmpty(queue.isEmpty()); long nextMediaSequence = segmentBaseHolder.partIndex == C.INDEX_UNSET ? segmentBaseHolder.mediaSequence + 1 @@ -597,13 +592,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; cmcdDataFactory); } - private boolean getIsMuxedAudioAndVideo() { - Format format = trackGroup.getFormat(trackSelection.getSelectedIndex()); - String audioMimeType = MimeTypes.getAudioMediaMimeType(format.codecs); - String videoMimeType = MimeTypes.getVideoMediaMimeType(format.codecs); - return audioMimeType != null && videoMimeType != null; - } - @Nullable private static SegmentBaseHolder getNextSegmentHolder( HlsMediaPlaylist mediaPlaylist, long nextMediaSequence, int nextPartIndex) { 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 4f08940c6d..fee868250b 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 @@ -367,8 +367,7 @@ public class DefaultSsChunkSource implements SsChunkSource { .setIsLive(manifest.isLive) .setDidRebuffer(loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs)) .setIsBufferEmpty(queue.isEmpty()) - .setChunkDurationUs(chunkEndTimeUs - chunkStartTimeUs) - .setObjectType(CmcdData.Factory.getObjectType(trackSelection)); + .setChunkDurationUs(chunkEndTimeUs - chunkStartTimeUs); if (chunkIndex + 1 < streamElement.chunkCount) { Uri nextUri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex + 1);