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