Make custom data adhere to Common Media Client Data(CMCD) specification
Added more comprehensive Javadoc around setting custom data and verification on key format. Changed adding custom data as a `String` to `List<String>`. This would enable us to sort all keys to reduce the fingerprinting surface. PiperOrigin-RevId: 558291240
This commit is contained in:
parent
6566387f70
commit
afb8d6c9e2
@ -24,7 +24,7 @@ import androidx.annotation.StringDef;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@ -160,24 +160,39 @@ public final class CmcdConfiguration {
|
||||
*
|
||||
* <p>By default, no custom data is provided.
|
||||
*
|
||||
* <p>The key should belong to the {@link HeaderKey}. The value should consist of key-value
|
||||
* pairs separated by commas. If the value contains one of the keys defined in the {@link
|
||||
* CmcdKey} list, then this key should not be {@linkplain #isKeyAllowed(String) allowed},
|
||||
* otherwise the key could be included twice in the produced log.
|
||||
* <p>The data payload consists of a series of key/value pairs constructed according to the
|
||||
* following rules:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Custom keys SHOULD be allocated to one of the four defined header names defined in the
|
||||
* {@link HeaderKey} annotation.
|
||||
* <li>All information in the payload MUST be represented as key=value pairs.
|
||||
* <li>The key and value MUST be separated by an equals sign. If the value type is boolean and
|
||||
* the value is {@code true}, then the equals sign and the value MUST be omitted.
|
||||
* <li>The key names are case-sensitive and reserved. Custom key names MUST carry a hyphenated
|
||||
* prefix to ensure no namespace collision with future revisions to Common Media Client
|
||||
* Data (CMCD) specification. Clients SHOULD use a reverse-DNS syntax when defining their
|
||||
* own prefix.
|
||||
* <li>Any value of type String MUST be enclosed by opening and closing double quotes. Double
|
||||
* quotes and backslashes MUST be escaped using a backslash "\" character. Any value that
|
||||
* is not of type string does not require quoting.
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>Note:</b> The key words MUST and SHOULD are to be interpreted as described in RFC 2119.
|
||||
*
|
||||
* <p>Example:
|
||||
*
|
||||
* <ul>
|
||||
* <li>CMCD-Request:customField1=25400
|
||||
* <li>CMCD-Object:customField2=3200,customField3=4004,customField4=v,customField5=6000
|
||||
* <li>CMCD-Status:customField6,customField7=15000
|
||||
* <li>CMCD-Session:customField8="6e2fb550-c457-11e9-bb97-0800200c9a66"
|
||||
* <li>CMCD-Request:custom-field1=25400
|
||||
* <li>CMCD-Object:custom-field2=3200,custom-field3=4004,custom-field4=v,custom-field5=6000
|
||||
* <li>CMCD-Status:custom-field6,custom-field7=15000
|
||||
* <li>CMCD-Session:custom-field8="stringValue"
|
||||
* </ul>
|
||||
*
|
||||
* @return An {@link ImmutableMap} containing the custom data.
|
||||
* @return An {@link ImmutableListMultimap} containing the custom data.
|
||||
*/
|
||||
default ImmutableMap<@HeaderKey String, String> getCustomData() {
|
||||
return ImmutableMap.of();
|
||||
default ImmutableListMultimap<@HeaderKey String, String> getCustomData() {
|
||||
return ImmutableListMultimap.of();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.exoplayer.upstream;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
@ -30,6 +31,8 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.lang.annotation.Documented;
|
||||
@ -37,6 +40,8 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* This class serves as a factory for generating Common Media Client Data (CMCD) HTTP request
|
||||
@ -50,6 +55,8 @@ import java.util.ArrayList;
|
||||
public final class CmcdHeadersFactory {
|
||||
|
||||
private static final Joiner COMMA_JOINER = Joiner.on(",");
|
||||
private static final Pattern CUSTOM_KEY_NAME_PATTERN =
|
||||
Pattern.compile("[a-zA-Z0-9]+(-[a-zA-Z0-9]+)+");
|
||||
|
||||
/**
|
||||
* Retrieves the object type value from the given {@link ExoTrackSelection}.
|
||||
@ -209,12 +216,15 @@ public final class CmcdHeadersFactory {
|
||||
|
||||
/** 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 =
|
||||
ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String> customData =
|
||||
cmcdConfiguration.requestConfig.getCustomData();
|
||||
for (String headerKey : customData.keySet()) {
|
||||
validateCustomDataListFormat(customData.get(headerKey));
|
||||
}
|
||||
|
||||
int bitrateKbps = Util.ceilDivide(trackSelection.getSelectedFormat().bitrate, 1000);
|
||||
|
||||
CmcdObject.Builder cmcdObject =
|
||||
new CmcdObject.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_OBJECT));
|
||||
CmcdObject.Builder cmcdObject = new CmcdObject.Builder();
|
||||
if (!getIsInitSegment()) {
|
||||
if (cmcdConfiguration.isBitrateLoggingAllowed()) {
|
||||
cmcdObject.setBitrateKbps(bitrateKbps);
|
||||
@ -231,13 +241,14 @@ public final class CmcdHeadersFactory {
|
||||
cmcdObject.setObjectDurationMs(Util.usToMs(chunkDurationUs));
|
||||
}
|
||||
}
|
||||
|
||||
if (cmcdConfiguration.isObjectTypeLoggingAllowed()) {
|
||||
cmcdObject.setObjectType(objectType);
|
||||
}
|
||||
if (customData.containsKey(CmcdConfiguration.KEY_CMCD_OBJECT)) {
|
||||
cmcdObject.setCustomDataList(customData.get(CmcdConfiguration.KEY_CMCD_OBJECT));
|
||||
}
|
||||
|
||||
CmcdRequest.Builder cmcdRequest =
|
||||
new CmcdRequest.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_REQUEST));
|
||||
CmcdRequest.Builder cmcdRequest = new CmcdRequest.Builder();
|
||||
if (!getIsInitSegment() && cmcdConfiguration.isBufferLengthLoggingAllowed()) {
|
||||
cmcdRequest.setBufferLengthMs(Util.usToMs(bufferedDurationUs));
|
||||
}
|
||||
@ -252,9 +263,11 @@ public final class CmcdHeadersFactory {
|
||||
if (cmcdConfiguration.isStartupLoggingAllowed()) {
|
||||
cmcdRequest.setStartup(didRebuffer || isBufferEmpty);
|
||||
}
|
||||
if (customData.containsKey(CmcdConfiguration.KEY_CMCD_REQUEST)) {
|
||||
cmcdRequest.setCustomDataList(customData.get(CmcdConfiguration.KEY_CMCD_REQUEST));
|
||||
}
|
||||
|
||||
CmcdSession.Builder cmcdSession =
|
||||
new CmcdSession.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_SESSION));
|
||||
CmcdSession.Builder cmcdSession = new CmcdSession.Builder();
|
||||
if (cmcdConfiguration.isContentIdLoggingAllowed()) {
|
||||
cmcdSession.setContentId(cmcdConfiguration.contentId);
|
||||
}
|
||||
@ -270,9 +283,11 @@ public final class CmcdHeadersFactory {
|
||||
if (cmcdConfiguration.isPlaybackRateLoggingAllowed()) {
|
||||
cmcdSession.setPlaybackRate(playbackRate);
|
||||
}
|
||||
if (customData.containsKey(CmcdConfiguration.KEY_CMCD_SESSION)) {
|
||||
cmcdSession.setCustomDataList(customData.get(CmcdConfiguration.KEY_CMCD_SESSION));
|
||||
}
|
||||
|
||||
CmcdStatus.Builder cmcdStatus =
|
||||
new CmcdStatus.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_STATUS));
|
||||
CmcdStatus.Builder cmcdStatus = new CmcdStatus.Builder();
|
||||
if (cmcdConfiguration.isMaximumRequestThroughputLoggingAllowed()) {
|
||||
cmcdStatus.setMaximumRequestedThroughputKbps(
|
||||
cmcdConfiguration.requestConfig.getRequestedMaximumThroughputKbps(bitrateKbps));
|
||||
@ -280,6 +295,9 @@ public final class CmcdHeadersFactory {
|
||||
if (cmcdConfiguration.isBufferStarvationLoggingAllowed()) {
|
||||
cmcdStatus.setBufferStarvation(didRebuffer);
|
||||
}
|
||||
if (customData.containsKey(CmcdConfiguration.KEY_CMCD_STATUS)) {
|
||||
cmcdStatus.setCustomDataList(customData.get(CmcdConfiguration.KEY_CMCD_STATUS));
|
||||
}
|
||||
|
||||
ImmutableMap.Builder<String, String> httpRequestHeaders = ImmutableMap.builder();
|
||||
cmcdObject.build().populateHttpRequestHeaders(httpRequestHeaders);
|
||||
@ -293,6 +311,13 @@ public final class CmcdHeadersFactory {
|
||||
return objectType != null && objectType.equals(OBJECT_TYPE_INIT_SEGMENT);
|
||||
}
|
||||
|
||||
private void validateCustomDataListFormat(List<String> customDataList) {
|
||||
for (String customData : customDataList) {
|
||||
String key = Util.split(customData, "=")[0];
|
||||
checkState(CUSTOM_KEY_NAME_PATTERN.matcher(key).matches());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keys whose values vary with the object being requested. Contains CMCD fields: {@code br},
|
||||
* {@code tb}, {@code d} and {@code ot}.
|
||||
@ -305,13 +330,14 @@ public final class CmcdHeadersFactory {
|
||||
private int topBitrateKbps;
|
||||
private long objectDurationMs;
|
||||
@Nullable private @ObjectType String objectType;
|
||||
@Nullable private String customData;
|
||||
private ImmutableList<String> customDataList;
|
||||
|
||||
/** 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;
|
||||
this.customDataList = ImmutableList.of();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -360,10 +386,10 @@ public final class CmcdHeadersFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdObject#customData}. The default value is {@code null}. */
|
||||
/** Sets the {@link CmcdObject#customDataList}. The default value is an empty list. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setCustomData(@Nullable String customData) {
|
||||
this.customData = customData;
|
||||
public Builder setCustomDataList(List<String> customDataList) {
|
||||
this.customDataList = ImmutableList.copyOf(customDataList);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -405,21 +431,15 @@ public final class CmcdHeadersFactory {
|
||||
*/
|
||||
@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.
|
||||
*
|
||||
* <p>The String consists of key-value pairs separated by commas.<br>
|
||||
* Example: {@code key1=intValue,key2="stringValue"}.
|
||||
*/
|
||||
@Nullable public final String customData;
|
||||
/** Custom data that vary based on the specific object being requested. */
|
||||
public final ImmutableList<String> customDataList;
|
||||
|
||||
private CmcdObject(Builder builder) {
|
||||
this.bitrateKbps = builder.bitrateKbps;
|
||||
this.topBitrateKbps = builder.topBitrateKbps;
|
||||
this.objectDurationMs = builder.objectDurationMs;
|
||||
this.objectType = builder.objectType;
|
||||
this.customData = builder.customData;
|
||||
this.customDataList = builder.customDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -443,9 +463,7 @@ public final class CmcdHeadersFactory {
|
||||
if (!TextUtils.isEmpty(objectType)) {
|
||||
headerValueList.add(CmcdConfiguration.KEY_OBJECT_TYPE + "=" + objectType);
|
||||
}
|
||||
if (!TextUtils.isEmpty(customData)) {
|
||||
headerValueList.add(customData);
|
||||
}
|
||||
headerValueList.addAll(customDataList);
|
||||
|
||||
if (!headerValueList.isEmpty()) {
|
||||
httpRequestHeaders.put(
|
||||
@ -466,13 +484,14 @@ public final class CmcdHeadersFactory {
|
||||
private long measuredThroughputInKbps;
|
||||
private long deadlineMs;
|
||||
private boolean startup;
|
||||
@Nullable private String customData;
|
||||
private ImmutableList<String> customDataList;
|
||||
|
||||
/** Creates a new instance with default values. */
|
||||
public Builder() {
|
||||
this.bufferLengthMs = C.TIME_UNSET;
|
||||
this.measuredThroughputInKbps = C.RATE_UNSET_INT;
|
||||
this.deadlineMs = C.TIME_UNSET;
|
||||
this.customDataList = ImmutableList.of();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -526,10 +545,10 @@ public final class CmcdHeadersFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdRequest#customData}. The default value is {@code null}. */
|
||||
/** Sets the {@link CmcdRequest#customDataList}. The default value is an empty list. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setCustomData(@Nullable String customData) {
|
||||
this.customData = customData;
|
||||
public Builder setCustomDataList(List<String> customDataList) {
|
||||
this.customDataList = ImmutableList.copyOf(customDataList);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -580,20 +599,15 @@ public final class CmcdHeadersFactory {
|
||||
*/
|
||||
public final boolean startup;
|
||||
|
||||
/**
|
||||
* Custom data where the values of the keys vary with each request, or {@code null} if unset.
|
||||
*
|
||||
* <p>The String consists of key-value pairs separated by commas.<br>
|
||||
* Example: {@code key1=intValue, key2="stringValue"}.
|
||||
*/
|
||||
@Nullable public final String customData;
|
||||
/** Custom data that vary with each request. */
|
||||
public final ImmutableList<String> customDataList;
|
||||
|
||||
private CmcdRequest(Builder builder) {
|
||||
this.bufferLengthMs = builder.bufferLengthMs;
|
||||
this.measuredThroughputInKbps = builder.measuredThroughputInKbps;
|
||||
this.deadlineMs = builder.deadlineMs;
|
||||
this.startup = builder.startup;
|
||||
this.customData = builder.customData;
|
||||
this.customDataList = builder.customDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -618,9 +632,7 @@ public final class CmcdHeadersFactory {
|
||||
if (startup) {
|
||||
headerValueList.add(CmcdConfiguration.KEY_STARTUP);
|
||||
}
|
||||
if (!TextUtils.isEmpty(customData)) {
|
||||
headerValueList.add(customData);
|
||||
}
|
||||
headerValueList.addAll(customDataList);
|
||||
|
||||
if (!headerValueList.isEmpty()) {
|
||||
httpRequestHeaders.put(
|
||||
@ -642,7 +654,12 @@ public final class CmcdHeadersFactory {
|
||||
@Nullable private @StreamingFormat String streamingFormat;
|
||||
@Nullable private @StreamType String streamType;
|
||||
private float playbackRate;
|
||||
@Nullable private String customData;
|
||||
private ImmutableList<String> customDataList;
|
||||
|
||||
/** Creates a new instance with default values. */
|
||||
public Builder() {
|
||||
this.customDataList = ImmutableList.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link CmcdSession#contentId}. Maximum length allowed is 64 characters. The
|
||||
@ -699,10 +716,10 @@ public final class CmcdHeadersFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdSession#customData}. The default value is {@code null}. */
|
||||
/** Sets the {@link CmcdSession#customDataList}. The default value is an empty list. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setCustomData(@Nullable String customData) {
|
||||
this.customData = customData;
|
||||
public Builder setCustomDataList(List<String> customDataList) {
|
||||
this.customDataList = ImmutableList.copyOf(customDataList);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -752,14 +769,8 @@ public final class CmcdHeadersFactory {
|
||||
*/
|
||||
public final float playbackRate;
|
||||
|
||||
/**
|
||||
* Custom data where the values of the keys are expected to be invariant over the life of the
|
||||
* session, or {@code null} if unset.
|
||||
*
|
||||
* <p>The String consists of key-value pairs separated by commas.<br>
|
||||
* Example: {@code key1=intValue, key2="stringValue"}.
|
||||
*/
|
||||
@Nullable public final String customData;
|
||||
/** Custom data that is expected to be invariant over the life of the session. */
|
||||
public final ImmutableList<String> customDataList;
|
||||
|
||||
private CmcdSession(Builder builder) {
|
||||
this.contentId = builder.contentId;
|
||||
@ -767,7 +778,7 @@ public final class CmcdHeadersFactory {
|
||||
this.streamingFormat = builder.streamingFormat;
|
||||
this.streamType = builder.streamType;
|
||||
this.playbackRate = builder.playbackRate;
|
||||
this.customData = builder.customData;
|
||||
this.customDataList = builder.customDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -800,9 +811,7 @@ public final class CmcdHeadersFactory {
|
||||
if (VERSION != 1) {
|
||||
headerValueList.add(CmcdConfiguration.KEY_VERSION + "=" + VERSION);
|
||||
}
|
||||
if (!TextUtils.isEmpty(customData)) {
|
||||
headerValueList.add(customData);
|
||||
}
|
||||
headerValueList.addAll(customDataList);
|
||||
|
||||
if (!headerValueList.isEmpty()) {
|
||||
httpRequestHeaders.put(
|
||||
@ -821,11 +830,12 @@ public final class CmcdHeadersFactory {
|
||||
public static final class Builder {
|
||||
private int maximumRequestedThroughputKbps;
|
||||
private boolean bufferStarvation;
|
||||
@Nullable private String customData;
|
||||
private ImmutableList<String> customDataList;
|
||||
|
||||
/** Creates a new instance with default values. */
|
||||
public Builder() {
|
||||
this.maximumRequestedThroughputKbps = C.RATE_UNSET_INT;
|
||||
this.customDataList = ImmutableList.of();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -856,10 +866,10 @@ public final class CmcdHeadersFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdStatus#customData}. The default value is {@code null}. */
|
||||
/** Sets the {@link CmcdStatus#customDataList}. The default value is an empty list. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setCustomData(@Nullable String customData) {
|
||||
this.customData = customData;
|
||||
public Builder setCustomDataList(List<String> customDataList) {
|
||||
this.customDataList = ImmutableList.copyOf(customDataList);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -882,19 +892,13 @@ public final class CmcdHeadersFactory {
|
||||
*/
|
||||
public final boolean bufferStarvation;
|
||||
|
||||
/**
|
||||
* Custom data where the values of the keys do not vary with every request or object, or {@code
|
||||
* null} if unset.
|
||||
*
|
||||
* <p>The String consists of key-value pairs separated by commas.<br>
|
||||
* Example: {@code key1=intValue, key2="stringValue"}.
|
||||
*/
|
||||
@Nullable public final String customData;
|
||||
/** Custom data that do not vary with every request or object. */
|
||||
public final ImmutableList<String> customDataList;
|
||||
|
||||
private CmcdStatus(Builder builder) {
|
||||
this.maximumRequestedThroughputKbps = builder.maximumRequestedThroughputKbps;
|
||||
this.bufferStarvation = builder.bufferStarvation;
|
||||
this.customData = builder.customData;
|
||||
this.customDataList = builder.customDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -913,9 +917,7 @@ public final class CmcdHeadersFactory {
|
||||
if (bufferStarvation) {
|
||||
headerValueList.add(CmcdConfiguration.KEY_BUFFER_STARVATION);
|
||||
}
|
||||
if (!TextUtils.isEmpty(customData)) {
|
||||
headerValueList.add(customData);
|
||||
}
|
||||
headerValueList.addAll(customDataList);
|
||||
|
||||
if (!headerValueList.isEmpty()) {
|
||||
httpRequestHeaders.put(
|
||||
|
@ -21,7 +21,7 @@ import static org.junit.Assert.assertThrows;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@ -97,11 +97,12 @@ public class CmcdConfigurationTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> getCustomData() {
|
||||
return new ImmutableMap.Builder<String, String>()
|
||||
.put("CMCD-Object", "key1=value1")
|
||||
.put("CMCD-Request", "key2=\"stringValue\"")
|
||||
.buildOrThrow();
|
||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||
getCustomData() {
|
||||
return new ImmutableListMultimap.Builder<String, String>()
|
||||
.putAll("CMCD-Object", "key-1=1", "key-2=2")
|
||||
.put("CMCD-Request", "key-3=\"stringValue1,stringValue2\"")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -121,8 +122,13 @@ public class CmcdConfigurationTest {
|
||||
assertThat(cmcdConfiguration.isContentIdLoggingAllowed()).isFalse();
|
||||
assertThat(cmcdConfiguration.isSessionIdLoggingAllowed()).isFalse();
|
||||
assertThat(cmcdConfiguration.isMaximumRequestThroughputLoggingAllowed()).isTrue();
|
||||
assertThat(cmcdConfiguration.requestConfig.getCustomData().keySet()).hasSize(2);
|
||||
assertThat(cmcdConfiguration.requestConfig.getCustomData())
|
||||
.containsExactly("CMCD-Object", "key1=value1", "CMCD-Request", "key2=\"stringValue\"");
|
||||
.valuesForKey(CmcdConfiguration.KEY_CMCD_OBJECT)
|
||||
.containsExactly("key-1=1", "key-2=2");
|
||||
assertThat(cmcdConfiguration.requestConfig.getCustomData())
|
||||
.valuesForKey(CmcdConfiguration.KEY_CMCD_REQUEST)
|
||||
.containsExactly("key-3=\"stringValue1,stringValue2\"");
|
||||
assertThat(
|
||||
cmcdConfiguration.requestConfig.getRequestedMaximumThroughputKbps(
|
||||
/* throughputKbps= */ 100))
|
||||
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.exoplayer.upstream;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ -24,6 +25,7 @@ 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.ImmutableListMultimap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@ -41,11 +43,13 @@ public class CmcdHeadersFactoryTest {
|
||||
mediaItem.mediaId,
|
||||
new CmcdConfiguration.RequestConfig() {
|
||||
@Override
|
||||
public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> getCustomData() {
|
||||
return new ImmutableMap.Builder<String, String>()
|
||||
.put("CMCD-Object", "key1=value1")
|
||||
.put("CMCD-Request", "key2=\"stringValue\"")
|
||||
.buildOrThrow();
|
||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||
getCustomData() {
|
||||
return new ImmutableListMultimap.Builder<String, String>()
|
||||
.putAll("CMCD-Object", "key-1=1", "key-2-separated-by-multiple-hyphens=2")
|
||||
.put("CMCD-Request", "key-3=\"stringValue1,stringValue2\"")
|
||||
.put("CMCD-Status", "key-4=\"stringValue3=stringValue4\"")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -79,12 +83,47 @@ public class CmcdHeadersFactoryTest {
|
||||
assertThat(requestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=840,tb=1000,d=3000,key1=value1",
|
||||
"br=840,tb=1000,d=3000,key-1=1,key-2-separated-by-multiple-hyphens=2",
|
||||
"CMCD-Request",
|
||||
"bl=1800,mtp=500,dl=900,su,key2=\"stringValue\"",
|
||||
"bl=1800,mtp=500,dl=900,su,key-3=\"stringValue1,stringValue2\"",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"sessionId\",sf=d,st=l,pr=2.00",
|
||||
"CMCD-Status",
|
||||
"rtp=1700,bs");
|
||||
"rtp=1700,bs,key-4=\"stringValue3=stringValue4\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInstance_withInvalidNonHyphenatedCustomKey_throwsIllegalStateException() {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||
mediaItem ->
|
||||
new CmcdConfiguration(
|
||||
null,
|
||||
null,
|
||||
new CmcdConfiguration.RequestConfig() {
|
||||
@Override
|
||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||
getCustomData() {
|
||||
return ImmutableListMultimap.of("CMCD-Object", "key1=1");
|
||||
}
|
||||
});
|
||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
ExoTrackSelection trackSelection = mock(ExoTrackSelection.class);
|
||||
when(trackSelection.getSelectedFormat()).thenReturn(new Format.Builder().build());
|
||||
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
() ->
|
||||
new CmcdHeadersFactory(
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
/* bufferedDurationUs= */ 0,
|
||||
/* playbackRate= */ 1.0f,
|
||||
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_DASH,
|
||||
/* isLive= */ true,
|
||||
/* didRebuffer= */ true,
|
||||
/* isBufferEmpty= */ false)
|
||||
.createHttpRequestHeaders());
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ import androidx.media3.test.utils.TestUtil;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.io.IOException;
|
||||
@ -435,13 +436,15 @@ public class DefaultDashChunkSourceTest {
|
||||
CmcdConfiguration.RequestConfig cmcdRequestConfig =
|
||||
new CmcdConfiguration.RequestConfig() {
|
||||
@Override
|
||||
public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> getCustomData() {
|
||||
return new ImmutableMap.Builder<@CmcdConfiguration.HeaderKey String, String>()
|
||||
.put(CmcdConfiguration.KEY_CMCD_OBJECT, "key1=value1")
|
||||
.put(CmcdConfiguration.KEY_CMCD_REQUEST, "key2=\"stringValue\"")
|
||||
.put(CmcdConfiguration.KEY_CMCD_SESSION, "key3=1")
|
||||
.put(CmcdConfiguration.KEY_CMCD_STATUS, "key4=5.0")
|
||||
.buildOrThrow();
|
||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||
getCustomData() {
|
||||
return new ImmutableListMultimap.Builder<
|
||||
@CmcdConfiguration.HeaderKey String, String>()
|
||||
.put(CmcdConfiguration.KEY_CMCD_OBJECT, "key-1=1")
|
||||
.put(CmcdConfiguration.KEY_CMCD_REQUEST, "key-2=\"stringValue\"")
|
||||
.put(CmcdConfiguration.KEY_CMCD_SESSION, "key-3=3")
|
||||
.put(CmcdConfiguration.KEY_CMCD_STATUS, "key-4=5.0")
|
||||
.build();
|
||||
}
|
||||
};
|
||||
|
||||
@ -463,13 +466,13 @@ public class DefaultDashChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=700,tb=1300,d=4000,ot=v,key1=value1",
|
||||
"br=700,tb=1300,d=4000,ot=v,key-1=1",
|
||||
"CMCD-Request",
|
||||
"bl=0,mtp=1000,dl=0,su,key2=\"stringValue\"",
|
||||
"bl=0,mtp=1000,dl=0,su,key-2=\"stringValue\"",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=d,st=v,key3=1",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=d,st=v,key-3=3",
|
||||
"CMCD-Status",
|
||||
"key4=5.0");
|
||||
"key-4=5.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -40,7 +40,7 @@ import androidx.media3.test.utils.TestUtil;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
@ -345,13 +345,15 @@ public class HlsChunkSourceTest {
|
||||
CmcdConfiguration.RequestConfig cmcdRequestConfig =
|
||||
new CmcdConfiguration.RequestConfig() {
|
||||
@Override
|
||||
public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> getCustomData() {
|
||||
return new ImmutableMap.Builder<@CmcdConfiguration.HeaderKey String, String>()
|
||||
.put(CmcdConfiguration.KEY_CMCD_OBJECT, "key1=value1")
|
||||
.put(CmcdConfiguration.KEY_CMCD_REQUEST, "key2=\"stringValue\"")
|
||||
.put(CmcdConfiguration.KEY_CMCD_SESSION, "key3=1")
|
||||
.put(CmcdConfiguration.KEY_CMCD_STATUS, "key4=5.0")
|
||||
.buildOrThrow();
|
||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||
getCustomData() {
|
||||
return new ImmutableListMultimap.Builder<
|
||||
@CmcdConfiguration.HeaderKey String, String>()
|
||||
.put(CmcdConfiguration.KEY_CMCD_OBJECT, "key-1=1")
|
||||
.put(CmcdConfiguration.KEY_CMCD_REQUEST, "key-2=\"stringValue\"")
|
||||
.put(CmcdConfiguration.KEY_CMCD_SESSION, "key-3=3")
|
||||
.put(CmcdConfiguration.KEY_CMCD_STATUS, "key-4=5.0")
|
||||
.build();
|
||||
}
|
||||
};
|
||||
|
||||
@ -374,13 +376,13 @@ public class HlsChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=800,tb=800,d=4000,ot=v,key1=value1",
|
||||
"br=800,tb=800,d=4000,ot=v,key-1=1",
|
||||
"CMCD-Request",
|
||||
"bl=0,dl=0,su,key2=\"stringValue\"",
|
||||
"bl=0,dl=0,su,key-2=\"stringValue\"",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=h,st=v,key3=1",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=h,st=v,key-3=3",
|
||||
"CMCD-Status",
|
||||
"key4=5.0");
|
||||
"key-4=5.0");
|
||||
}
|
||||
|
||||
private HlsChunkSource createHlsChunkSource(@Nullable CmcdConfiguration cmcdConfiguration) {
|
||||
|
@ -38,7 +38,7 @@ import androidx.media3.test.utils.TestUtil;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import org.junit.Test;
|
||||
@ -186,13 +186,15 @@ public class DefaultSsChunkSourceTest {
|
||||
CmcdConfiguration.RequestConfig cmcdRequestConfig =
|
||||
new CmcdConfiguration.RequestConfig() {
|
||||
@Override
|
||||
public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> getCustomData() {
|
||||
return new ImmutableMap.Builder<@CmcdConfiguration.HeaderKey String, String>()
|
||||
.put(CmcdConfiguration.KEY_CMCD_OBJECT, "key1=value1")
|
||||
.put(CmcdConfiguration.KEY_CMCD_REQUEST, "key2=\"stringValue\"")
|
||||
.put(CmcdConfiguration.KEY_CMCD_SESSION, "key3=1")
|
||||
.put(CmcdConfiguration.KEY_CMCD_STATUS, "key4=5.0")
|
||||
.buildOrThrow();
|
||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||
getCustomData() {
|
||||
return new ImmutableListMultimap.Builder<
|
||||
@CmcdConfiguration.HeaderKey String, String>()
|
||||
.put(CmcdConfiguration.KEY_CMCD_OBJECT, "key-1=1")
|
||||
.put(CmcdConfiguration.KEY_CMCD_REQUEST, "key-2=\"stringValue\"")
|
||||
.put(CmcdConfiguration.KEY_CMCD_SESSION, "key-3=3")
|
||||
.put(CmcdConfiguration.KEY_CMCD_STATUS, "key-4=5.0")
|
||||
.build();
|
||||
}
|
||||
};
|
||||
|
||||
@ -214,13 +216,13 @@ public class DefaultSsChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=308,tb=1536,d=1968,ot=v,key1=value1",
|
||||
"br=308,tb=1536,d=1968,ot=v,key-1=1",
|
||||
"CMCD-Request",
|
||||
"bl=0,mtp=1000,dl=0,su,key2=\"stringValue\"",
|
||||
"bl=0,mtp=1000,dl=0,su,key-2=\"stringValue\"",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=s,st=v,key3=1",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=s,st=v,key-3=3",
|
||||
"CMCD-Status",
|
||||
"key4=5.0");
|
||||
"key-4=5.0");
|
||||
}
|
||||
|
||||
private SsChunkSource createSsChunkSource(
|
||||
|
Loading…
x
Reference in New Issue
Block a user