Add fields next object request (nor) and next range request (nrr)

Added this CMCD-Request fields to Common Media Client Data (CMCD) logging.

PiperOrigin-RevId: 558317146
This commit is contained in:
rohks 2023-08-19 04:42:59 +01:00 committed by Julia Bibik
parent b6b16b2895
commit e0d3cad8bb
12 changed files with 351 additions and 58 deletions

View File

@ -4,6 +4,9 @@
* Common Library:
* ExoPlayer:
* Add additional fields to Common Media Client Data (CMCD) logging: next
object request (`nor`) and next range request (`nrr`)
([#8699](https://github.com/google/ExoPlayer/issues/8699)).
* Transformer:
* Track Selection:
* Extractors:

View File

@ -15,9 +15,13 @@
*/
package androidx.media3.common.util;
import static java.lang.Math.min;
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.google.common.base.Ascii;
import java.util.List;
/** Utility methods for manipulating URIs. */
@UnstableApi
@ -283,4 +287,55 @@ public final class UriUtil {
indices[FRAGMENT] = fragmentIndex;
return indices;
}
/**
* Calculates the relative path from a base URI to a target URI.
*
* @return The relative path from the base URI to the target URI, or {@code targetUri} if the URIs
* have different schemes or authorities.
*/
@UnstableApi
public static String getRelativePath(Uri baseUri, Uri targetUri) {
if (baseUri.isOpaque() || targetUri.isOpaque()) {
return targetUri.toString();
}
String baseUriScheme = baseUri.getScheme();
String targetUriScheme = targetUri.getScheme();
boolean isSameScheme =
baseUriScheme == null
? targetUriScheme == null
: targetUriScheme != null && Ascii.equalsIgnoreCase(baseUriScheme, targetUriScheme);
if (!isSameScheme || !Util.areEqual(baseUri.getAuthority(), targetUri.getAuthority())) {
// Different schemes or authorities, cannot find relative path, return targetUri.
return targetUri.toString();
}
List<String> basePathSegments = baseUri.getPathSegments();
List<String> targetPathSegments = targetUri.getPathSegments();
int commonPrefixCount = 0;
int minSize = min(basePathSegments.size(), targetPathSegments.size());
for (int i = 0; i < minSize; i++) {
if (!basePathSegments.get(i).equals(targetPathSegments.get(i))) {
break;
}
commonPrefixCount++;
}
StringBuilder relativePath = new StringBuilder();
for (int i = commonPrefixCount; i < basePathSegments.size(); i++) {
relativePath.append("../");
}
for (int i = commonPrefixCount; i < targetPathSegments.size(); i++) {
relativePath.append(targetPathSegments.get(i));
if (i < targetPathSegments.size() - 1) {
relativePath.append("/");
}
}
return relativePath.toString();
}
}

View File

@ -155,4 +155,77 @@ public final class UriUtilTest {
assertThat(UriUtil.isAbsolute("/path/to/file")).isFalse();
assertThat(UriUtil.isAbsolute("path/to/file")).isFalse();
}
@Test
public void getRelativePath_withDifferentSchemes_shouldReturnTargetUri() {
Uri baseUri = Uri.parse("http://uri");
Uri targetUri = Uri.parse("https://uri");
assertThat(UriUtil.getRelativePath(baseUri, targetUri)).isEqualTo(targetUri.toString());
}
@Test
public void getRelativePath_withDifferentAuthorities_shouldReturnTargetUri() {
Uri baseUri = Uri.parse("http://baseUri");
Uri targetUri = Uri.parse("http://targetUri");
assertThat(UriUtil.getRelativePath(baseUri, targetUri)).isEqualTo(targetUri.toString());
}
@Test
public void getRelativePath_withoutSchemesAndDifferentAuthorities_shouldReturnTargetUri() {
Uri baseUri = Uri.parse("//baseUri/a");
Uri targetUri = Uri.parse("//targetUri/b");
assertThat(UriUtil.getRelativePath(baseUri, targetUri)).isEqualTo(targetUri.toString());
}
@Test
public void getRelativePath_withoutSchemesAndSameAuthority_shouldReturnCorrectRelativePath() {
Uri baseUri = Uri.parse("//uri/a");
Uri targetUri = Uri.parse("//uri/b");
assertThat(UriUtil.getRelativePath(baseUri, targetUri)).isEqualTo("../b");
}
@Test
public void
getRelativePath_withoutSchemesAndWithoutAuthorities_shouldReturnCorrectRelativePath() {
Uri baseUri = Uri.parse("a/b/c");
Uri targetUri = Uri.parse("d/e/f");
assertThat(UriUtil.getRelativePath(baseUri, targetUri)).isEqualTo("../../../d/e/f");
}
@Test
public void getRelativePath_withEqualPathSegmentsLength_shouldReturnCorrectRelativePath() {
Uri baseUri = Uri.parse("http://uri/a/b/c");
Uri targetUri = Uri.parse("http://uri/d/e/f");
assertThat(UriUtil.getRelativePath(baseUri, targetUri)).isEqualTo("../../../d/e/f");
}
@Test
public void getRelativePath_withUnEqualPathSegmentsLength_shouldReturnCorrectRelativePath() {
Uri baseUri = Uri.parse("http://uri/a/b/c");
Uri targetUri = Uri.parse("http://uri/a/b/d/e/f");
assertThat(UriUtil.getRelativePath(baseUri, targetUri)).isEqualTo("../d/e/f");
}
@Test
public void getRelativePath_withEqualUris_shouldReturnEmptyString() {
Uri baseUri = Uri.parse("http://uri/a/b/c");
Uri targetUri = Uri.parse("http://uri/a/b/c");
assertThat(UriUtil.getRelativePath(baseUri, targetUri)).isEmpty();
}
@Test
public void getRelativePath_nonHierarchicalUris_shouldReturnCorrectRelativePath() {
Uri baseUri = Uri.parse("schema:a@b");
Uri targetUri = Uri.parse("schema:a@c");
assertThat(UriUtil.getRelativePath(baseUri, targetUri)).isEqualTo(targetUri.toString());
}
}

