Skip to content

Commit

Permalink
Add field object type (ot)
Browse files Browse the repository at this point in the history
Added this CMCD-Object field to Common Media Client Data (CMCD) logging.

#minor-release

PiperOrigin-RevId: 554843305
  • Loading branch information
rohitjoins authored and tianyif committed Aug 10, 2023
1 parent cba027c commit 3ec462d
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 41 deletions.
3 changes: 2 additions & 1 deletion RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
(([#33](https://github.com/androidx/media/issues/33)),([#9978](https://github.com/google/ExoPlayer/issues/9978))).
* Add additional fields to Common Media Client Data (CMCD) logging:
streaming format (sf), stream type (st), version (v), top birate (tb),
object duration (d) and measured throughput (mtp).
object duration (d), measured throughput (mtp) and object type(ot)
([#8699](https://github.com/google/ExoPlayer/issues/8699)).
* Rename `MimeTypes.TEXT_EXOPLAYER_CUES` to
`MimeTypes.APPLICATION_MEDIA3_CUES`.
* Add `PngExtractor` that sends and reads a whole png file into the the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public final class CmcdConfiguration {
KEY_VERSION,
KEY_TOP_BITRATE,
KEY_OBJECT_DURATION,
KEY_MEASURED_THROUGHPUT
KEY_MEASURED_THROUGHPUT,
KEY_OBJECT_TYPE
})
@Documented
@Target(TYPE_USE)
Expand All @@ -90,6 +91,7 @@ public final class CmcdConfiguration {
public static final String KEY_TOP_BITRATE = "tb";
public static final String KEY_OBJECT_DURATION = "d";
public static final String KEY_MEASURED_THROUGHPUT = "mtp";
public static final String KEY_OBJECT_TYPE = "ot";

/**
* Factory for {@link CmcdConfiguration} instances.
Expand Down Expand Up @@ -291,4 +293,12 @@ public boolean isObjectDurationLoggingAllowed() {
public boolean isMeasuredThroughputLoggingAllowed() {
return requestConfig.isKeyAllowed(KEY_MEASURED_THROUGHPUT);
}

/**
* Returns whether logging object type is allowed based on the {@linkplain RequestConfig request
* configuration}.
*/
public boolean isObjectTypeLoggingAllowed() {
return requestConfig.isKeyAllowed(KEY_OBJECT_TYPE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import androidx.annotation.Nullable;
import androidx.annotation.StringDef;
import androidx.media3.common.C;
import androidx.media3.common.C.TrackType;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
Expand All @@ -45,6 +47,33 @@
@UnstableApi
public final class CmcdHeadersFactory {

/**
* 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 ObjectType} annotation, or {@code null}.
* @throws IllegalArgumentException if the provided {@link ExoTrackSelection} is {@code null}.
*/
@Nullable
public static @ObjectType String getObjectType(ExoTrackSelection trackSelection) {
checkArgument(trackSelection != null);
@C.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;
}
}

/** Indicates the streaming format used for media content. */
@Retention(RetentionPolicy.SOURCE)
@StringDef({STREAMING_FORMAT_DASH, STREAMING_FORMAT_HLS, STREAMING_FORMAT_SS})
Expand All @@ -59,6 +88,18 @@ public final class CmcdHeadersFactory {
@Target(TYPE_USE)
public @interface StreamType {}

/** Indicates the media type of current object being requested. */
@Retention(RetentionPolicy.SOURCE)
@StringDef({
OBJECT_TYPE_INIT_SEGMENT,
OBJECT_TYPE_AUDIO_ONLY,
OBJECT_TYPE_VIDEO_ONLY,
OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO
})
@Documented
@Target(TYPE_USE)
public @interface ObjectType {}

/** Represents the Dynamic Adaptive Streaming over HTTP (DASH) format. */
public static final String STREAMING_FORMAT_DASH = "d";

Expand All @@ -74,12 +115,25 @@ public final class CmcdHeadersFactory {
/** Represents the Live Streaming stream type. */
public static final String STREAM_TYPE_LIVE = "l";

/** Represents the object type for an initialization segment in a media container. */
public static final String OBJECT_TYPE_INIT_SEGMENT = "i";

/** Represents the object type for audio-only content in a media container. */
public static final String OBJECT_TYPE_AUDIO_ONLY = "a";

/** Represents the object type for video-only content in a media container. */
public static final String OBJECT_TYPE_VIDEO_ONLY = "v";

/** Represents the object type for muxed audio and video content in a media container. */
public static final String OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO = "av";

private final CmcdConfiguration cmcdConfiguration;
private final ExoTrackSelection trackSelection;
private final long bufferedDurationUs;
private final @StreamingFormat String streamingFormat;
private final boolean isLive;
private long chunkDurationUs;
private @Nullable @ObjectType String objectType;

/**
* Creates an instance.
Expand Down Expand Up @@ -122,6 +176,18 @@ public CmcdHeadersFactory setChunkDurationUs(long chunkDurationUs) {
return this;
}

/**
* Sets the object type of the current object being requested. Must be one of the allowed object
* types specified by the {@link ObjectType} annotation.
*
* <p>Default is {@code null}.
*/
@CanIgnoreReturnValue
public CmcdHeadersFactory setObjectType(@Nullable @ObjectType String objectType) {
this.objectType = objectType;
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 =
Expand All @@ -130,24 +196,30 @@ public CmcdHeadersFactory setChunkDurationUs(long chunkDurationUs) {

CmcdObject.Builder cmcdObject =
new CmcdObject.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_OBJECT));
if (cmcdConfiguration.isBitrateLoggingAllowed()) {
cmcdObject.setBitrateKbps(bitrateKbps);
}
if (cmcdConfiguration.isTopBitrateLoggingAllowed()) {
TrackGroup trackGroup = trackSelection.getTrackGroup();
int topBitrate = trackSelection.getSelectedFormat().bitrate;
for (int i = 0; i < trackGroup.length; i++) {
topBitrate = max(topBitrate, trackGroup.getFormat(i).bitrate);
if (!getIsInitSegment()) {
if (cmcdConfiguration.isBitrateLoggingAllowed()) {
cmcdObject.setBitrateKbps(bitrateKbps);
}
if (cmcdConfiguration.isTopBitrateLoggingAllowed()) {
TrackGroup trackGroup = trackSelection.getTrackGroup();
int topBitrate = trackSelection.getSelectedFormat().bitrate;
for (int i = 0; i < trackGroup.length; i++) {
topBitrate = max(topBitrate, trackGroup.getFormat(i).bitrate);
}
cmcdObject.setTopBitrateKbps(Util.ceilDivide(topBitrate, 1000));
}
if (cmcdConfiguration.isObjectDurationLoggingAllowed() && chunkDurationUs != C.TIME_UNSET) {
cmcdObject.setObjectDurationMs(chunkDurationUs / 1000);
}
cmcdObject.setTopBitrateKbps(Util.ceilDivide(topBitrate, 1000));
}
if (cmcdConfiguration.isObjectDurationLoggingAllowed() && chunkDurationUs != C.TIME_UNSET) {
cmcdObject.setObjectDurationMs(chunkDurationUs / 1000);

if (cmcdConfiguration.isObjectTypeLoggingAllowed()) {
cmcdObject.setObjectType(objectType);
}

CmcdRequest.Builder cmcdRequest =
new CmcdRequest.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_REQUEST));
if (cmcdConfiguration.isBufferLengthLoggingAllowed()) {
if (!getIsInitSegment() && cmcdConfiguration.isBufferLengthLoggingAllowed()) {
cmcdRequest.setBufferLengthMs(bufferedDurationUs / 1000);
}
if (cmcdConfiguration.isMeasuredThroughputLoggingAllowed()
Expand Down Expand Up @@ -186,6 +258,10 @@ public CmcdHeadersFactory setChunkDurationUs(long chunkDurationUs) {
return httpRequestHeaders.buildOrThrow();
}

private boolean getIsInitSegment() {
return objectType != null && objectType.equals(OBJECT_TYPE_INIT_SEGMENT);
}

/** Keys whose values vary with the object being requested. Contains CMCD fields: {@code br}. */
private static final class CmcdObject {

Expand All @@ -194,6 +270,7 @@ public static final class Builder {
private int bitrateKbps;
private int topBitrateKbps;
private long objectDurationMs;
@Nullable private @ObjectType String objectType;
@Nullable private String customData;

/** Creates a new instance with default values. */
Expand Down Expand Up @@ -231,6 +308,13 @@ public Builder setObjectDurationMs(long objectDurationMs) {
return this;
}

/** Sets the {@link CmcdObject#objectType}. The default value is {@code null}. */
@CanIgnoreReturnValue
public Builder setObjectType(@Nullable @ObjectType String objectType) {
this.objectType = objectType;
return this;
}

/** Sets the {@link CmcdObject#customData}. The default value is {@code null}. */
@CanIgnoreReturnValue
public Builder setCustomData(@Nullable String customData) {
Expand Down Expand Up @@ -268,6 +352,14 @@ public CmcdObject build() {
*/
public final long objectDurationMs;

/**
* The media type of the current object being requested , or {@code null} if unset. Must be one
* of the allowed object types specified by the {@link ObjectType} annotation.
*
* <p>If the object type being requested is unknown, then this key MUST NOT be used.
*/
@Nullable public final @ObjectType String objectType;

/**
* Custom data where the values of the keys vary with the object being requested, or {@code
* null} if unset.
Expand All @@ -281,6 +373,7 @@ private CmcdObject(Builder builder) {
this.bitrateKbps = builder.bitrateKbps;
this.topBitrateKbps = builder.topBitrateKbps;
this.objectDurationMs = builder.objectDurationMs;
this.objectType = builder.objectType;
this.customData = builder.customData;
}

Expand All @@ -306,6 +399,10 @@ public void populateHttpRequestHeaders(
Util.formatInvariant(
"%s=%d,", CmcdConfiguration.KEY_OBJECT_DURATION, objectDurationMs));
}
if (!TextUtils.isEmpty(objectType)) {
headerValue.append(
Util.formatInvariant("%s=%s,", CmcdConfiguration.KEY_OBJECT_TYPE, objectType));
}
if (!TextUtils.isEmpty(customData)) {
headerValue.append(Util.formatInvariant("%s,", customData));
}
Expand Down Expand Up @@ -377,6 +474,10 @@ public CmcdRequest build() {
* The buffer length in milliseconds associated with the media object being requested, or {@link
* C#TIME_UNSET} if unset.
*
* <p>This key SHOULD only be sent with an {@link CmcdObject#objectType} of {@link
* #OBJECT_TYPE_AUDIO_ONLY}, {@link #OBJECT_TYPE_VIDEO_ONLY} or {@link
* #OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO}.
*
* <p>This value MUST be rounded to the nearest 100 ms.
*/
public final long bufferLengthMs;
Expand Down Expand Up @@ -531,15 +632,16 @@ public CmcdSession build() {
@Nullable public final String sessionId;

/**
* The streaming format that defines the current request. d = MPEG DASH, h = HTTP Live Streaming
* (HLS), s = Smooth Streaming and o = other. If the streaming format being requested is
* unknown, then this key MUST NOT be used.
* The streaming format that defines the current request , or {@code null} if unset. Must be one
* of the allowed streaming formats specified by the {@link StreamingFormat} annotation.
*
* <p>If the streaming format being requested is unknown, then this key MUST NOT be used.
*/
@Nullable public final @StreamingFormat String streamingFormat;

/**
* Type of stream. v = all segments are available – e.g., VOD and l = segments become available
* over time – e.g., LIVE.
* Type of stream, or {@code null} if unset. Must be one of the allowed stream types specified
* by the {@link StreamType} annotation.
*/
@Nullable public final @StreamType String streamType;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,9 @@ protected Chunk newInitializationChunk(
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
cmcdHeadersFactory == null
? ImmutableMap.of()
: cmcdHeadersFactory.createHttpRequestHeaders();
: cmcdHeadersFactory
.setObjectType(CmcdHeadersFactory.OBJECT_TYPE_INIT_SEGMENT)
.createHttpRequestHeaders();
DataSpec dataSpec =
DashUtil.buildDataSpec(
representation,
Expand Down Expand Up @@ -706,6 +708,7 @@ protected Chunk newMediaChunk(
? ImmutableMap.of()
: cmcdHeadersFactory
.setChunkDurationUs(endTimeUs - startTimeUs)
.setObjectType(CmcdHeadersFactory.getObjectType(trackSelection))
.createHttpRequestHeaders();
DataSpec dataSpec =
DashUtil.buildDataSpec(
Expand Down Expand Up @@ -755,6 +758,7 @@ protected Chunk newMediaChunk(
? ImmutableMap.of()
: cmcdHeadersFactory
.setChunkDurationUs(endTimeUs - startTimeUs)
.setObjectType(CmcdHeadersFactory.getObjectType(trackSelection))
.createHttpRequestHeaders();
DataSpec dataSpec =
DashUtil.buildDataSpec(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ public void getNextChunk_chunkSourceWithDefaultCmcdConfiguration_setsCmcdLogging
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
"br=700,tb=1300,d=4000",
"br=700,tb=1300,d=4000,ot=v",
"CMCD-Request",
"bl=0,mtp=1000",
"CMCD-Session",
Expand Down Expand Up @@ -359,7 +359,7 @@ public int getRequestedMaximumThroughputKbps(int throughputKbps) {
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
"br=700,tb=1300,d=4000",
"br=700,tb=1300,d=4000,ot=v",
"CMCD-Request",
"bl=0,mtp=1000",
"CMCD-Session",
Expand Down Expand Up @@ -405,7 +405,7 @@ public int getRequestedMaximumThroughputKbps(int throughputKbps) {
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
"br=700,tb=1300,d=4000,key1=value1",
"br=700,tb=1300,d=4000,ot=v,key1=value1",
"CMCD-Request",
"bl=0,mtp=1000,key2=\"stringValue\"",
"CMCD-Session",
Expand Down
Loading

0 comments on commit 3ec462d

Please sign in to comment.