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
This commit is contained in:
rohks 2024-12-12 04:33:03 -08:00 committed by Copybara-Service
parent 0b0c198f59
commit 4997ea82fa
5 changed files with 138 additions and 56 deletions

View File

@ -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.
*
* <p>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.
*
* <p>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);
}

View File

@ -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(

View File

@ -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<String, String> 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<String, String> nextObjectAndRangeRequest =
getNextObjectAndRangeRequest(firstSegmentNum, segmentUri, representationHolder);

View File

@ -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) {

View File

@ -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);