mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
0b0c198f59
commit
4997ea82fa
@ -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);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user