Add fields top bitrate(tb) and object duration(d)
Added these CMCD-Object fields to Common Media Client Data (CMCD) logging. #minor-release PiperOrigin-RevId: 548950296
This commit is contained in:
parent
0fa66534fe
commit
1b2a2fcde0
@ -34,6 +34,8 @@
|
||||
(([#33](https://github.com/androidx/media/issues/33)),([#9978](https://github.com/google/ExoPlayer/issues/9978))).
|
||||
* Add fields streaming format (sf), stream type (st) and version (v) to
|
||||
Common Media Client Data (CMCD) logging.
|
||||
* Add fields top birate (tb) and object duration (d) to Common Media
|
||||
Client Data (CMCD) logging.
|
||||
* Transformer:
|
||||
* Parse EXIF rotation data for image inputs.
|
||||
* Remove `TransformationRequest.HdrMode` annotation type and its
|
||||
|
@ -63,7 +63,9 @@ public final class CmcdConfiguration {
|
||||
KEY_MAXIMUM_REQUESTED_BITRATE,
|
||||
KEY_STREAMING_FORMAT,
|
||||
KEY_STREAM_TYPE,
|
||||
KEY_VERSION
|
||||
KEY_VERSION,
|
||||
KEY_TOP_BITRATE,
|
||||
KEY_OBJECT_DURATION
|
||||
})
|
||||
@Documented
|
||||
@Target(TYPE_USE)
|
||||
@ -84,6 +86,8 @@ public final class CmcdConfiguration {
|
||||
public static final String KEY_STREAMING_FORMAT = "sf";
|
||||
public static final String KEY_STREAM_TYPE = "st";
|
||||
public static final String KEY_VERSION = "v";
|
||||
public static final String KEY_TOP_BITRATE = "tb";
|
||||
public static final String KEY_OBJECT_DURATION = "d";
|
||||
|
||||
/**
|
||||
* Factory for {@link CmcdConfiguration} instances.
|
||||
@ -261,4 +265,20 @@ public final class CmcdConfiguration {
|
||||
public boolean isStreamTypeLoggingAllowed() {
|
||||
return requestConfig.isKeyAllowed(KEY_STREAM_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether logging top bitrate is allowed based on the {@linkplain RequestConfig request
|
||||
* configuration}.
|
||||
*/
|
||||
public boolean isTopBitrateLoggingAllowed() {
|
||||
return requestConfig.isKeyAllowed(KEY_TOP_BITRATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether logging object duration is allowed based on the {@linkplain RequestConfig
|
||||
* request configuration}.
|
||||
*/
|
||||
public boolean isObjectDurationLoggingAllowed() {
|
||||
return requestConfig.isKeyAllowed(KEY_OBJECT_DURATION);
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,14 @@
|
||||
package androidx.media3.exoplayer.upstream;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringDef;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
@ -79,6 +81,7 @@ public final class CmcdLog {
|
||||
* @param trackSelection The {@linkplain ExoTrackSelection track selection}.
|
||||
* @param bufferedDurationUs The duration of media currently buffered from the current playback
|
||||
* position, in microseconds.
|
||||
* @param chunkDurationUs The duration of current media chunk being requested, in microseconds.
|
||||
* @param streamingFormat The streaming format of the media content. Must be one of the allowed
|
||||
* streaming formats specified by the {@link StreamingFormat} annotation.
|
||||
* @param isLive {@code true} if the media content is being streamed live, {@code false}
|
||||
@ -88,6 +91,7 @@ public final class CmcdLog {
|
||||
CmcdConfiguration cmcdConfiguration,
|
||||
ExoTrackSelection trackSelection,
|
||||
long bufferedDurationUs,
|
||||
long chunkDurationUs,
|
||||
@StreamingFormat String streamingFormat,
|
||||
boolean isLive) {
|
||||
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> customData =
|
||||
@ -100,6 +104,17 @@ public final class CmcdLog {
|
||||
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(topBitrate / 1000);
|
||||
}
|
||||
if (cmcdConfiguration.isObjectDurationLoggingAllowed()) {
|
||||
cmcdObject.setObjectDurationMs(chunkDurationUs / 1000);
|
||||
}
|
||||
|
||||
CmcdLog.CmcdRequest.Builder cmcdRequest =
|
||||
new CmcdLog.CmcdRequest.Builder()
|
||||
@ -167,11 +182,15 @@ public final class CmcdLog {
|
||||
/** Builder for {@link CmcdObject} instances. */
|
||||
public static final class Builder {
|
||||
private int bitrateKbps;
|
||||
private int topBitrateKbps;
|
||||
private long objectDurationMs;
|
||||
@Nullable private String customData;
|
||||
|
||||
/** Creates a new instance with default values. */
|
||||
public Builder() {
|
||||
this.bitrateKbps = C.RATE_UNSET_INT;
|
||||
this.topBitrateKbps = C.RATE_UNSET_INT;
|
||||
this.objectDurationMs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdObject#bitrateKbps}. The default value is {@link C#RATE_UNSET_INT}. */
|
||||
@ -181,6 +200,28 @@ public final class CmcdLog {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link CmcdObject#topBitrateKbps}. The default value is {@link C#RATE_UNSET_INT}.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setTopBitrateKbps(int topBitrateKbps) {
|
||||
this.topBitrateKbps = topBitrateKbps;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link CmcdObject#objectDurationMs}. The default value is {@link C#TIME_UNSET}.
|
||||
*
|
||||
* @throws IllegalArgumentException If {@code objectDurationMs} is not equal to {@link
|
||||
* C#TIME_UNSET} and is non-positive.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setObjectDurationMs(long objectDurationMs) {
|
||||
checkArgument(objectDurationMs == C.TIME_UNSET || objectDurationMs >= 0);
|
||||
this.objectDurationMs = objectDurationMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdObject#customData}. The default value is {@code null}. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setCustomData(@Nullable String customData) {
|
||||
@ -203,6 +244,21 @@ public final class CmcdLog {
|
||||
*/
|
||||
public final int bitrateKbps;
|
||||
|
||||
/**
|
||||
* The highest bitrate rendition, in kbps, in the manifest or playlist that the client is
|
||||
* allowed to play, given current codec, licensing and sizing constraints. If unset, it is
|
||||
* represented by the value {@link C#RATE_UNSET_INT}.
|
||||
*/
|
||||
public final int topBitrateKbps;
|
||||
|
||||
/**
|
||||
* The playback duration in milliseconds of the object being requested, or {@link C#TIME_UNSET}
|
||||
* if unset. If a partial segment is being requested, then this value MUST indicate the playback
|
||||
* duration of that part and not that of its parent segment. This value can be an approximation
|
||||
* of the estimated duration if the explicit value is not known.
|
||||
*/
|
||||
public final long objectDurationMs;
|
||||
|
||||
/**
|
||||
* Custom data where the values of the keys vary with the object being requested, or {@code
|
||||
* null} if unset.
|
||||
@ -214,6 +270,8 @@ public final class CmcdLog {
|
||||
|
||||
private CmcdObject(Builder builder) {
|
||||
this.bitrateKbps = builder.bitrateKbps;
|
||||
this.topBitrateKbps = builder.topBitrateKbps;
|
||||
this.objectDurationMs = builder.objectDurationMs;
|
||||
this.customData = builder.customData;
|
||||
}
|
||||
|
||||
@ -230,6 +288,15 @@ public final class CmcdLog {
|
||||
headerValue.append(
|
||||
Util.formatInvariant("%s=%d,", CmcdConfiguration.KEY_BITRATE, bitrateKbps));
|
||||
}
|
||||
if (topBitrateKbps != C.RATE_UNSET_INT) {
|
||||
headerValue.append(
|
||||
Util.formatInvariant("%s=%d,", CmcdConfiguration.KEY_TOP_BITRATE, topBitrateKbps));
|
||||
}
|
||||
if (objectDurationMs != C.TIME_UNSET) {
|
||||
headerValue.append(
|
||||
Util.formatInvariant(
|
||||
"%s=%d,", CmcdConfiguration.KEY_OBJECT_DURATION, objectDurationMs));
|
||||
}
|
||||
if (!TextUtils.isEmpty(customData)) {
|
||||
headerValue.append(Util.formatInvariant("%s,", customData));
|
||||
}
|
||||
@ -259,6 +326,9 @@ public final class CmcdLog {
|
||||
/**
|
||||
* Sets the {@link CmcdRequest#bufferLengthMs}. Rounded to nearest 100 ms. The default value
|
||||
* is {@link C#TIME_UNSET}.
|
||||
*
|
||||
* @throws IllegalArgumentException If {@code bufferLengthMs} is not equal to {@link
|
||||
* C#TIME_UNSET} and is non-positive.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setBufferLengthMs(long bufferLengthMs) {
|
||||
@ -270,7 +340,7 @@ public final class CmcdLog {
|
||||
|
||||
/** Sets the {@link CmcdRequest#customData}. The default value is {@code null}. */
|
||||
@CanIgnoreReturnValue
|
||||
public CmcdRequest.Builder setCustomData(@Nullable String customData) {
|
||||
public Builder setCustomData(@Nullable String customData) {
|
||||
this.customData = customData;
|
||||
return this;
|
||||
}
|
||||
@ -344,6 +414,9 @@ public final class CmcdLog {
|
||||
/**
|
||||
* Sets the {@link CmcdSession#contentId}. Maximum length allowed is 64 characters. The
|
||||
* default value is {@code null}.
|
||||
*
|
||||
* @throws IllegalArgumentException If {@code contentId} is null or its length exceeds {@link
|
||||
* CmcdConfiguration#MAX_ID_LENGTH}.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setContentId(@Nullable String contentId) {
|
||||
@ -355,6 +428,9 @@ public final class CmcdLog {
|
||||
/**
|
||||
* Sets the {@link CmcdSession#sessionId}. Maximum length allowed is 64 characters. The
|
||||
* default value is {@code null}.
|
||||
*
|
||||
* @throws IllegalArgumentException If {@code sessionId} is null or its length exceeds {@link
|
||||
* CmcdConfiguration#MAX_ID_LENGTH}.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setSessionId(@Nullable String sessionId) {
|
||||
@ -502,6 +578,9 @@ public final class CmcdLog {
|
||||
/**
|
||||
* Sets the {@link CmcdStatus#maximumRequestedThroughputKbps}. Rounded to nearest 100 kbps.
|
||||
* The default value is {@link C#RATE_UNSET_INT}.
|
||||
*
|
||||
* @throws IllegalArgumentException If {@code maximumRequestedThroughputKbps} is not equal to
|
||||
* {@link C#RATE_UNSET_INT} and is non-positive.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setMaximumRequestedThroughputKbps(int maximumRequestedThroughputKbps) {
|
||||
|
@ -21,6 +21,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@ -56,13 +57,16 @@ public class CmcdLogTest {
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
ExoTrackSelection trackSelection = mock(ExoTrackSelection.class);
|
||||
when(trackSelection.getSelectedFormat())
|
||||
.thenReturn(new Format.Builder().setPeakBitrate(840_000).build());
|
||||
Format format = new Format.Builder().setPeakBitrate(840_000).build();
|
||||
when(trackSelection.getSelectedFormat()).thenReturn(format);
|
||||
when(trackSelection.getTrackGroup())
|
||||
.thenReturn(new TrackGroup(format, new Format.Builder().setPeakBitrate(1_000_000).build()));
|
||||
CmcdLog cmcdLog =
|
||||
CmcdLog.createInstance(
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
/* bufferedDurationUs= */ 1_760_000,
|
||||
/* chunkDurationUs= */ 3_000_000,
|
||||
CmcdLog.STREAMING_FORMAT_DASH,
|
||||
true);
|
||||
|
||||
@ -72,7 +76,7 @@ public class CmcdLogTest {
|
||||
assertThat(requestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=840,key1=value1",
|
||||
"br=840,tb=1000,d=3000,key1=value1",
|
||||
"CMCD-Request",
|
||||
"bl=1800,key2=\"stringValue\"",
|
||||
"CMCD-Session",
|
||||
|
@ -370,6 +370,13 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
trackSelection.updateSelectedTrack(
|
||||
playbackPositionUs, bufferedDurationUs, availableLiveDurationUs, queue, chunkIterators);
|
||||
|
||||
int selectedTrackIndex = trackSelection.getSelectedIndex();
|
||||
long chunkDurationUs = 0;
|
||||
if (selectedTrackIndex < chunkIterators.length && chunkIterators[selectedTrackIndex].next()) {
|
||||
chunkDurationUs =
|
||||
chunkIterators[selectedTrackIndex].getChunkEndTimeUs()
|
||||
- chunkIterators[selectedTrackIndex].getChunkStartTimeUs();
|
||||
}
|
||||
@Nullable
|
||||
CmcdLog cmcdLog =
|
||||
cmcdConfiguration == null
|
||||
@ -378,11 +385,11 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
bufferedDurationUs,
|
||||
chunkDurationUs,
|
||||
CmcdLog.STREAMING_FORMAT_DASH,
|
||||
manifest.dynamic);
|
||||
|
||||
RepresentationHolder representationHolder =
|
||||
updateSelectedBaseUrl(trackSelection.getSelectedIndex());
|
||||
RepresentationHolder representationHolder = updateSelectedBaseUrl(selectedTrackIndex);
|
||||
if (representationHolder.chunkExtractor != null) {
|
||||
Representation selectedRepresentation = representationHolder.representation;
|
||||
@Nullable RangedUri pendingInitializationUri = null;
|
||||
|
@ -314,7 +314,7 @@ public class DefaultDashChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=700",
|
||||
"br=700,tb=1300,d=4000",
|
||||
"CMCD-Request",
|
||||
"bl=0",
|
||||
"CMCD-Session",
|
||||
@ -359,7 +359,7 @@ public class DefaultDashChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=700",
|
||||
"br=700,tb=1300,d=4000",
|
||||
"CMCD-Request",
|
||||
"bl=0",
|
||||
"CMCD-Session",
|
||||
@ -405,7 +405,7 @@ public class DefaultDashChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=700,key1=value1",
|
||||
"br=700,tb=1300,d=4000,key1=value1",
|
||||
"CMCD-Request",
|
||||
"bl=0,key2=\"stringValue\"",
|
||||
"CMCD-Session",
|
||||
|
@ -481,6 +481,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
seenExpectedPlaylistError = false;
|
||||
expectedPlaylistUrl = null;
|
||||
|
||||
long chunkDurationUs = 0;
|
||||
if (selectedTrackIndex < mediaChunkIterators.length
|
||||
&& mediaChunkIterators[selectedTrackIndex].next()) {
|
||||
chunkDurationUs =
|
||||
mediaChunkIterators[selectedTrackIndex].getChunkEndTimeUs()
|
||||
- mediaChunkIterators[selectedTrackIndex].getChunkStartTimeUs();
|
||||
}
|
||||
@Nullable
|
||||
CmcdLog cmcdLog =
|
||||
cmcdConfiguration == null
|
||||
@ -489,6 +496,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
bufferedDurationUs,
|
||||
chunkDurationUs,
|
||||
CmcdLog.STREAMING_FORMAT_HLS,
|
||||
!playlist.hasEndTag);
|
||||
|
||||
|
@ -210,7 +210,7 @@ public class HlsChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=800",
|
||||
"br=800,tb=800,d=0",
|
||||
"CMCD-Request",
|
||||
"bl=0",
|
||||
"CMCD-Session",
|
||||
@ -256,7 +256,7 @@ public class HlsChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=800",
|
||||
"br=800,tb=800,d=0",
|
||||
"CMCD-Request",
|
||||
"bl=0",
|
||||
"CMCD-Session",
|
||||
@ -303,7 +303,7 @@ public class HlsChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=800,key1=value1",
|
||||
"br=800,tb=800,d=0,key1=value1",
|
||||
"CMCD-Request",
|
||||
"bl=0,key2=\"stringValue\"",
|
||||
"CMCD-Session",
|
||||
|
@ -280,6 +280,12 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
||||
int manifestTrackIndex = trackSelection.getIndexInTrackGroup(trackSelectionIndex);
|
||||
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
|
||||
|
||||
long chunkDurationUs = 0;
|
||||
if (trackSelectionIndex < chunkIterators.length && chunkIterators[trackSelectionIndex].next()) {
|
||||
chunkDurationUs =
|
||||
chunkIterators[trackSelectionIndex].getChunkEndTimeUs()
|
||||
- chunkIterators[trackSelectionIndex].getChunkStartTimeUs();
|
||||
}
|
||||
@Nullable
|
||||
CmcdLog cmcdLog =
|
||||
cmcdConfiguration == null
|
||||
@ -288,6 +294,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
bufferedDurationUs,
|
||||
chunkDurationUs,
|
||||
CmcdLog.STREAMING_FORMAT_SS,
|
||||
manifest.isLive);
|
||||
|
||||
|
@ -64,7 +64,7 @@ public class DefaultSsChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=307",
|
||||
"br=307,tb=1536,d=1968",
|
||||
"CMCD-Request",
|
||||
"bl=0",
|
||||
"CMCD-Session",
|
||||
@ -109,7 +109,7 @@ public class DefaultSsChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=307",
|
||||
"br=307,tb=1536,d=1968",
|
||||
"CMCD-Request",
|
||||
"bl=0",
|
||||
"CMCD-Session",
|
||||
@ -155,7 +155,7 @@ public class DefaultSsChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=307,key1=value1",
|
||||
"br=307,tb=1536,d=1968,key1=value1",
|
||||
"CMCD-Request",
|
||||
"bl=0,key2=\"stringValue\"",
|
||||
"CMCD-Session",
|
||||
|
Loading…
x
Reference in New Issue
Block a user