View File

@ -71,7 +71,9 @@ public final class CmcdConfiguration {
KEY_BUFFER_STARVATION,
KEY_DEADLINE,
KEY_PLAYBACK_RATE,
KEY_STARTUP
KEY_STARTUP,
KEY_NEXT_OBJECT_REQUEST,
KEY_NEXT_RANGE_REQUEST
})
@Documented
@Target(TYPE_USE)
@ -100,6 +102,8 @@ public final class CmcdConfiguration {
public static final String KEY_DEADLINE = "dl";
public static final String KEY_PLAYBACK_RATE = "pr";
public static final String KEY_STARTUP = "su";
public static final String KEY_NEXT_OBJECT_REQUEST = "nor";
public static final String KEY_NEXT_RANGE_REQUEST = "nrr";
/**
* Factory for {@link CmcdConfiguration} instances.
@ -356,4 +360,20 @@ public final class CmcdConfiguration {
public boolean isStartupLoggingAllowed() {
return requestConfig.isKeyAllowed(KEY_STARTUP);
}
/**
* Returns whether logging next object request is allowed based on the {@linkplain RequestConfig
* request configuration}.
*/
public boolean isNextObjectRequestLoggingAllowed() {
return requestConfig.isKeyAllowed(KEY_NEXT_OBJECT_REQUEST);
}
/**
* Returns whether logging next range request is allowed based on the {@linkplain RequestConfig
* request configuration}.
*/
public boolean isNextRangeRequestLoggingAllowed() {
return requestConfig.isKeyAllowed(KEY_NEXT_RANGE_REQUEST);
}
}

View File

