mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
b6b16b2895
commit
e0d3cad8bb
@ -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:
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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.
|
||||
|
@ -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",
|
||||
|
@ -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 =
|
||||
|
@ -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",
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user