Add buffer starvation(bs), deadline(dl), playback rate(pr), startup(su)
Enhanced the Common Media Client Data (CMCD) logging by incorporating additional fields: * buffer starvation (bs) : CMCD-Status * deadline (dl) and startup (su) : CMCD-Request * playback rate (pr) : CMCD-Session PiperOrigin-RevId: 555553357
This commit is contained in:
parent
c1913e8d89
commit
4282a6ecd7
@ -52,6 +52,9 @@
|
||||
* Enhance `ChunkSource.getNextChunk(long, long, List, ChunkHolder)` method
|
||||
in the `ChunkSource` interface to `ChunkSource.getNextChunk(LoadingInfo,
|
||||
long, List, ChunkHolder)`.
|
||||
* Add additional fields to Common Media Client Data (CMCD) logging: buffer
|
||||
starvation (`bs`), deadline (`dl`), playback rate (`pr`) and startup
|
||||
(`su`) ([#8699](https://github.com/google/ExoPlayer/issues/8699)).
|
||||
* Transformer:
|
||||
* Parse EXIF rotation data for image inputs.
|
||||
* Remove `TransformationRequest.HdrMode` annotation type and its
|
||||
|
@ -120,4 +120,17 @@ public final class LoadingInfo {
|
||||
public LoadingInfo.Builder buildUpon() {
|
||||
return new LoadingInfo.Builder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if rebuffering has occurred since {@code realtimeMs}.
|
||||
*
|
||||
* @param realtimeMs The time to compare against, as measured by {@link
|
||||
* SystemClock#elapsedRealtime()}.
|
||||
* @return Whether rebuffering has occurred since the provided timestamp.
|
||||
*/
|
||||
public boolean rebufferedSince(long realtimeMs) {
|
||||
return lastRebufferRealtimeMs != C.TIME_UNSET
|
||||
&& realtimeMs != C.TIME_UNSET
|
||||
&& lastRebufferRealtimeMs >= realtimeMs;
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,11 @@ public final class CmcdConfiguration {
|
||||
KEY_TOP_BITRATE,
|
||||
KEY_OBJECT_DURATION,
|
||||
KEY_MEASURED_THROUGHPUT,
|
||||
KEY_OBJECT_TYPE
|
||||
KEY_OBJECT_TYPE,
|
||||
KEY_BUFFER_STARVATION,
|
||||
KEY_DEADLINE,
|
||||
KEY_PLAYBACK_RATE,
|
||||
KEY_STARTUP
|
||||
})
|
||||
@Documented
|
||||
@Target(TYPE_USE)
|
||||
@ -92,6 +96,10 @@ public final class CmcdConfiguration {
|
||||
public static final String KEY_OBJECT_DURATION = "d";
|
||||
public static final String KEY_MEASURED_THROUGHPUT = "mtp";
|
||||
public static final String KEY_OBJECT_TYPE = "ot";
|
||||
public static final String KEY_BUFFER_STARVATION = "bs";
|
||||
public static final String KEY_DEADLINE = "dl";
|
||||
public static final String KEY_PLAYBACK_RATE = "pr";
|
||||
public static final String KEY_STARTUP = "su";
|
||||
|
||||
/**
|
||||
* Factory for {@link CmcdConfiguration} instances.
|
||||
@ -301,4 +309,36 @@ public final class CmcdConfiguration {
|
||||
public boolean isObjectTypeLoggingAllowed() {
|
||||
return requestConfig.isKeyAllowed(KEY_OBJECT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether logging buffer starvation is allowed based on the {@linkplain RequestConfig
|
||||
* request configuration}.
|
||||
*/
|
||||
public boolean isBufferStarvationLoggingAllowed() {
|
||||
return requestConfig.isKeyAllowed(KEY_BUFFER_STARVATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether logging deadline is allowed based on the {@linkplain RequestConfig request
|
||||
* configuration}.
|
||||
*/
|
||||
public boolean isDeadlineLoggingAllowed() {
|
||||
return requestConfig.isKeyAllowed(KEY_DEADLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether logging playback rate is allowed based on the {@linkplain RequestConfig request
|
||||
* configuration}.
|
||||
*/
|
||||
public boolean isPlaybackRateLoggingAllowed() {
|
||||
return requestConfig.isKeyAllowed(KEY_PLAYBACK_RATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether logging startup is allowed based on the {@linkplain RequestConfig request
|
||||
* configuration}.
|
||||
*/
|
||||
public boolean isStartupLoggingAllowed() {
|
||||
return requestConfig.isKeyAllowed(KEY_STARTUP);
|
||||
}
|
||||
}
|
||||
|
@ -130,8 +130,11 @@ public final class CmcdHeadersFactory {
|
||||
private final CmcdConfiguration cmcdConfiguration;
|
||||
private final ExoTrackSelection trackSelection;
|
||||
private final long bufferedDurationUs;
|
||||
private final float playbackRate;
|
||||
private final @StreamingFormat String streamingFormat;
|
||||
private final boolean isLive;
|
||||
private final boolean didRebuffer;
|
||||
private final boolean isBufferEmpty;
|
||||
private long chunkDurationUs;
|
||||
private @Nullable @ObjectType String objectType;
|
||||
|
||||
@ -142,24 +145,36 @@ public final class CmcdHeadersFactory {
|
||||
* @param trackSelection The {@linkplain ExoTrackSelection track selection}.
|
||||
* @param bufferedDurationUs The duration of media currently buffered from the current playback
|
||||
* position, in microseconds.
|
||||
* @param playbackRate The playback rate indicating the current speed of playback.
|
||||
* @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}
|
||||
* otherwise.
|
||||
* @param didRebuffer {@code true} if a rebuffering event happened between the previous request
|
||||
* and this one, {@code false} otherwise.
|
||||
* @param isBufferEmpty {@code true} if the queue of buffered chunks is empty, {@code false}
|
||||
* otherwise.
|
||||
* @throws IllegalArgumentException If {@code bufferedDurationUs} is negative.
|
||||
*/
|
||||
public CmcdHeadersFactory(
|
||||
CmcdConfiguration cmcdConfiguration,
|
||||
ExoTrackSelection trackSelection,
|
||||
long bufferedDurationUs,
|
||||
float playbackRate,
|
||||
@StreamingFormat String streamingFormat,
|
||||
boolean isLive) {
|
||||
boolean isLive,
|
||||
boolean didRebuffer,
|
||||
boolean isBufferEmpty) {
|
||||
checkArgument(bufferedDurationUs >= 0);
|
||||
checkArgument(playbackRate > 0);
|
||||
this.cmcdConfiguration = cmcdConfiguration;
|
||||
this.trackSelection = trackSelection;
|
||||
this.bufferedDurationUs = bufferedDurationUs;
|
||||
this.playbackRate = playbackRate;
|
||||
this.streamingFormat = streamingFormat;
|
||||
this.isLive = isLive;
|
||||
this.didRebuffer = didRebuffer;
|
||||
this.isBufferEmpty = isBufferEmpty;
|
||||
this.chunkDurationUs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@ -227,6 +242,12 @@ public final class CmcdHeadersFactory {
|
||||
cmcdRequest.setMeasuredThroughputInKbps(
|
||||
Util.ceilDivide(trackSelection.getLatestBitrateEstimate(), 1000));
|
||||
}
|
||||
if (cmcdConfiguration.isDeadlineLoggingAllowed()) {
|
||||
cmcdRequest.setDeadlineMs(bufferedDurationUs / (long) (playbackRate * 1000));
|
||||
}
|
||||
if (cmcdConfiguration.isStartupLoggingAllowed()) {
|
||||
cmcdRequest.setStartup(didRebuffer || isBufferEmpty);
|
||||
}
|
||||
|
||||
CmcdSession.Builder cmcdSession =
|
||||
new CmcdSession.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_SESSION));
|
||||
@ -242,6 +263,9 @@ public final class CmcdHeadersFactory {
|
||||
if (cmcdConfiguration.isStreamTypeLoggingAllowed()) {
|
||||
cmcdSession.setStreamType(isLive ? STREAM_TYPE_LIVE : STREAM_TYPE_VOD);
|
||||
}
|
||||
if (cmcdConfiguration.isPlaybackRateLoggingAllowed()) {
|
||||
cmcdSession.setPlaybackRate(playbackRate);
|
||||
}
|
||||
|
||||
CmcdStatus.Builder cmcdStatus =
|
||||
new CmcdStatus.Builder().setCustomData(customData.get(CmcdConfiguration.KEY_CMCD_STATUS));
|
||||
@ -249,6 +273,9 @@ public final class CmcdHeadersFactory {
|
||||
cmcdStatus.setMaximumRequestedThroughputKbps(
|
||||
cmcdConfiguration.requestConfig.getRequestedMaximumThroughputKbps(bitrateKbps));
|
||||
}
|
||||
if (cmcdConfiguration.isBufferStarvationLoggingAllowed()) {
|
||||
cmcdStatus.setBufferStarvation(didRebuffer);
|
||||
}
|
||||
|
||||
ImmutableMap.Builder<String, String> httpRequestHeaders = ImmutableMap.builder();
|
||||
cmcdObject.build().populateHttpRequestHeaders(httpRequestHeaders);
|
||||
@ -262,7 +289,10 @@ public final class CmcdHeadersFactory {
|
||||
return objectType != null && objectType.equals(OBJECT_TYPE_INIT_SEGMENT);
|
||||
}
|
||||
|
||||
/** Keys whose values vary with the object being requested. Contains CMCD fields: {@code br}. */
|
||||
/**
|
||||
* Keys whose values vary with the object being requested. Contains CMCD fields: {@code br},
|
||||
* {@code tb}, {@code d} and {@code ot}.
|
||||
*/
|
||||
private static final class CmcdObject {
|
||||
|
||||
/** Builder for {@link CmcdObject} instances. */
|
||||
@ -416,19 +446,25 @@ public final class CmcdHeadersFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/** Keys whose values vary with each request. Contains CMCD fields: {@code bl}. */
|
||||
/**
|
||||
* Keys whose values vary with each request. Contains CMCD fields: {@code bl}, {@code mtp}, {@code
|
||||
* dl} and {@code su}.
|
||||
*/
|
||||
private static final class CmcdRequest {
|
||||
|
||||
/** Builder for {@link CmcdRequest} instances. */
|
||||
public static final class Builder {
|
||||
private long bufferLengthMs;
|
||||
private long measuredThroughputInKbps;
|
||||
private long deadlineMs;
|
||||
private boolean startup;
|
||||
@Nullable private String customData;
|
||||
|
||||
/** Creates a new instance with default values. */
|
||||
public Builder() {
|
||||
this.bufferLengthMs = C.TIME_UNSET;
|
||||
this.measuredThroughputInKbps = Long.MIN_VALUE;
|
||||
this.deadlineMs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -458,6 +494,27 @@ public final class CmcdHeadersFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link CmcdRequest#deadlineMs}. Rounded to nearest 100 ms. The default value is
|
||||
* {@link C#TIME_UNSET}.
|
||||
*
|
||||
* @throws IllegalArgumentException If {@code deadlineMs} is not equal to {@link C#TIME_UNSET}
|
||||
* and is negative.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setDeadlineMs(long deadlineMs) {
|
||||
checkArgument(deadlineMs >= 0 || deadlineMs == C.TIME_UNSET);
|
||||
this.deadlineMs = ((deadlineMs + 50) / 100) * 100;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdRequest#startup}. The default value is {@code false}. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setStartup(boolean startup) {
|
||||
this.startup = startup;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdRequest#customData}. The default value is {@code null}. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setCustomData(@Nullable String customData) {
|
||||
@ -495,6 +552,23 @@ public final class CmcdHeadersFactory {
|
||||
*/
|
||||
public final long measuredThroughputInKbps;
|
||||
|
||||
/**
|
||||
* Deadline in milliseconds from the request time until the first sample of this Segment/Object
|
||||
* needs to be available in order to not create a buffer underrun or any other playback
|
||||
* problems, or {@link C#TIME_UNSET} if unset.
|
||||
*
|
||||
* <p>This value MUST be rounded to the nearest 100 ms. For a playback rate of 1, this may be
|
||||
* equivalent to the player’s remaining buffer length.
|
||||
*/
|
||||
public final long deadlineMs;
|
||||
|
||||
/**
|
||||
* A boolean indicating whether the chunk is needed urgently due to startup, seeking or recovery
|
||||
* after a buffer-empty event, or {@code false} if unknown. The media SHOULD not be rendering
|
||||
* when this request is made.
|
||||
*/
|
||||
public final boolean startup;
|
||||
|
||||
/**
|
||||
* Custom data where the values of the keys vary with each request, or {@code null} if unset.
|
||||
*
|
||||
@ -506,6 +580,8 @@ public final class CmcdHeadersFactory {
|
||||
private CmcdRequest(Builder builder) {
|
||||
this.bufferLengthMs = builder.bufferLengthMs;
|
||||
this.measuredThroughputInKbps = builder.measuredThroughputInKbps;
|
||||
this.deadlineMs = builder.deadlineMs;
|
||||
this.startup = builder.startup;
|
||||
this.customData = builder.customData;
|
||||
}
|
||||
|
||||
@ -527,6 +603,16 @@ public final class CmcdHeadersFactory {
|
||||
Util.formatInvariant(
|
||||
"%s=%d,", CmcdConfiguration.KEY_MEASURED_THROUGHPUT, measuredThroughputInKbps));
|
||||
}
|
||||
if (deadlineMs != C.TIME_UNSET) {
|
||||
headerValue
|
||||
.append(CmcdConfiguration.KEY_DEADLINE)
|
||||
.append("=")
|
||||
.append(deadlineMs)
|
||||
.append(",");
|
||||
}
|
||||
if (startup) {
|
||||
headerValue.append(CmcdConfiguration.KEY_STARTUP).append(",");
|
||||
}
|
||||
if (!TextUtils.isEmpty(customData)) {
|
||||
headerValue.append(Util.formatInvariant("%s,", customData));
|
||||
}
|
||||
@ -542,7 +628,7 @@ public final class CmcdHeadersFactory {
|
||||
|
||||
/**
|
||||
* Keys whose values are expected to be invariant over the life of the session. Contains CMCD
|
||||
* fields: {@code cid} and {@code sid}.
|
||||
* fields: {@code cid}, {@code sid}, {@code sf}, {@code st}, {@code pr} and {@code v}.
|
||||
*/
|
||||
private static final class CmcdSession {
|
||||
|
||||
@ -552,6 +638,7 @@ public final class CmcdHeadersFactory {
|
||||
@Nullable private String sessionId;
|
||||
@Nullable private @StreamingFormat String streamingFormat;
|
||||
@Nullable private @StreamType String streamType;
|
||||
private float playbackRate;
|
||||
@Nullable private String customData;
|
||||
|
||||
/**
|
||||
@ -596,6 +683,13 @@ public final class CmcdHeadersFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdSession#playbackRate}. The default value is {@link C#RATE_UNSET}. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setPlaybackRate(float playbackRate) {
|
||||
this.playbackRate = playbackRate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdSession#customData}. The default value is {@code null}. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setCustomData(@Nullable String customData) {
|
||||
@ -632,10 +726,9 @@ public final class CmcdHeadersFactory {
|
||||
@Nullable public final String sessionId;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* The streaming format that defines the current request, or{@code null} if unset. Must be one
|
||||
* of the allowed stream formats specified by the {@link StreamingFormat} annotation. If the
|
||||
* streaming format being requested is unknown, then this key MUST NOT be used.
|
||||
*/
|
||||
@Nullable public final @StreamingFormat String streamingFormat;
|
||||
|
||||
@ -645,6 +738,11 @@ public final class CmcdHeadersFactory {
|
||||
*/
|
||||
@Nullable public final @StreamType String streamType;
|
||||
|
||||
/**
|
||||
* The playback rate indicating the current rate of playback, or {@link C#RATE_UNSET} if unset.
|
||||
*/
|
||||
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.
|
||||
@ -659,6 +757,7 @@ public final class CmcdHeadersFactory {
|
||||
this.sessionId = builder.sessionId;
|
||||
this.streamingFormat = builder.streamingFormat;
|
||||
this.streamType = builder.streamType;
|
||||
this.playbackRate = builder.playbackRate;
|
||||
this.customData = builder.customData;
|
||||
}
|
||||
|
||||
@ -688,6 +787,10 @@ public final class CmcdHeadersFactory {
|
||||
headerValue.append(
|
||||
Util.formatInvariant("%s=%s,", CmcdConfiguration.KEY_STREAM_TYPE, streamType));
|
||||
}
|
||||
if (playbackRate != C.RATE_UNSET && playbackRate != 1.0f) {
|
||||
headerValue.append(
|
||||
Util.formatInvariant("%s=%.2f,", CmcdConfiguration.KEY_PLAYBACK_RATE, playbackRate));
|
||||
}
|
||||
if (VERSION != 1) {
|
||||
headerValue.append(Util.formatInvariant("%s=%d,", CmcdConfiguration.KEY_VERSION, VERSION));
|
||||
}
|
||||
@ -705,13 +808,15 @@ public final class CmcdHeadersFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Keys whose values do not vary with every request or object. Contains CMCD fields: {@code rtp}.
|
||||
* Keys whose values do not vary with every request or object. Contains CMCD fields: {@code rtp}
|
||||
* and {@code bs}.
|
||||
*/
|
||||
private static final class CmcdStatus {
|
||||
|
||||
/** Builder for {@link CmcdStatus} instances. */
|
||||
public static final class Builder {
|
||||
private int maximumRequestedThroughputKbps;
|
||||
private boolean bufferStarvation;
|
||||
@Nullable private String customData;
|
||||
|
||||
/** Creates a new instance with default values. */
|
||||
@ -740,6 +845,13 @@ public final class CmcdHeadersFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdStatus#bufferStarvation}. The default value is {@code false}. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setBufferStarvation(boolean bufferStarvation) {
|
||||
this.bufferStarvation = bufferStarvation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the {@link CmcdStatus#customData}. The default value is {@code null}. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setCustomData(@Nullable String customData) {
|
||||
@ -759,6 +871,13 @@ public final class CmcdHeadersFactory {
|
||||
*/
|
||||
public final int maximumRequestedThroughputKbps;
|
||||
|
||||
/**
|
||||
* A boolean indicating whether the buffer was starved at some point between the prior request
|
||||
* and this chunk request, resulting in the player being in a rebuffering state and the video or
|
||||
* audio playback being stalled, or {@code false} if unknown.
|
||||
*/
|
||||
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.
|
||||
@ -770,6 +889,7 @@ public final class CmcdHeadersFactory {
|
||||
|
||||
private CmcdStatus(Builder builder) {
|
||||
this.maximumRequestedThroughputKbps = builder.maximumRequestedThroughputKbps;
|
||||
this.bufferStarvation = builder.bufferStarvation;
|
||||
this.customData = builder.customData;
|
||||
}
|
||||
|
||||
@ -788,6 +908,9 @@ public final class CmcdHeadersFactory {
|
||||
"%s=%d,",
|
||||
CmcdConfiguration.KEY_MAXIMUM_REQUESTED_BITRATE, maximumRequestedThroughputKbps));
|
||||
}
|
||||
if (bufferStarvation) {
|
||||
headerValue.append(CmcdConfiguration.KEY_BUFFER_STARVATION).append(",");
|
||||
}
|
||||
if (!TextUtils.isEmpty(customData)) {
|
||||
headerValue.append(Util.formatInvariant("%s,", customData));
|
||||
}
|
||||
|
@ -68,8 +68,11 @@ public class CmcdHeadersFactoryTest {
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
/* bufferedDurationUs= */ 1_760_000,
|
||||
/* playbackRate= */ 2.0f,
|
||||
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_DASH,
|
||||
/* isLive= */ true)
|
||||
/* isLive= */ true,
|
||||
/* didRebuffer= */ true,
|
||||
/* isBufferEmpty= */ false)
|
||||
.setChunkDurationUs(3_000_000)
|
||||
.createHttpRequestHeaders();
|
||||
|
||||
@ -78,10 +81,10 @@ public class CmcdHeadersFactoryTest {
|
||||
"CMCD-Object",
|
||||
"br=840,tb=1000,d=3000,key1=value1",
|
||||
"CMCD-Request",
|
||||
"bl=1800,mtp=500,key2=\"stringValue\"",
|
||||
"bl=1800,mtp=500,dl=900,su,key2=\"stringValue\"",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"sessionId\",sf=d,st=l",
|
||||
"cid=\"mediaId\",sid=\"sessionId\",sf=d,st=l,pr=2.00",
|
||||
"CMCD-Status",
|
||||
"rtp=1700");
|
||||
"rtp=1700,bs");
|
||||
}
|
||||
}
|
||||
|
@ -162,6 +162,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
@Nullable private IOException fatalError;
|
||||
private boolean missingLastSegment;
|
||||
|
||||
/**
|
||||
* The time at which the last {@link #getNextChunk(LoadingInfo, long, List, ChunkHolder)} method
|
||||
* was called, as measured by {@link SystemClock#elapsedRealtime}.
|
||||
*/
|
||||
private long lastChunkRequestRealtimeMs;
|
||||
|
||||
/**
|
||||
* @param chunkExtractorFactory Creates {@link ChunkExtractor} instances to use for extracting
|
||||
* chunks.
|
||||
@ -215,6 +221,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
this.maxSegmentsPerLoad = maxSegmentsPerLoad;
|
||||
this.playerTrackEmsgHandler = playerTrackEmsgHandler;
|
||||
this.cmcdConfiguration = cmcdConfiguration;
|
||||
this.lastChunkRequestRealtimeMs = C.TIME_UNSET;
|
||||
|
||||
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
|
||||
|
||||
@ -371,7 +378,6 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
long availableLiveDurationUs = getAvailableLiveDurationUs(nowUnixTimeUs, playbackPositionUs);
|
||||
trackSelection.updateSelectedTrack(
|
||||
playbackPositionUs, bufferedDurationUs, availableLiveDurationUs, queue, chunkIterators);
|
||||
|
||||
int selectedTrackIndex = trackSelection.getSelectedIndex();
|
||||
|
||||
@Nullable
|
||||
@ -382,8 +388,13 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
bufferedDurationUs,
|
||||
/* playbackRate= */ loadingInfo.playbackSpeed,
|
||||
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_DASH,
|
||||
/* isLive= */ manifest.dynamic);
|
||||
/* isLive= */ manifest.dynamic,
|
||||
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
|
||||
/* isBufferEmpty= */ queue.isEmpty());
|
||||
lastChunkRequestRealtimeMs = SystemClock.elapsedRealtime();
|
||||
|
||||
RepresentationHolder representationHolder = updateSelectedBaseUrl(selectedTrackIndex);
|
||||
if (representationHolder.chunkExtractor != null) {
|
||||
Representation selectedRepresentation = representationHolder.representation;
|
||||
|
@ -310,7 +310,7 @@ public class DefaultDashChunkSourceTest {
|
||||
ChunkHolder output = new ChunkHolder();
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).build(),
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 0,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
output);
|
||||
@ -320,9 +320,63 @@ public class DefaultDashChunkSourceTest {
|
||||
"CMCD-Object",
|
||||
"br=700,tb=1300,d=4000,ot=v",
|
||||
"CMCD-Request",
|
||||
"bl=0,mtp=1000",
|
||||
"bl=0,mtp=1000,dl=0,su",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=d,st=v");
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(3_000_000).setPlaybackSpeed(1.25f).build(),
|
||||
/* loadPositionUs= */ 4_000_000,
|
||||
/* queue= */ ImmutableList.of((MediaChunk) output.chunk),
|
||||
output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=700,tb=1300,d=4000,ot=v",
|
||||
"CMCD-Request",
|
||||
"bl=1000,mtp=1000,dl=800",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=d,st=v,pr=1.25");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNextChunk_chunkSourceWithDefaultCmcdConfiguration_setsCorrectBufferStarvationKey()
|
||||
throws Exception {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory = CmcdConfiguration.Factory.DEFAULT;
|
||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
DashChunkSource chunkSource = createDashChunkSource(/* numberOfTracks= */ 2, cmcdConfiguration);
|
||||
ChunkHolder output = new ChunkHolder();
|
||||
LoadingInfo loadingInfo =
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build();
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
loadingInfo, /* loadPositionUs= */ 0, /* queue= */ ImmutableList.of(), output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders).doesNotContainKey("CMCD-Status");
|
||||
|
||||
loadingInfo =
|
||||
loadingInfo
|
||||
.buildUpon()
|
||||
.setPlaybackPositionUs(2_000_000)
|
||||
.setLastRebufferRealtimeMs(SystemClock.elapsedRealtime())
|
||||
.build();
|
||||
ShadowSystemClock.advanceBy(Duration.ofMillis(100));
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
loadingInfo, /* loadPositionUs= */ 4_000_000, /* queue= */ ImmutableList.of(), output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders).containsEntry("CMCD-Status", "bs");
|
||||
|
||||
loadingInfo = loadingInfo.buildUpon().setPlaybackPositionUs(6_000_000).build();
|
||||
ShadowSystemClock.advanceBy(Duration.ofMillis(100));
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
loadingInfo, /* loadPositionUs= */ 8_000_000, /* queue= */ ImmutableList.of(), output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders).doesNotContainKey("CMCD-Status");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -355,7 +409,7 @@ public class DefaultDashChunkSourceTest {
|
||||
ChunkHolder output = new ChunkHolder();
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).build(),
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 0,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
output);
|
||||
@ -365,7 +419,7 @@ public class DefaultDashChunkSourceTest {
|
||||
"CMCD-Object",
|
||||
"br=700,tb=1300,d=4000,ot=v",
|
||||
"CMCD-Request",
|
||||
"bl=0,mtp=1000",
|
||||
"bl=0,mtp=1000,dl=0,su",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaIdcontentIdSuffix\",sf=d,st=v",
|
||||
"CMCD-Status",
|
||||
@ -401,7 +455,7 @@ public class DefaultDashChunkSourceTest {
|
||||
ChunkHolder output = new ChunkHolder();
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).build(),
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 0,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
output);
|
||||
@ -411,7 +465,7 @@ public class DefaultDashChunkSourceTest {
|
||||
"CMCD-Object",
|
||||
"br=700,tb=1300,d=4000,ot=v,key1=value1",
|
||||
"CMCD-Request",
|
||||
"bl=0,mtp=1000,key2=\"stringValue\"",
|
||||
"bl=0,mtp=1000,dl=0,su,key2=\"stringValue\"",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=d,st=v,key3=1",
|
||||
"CMCD-Status",
|
||||
|
@ -153,6 +153,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private long liveEdgeInPeriodTimeUs;
|
||||
private boolean seenExpectedPlaylistError;
|
||||
|
||||
/**
|
||||
* The time at which the last {@link #getNextChunk(LoadingInfo, long, List, boolean,
|
||||
* HlsChunkHolder)} method was called, as measured by {@link SystemClock#elapsedRealtime}.
|
||||
*/
|
||||
private long lastChunkRequestRealtimeMs;
|
||||
|
||||
/**
|
||||
* @param extractorFactory An {@link HlsExtractorFactory} from which to obtain the extractors for
|
||||
* media chunks.
|
||||
@ -194,6 +200,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
this.muxedCaptionFormats = muxedCaptionFormats;
|
||||
this.playerId = playerId;
|
||||
this.cmcdConfiguration = cmcdConfiguration;
|
||||
this.lastChunkRequestRealtimeMs = C.TIME_UNSET;
|
||||
keyCache = new FullSegmentEncryptionKeyCache(KEY_CACHE_SIZE);
|
||||
scratchSpace = Util.EMPTY_BYTE_ARRAY;
|
||||
liveEdgeInPeriodTimeUs = C.TIME_UNSET;
|
||||
@ -489,12 +496,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
bufferedDurationUs,
|
||||
/* playbackRate= */ loadingInfo.playbackSpeed,
|
||||
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_HLS,
|
||||
/* isLive= */ !playlist.hasEndTag)
|
||||
/* isLive= */ !playlist.hasEndTag,
|
||||
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
|
||||
/* isBufferEmpty= */ queue.isEmpty())
|
||||
.setObjectType(
|
||||
getIsMuxedAudioAndVideo()
|
||||
? CmcdHeadersFactory.OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO
|
||||
: CmcdHeadersFactory.getObjectType(trackSelection));
|
||||
lastChunkRequestRealtimeMs = SystemClock.elapsedRealtime();
|
||||
|
||||
// Check if the media segment or its initialization segment are fully encrypted.
|
||||
@Nullable
|
||||
|
@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
@ -42,11 +43,13 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.robolectric.shadows.ShadowSystemClock;
|
||||
|
||||
/** Unit tests for {@link HlsChunkSource}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@ -202,7 +205,7 @@ public class HlsChunkSourceTest {
|
||||
HlsChunkSource.HlsChunkHolder output = new HlsChunkSource.HlsChunkHolder();
|
||||
|
||||
testChunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).build(),
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 0,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
/* allowEndOfStream= */ true,
|
||||
@ -213,9 +216,76 @@ public class HlsChunkSourceTest {
|
||||
"CMCD-Object",
|
||||
"br=800,tb=800,d=4000,ot=v",
|
||||
"CMCD-Request",
|
||||
"bl=0",
|
||||
"bl=0,dl=0,su",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=h,st=v");
|
||||
|
||||
testChunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(3_000_000).setPlaybackSpeed(1.25f).build(),
|
||||
/* loadPositionUs= */ 4_000_000,
|
||||
/* queue= */ ImmutableList.of((HlsMediaChunk) output.chunk),
|
||||
/* allowEndOfStream= */ true,
|
||||
output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=800,tb=800,d=4000,ot=v",
|
||||
"CMCD-Request",
|
||||
"bl=1000,dl=800",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=h,st=v,pr=1.25");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNextChunk_chunkSourceWithDefaultCmcdConfiguration_setsCorrectBufferStarvationKey()
|
||||
throws Exception {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory = CmcdConfiguration.Factory.DEFAULT;
|
||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
HlsChunkSource testChunkSource = createHlsChunkSource(cmcdConfiguration);
|
||||
HlsChunkSource.HlsChunkHolder output = new HlsChunkSource.HlsChunkHolder();
|
||||
LoadingInfo loadingInfo =
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build();
|
||||
|
||||
testChunkSource.getNextChunk(
|
||||
loadingInfo,
|
||||
/* loadPositionUs= */ 0,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
/* allowEndOfStream= */ true,
|
||||
output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders).doesNotContainKey("CMCD-Status");
|
||||
|
||||
loadingInfo =
|
||||
loadingInfo
|
||||
.buildUpon()
|
||||
.setPlaybackPositionUs(2_000_000)
|
||||
.setLastRebufferRealtimeMs(SystemClock.elapsedRealtime())
|
||||
.build();
|
||||
ShadowSystemClock.advanceBy(Duration.ofMillis(100));
|
||||
|
||||
testChunkSource.getNextChunk(
|
||||
loadingInfo,
|
||||
/* loadPositionUs= */ 4_000_000,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
/* allowEndOfStream= */ true,
|
||||
output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders).containsEntry("CMCD-Status", "bs");
|
||||
|
||||
loadingInfo = loadingInfo.buildUpon().setPlaybackPositionUs(6_000_000).build();
|
||||
ShadowSystemClock.advanceBy(Duration.ofMillis(100));
|
||||
|
||||
testChunkSource.getNextChunk(
|
||||
loadingInfo,
|
||||
/* loadPositionUs= */ 8_000_000,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
/* allowEndOfStream= */ true,
|
||||
output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders).doesNotContainKey("CMCD-Status");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -248,7 +318,7 @@ public class HlsChunkSourceTest {
|
||||
HlsChunkSource.HlsChunkHolder output = new HlsChunkSource.HlsChunkHolder();
|
||||
|
||||
testChunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).build(),
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 0,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
/* allowEndOfStream= */ true,
|
||||
@ -259,7 +329,7 @@ public class HlsChunkSourceTest {
|
||||
"CMCD-Object",
|
||||
"br=800,tb=800,d=4000,ot=v",
|
||||
"CMCD-Request",
|
||||
"bl=0",
|
||||
"bl=0,dl=0,su",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaIdcontentIdSuffix\",sf=h,st=v",
|
||||
"CMCD-Status",
|
||||
@ -295,7 +365,7 @@ public class HlsChunkSourceTest {
|
||||
HlsChunkSource.HlsChunkHolder output = new HlsChunkSource.HlsChunkHolder();
|
||||
|
||||
testChunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).build(),
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 0,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
/* allowEndOfStream= */ true,
|
||||
@ -306,7 +376,7 @@ public class HlsChunkSourceTest {
|
||||
"CMCD-Object",
|
||||
"br=800,tb=800,d=4000,ot=v,key1=value1",
|
||||
"CMCD-Request",
|
||||
"bl=0,key2=\"stringValue\"",
|
||||
"bl=0,dl=0,su,key2=\"stringValue\"",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=h,st=v,key3=1",
|
||||
"CMCD-Status",
|
||||
|
@ -18,6 +18,7 @@ package androidx.media3.exoplayer.smoothstreaming;
|
||||
import static androidx.media3.exoplayer.trackselection.TrackSelectionUtil.createFallbackOptions;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
@ -98,6 +99,12 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
||||
|
||||
@Nullable private IOException fatalError;
|
||||
|
||||
/**
|
||||
* The time at which the last {@link #getNextChunk(LoadingInfo, long, List, ChunkHolder)} method
|
||||
* was called, as measured by {@link SystemClock#elapsedRealtime}.
|
||||
*/
|
||||
private long lastChunkRequestRealtimeMs;
|
||||
|
||||
/**
|
||||
* @param manifestLoaderErrorThrower Throws errors affecting loading of manifests.
|
||||
* @param manifest The initial manifest.
|
||||
@ -119,6 +126,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
||||
this.trackSelection = trackSelection;
|
||||
this.dataSource = dataSource;
|
||||
this.cmcdConfiguration = cmcdConfiguration;
|
||||
this.lastChunkRequestRealtimeMs = C.TIME_UNSET;
|
||||
|
||||
StreamElement streamElement = manifest.streamElements[streamElementIndex];
|
||||
chunkExtractors = new ChunkExtractor[trackSelection.length()];
|
||||
@ -290,10 +298,14 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
bufferedDurationUs,
|
||||
/* playbackRate= */ loadingInfo.playbackSpeed,
|
||||
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_SS,
|
||||
/* isLive= */ manifest.isLive)
|
||||
/* isLive= */ manifest.isLive,
|
||||
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
|
||||
/* isBufferEmpty= */ queue.isEmpty())
|
||||
.setChunkDurationUs(chunkEndTimeUs - chunkStartTimeUs)
|
||||
.setObjectType(CmcdHeadersFactory.getObjectType(trackSelection));
|
||||
lastChunkRequestRealtimeMs = SystemClock.elapsedRealtime();
|
||||
|
||||
out.chunk =
|
||||
newMediaChunk(
|
||||
|
@ -18,6 +18,7 @@ package androidx.media3.exoplayer.smoothstreaming;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MediaItem;
|
||||
@ -27,6 +28,7 @@ import androidx.media3.exoplayer.LoadingInfo;
|
||||
import androidx.media3.exoplayer.smoothstreaming.manifest.SsManifest;
|
||||
import androidx.media3.exoplayer.smoothstreaming.manifest.SsManifestParser;
|
||||
import androidx.media3.exoplayer.source.chunk.ChunkHolder;
|
||||
import androidx.media3.exoplayer.source.chunk.MediaChunk;
|
||||
import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection;
|
||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
@ -38,8 +40,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.shadows.ShadowSystemClock;
|
||||
|
||||
/** Unit test for {@link DefaultSsChunkSource}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@ -57,7 +61,7 @@ public class DefaultSsChunkSourceTest {
|
||||
ChunkHolder output = new ChunkHolder();
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).build(),
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 0,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
output);
|
||||
@ -67,9 +71,63 @@ public class DefaultSsChunkSourceTest {
|
||||
"CMCD-Object",
|
||||
"br=308,tb=1536,d=1968,ot=v",
|
||||
"CMCD-Request",
|
||||
"bl=0,mtp=1000",
|
||||
"bl=0,mtp=1000,dl=0,su",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=s,st=v");
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(3_000_000).setPlaybackSpeed(2.0f).build(),
|
||||
/* loadPositionUs= */ 4_000_000,
|
||||
/* queue= */ ImmutableList.of((MediaChunk) output.chunk),
|
||||
output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=308,tb=1536,d=898,ot=v",
|
||||
"CMCD-Request",
|
||||
"bl=1000,mtp=1000,dl=500",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=s,st=v,pr=2.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNextChunk_chunkSourceWithDefaultCmcdConfiguration_setsCorrectBufferStarvationKey()
|
||||
throws Exception {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory = CmcdConfiguration.Factory.DEFAULT;
|
||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
SsChunkSource chunkSource = createSsChunkSource(/* numberOfTracks= */ 2, cmcdConfiguration);
|
||||
ChunkHolder output = new ChunkHolder();
|
||||
LoadingInfo loadingInfo =
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build();
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
loadingInfo, /* loadPositionUs= */ 0, /* queue= */ ImmutableList.of(), output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders).doesNotContainKey("CMCD-Status");
|
||||
|
||||
loadingInfo =
|
||||
loadingInfo
|
||||
.buildUpon()
|
||||
.setPlaybackPositionUs(2_000_000)
|
||||
.setLastRebufferRealtimeMs(SystemClock.elapsedRealtime())
|
||||
.build();
|
||||
ShadowSystemClock.advanceBy(Duration.ofMillis(100));
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
loadingInfo, /* loadPositionUs= */ 4_000_000, /* queue= */ ImmutableList.of(), output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders).containsEntry("CMCD-Status", "bs");
|
||||
|
||||
loadingInfo = loadingInfo.buildUpon().setPlaybackPositionUs(6_000_000).build();
|
||||
ShadowSystemClock.advanceBy(Duration.ofMillis(100));
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
loadingInfo, /* loadPositionUs= */ 8_000_000, /* queue= */ ImmutableList.of(), output);
|
||||
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders).doesNotContainKey("CMCD-Status");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -102,7 +160,7 @@ public class DefaultSsChunkSourceTest {
|
||||
ChunkHolder output = new ChunkHolder();
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).build(),
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 0,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
output);
|
||||
@ -112,7 +170,7 @@ public class DefaultSsChunkSourceTest {
|
||||
"CMCD-Object",
|
||||
"br=308,tb=1536,d=1968,ot=v",
|
||||
"CMCD-Request",
|
||||
"bl=0,mtp=1000",
|
||||
"bl=0,mtp=1000,dl=0,su",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaIdcontentIdSuffix\",sf=s,st=v",
|
||||
"CMCD-Status",
|
||||
@ -148,7 +206,7 @@ public class DefaultSsChunkSourceTest {
|
||||
ChunkHolder output = new ChunkHolder();
|
||||
|
||||
chunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).build(),
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(0).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 0,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
output);
|
||||
@ -158,7 +216,7 @@ public class DefaultSsChunkSourceTest {
|
||||
"CMCD-Object",
|
||||
"br=308,tb=1536,d=1968,ot=v,key1=value1",
|
||||
"CMCD-Request",
|
||||
"bl=0,mtp=1000,key2=\"stringValue\"",
|
||||
"bl=0,mtp=1000,dl=0,su,key2=\"stringValue\"",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=s,st=v,key3=1",
|
||||
"CMCD-Status",
|
||||
|
Loading…
x
Reference in New Issue
Block a user