@ -20,6 +20,7 @@ import static androidx.media3.common.util.Assertions.checkState;
import static java.lang.Math.max;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.annotation.StringDef;
@ -149,6 +150,8 @@ public final class CmcdHeadersFactory {
private final boolean isBufferEmpty;
private long chunkDurationUs;
private @Nullable @ObjectType String objectType;
@Nullable private String nextObjectRequest;
@Nullable private String nextRangeRequest;
/**
* Creates an instance.
@ -215,6 +218,30 @@ public final class CmcdHeadersFactory {
return this;
}
/**
* Sets the relative path of the next object to be requested. This can be used to trigger
* pre-fetching by the CDN.
*
* <p>Default is {@code null}.
*/
@CanIgnoreReturnValue
public CmcdHeadersFactory setNextObjectRequest(@Nullable String nextObjectRequest) {
this.nextObjectRequest = nextObjectRequest;
return this;
}
/**
* Sets the byte range representing the partial object request. This can be used to trigger
* pre-fetching by the CDN.
*
* <p>Default is {@code null}.
*/
@CanIgnoreReturnValue
public CmcdHeadersFactory setNextRangeRequest(@Nullable String nextRangeRequest) {
this.nextRangeRequest = nextRangeRequest;
return this;
}
/** Creates and returns a new {@link ImmutableMap} containing the CMCD HTTP request headers. */
public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> createHttpRequestHeaders() {
ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String> customData =
@ -264,6 +291,12 @@ public final class CmcdHeadersFactory {
if (cmcdConfiguration.isStartupLoggingAllowed()) {
cmcdRequest.setStartup(didRebuffer || isBufferEmpty);
}
if (cmcdConfiguration.isNextObjectRequestLoggingAllowed()) {
cmcdRequest.setNextObjectRequest(nextObjectRequest);
}
if (cmcdConfiguration.isNextRangeRequestLoggingAllowed()) {
cmcdRequest.setNextRangeRequest(nextRangeRequest);
}
if (customData.containsKey(CmcdConfiguration.KEY_CMCD_REQUEST)) {
cmcdRequest.setCustomDataList(customData.get(CmcdConfiguration.KEY_CMCD_REQUEST));
}
@ -476,7 +509,7 @@ public final class CmcdHeadersFactory {
/**
* Keys whose values vary with each request. Contains CMCD fields: {@code bl}, {@code mtp}, {@code
* dl} and {@code su}.
* dl}, {@code su}, {@code nor} and {@code nrr}.
*/
private static final class CmcdRequest {
@ -486,6 +519,8 @@ public final class CmcdHeadersFactory {
private long measuredThroughputInKbps;
private long deadlineMs;
private boolean startup;
@Nullable private String nextObjectRequest;
@Nullable private String nextRangeRequest;
private ImmutableList<String> customDataList;
/** Creates a new instance with default values. */
@ -547,6 +582,23 @@ public final class CmcdHeadersFactory {
return this;
}
/**
* Sets the {@link CmcdRequest#nextObjectRequest}. This string is URL encoded. The default
* value is {@code null}.
*/
@CanIgnoreReturnValue
public Builder setNextObjectRequest(@Nullable String nextObjectRequest) {
this.nextObjectRequest = nextObjectRequest == null ? null : Uri.encode(nextObjectRequest);
return this;
}
/** Sets the {@link CmcdRequest#nextRangeRequest}. The default value is {@code null}. */
@CanIgnoreReturnValue
public Builder setNextRangeRequest(@Nullable String nextRangeRequest) {
this.nextRangeRequest = nextRangeRequest;
return this;
}
/** Sets the {@link CmcdRequest#customDataList}. The default value is an empty list. */
@CanIgnoreReturnValue
public Builder setCustomDataList(List<String> customDataList) {
@ -601,6 +653,27 @@ public final class CmcdHeadersFactory {
*/
public final boolean startup;
/**
* Relative path of the next object to be requested, or {@code null} if unset. This can be used
* to trigger pre-fetching by the CDN. This MUST be a path relative to the current request.
*
* <p>This string MUST be URL encoded.
*
* <p><b>Note:</b> The client SHOULD NOT depend upon any pre-fetch action being taken - it is
* merely a request for such a pre-fetch to take place.
*/
@Nullable public final String nextObjectRequest;
/**
* The byte range representing the partial object request, or {@code null} if unset. If the
* {@link #nextObjectRequest} field is not set, then the object is assumed to match the object
* currently being requested.
*
* <p><b>Note:</b> The client SHOULD NOT depend upon any pre-fetch action being taken - it is
* merely a request for such a pre-fetch to take place.
*/
@Nullable public final String nextRangeRequest;
/** Custom data that vary with each request. */
public final ImmutableList<String> customDataList;
@ -609,6 +682,8 @@ public final class CmcdHeadersFactory {
this.measuredThroughputInKbps = builder.measuredThroughputInKbps;
this.deadlineMs = builder.deadlineMs;
this.startup = builder.startup;
this.nextObjectRequest = builder.nextObjectRequest;
this.nextRangeRequest = builder.nextRangeRequest;
this.customDataList = builder.customDataList;
}
@ -634,6 +709,16 @@ public final class CmcdHeadersFactory {
if (startup) {
headerValueList.add(CmcdConfiguration.KEY_STARTUP);
}
if (!TextUtils.isEmpty(nextObjectRequest)) {
headerValueList.add(
Util.formatInvariant(
"%s=\"%s\"", CmcdConfiguration.KEY_NEXT_OBJECT_REQUEST, nextObjectRequest));
}
if (!TextUtils.isEmpty(nextRangeRequest)) {
headerValueList.add(
Util.formatInvariant(
"%s=\"%s\"", CmcdConfiguration.KEY_NEXT_RANGE_REQUEST, nextRangeRequest));
}
headerValueList.addAll(customDataList);
if (!headerValueList.isEmpty()) {

View File

@ -20,11 +20,13 @@ import static java.lang.Math.min;
import android.net.Uri;
import android.os.SystemClock;
import android.util.Pair;
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.UriUtil;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSpec;
@ -729,12 +731,17 @@ public class DefaultDashChunkSource implements DashChunkSource {
? 0
: DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED;
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
cmcdHeadersFactory == null
? ImmutableMap.of()
: cmcdHeadersFactory
.setChunkDurationUs(endTimeUs - startTimeUs)
.setObjectType(CmcdHeadersFactory.getObjectType(trackSelection))
.createHttpRequestHeaders();
ImmutableMap.of();
if (cmcdHeadersFactory != null) {
Pair<String, String> nextObjectAndRangeRequest =
getNextObjectAndRangeRequest(firstSegmentNum, segmentUri, representationHolder);
cmcdHeadersFactory
.setChunkDurationUs(endTimeUs - startTimeUs)
.setObjectType(CmcdHeadersFactory.getObjectType(trackSelection))
.setNextObjectRequest(nextObjectAndRangeRequest.first)
.setNextRangeRequest(nextObjectAndRangeRequest.second);
httpRequestHeaders = cmcdHeadersFactory.createHttpRequestHeaders();
}
DataSpec dataSpec =
DashUtil.buildDataSpec(
representation,
@ -779,12 +786,17 @@ public class DefaultDashChunkSource implements DashChunkSource {
? 0
: DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED;
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
cmcdHeadersFactory == null
? ImmutableMap.of()
: cmcdHeadersFactory
.setChunkDurationUs(endTimeUs - startTimeUs)
.setObjectType(CmcdHeadersFactory.getObjectType(trackSelection))
.createHttpRequestHeaders();
ImmutableMap.of();
if (cmcdHeadersFactory != null) {
Pair<String, String> nextObjectAndRangeRequest =
getNextObjectAndRangeRequest(segmentNum, segmentUri, representationHolder);
cmcdHeadersFactory
.setChunkDurationUs(endTimeUs - startTimeUs)
.setObjectType(CmcdHeadersFactory.getObjectType(trackSelection))
.setNextObjectRequest(nextObjectAndRangeRequest.first)
.setNextRangeRequest(nextObjectAndRangeRequest.second);
httpRequestHeaders = cmcdHeadersFactory.createHttpRequestHeaders();
}
DataSpec dataSpec =
DashUtil.buildDataSpec(
representation,
@ -810,6 +822,23 @@ public class DefaultDashChunkSource implements DashChunkSource {
}
}
private Pair<String, String> getNextObjectAndRangeRequest(
long segmentNum, RangedUri segmentUri, RepresentationHolder representationHolder) {
if (segmentNum + 1 < representationHolder.getSegmentCount()) {
RangedUri nextSegmentUri = representationHolder.getSegmentUrl(segmentNum + 1);
Uri uri = segmentUri.resolveUri(representationHolder.selectedBaseUrl.url);
Uri nextUri = nextSegmentUri.resolveUri(representationHolder.selectedBaseUrl.url);
String nextObjectRequest = UriUtil.getRelativePath(uri, nextUri);
String nextRangeRequest = nextSegmentUri.start + "-";
if (nextSegmentUri.length != C.LENGTH_UNSET) {
nextRangeRequest += (nextSegmentUri.start + nextSegmentUri.length);
}
return new Pair<>(nextObjectRequest, nextRangeRequest);
}
return new Pair<>(null, null);
}
private RepresentationHolder updateSelectedBaseUrl(int trackIndex) {
RepresentationHolder representationHolder = representationHolders[trackIndex];
@Nullable

View File

@ -321,7 +321,7 @@ public class DefaultDashChunkSourceTest {
"CMCD-Object",
"br=700,d=4000,ot=v,tb=1300",
"CMCD-Request",
"bl=0,dl=0,mtp=1000,su",
"bl=0,dl=0,mtp=1000,nor=\"..%2Fvideo_4000_700000.m4s\",nrr=\"0-\",su",
"CMCD-Session",
"cid=\"mediaId\",sf=d,sid=\"" + cmcdConfiguration.sessionId + "\",st=v");
@ -336,7 +336,7 @@ public class DefaultDashChunkSourceTest {
"CMCD-Object",
"br=700,d=4000,ot=v,tb=1300",
"CMCD-Request",
"bl=1000,dl=800,mtp=1000",
"bl=1000,dl=800,mtp=1000,nor=\"..%2Fvideo_8000_700000.m4s\",nrr=\"0-\"",
"CMCD-Session",
"cid=\"mediaId\",pr=1.25,sf=d,sid=\"" + cmcdConfiguration.sessionId + "\",st=v");
}
@ -420,7 +420,7 @@ public class DefaultDashChunkSourceTest {
"CMCD-Object",
"br=700,d=4000,ot=v,tb=1300",
"CMCD-Request",
"bl=0,dl=0,mtp=1000,su",
"bl=0,dl=0,mtp=1000,nor=\"..%2Fvideo_4000_700000.m4s\",nrr=\"0-\",su",
"CMCD-Session",
"cid=\"mediaIdcontentIdSuffix\",sf=d,st=v",
"CMCD-Status",
@ -468,7 +468,7 @@ public class DefaultDashChunkSourceTest {
"CMCD-Object",
"br=700,d=4000,key-1=1,ot=v,tb=1300",
"CMCD-Request",
"bl=0,dl=0,key-2=\"stringValue\",mtp=1000,su",
"bl=0,dl=0,key-2=\"stringValue\",mtp=1000,nor=\"..%2Fvideo_4000_700000.m4s\",nrr=\"0-\",su",
"CMCD-Session",
"cid=\"mediaId\",key-3=3,sf=d,sid=\"" + cmcdConfiguration.sessionId + "\",st=v",
"CMCD-Status",

View File

@ -488,23 +488,44 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
seenExpectedPlaylistError = false;
expectedPlaylistUrl = null;
@Nullable
CmcdHeadersFactory cmcdHeadersFactory =
cmcdConfiguration == null
? null
: new CmcdHeadersFactory(
cmcdConfiguration,
trackSelection,
bufferedDurationUs,
/* playbackRate= */ loadingInfo.playbackSpeed,
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_HLS,
/* isLive= */ !playlist.hasEndTag,
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
/* isBufferEmpty= */ queue.isEmpty())
.setObjectType(
getIsMuxedAudioAndVideo()
? CmcdHeadersFactory.OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO
: CmcdHeadersFactory.getObjectType(trackSelection));
@Nullable CmcdHeadersFactory cmcdHeadersFactory = null;
if (cmcdConfiguration != null) {
cmcdHeadersFactory =
new CmcdHeadersFactory(
cmcdConfiguration,
trackSelection,
bufferedDurationUs,
/* playbackRate= */ loadingInfo.playbackSpeed,
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_HLS,
/* isLive= */ !playlist.hasEndTag,
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
/* isBufferEmpty= */ queue.isEmpty())
.setObjectType(
getIsMuxedAudioAndVideo()
? CmcdHeadersFactory.OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO
: CmcdHeadersFactory.getObjectType(trackSelection));
long nextChunkMediaSequence =
partIndex == C.LENGTH_UNSET
? (chunkMediaSequence == C.LENGTH_UNSET ? C.LENGTH_UNSET : chunkMediaSequence + 1)
: chunkMediaSequence;
int nextPartIndex = partIndex == C.LENGTH_UNSET ? C.LENGTH_UNSET : partIndex + 1;
SegmentBaseHolder nextSegmentBaseHolder =
getNextSegmentHolder(playlist, nextChunkMediaSequence, nextPartIndex);
if (nextSegmentBaseHolder != null) {
Uri uri = UriUtil.resolveToUri(playlist.baseUri, segmentBaseHolder.segmentBase.url);
Uri nextUri = UriUtil.resolveToUri(playlist.baseUri, nextSegmentBaseHolder.segmentBase.url);
cmcdHeadersFactory.setNextObjectRequest(UriUtil.getRelativePath(uri, nextUri));
String nextRangeRequest = nextSegmentBaseHolder.segmentBase.byteRangeOffset + "-";
if (nextSegmentBaseHolder.segmentBase.byteRangeLength != C.LENGTH_UNSET) {
nextRangeRequest +=
(nextSegmentBaseHolder.segmentBase.byteRangeOffset
+ nextSegmentBaseHolder.segmentBase.byteRangeLength);
}
cmcdHeadersFactory.setNextRangeRequest(nextRangeRequest);
}
}
lastChunkRequestRealtimeMs = SystemClock.elapsedRealtime();
// Check if the media segment or its initialization segment are fully encrypted.

View File

@ -216,7 +216,7 @@ public class HlsChunkSourceTest {
"CMCD-Object",
"br=800,d=4000,ot=v,tb=800",
"CMCD-Request",
"bl=0,dl=0,su",
"bl=0,dl=0,nor=\"..%2F3.mp4\",nrr=\"0-\",su",
"CMCD-Session",
"cid=\"mediaId\",sf=h,sid=\"" + cmcdConfiguration.sessionId + "\",st=v");
@ -232,7 +232,7 @@ public class HlsChunkSourceTest {
"CMCD-Object",
"br=800,d=4000,ot=v,tb=800",
"CMCD-Request",
"bl=1000,dl=800",
"bl=1000,dl=800,nor=\"..%2F3.mp4\",nrr=\"0-\"",
"CMCD-Session",
"cid=\"mediaId\",pr=1.25,sf=h,sid=\"" + cmcdConfiguration.sessionId + "\",st=v");
}
@ -329,7 +329,7 @@ public class HlsChunkSourceTest {
"CMCD-Object",
"br=800,d=4000,ot=v,tb=800",
"CMCD-Request",
"bl=0,dl=0,su",
"bl=0,dl=0,nor=\"..%2F3.mp4\",nrr=\"0-\",su",
"CMCD-Session",
"cid=\"mediaIdcontentIdSuffix\",sf=h,st=v",
"CMCD-Status",
@ -378,7 +378,7 @@ public class HlsChunkSourceTest {
"CMCD-Object",
"br=800,d=4000,key-1=1,ot=v,tb=800",
"CMCD-Request",
"bl=0,dl=0,key-2=\"stringValue\",su",
"bl=0,dl=0,key-2=\"stringValue\",nor=\"..%2F3.mp4\",nrr=\"0-\",su",
"CMCD-Session",
"cid=\"mediaId\",key-3=3,sf=h,sid=\"" + cmcdConfiguration.sessionId + "\",st=v",
"CMCD-Status",

View File

@ -24,6 +24,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.UriUtil;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.TransferListener;
@ -290,21 +291,26 @@ public class DefaultSsChunkSource implements SsChunkSource {
int manifestTrackIndex = trackSelection.getIndexInTrackGroup(trackSelectionIndex);
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
@Nullable
CmcdHeadersFactory cmcdHeadersFactory =
cmcdConfiguration == null
? null
: new CmcdHeadersFactory(
cmcdConfiguration,
trackSelection,
bufferedDurationUs,
/* playbackRate= */ loadingInfo.playbackSpeed,
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_SS,
/* isLive= */ manifest.isLive,
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
/* isBufferEmpty= */ queue.isEmpty())
.setChunkDurationUs(chunkEndTimeUs - chunkStartTimeUs)
.setObjectType(CmcdHeadersFactory.getObjectType(trackSelection));
@Nullable CmcdHeadersFactory cmcdHeadersFactory = null;
if (cmcdConfiguration != null) {
cmcdHeadersFactory =
new CmcdHeadersFactory(
cmcdConfiguration,
trackSelection,
bufferedDurationUs,
/* playbackRate= */ loadingInfo.playbackSpeed,
/* streamingFormat= */ CmcdHeadersFactory.STREAMING_FORMAT_SS,
/* isLive= */ manifest.isLive,
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
/* isBufferEmpty= */ queue.isEmpty())
.setChunkDurationUs(chunkEndTimeUs - chunkStartTimeUs)
.setObjectType(CmcdHeadersFactory.getObjectType(trackSelection));
if (chunkIndex + 1 < streamElement.chunkCount) {
Uri nextUri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex + 1);
cmcdHeadersFactory.setNextObjectRequest(UriUtil.getRelativePath(uri, nextUri));
}
}
lastChunkRequestRealtimeMs = SystemClock.elapsedRealtime();
out.chunk =

View File

@ -71,7 +71,7 @@ public class DefaultSsChunkSourceTest {
"CMCD-Object",
"br=308,d=1968,ot=v,tb=1536",
"CMCD-Request",
"bl=0,dl=0,mtp=1000,su",
"bl=0,dl=0,mtp=1000,nor=\"..%2FFragments(video%3D19680000)\",su",
"CMCD-Session",
"cid=\"mediaId\",sf=s,sid=\"" + cmcdConfiguration.sessionId + "\",st=v");
@ -86,7 +86,7 @@ public class DefaultSsChunkSourceTest {
"CMCD-Object",
"br=308,d=898,ot=v,tb=1536",
"CMCD-Request",
"bl=1000,dl=500,mtp=1000",
"bl=1000,dl=500,mtp=1000,nor=\"..%2FFragments(video%3D28660000)\"",
"CMCD-Session",
"cid=\"mediaId\",pr=2.00,sf=s,sid=\"" + cmcdConfiguration.sessionId + "\",st=v");
}
@ -170,7 +170,7 @@ public class DefaultSsChunkSourceTest {
"CMCD-Object",
"br=308,d=1968,ot=v,tb=1536",
"CMCD-Request",
"bl=0,dl=0,mtp=1000,su",
"bl=0,dl=0,mtp=1000,nor=\"..%2FFragments(video%3D19680000)\",su",
"CMCD-Session",
"cid=\"mediaIdcontentIdSuffix\",sf=s,st=v",
"CMCD-Status",
@ -218,7 +218,7 @@ public class DefaultSsChunkSourceTest {
"CMCD-Object",
"br=308,d=1968,key-1=1,ot=v,tb=1536",
"CMCD-Request",
"bl=0,dl=0,key-2=\"stringValue\",mtp=1000,su",
"bl=0,dl=0,key-2=\"stringValue\",mtp=1000,nor=\"..%2FFragments(video%3D19680000)\",su",
"CMCD-Session",
"cid=\"mediaId\",key-3=3,sf=s,sid=\"" + cmcdConfiguration.sessionId + "\",st=v",
"CMCD-Status",

View File

@ -39,6 +39,7 @@
<c t = "0" d = "19680000" />
<c n = "1" t = "19680000" d="8980000" />
<c n = "2" t = "28660000" d="8980000" />
</StreamIndex>
</SmoothStreamingMedia>