mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Enable sending CmcdData
for manifest requests in DASH, HLS and SS
Issue: androidx/media#1951 PiperOrigin-RevId: 704875765
This commit is contained in:
parent
c377a34a5a
commit
8d2f531470
@ -27,6 +27,9 @@
|
||||
* `RenderersFactory.createSecondaryRenderer` can be implemented to provide
|
||||
secondary renderers for pre-warming. Pre-warming enables quicker media
|
||||
item transitions during playback.
|
||||
* Enable sending `CmcdData` for manifest requests in adaptive streaming
|
||||
formats DASH, HLS, and SmoothStreaming
|
||||
([#1951](https://github.com/androidx/media/issues/1951)).
|
||||
* Transformer:
|
||||
* Update parameters of `VideoFrameProcessor.registerInputStream` and
|
||||
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.
|
||||
|
@ -16,7 +16,9 @@
|
||||
package androidx.media3.exoplayer.upstream;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
@ -46,6 +48,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@ -89,6 +92,9 @@ public final class CmcdData {
|
||||
/** Represents the object type for muxed audio and video content in a media container. */
|
||||
public static final String OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO = "av";
|
||||
|
||||
/** Represents the object type for a manifest or playlist file, in a media container. */
|
||||
public static final String OBJECT_TYPE_MANIFEST = "m";
|
||||
|
||||
/**
|
||||
* Custom key names MUST carry a hyphenated prefix to ensure that there will not be a namespace
|
||||
* collision with future revisions to this specification. Clients SHOULD use a reverse-DNS
|
||||
@ -97,13 +103,13 @@ public final class CmcdData {
|
||||
private static final Pattern CUSTOM_KEY_NAME_PATTERN = Pattern.compile(".*-.*");
|
||||
|
||||
private final CmcdConfiguration cmcdConfiguration;
|
||||
private final ExoTrackSelection trackSelection;
|
||||
private final long bufferedDurationUs;
|
||||
private final float playbackRate;
|
||||
private final @CmcdData.StreamingFormat String streamingFormat;
|
||||
private final boolean isLive;
|
||||
private final boolean didRebuffer;
|
||||
private final boolean isBufferEmpty;
|
||||
@Nullable private ExoTrackSelection trackSelection;
|
||||
private long bufferedDurationUs;
|
||||
private float playbackRate;
|
||||
@Nullable private Boolean isLive;
|
||||
private boolean didRebuffer;
|
||||
private boolean isBufferEmpty;
|
||||
private long chunkDurationUs;
|
||||
@Nullable private @CmcdData.ObjectType String objectType;
|
||||
@Nullable private String nextObjectRequest;
|
||||
@ -112,41 +118,15 @@ public final class CmcdData {
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param cmcdConfiguration The {@link CmcdConfiguration} for this chunk source.
|
||||
* @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 CmcdData.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 or {@code
|
||||
* playbackRate} is non-positive.
|
||||
* @param cmcdConfiguration The {@link CmcdConfiguration} for this source.
|
||||
* @param streamingFormat The streaming format of the media content.
|
||||
*/
|
||||
public Factory(
|
||||
CmcdConfiguration cmcdConfiguration,
|
||||
ExoTrackSelection trackSelection,
|
||||
long bufferedDurationUs,
|
||||
float playbackRate,
|
||||
@CmcdData.StreamingFormat String streamingFormat,
|
||||
boolean isLive,
|
||||
boolean didRebuffer,
|
||||
boolean isBufferEmpty) {
|
||||
checkArgument(bufferedDurationUs >= 0);
|
||||
checkArgument(playbackRate == C.RATE_UNSET || playbackRate > 0);
|
||||
CmcdConfiguration cmcdConfiguration, @CmcdData.StreamingFormat String streamingFormat) {
|
||||
this.cmcdConfiguration = cmcdConfiguration;
|
||||
this.trackSelection = trackSelection;
|
||||
this.bufferedDurationUs = bufferedDurationUs;
|
||||
this.playbackRate = playbackRate;
|
||||
this.bufferedDurationUs = C.TIME_UNSET;
|
||||
this.playbackRate = C.RATE_UNSET;
|
||||
this.streamingFormat = streamingFormat;
|
||||
this.isLive = isLive;
|
||||
this.didRebuffer = didRebuffer;
|
||||
this.isBufferEmpty = isBufferEmpty;
|
||||
this.chunkDurationUs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@ -160,7 +140,6 @@ public final class CmcdData {
|
||||
*/
|
||||
@Nullable
|
||||
public static @CmcdData.ObjectType String getObjectType(ExoTrackSelection trackSelection) {
|
||||
checkArgument(trackSelection != null);
|
||||
@TrackType
|
||||
int trackType = MimeTypes.getTrackType(trackSelection.getSelectedFormat().sampleMimeType);
|
||||
if (trackType == C.TRACK_TYPE_UNKNOWN) {
|
||||
@ -178,8 +157,13 @@ public final class CmcdData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration of current media chunk being requested, in microseconds. The default value
|
||||
* is {@link C#TIME_UNSET}.
|
||||
* Sets the duration of current media chunk being requested, in microseconds.
|
||||
*
|
||||
* <p>Must be set to a non-negative value if the {@linkplain #setObjectType(String) object type}
|
||||
* is set and one of {@link #OBJECT_TYPE_AUDIO_ONLY}, {@link #OBJECT_TYPE_VIDEO_ONLY} or {@link
|
||||
* #OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO}.
|
||||
*
|
||||
* <p>Default value is {@link C#TIME_UNSET}.
|
||||
*
|
||||
* @throws IllegalArgumentException If {@code chunkDurationUs} is negative.
|
||||
*/
|
||||
@ -191,8 +175,7 @@ public final class CmcdData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the object type of the current object being requested. Must be one of the allowed object
|
||||
* types specified by the {@link CmcdData.ObjectType} annotation.
|
||||
* Sets the object type of the current object being requested.
|
||||
*
|
||||
* <p>Default is {@code null}.
|
||||
*/
|
||||
@ -226,31 +209,145 @@ public final class CmcdData {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@linkplain ExoTrackSelection track selection} for the media being played.
|
||||
*
|
||||
* <p>Must be set to a non-null value if the {@link #setObjectType(String)} is not {@link
|
||||
* #OBJECT_TYPE_MANIFEST}
|
||||
*
|
||||
* <p>Default is {@code null}.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setTrackSelection(ExoTrackSelection trackSelection) {
|
||||
this.trackSelection = trackSelection;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration of media currently buffered from the current playback position, in
|
||||
* microseconds.
|
||||
*
|
||||
* <p>Must be set to a non-negative value if the {@linkplain #setObjectType(String) object type}
|
||||
* is set and one of {@link #OBJECT_TYPE_AUDIO_ONLY}, {@link #OBJECT_TYPE_VIDEO_ONLY} or {@link
|
||||
* #OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO}.
|
||||
*
|
||||
* <p>Default value is {@link C#TIME_UNSET}.
|
||||
*
|
||||
* @throws IllegalArgumentException If {@code bufferedDurationUs} is negative.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setBufferedDurationUs(long bufferedDurationUs) {
|
||||
checkArgument(bufferedDurationUs >= 0);
|
||||
this.bufferedDurationUs = bufferedDurationUs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the playback rate indicating the current speed of playback.
|
||||
*
|
||||
* <p>Default value is {@link C#RATE_UNSET}.
|
||||
*
|
||||
* @throws IllegalArgumentException If {@code playbackRate} is non-positive and not {@link
|
||||
* C#RATE_UNSET}.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setPlaybackRate(float playbackRate) {
|
||||
checkArgument(playbackRate == C.RATE_UNSET || playbackRate > 0);
|
||||
this.playbackRate = playbackRate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the media content is being streamed live.
|
||||
*
|
||||
* <p>Default value is {@code false}.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setIsLive(boolean isLive) {
|
||||
this.isLive = isLive;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether a rebuffering event occurred between the previous request and this one.
|
||||
*
|
||||
* <p>Default value is {@code false}.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setDidRebuffer(boolean didRebuffer) {
|
||||
this.didRebuffer = didRebuffer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the queue of buffered chunks is empty.
|
||||
*
|
||||
* <p>Default value is {@code false}.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setIsBufferEmpty(boolean isBufferEmpty) {
|
||||
this.isBufferEmpty = isBufferEmpty;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CmcdData} instance.
|
||||
*
|
||||
* @throws IllegalStateException If any required parameters have not been set.
|
||||
*/
|
||||
public CmcdData createCmcdData() {
|
||||
boolean isManifestObjectType = isManifestObjectType(objectType);
|
||||
boolean isMediaObjectType = isMediaObjectType(objectType);
|
||||
|
||||
if (!isManifestObjectType) {
|
||||
checkStateNotNull(trackSelection, "Track selection must be set");
|
||||
}
|
||||
if (isMediaObjectType) {
|
||||
checkState(bufferedDurationUs != C.TIME_UNSET, "Buffered duration must be set");
|
||||
checkState(chunkDurationUs != C.TIME_UNSET, "Chunk duration must be set");
|
||||
}
|
||||
|
||||
ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String> customData =
|
||||
cmcdConfiguration.requestConfig.getCustomData();
|
||||
for (String headerKey : customData.keySet()) {
|
||||
validateCustomDataListFormat(customData.get(headerKey));
|
||||
}
|
||||
|
||||
int bitrateKbps = Util.ceilDivide(trackSelection.getSelectedFormat().bitrate, 1000);
|
||||
int bitrateKbps = C.RATE_UNSET_INT;
|
||||
int topBitrateKbps = C.RATE_UNSET_INT;
|
||||
long latestBitrateEstimateKbps = C.RATE_UNSET_INT;
|
||||
int requestedMaximumThroughputKbps = C.RATE_UNSET_INT;
|
||||
|
||||
if (!isManifestObjectType) {
|
||||
ExoTrackSelection trackSelection = checkNotNull(this.trackSelection);
|
||||
int selectedTrackBitrate = trackSelection.getSelectedFormat().bitrate;
|
||||
bitrateKbps = Util.ceilDivide(selectedTrackBitrate, 1000);
|
||||
|
||||
TrackGroup trackGroup = trackSelection.getTrackGroup();
|
||||
int topBitrate = selectedTrackBitrate;
|
||||
for (int i = 0; i < trackGroup.length; i++) {
|
||||
topBitrate = max(topBitrate, trackGroup.getFormat(i).bitrate);
|
||||
}
|
||||
topBitrateKbps = Util.ceilDivide(topBitrate, 1000);
|
||||
|
||||
if (trackSelection.getLatestBitrateEstimate() != C.RATE_UNSET_INT) {
|
||||
latestBitrateEstimateKbps =
|
||||
Util.ceilDivide(trackSelection.getLatestBitrateEstimate(), 1000);
|
||||
}
|
||||
|
||||
requestedMaximumThroughputKbps =
|
||||
cmcdConfiguration.requestConfig.getRequestedMaximumThroughputKbps(bitrateKbps);
|
||||
}
|
||||
|
||||
CmcdObject.Builder cmcdObject = new CmcdObject.Builder();
|
||||
if (!getIsInitSegment()) {
|
||||
if (cmcdConfiguration.isBitrateLoggingAllowed()) {
|
||||
cmcdObject.setBitrateKbps(bitrateKbps);
|
||||
}
|
||||
if (cmcdConfiguration.isTopBitrateLoggingAllowed()) {
|
||||
TrackGroup trackGroup = trackSelection.getTrackGroup();
|
||||
int topBitrate = trackSelection.getSelectedFormat().bitrate;
|
||||
for (int i = 0; i < trackGroup.length; i++) {
|
||||
topBitrate = max(topBitrate, trackGroup.getFormat(i).bitrate);
|
||||
}
|
||||
cmcdObject.setTopBitrateKbps(Util.ceilDivide(topBitrate, 1000));
|
||||
}
|
||||
if (cmcdConfiguration.isObjectDurationLoggingAllowed()) {
|
||||
cmcdObject.setObjectDurationMs(Util.usToMs(chunkDurationUs));
|
||||
}
|
||||
if (cmcdConfiguration.isBitrateLoggingAllowed()) {
|
||||
cmcdObject.setBitrateKbps(bitrateKbps);
|
||||
}
|
||||
if (cmcdConfiguration.isTopBitrateLoggingAllowed()) {
|
||||
cmcdObject.setTopBitrateKbps(topBitrateKbps);
|
||||
}
|
||||
if (isMediaObjectType && cmcdConfiguration.isObjectDurationLoggingAllowed()) {
|
||||
cmcdObject.setObjectDurationMs(Util.usToMs(chunkDurationUs));
|
||||
}
|
||||
if (cmcdConfiguration.isObjectTypeLoggingAllowed()) {
|
||||
cmcdObject.setObjectType(objectType);
|
||||
@ -260,16 +357,16 @@ public final class CmcdData {
|
||||
}
|
||||
|
||||
CmcdRequest.Builder cmcdRequest = new CmcdRequest.Builder();
|
||||
if (!getIsInitSegment() && cmcdConfiguration.isBufferLengthLoggingAllowed()) {
|
||||
cmcdRequest.setBufferLengthMs(Util.usToMs(bufferedDurationUs));
|
||||
if (isMediaObjectType) {
|
||||
if (cmcdConfiguration.isBufferLengthLoggingAllowed()) {
|
||||
cmcdRequest.setBufferLengthMs(Util.usToMs(bufferedDurationUs));
|
||||
}
|
||||
if (cmcdConfiguration.isDeadlineLoggingAllowed()) {
|
||||
cmcdRequest.setDeadlineMs(Util.usToMs((long) (bufferedDurationUs / playbackRate)));
|
||||
}
|
||||
}
|
||||
if (cmcdConfiguration.isMeasuredThroughputLoggingAllowed()
|
||||
&& trackSelection.getLatestBitrateEstimate() != C.RATE_UNSET_INT) {
|
||||
cmcdRequest.setMeasuredThroughputInKbps(
|
||||
Util.ceilDivide(trackSelection.getLatestBitrateEstimate(), 1000));
|
||||
}
|
||||
if (cmcdConfiguration.isDeadlineLoggingAllowed()) {
|
||||
cmcdRequest.setDeadlineMs(Util.usToMs((long) (bufferedDurationUs / playbackRate)));
|
||||
if (cmcdConfiguration.isMeasuredThroughputLoggingAllowed()) {
|
||||
cmcdRequest.setMeasuredThroughputInKbps(latestBitrateEstimateKbps);
|
||||
}
|
||||
if (cmcdConfiguration.isStartupLoggingAllowed()) {
|
||||
cmcdRequest.setStartup(didRebuffer || isBufferEmpty);
|
||||
@ -294,8 +391,8 @@ public final class CmcdData {
|
||||
if (cmcdConfiguration.isStreamingFormatLoggingAllowed()) {
|
||||
cmcdSession.setStreamingFormat(streamingFormat);
|
||||
}
|
||||
if (cmcdConfiguration.isStreamTypeLoggingAllowed()) {
|
||||
cmcdSession.setStreamType(isLive ? STREAM_TYPE_LIVE : STREAM_TYPE_VOD);
|
||||
if (isLive != null && cmcdConfiguration.isStreamTypeLoggingAllowed()) {
|
||||
cmcdSession.setStreamType(checkNotNull(isLive) ? STREAM_TYPE_LIVE : STREAM_TYPE_VOD);
|
||||
}
|
||||
if (cmcdConfiguration.isPlaybackRateLoggingAllowed()) {
|
||||
cmcdSession.setPlaybackRate(playbackRate);
|
||||
@ -306,8 +403,7 @@ public final class CmcdData {
|
||||
|
||||
CmcdStatus.Builder cmcdStatus = new CmcdStatus.Builder();
|
||||
if (cmcdConfiguration.isMaximumRequestThroughputLoggingAllowed()) {
|
||||
cmcdStatus.setMaximumRequestedThroughputKbps(
|
||||
cmcdConfiguration.requestConfig.getRequestedMaximumThroughputKbps(bitrateKbps));
|
||||
cmcdStatus.setMaximumRequestedThroughputKbps(requestedMaximumThroughputKbps);
|
||||
}
|
||||
if (cmcdConfiguration.isBufferStarvationLoggingAllowed()) {
|
||||
cmcdStatus.setBufferStarvation(didRebuffer);
|
||||
@ -324,8 +420,14 @@ public final class CmcdData {
|
||||
cmcdConfiguration.dataTransmissionMode);
|
||||
}
|
||||
|
||||
private boolean getIsInitSegment() {
|
||||
return objectType != null && objectType.equals(OBJECT_TYPE_INIT_SEGMENT);
|
||||
private static boolean isManifestObjectType(@Nullable @ObjectType String objectType) {
|
||||
return Objects.equals(objectType, Factory.OBJECT_TYPE_MANIFEST);
|
||||
}
|
||||
|
||||
private static boolean isMediaObjectType(@Nullable @ObjectType String objectType) {
|
||||
return Objects.equals(objectType, Factory.OBJECT_TYPE_AUDIO_ONLY)
|
||||
|| Objects.equals(objectType, Factory.OBJECT_TYPE_VIDEO_ONLY)
|
||||
|| Objects.equals(objectType, Factory.OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO);
|
||||
}
|
||||
|
||||
private void validateCustomDataListFormat(List<String> customDataList) {
|
||||
@ -360,7 +462,8 @@ public final class CmcdData {
|
||||
Factory.OBJECT_TYPE_INIT_SEGMENT,
|
||||
Factory.OBJECT_TYPE_AUDIO_ONLY,
|
||||
Factory.OBJECT_TYPE_VIDEO_ONLY,
|
||||
Factory.OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO
|
||||
Factory.OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO,
|
||||
Factory.OBJECT_TYPE_MANIFEST
|
||||
})
|
||||
@Documented
|
||||
@Target(TYPE_USE)
|
||||
@ -528,10 +631,8 @@ public final class CmcdData {
|
||||
public final long objectDurationMs;
|
||||
|
||||
/**
|
||||
* The media type of the current object being requested , or {@code null} if unset. Must be one
|
||||
* of the allowed object types specified by the {@link ObjectType} annotation.
|
||||
*
|
||||
* <p>If the object type being requested is unknown, then this key MUST NOT be used.
|
||||
* The media type of the current object being requested. Must be one of the allowed object types
|
||||
* specified by the {@link ObjectType} annotation.
|
||||
*/
|
||||
@Nullable public final @ObjectType String objectType;
|
||||
|
||||
@ -607,8 +708,13 @@ public final class CmcdData {
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setBufferLengthMs(long bufferLengthMs) {
|
||||
checkArgument(bufferLengthMs >= 0 || bufferLengthMs == C.TIME_UNSET);
|
||||
this.bufferLengthMs = ((bufferLengthMs + 50) / 100) * 100;
|
||||
if (bufferLengthMs == C.TIME_UNSET) {
|
||||
this.bufferLengthMs = bufferLengthMs;
|
||||
} else if (bufferLengthMs >= 0) {
|
||||
this.bufferLengthMs = ((bufferLengthMs + 50) / 100) * 100;
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -621,10 +727,13 @@ public final class CmcdData {
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setMeasuredThroughputInKbps(long measuredThroughputInKbps) {
|
||||
checkArgument(
|
||||
measuredThroughputInKbps >= 0 || measuredThroughputInKbps == C.RATE_UNSET_INT);
|
||||
this.measuredThroughputInKbps = ((measuredThroughputInKbps + 50) / 100) * 100;
|
||||
|
||||
if (measuredThroughputInKbps == C.RATE_UNSET_INT) {
|
||||
this.measuredThroughputInKbps = measuredThroughputInKbps;
|
||||
} else if (measuredThroughputInKbps >= 0) {
|
||||
this.measuredThroughputInKbps = ((measuredThroughputInKbps + 50) / 100) * 100;
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -637,8 +746,13 @@ public final class CmcdData {
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setDeadlineMs(long deadlineMs) {
|
||||
checkArgument(deadlineMs >= 0 || deadlineMs == C.TIME_UNSET);
|
||||
this.deadlineMs = ((deadlineMs + 50) / 100) * 100;
|
||||
if (deadlineMs == C.TIME_UNSET) {
|
||||
this.deadlineMs = deadlineMs;
|
||||
} else if (deadlineMs >= 0) {
|
||||
this.deadlineMs = ((deadlineMs + 50) / 100) * 100;
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -36,23 +36,48 @@ import org.junit.runner.RunWith;
|
||||
public class CmcdDataTest {
|
||||
|
||||
@Test
|
||||
public void createInstance_populatesCmcdHttRequestHeaders() {
|
||||
public void createInstance_withInvalidFactoryState_throwsIllegalStateException() {
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
CmcdConfiguration.Factory.DEFAULT.createCmcdConfiguration(MediaItem.EMPTY);
|
||||
ExoTrackSelection trackSelection = mock(ExoTrackSelection.class);
|
||||
|
||||
assertThrows(
|
||||
"Track selection must be set",
|
||||
IllegalStateException.class,
|
||||
() ->
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_INIT_SEGMENT)
|
||||
.createCmcdData());
|
||||
|
||||
assertThrows(
|
||||
"Buffered duration must be set",
|
||||
IllegalStateException.class,
|
||||
() ->
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_AUDIO_ONLY)
|
||||
.setTrackSelection(trackSelection)
|
||||
.setChunkDurationUs(100_000)
|
||||
.createCmcdData());
|
||||
|
||||
assertThrows(
|
||||
"Chunk duration must be set",
|
||||
IllegalStateException.class,
|
||||
() ->
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_AUDIO_ONLY)
|
||||
.setTrackSelection(trackSelection)
|
||||
.setBufferedDurationUs(100_000)
|
||||
.createCmcdData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInstance_audioObjectType_setsCorrectHttpHeaders() {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||
mediaItem ->
|
||||
new CmcdConfiguration(
|
||||
"sessionId",
|
||||
mediaItem.mediaId,
|
||||
new CmcdConfiguration.RequestConfig() {
|
||||
@Override
|
||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||
getCustomData() {
|
||||
return new ImmutableListMultimap.Builder<String, String>()
|
||||
.putAll("CMCD-Object", "key-1=1", "key-2-separated-by-multiple-hyphens=2")
|
||||
.put("CMCD-Request", "key-3=\"stringValue1,stringValue2\"")
|
||||
.put("CMCD-Status", "key-4=\"stringValue3=stringValue4\"")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequestedMaximumThroughputKbps(int throughputKbps) {
|
||||
return 2 * throughputKbps;
|
||||
@ -69,15 +94,14 @@ public class CmcdDataTest {
|
||||
when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L);
|
||||
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
||||
CmcdData cmcdData =
|
||||
new CmcdData.Factory(
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
/* bufferedDurationUs= */ 1_760_000,
|
||||
/* playbackRate= */ 2.0f,
|
||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_DASH,
|
||||
/* isLive= */ true,
|
||||
/* didRebuffer= */ true,
|
||||
/* isBufferEmpty= */ false)
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setTrackSelection(trackSelection)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_AUDIO_ONLY)
|
||||
.setBufferedDurationUs(1_760_000)
|
||||
.setPlaybackRate(2.0f)
|
||||
.setIsLive(true)
|
||||
.setDidRebuffer(true)
|
||||
.setIsBufferEmpty(false)
|
||||
.setChunkDurationUs(3_000_000)
|
||||
.createCmcdData();
|
||||
|
||||
@ -86,32 +110,23 @@ public class CmcdDataTest {
|
||||
assertThat(dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=840,d=3000,key-1=1,key-2-separated-by-multiple-hyphens=2,tb=1000",
|
||||
"br=840,d=3000,ot=a,tb=1000",
|
||||
"CMCD-Request",
|
||||
"bl=1800,dl=900,key-3=\"stringValue1,stringValue2\",mtp=500,su",
|
||||
"bl=1800,dl=900,mtp=500,su",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",pr=2.00,sf=d,sid=\"sessionId\",st=l",
|
||||
"CMCD-Status",
|
||||
"bs,key-4=\"stringValue3=stringValue4\",rtp=1700");
|
||||
"bs,rtp=1700");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInstance_populatesCmcdHttpQueryParameters() {
|
||||
public void createInstance_audioObjectType_setsCorrectQueryParameters() {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||
mediaItem ->
|
||||
new CmcdConfiguration(
|
||||
"sessionId",
|
||||
mediaItem.mediaId,
|
||||
new CmcdConfiguration.RequestConfig() {
|
||||
@Override
|
||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||
getCustomData() {
|
||||
return new ImmutableListMultimap.Builder<String, String>()
|
||||
.put("CMCD-Object", "key-1=1")
|
||||
.put("CMCD-Request", "key-2=\"stringVälue1,stringVälue2\"")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequestedMaximumThroughputKbps(int throughputKbps) {
|
||||
return 2 * throughputKbps;
|
||||
@ -129,32 +144,148 @@ public class CmcdDataTest {
|
||||
when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L);
|
||||
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
||||
CmcdData cmcdData =
|
||||
new CmcdData.Factory(
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
/* bufferedDurationUs= */ 1_760_000,
|
||||
/* playbackRate= */ 2.0f,
|
||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_DASH,
|
||||
/* isLive= */ true,
|
||||
/* didRebuffer= */ true,
|
||||
/* isBufferEmpty= */ false)
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_AUDIO_ONLY)
|
||||
.setTrackSelection(trackSelection)
|
||||
.setBufferedDurationUs(1_760_000)
|
||||
.setPlaybackRate(2.0f)
|
||||
.setIsLive(true)
|
||||
.setDidRebuffer(true)
|
||||
.setIsBufferEmpty(false)
|
||||
.setChunkDurationUs(3_000_000)
|
||||
.createCmcdData();
|
||||
|
||||
dataSpec = cmcdData.addToDataSpec(dataSpec);
|
||||
|
||||
// Confirm that the values above are URL-encoded
|
||||
assertThat(dataSpec.uri.toString()).doesNotContain("ä");
|
||||
assertThat(dataSpec.uri.toString()).contains(Uri.encode("ä"));
|
||||
assertThat(dataSpec.uri.getQueryParameter(CmcdConfiguration.CMCD_QUERY_PARAMETER_KEY))
|
||||
.isEqualTo(
|
||||
"bl=1800,br=840,bs,cid=\"mediaId\",d=3000,dl=900,key-1=1,"
|
||||
+ "key-2=\"stringVälue1,stringVälue2\",mtp=500,pr=2.00,rtp=1700,sf=d,"
|
||||
+ "sid=\"sessionId\",st=l,su,tb=1000");
|
||||
"bl=1800,br=840,bs,cid=\"mediaId\",d=3000,dl=900,mtp=500,ot=a,pr=2.00,"
|
||||
+ "rtp=1700,sf=d,sid=\"sessionId\",st=l,su,tb=1000");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInstance_withInvalidNonHyphenatedCustomKey_throwsIllegalStateException() {
|
||||
public void createInstance_manifestObjectType_setsCorrectHttpHeaders() {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||
mediaItem ->
|
||||
new CmcdConfiguration(
|
||||
"sessionId", mediaItem.mediaId, new CmcdConfiguration.RequestConfig() {});
|
||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
||||
CmcdData cmcdData =
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_MANIFEST)
|
||||
.createCmcdData();
|
||||
|
||||
dataSpec = cmcdData.addToDataSpec(dataSpec);
|
||||
|
||||
assertThat(dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object", "ot=m", "CMCD-Session", "cid=\"mediaId\",sf=d,sid=\"sessionId\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInstance_manifestObjectType_setsCorrectQueryParameters() {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||
mediaItem ->
|
||||
new CmcdConfiguration(
|
||||
"sessionId",
|
||||
mediaItem.mediaId,
|
||||
new CmcdConfiguration.RequestConfig() {},
|
||||
CmcdConfiguration.MODE_QUERY_PARAMETER);
|
||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
||||
CmcdData cmcdData =
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_MANIFEST)
|
||||
.createCmcdData();
|
||||
|
||||
dataSpec = cmcdData.addToDataSpec(dataSpec);
|
||||
|
||||
assertThat(dataSpec.uri.getQueryParameter(CmcdConfiguration.CMCD_QUERY_PARAMETER_KEY))
|
||||
.isEqualTo("cid=\"mediaId\",ot=m,sf=d,sid=\"sessionId\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInstance_unsetObjectType_setsCorrectHttpHeaders() {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||
mediaItem ->
|
||||
new CmcdConfiguration(
|
||||
"sessionId", mediaItem.mediaId, new CmcdConfiguration.RequestConfig() {});
|
||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
ExoTrackSelection trackSelection = mock(ExoTrackSelection.class);
|
||||
Format format = new Format.Builder().setPeakBitrate(840_000).build();
|
||||
when(trackSelection.getSelectedFormat()).thenReturn(format);
|
||||
when(trackSelection.getTrackGroup())
|
||||
.thenReturn(new TrackGroup(format, new Format.Builder().setPeakBitrate(1_000_000).build()));
|
||||
when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L);
|
||||
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
||||
CmcdData cmcdData =
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setTrackSelection(trackSelection)
|
||||
.setPlaybackRate(2.0f)
|
||||
.setIsLive(true)
|
||||
.setDidRebuffer(true)
|
||||
.setIsBufferEmpty(false)
|
||||
.createCmcdData();
|
||||
|
||||
dataSpec = cmcdData.addToDataSpec(dataSpec);
|
||||
|
||||
assertThat(dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"br=840,tb=1000",
|
||||
"CMCD-Request",
|
||||
"mtp=500,su",
|
||||
"CMCD-Session",
|
||||
"cid=\"mediaId\",pr=2.00,sf=d,sid=\"sessionId\",st=l",
|
||||
"CMCD-Status",
|
||||
"bs");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInstance_unsetObjectType_setsCorrectQueryParameters() {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||
mediaItem ->
|
||||
new CmcdConfiguration(
|
||||
"sessionId",
|
||||
mediaItem.mediaId,
|
||||
new CmcdConfiguration.RequestConfig() {},
|
||||
CmcdConfiguration.MODE_QUERY_PARAMETER);
|
||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
ExoTrackSelection trackSelection = mock(ExoTrackSelection.class);
|
||||
Format format = new Format.Builder().setPeakBitrate(840_000).build();
|
||||
when(trackSelection.getSelectedFormat()).thenReturn(format);
|
||||
when(trackSelection.getTrackGroup())
|
||||
.thenReturn(new TrackGroup(format, new Format.Builder().setPeakBitrate(1_000_000).build()));
|
||||
when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L);
|
||||
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
||||
CmcdData cmcdData =
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setTrackSelection(trackSelection)
|
||||
.setPlaybackRate(2.0f)
|
||||
.setIsLive(true)
|
||||
.setDidRebuffer(true)
|
||||
.setIsBufferEmpty(false)
|
||||
.createCmcdData();
|
||||
|
||||
dataSpec = cmcdData.addToDataSpec(dataSpec);
|
||||
|
||||
assertThat(dataSpec.uri.getQueryParameter(CmcdConfiguration.CMCD_QUERY_PARAMETER_KEY))
|
||||
.isEqualTo(
|
||||
"br=840,bs,cid=\"mediaId\",mtp=500,pr=2.00,"
|
||||
+ "sf=d,sid=\"sessionId\",st=l,su,tb=1000");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInstance_customData_setsCorrectHttpHeaders() {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||
mediaItem ->
|
||||
new CmcdConfiguration(
|
||||
@ -164,27 +295,94 @@ public class CmcdDataTest {
|
||||
@Override
|
||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||
getCustomData() {
|
||||
return new ImmutableListMultimap.Builder<String, String>()
|
||||
.putAll("CMCD-Object", "key-1=1", "key-2-separated-by-multiple-hyphens=2")
|
||||
.put("CMCD-Request", "key-3=\"stringValue1,stringValue2\"")
|
||||
.put("CMCD-Session", "key-4=0.5")
|
||||
.put("CMCD-Status", "key-5=\"stringValue3=stringValue4\"")
|
||||
.build();
|
||||
}
|
||||
});
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(MediaItem.EMPTY);
|
||||
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
||||
CmcdData cmcdData =
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_MANIFEST)
|
||||
.createCmcdData();
|
||||
|
||||
dataSpec = cmcdData.addToDataSpec(dataSpec);
|
||||
|
||||
assertThat(dataSpec.httpRequestHeaders)
|
||||
.containsExactly(
|
||||
"CMCD-Object",
|
||||
"key-1=1,key-2-separated-by-multiple-hyphens=2,ot=m",
|
||||
"CMCD-Request",
|
||||
"key-3=\"stringValue1,stringValue2\"",
|
||||
"CMCD-Session",
|
||||
"key-4=0.5,sf=d",
|
||||
"CMCD-Status",
|
||||
"key-5=\"stringValue3=stringValue4\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInstance_customData_setsCorrectQueryParameters() {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||
mediaItem ->
|
||||
new CmcdConfiguration(
|
||||
null,
|
||||
null,
|
||||
new CmcdConfiguration.RequestConfig() {
|
||||
@Override
|
||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||
getCustomData() {
|
||||
return new ImmutableListMultimap.Builder<String, String>()
|
||||
.put("CMCD-Object", "key-1=1")
|
||||
.put("CMCD-Request", "key-2=\"stringVälue1,stringVälue2\"")
|
||||
.build();
|
||||
}
|
||||
},
|
||||
CmcdConfiguration.MODE_QUERY_PARAMETER);
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(MediaItem.EMPTY);
|
||||
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
||||
CmcdData cmcdData =
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_MANIFEST)
|
||||
.createCmcdData();
|
||||
|
||||
dataSpec = cmcdData.addToDataSpec(dataSpec);
|
||||
|
||||
// Confirm that the values above are URL-encoded
|
||||
assertThat(dataSpec.uri.toString()).doesNotContain("ä");
|
||||
assertThat(dataSpec.uri.toString()).contains(Uri.encode("ä"));
|
||||
assertThat(dataSpec.uri.getQueryParameter(CmcdConfiguration.CMCD_QUERY_PARAMETER_KEY))
|
||||
.isEqualTo("key-1=1,key-2=\"stringVälue1,stringVälue2\",ot=m,sf=d");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInstance_invalidCustomDataKey_throwsException() {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||
mediaItem ->
|
||||
new CmcdConfiguration(
|
||||
null,
|
||||
null,
|
||||
new CmcdConfiguration.RequestConfig() {
|
||||
@Override
|
||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||
getCustomData() {
|
||||
// Invalid non-hyphenated key1
|
||||
return ImmutableListMultimap.of("CMCD-Object", "key1=1");
|
||||
}
|
||||
});
|
||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
ExoTrackSelection trackSelection = mock(ExoTrackSelection.class);
|
||||
when(trackSelection.getSelectedFormat()).thenReturn(new Format.Builder().build());
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(MediaItem.EMPTY);
|
||||
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
() ->
|
||||
new CmcdData.Factory(
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
/* bufferedDurationUs= */ 0,
|
||||
/* playbackRate= */ 1.0f,
|
||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_DASH,
|
||||
/* isLive= */ true,
|
||||
/* didRebuffer= */ true,
|
||||
/* isBufferEmpty= */ false)
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_MANIFEST)
|
||||
.createCmcdData());
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
import androidx.media3.exoplayer.dash.PlayerEmsgHandler.PlayerEmsgCallback;
|
||||
import androidx.media3.exoplayer.dash.manifest.AdaptationSet;
|
||||
@ -69,6 +70,7 @@ import androidx.media3.exoplayer.source.MediaSourceFactory;
|
||||
import androidx.media3.exoplayer.source.SequenceableLoader;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||
import androidx.media3.exoplayer.upstream.CmcdData;
|
||||
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
||||
@ -1099,8 +1101,19 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
manifestUri = this.manifestUri;
|
||||
}
|
||||
manifestLoadPending = false;
|
||||
DataSpec dataSpec =
|
||||
new DataSpec.Builder().setUri(manifestUri).setFlags(DataSpec.FLAG_ALLOW_GZIP).build();
|
||||
if (cmcdConfiguration != null) {
|
||||
CmcdData.Factory cmcdDataFactory =
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_MANIFEST);
|
||||
if (manifest != null) {
|
||||
cmcdDataFactory.setIsLive(manifest.dynamic);
|
||||
}
|
||||
cmcdDataFactory.createCmcdData().addToDataSpec(dataSpec);
|
||||
}
|
||||
startLoading(
|
||||
new ParsingLoadable<>(dataSource, manifestUri, C.DATA_TYPE_MANIFEST, manifestParser),
|
||||
new ParsingLoadable<>(dataSource, dataSpec, C.DATA_TYPE_MANIFEST, manifestParser),
|
||||
manifestCallback,
|
||||
loadErrorHandlingPolicy.getMinimumLoadableRetryCount(C.DATA_TYPE_MANIFEST));
|
||||
}
|
||||
|
@ -421,15 +421,13 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
CmcdData.Factory cmcdDataFactory =
|
||||
cmcdConfiguration == null
|
||||
? null
|
||||
: new CmcdData.Factory(
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
max(0, bufferedDurationUs),
|
||||
/* playbackRate= */ loadingInfo.playbackSpeed,
|
||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_DASH,
|
||||
/* isLive= */ manifest.dynamic,
|
||||
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
|
||||
/* isBufferEmpty= */ queue.isEmpty());
|
||||
: new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||
.setTrackSelection(trackSelection)
|
||||
.setBufferedDurationUs(max(0, bufferedDurationUs))
|
||||
.setPlaybackRate(loadingInfo.playbackSpeed)
|
||||
.setIsLive(manifest.dynamic)
|
||||
.setDidRebuffer(loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs))
|
||||
.setIsBufferEmpty(queue.isEmpty());
|
||||
lastChunkRequestRealtimeMs = SystemClock.elapsedRealtime();
|
||||
|
||||
RepresentationHolder representationHolder = updateSelectedBaseUrl(selectedTrackIndex);
|
||||
@ -715,7 +713,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
* indexUri} is not {@code null}.
|
||||
* @param indexUri The URI pointing to index data. Can be {@code null} if {@code
|
||||
* initializationUri} is not {@code null}.
|
||||
* @param cmcdDataFactory The {@link CmcdData.Factory} for generating CMCD data.
|
||||
* @param cmcdDataFactory The {@link CmcdData.Factory} for generating {@link CmcdData}.
|
||||
*/
|
||||
@RequiresNonNull("#1.chunkExtractor")
|
||||
protected Chunk newInitializationChunk(
|
||||
|
@ -507,20 +507,17 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@Nullable CmcdData.Factory cmcdDataFactory = null;
|
||||
if (cmcdConfiguration != null) {
|
||||
cmcdDataFactory =
|
||||
new CmcdData.Factory(
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
max(0, bufferedDurationUs),
|
||||
/* playbackRate= */ loadingInfo.playbackSpeed,
|
||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_HLS,
|
||||
/* isLive= */ !playlist.hasEndTag,
|
||||
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
|
||||
/* isBufferEmpty= */ queue.isEmpty())
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_HLS)
|
||||
.setTrackSelection(trackSelection)
|
||||
.setBufferedDurationUs(max(0, bufferedDurationUs))
|
||||
.setPlaybackRate(loadingInfo.playbackSpeed)
|
||||
.setIsLive(!playlist.hasEndTag)
|
||||
.setDidRebuffer(loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs))
|
||||
.setIsBufferEmpty(queue.isEmpty())
|
||||
.setObjectType(
|
||||
getIsMuxedAudioAndVideo()
|
||||
? CmcdData.Factory.OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO
|
||||
: CmcdData.Factory.getObjectType(trackSelection));
|
||||
|
||||
long nextMediaSequence =
|
||||
segmentBaseHolder.partIndex == C.INDEX_UNSET
|
||||
? segmentBaseHolder.mediaSequence + 1
|
||||
|
@ -417,7 +417,10 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
drmSessionManagerProvider.get(mediaItem),
|
||||
loadErrorHandlingPolicy,
|
||||
playlistTrackerFactory.createTracker(
|
||||
hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory),
|
||||
hlsDataSourceFactory,
|
||||
loadErrorHandlingPolicy,
|
||||
playlistParserFactory,
|
||||
cmcdConfiguration),
|
||||
elapsedRealTimeOffsetMs,
|
||||
allowChunklessPreparation,
|
||||
metadataType,
|
||||
|
@ -29,6 +29,7 @@ import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.datasource.HttpDataSource;
|
||||
import androidx.media3.exoplayer.hls.HlsDataSourceFactory;
|
||||
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Part;
|
||||
@ -38,6 +39,8 @@ import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant;
|
||||
import androidx.media3.exoplayer.source.LoadEventInfo;
|
||||
import androidx.media3.exoplayer.source.MediaLoadData;
|
||||
import androidx.media3.exoplayer.source.MediaSourceEventListener.EventDispatcher;
|
||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||
import androidx.media3.exoplayer.upstream.CmcdData;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
||||
import androidx.media3.exoplayer.upstream.Loader;
|
||||
@ -69,6 +72,7 @@ public final class DefaultHlsPlaylistTracker
|
||||
private final HashMap<Uri, MediaPlaylistBundle> playlistBundles;
|
||||
private final CopyOnWriteArrayList<PlaylistEventListener> listeners;
|
||||
private final double playlistStuckTargetDurationCoefficient;
|
||||
@Nullable private final CmcdConfiguration cmcdConfiguration;
|
||||
|
||||
@Nullable private EventDispatcher eventDispatcher;
|
||||
@Nullable private Loader initialPlaylistLoader;
|
||||
@ -86,15 +90,18 @@ public final class DefaultHlsPlaylistTracker
|
||||
* @param dataSourceFactory A factory for {@link DataSource} instances.
|
||||
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}.
|
||||
* @param playlistParserFactory An {@link HlsPlaylistParserFactory}.
|
||||
* @param cmcdConfiguration The {@link CmcdConfiguration}.
|
||||
*/
|
||||
public DefaultHlsPlaylistTracker(
|
||||
HlsDataSourceFactory dataSourceFactory,
|
||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
||||
HlsPlaylistParserFactory playlistParserFactory) {
|
||||
HlsPlaylistParserFactory playlistParserFactory,
|
||||
@Nullable CmcdConfiguration cmcdConfiguration) {
|
||||
this(
|
||||
dataSourceFactory,
|
||||
loadErrorHandlingPolicy,
|
||||
playlistParserFactory,
|
||||
cmcdConfiguration,
|
||||
DEFAULT_PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT);
|
||||
}
|
||||
|
||||
@ -104,6 +111,7 @@ public final class DefaultHlsPlaylistTracker
|
||||
* @param dataSourceFactory A factory for {@link DataSource} instances.
|
||||
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}.
|
||||
* @param playlistParserFactory An {@link HlsPlaylistParserFactory}.
|
||||
* @param cmcdConfiguration The {@link CmcdConfiguration}.
|
||||
* @param playlistStuckTargetDurationCoefficient A coefficient to apply to the target duration of
|
||||
* media playlists in order to determine that a non-changing playlist is stuck. Once a
|
||||
* playlist is deemed stuck, a {@link PlaylistStuckException} is thrown via {@link
|
||||
@ -113,10 +121,12 @@ public final class DefaultHlsPlaylistTracker
|
||||
HlsDataSourceFactory dataSourceFactory,
|
||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
||||
HlsPlaylistParserFactory playlistParserFactory,
|
||||
@Nullable CmcdConfiguration cmcdConfiguration,
|
||||
double playlistStuckTargetDurationCoefficient) {
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.playlistParserFactory = playlistParserFactory;
|
||||
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
||||
this.cmcdConfiguration = cmcdConfiguration;
|
||||
this.playlistStuckTargetDurationCoefficient = playlistStuckTargetDurationCoefficient;
|
||||
listeners = new CopyOnWriteArrayList<>();
|
||||
playlistBundles = new HashMap<>();
|
||||
@ -133,10 +143,22 @@ public final class DefaultHlsPlaylistTracker
|
||||
this.playlistRefreshHandler = Util.createHandlerForCurrentLooper();
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
this.primaryPlaylistListener = primaryPlaylistListener;
|
||||
DataSpec dataSpec =
|
||||
new DataSpec.Builder()
|
||||
.setUri(initialPlaylistUri)
|
||||
.setFlags(DataSpec.FLAG_ALLOW_GZIP)
|
||||
.build();
|
||||
if (cmcdConfiguration != null) {
|
||||
CmcdData cmcdData =
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_HLS)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_MANIFEST)
|
||||
.createCmcdData();
|
||||
cmcdData.addToDataSpec(dataSpec);
|
||||
}
|
||||
ParsingLoadable<HlsPlaylist> multivariantPlaylistLoadable =
|
||||
new ParsingLoadable<>(
|
||||
dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST),
|
||||
initialPlaylistUri,
|
||||
dataSpec,
|
||||
C.DATA_TYPE_MANIFEST,
|
||||
playlistParserFactory.createPlaylistParser());
|
||||
Assertions.checkState(initialPlaylistLoader == null);
|
||||
@ -762,12 +784,23 @@ public final class DefaultHlsPlaylistTracker
|
||||
private void loadPlaylistImmediately(Uri playlistRequestUri) {
|
||||
ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser =
|
||||
playlistParserFactory.createPlaylistParser(multivariantPlaylist, playlistSnapshot);
|
||||
DataSpec dataSpec =
|
||||
new DataSpec.Builder()
|
||||
.setUri(playlistRequestUri)
|
||||
.setFlags(DataSpec.FLAG_ALLOW_GZIP)
|
||||
.build();
|
||||
if (cmcdConfiguration != null) {
|
||||
CmcdData.Factory cmcdDataFactory =
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_HLS)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_MANIFEST);
|
||||
if (primaryMediaPlaylistSnapshot != null) {
|
||||
cmcdDataFactory.setIsLive(!primaryMediaPlaylistSnapshot.hasEndTag);
|
||||
}
|
||||
cmcdDataFactory.createCmcdData().addToDataSpec(dataSpec);
|
||||
}
|
||||
ParsingLoadable<HlsPlaylist> mediaPlaylistLoadable =
|
||||
new ParsingLoadable<>(
|
||||
mediaPlaylistDataSource,
|
||||
playlistRequestUri,
|
||||
C.DATA_TYPE_MANIFEST,
|
||||
mediaPlaylistParser);
|
||||
mediaPlaylistDataSource, dataSpec, C.DATA_TYPE_MANIFEST, mediaPlaylistParser);
|
||||
mediaPlaylistLoader.startLoading(
|
||||
mediaPlaylistLoadable,
|
||||
/* callback= */ this,
|
||||
|
@ -21,6 +21,7 @@ import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.hls.HlsDataSourceFactory;
|
||||
import androidx.media3.exoplayer.source.MediaSourceEventListener.EventDispatcher;
|
||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||
import java.io.IOException;
|
||||
|
||||
@ -48,11 +49,13 @@ public interface HlsPlaylistTracker {
|
||||
* @param dataSourceFactory The {@link HlsDataSourceFactory} to use for playlist loading.
|
||||
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy} for playlist load errors.
|
||||
* @param playlistParserFactory The {@link HlsPlaylistParserFactory} for playlist parsing.
|
||||
* @param cmcdConfiguration The {@link CmcdConfiguration} to use for playlist loading.
|
||||
*/
|
||||
HlsPlaylistTracker createTracker(
|
||||
HlsDataSourceFactory dataSourceFactory,
|
||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
||||
HlsPlaylistParserFactory playlistParserFactory);
|
||||
HlsPlaylistParserFactory playlistParserFactory,
|
||||
@Nullable CmcdConfiguration cmcdConfiguration);
|
||||
}
|
||||
|
||||
/** Listener for primary playlist changes. */
|
||||
|
@ -404,7 +404,8 @@ public class DefaultHlsPlaylistTrackerTest {
|
||||
new DefaultHlsPlaylistTracker(
|
||||
dataType -> new DefaultHttpDataSource.Factory().createDataSource(),
|
||||
new DefaultLoadErrorHandlingPolicy(),
|
||||
new DefaultHlsPlaylistParserFactory());
|
||||
new DefaultHlsPlaylistParserFactory(),
|
||||
/* cmcdConfiguration= */ null);
|
||||
AtomicInteger playlistChangedCounter = new AtomicInteger();
|
||||
AtomicReference<TimeoutException> audioPlaylistRefreshExceptionRef = new AtomicReference<>();
|
||||
defaultHlsPlaylistTracker.addListener(
|
||||
@ -486,7 +487,8 @@ public class DefaultHlsPlaylistTrackerTest {
|
||||
new DefaultHlsPlaylistTracker(
|
||||
dataType -> new DefaultHttpDataSource.Factory().createDataSource(),
|
||||
new DefaultLoadErrorHandlingPolicy(),
|
||||
new DefaultHlsPlaylistParserFactory());
|
||||
new DefaultHlsPlaylistParserFactory(),
|
||||
/* cmcdConfiguration= */ null);
|
||||
List<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>();
|
||||
AtomicInteger playlistCounter = new AtomicInteger();
|
||||
AtomicReference<TimeoutException> primaryPlaylistChangeExceptionRef = new AtomicReference<>();
|
||||
@ -562,7 +564,8 @@ public class DefaultHlsPlaylistTrackerTest {
|
||||
new DefaultHlsPlaylistTracker(
|
||||
dataType -> new DefaultHttpDataSource.Factory().createDataSource(),
|
||||
new DefaultLoadErrorHandlingPolicy(),
|
||||
new DefaultHlsPlaylistParserFactory());
|
||||
new DefaultHlsPlaylistParserFactory(),
|
||||
/* cmcdConfiguration= */ null);
|
||||
List<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>();
|
||||
AtomicInteger playlistCounter = new AtomicInteger();
|
||||
AtomicReference<TimeoutException> playlistRefreshExceptionRef = new AtomicReference<>();
|
||||
@ -674,7 +677,8 @@ public class DefaultHlsPlaylistTrackerTest {
|
||||
new DefaultHlsPlaylistTracker(
|
||||
dataType -> dataSourceFactory.createDataSource(),
|
||||
new DefaultLoadErrorHandlingPolicy(),
|
||||
new DefaultHlsPlaylistParserFactory());
|
||||
new DefaultHlsPlaylistParserFactory(),
|
||||
/* cmcdConfiguration= */ null);
|
||||
|
||||
List<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>();
|
||||
AtomicInteger playlistCounter = new AtomicInteger();
|
||||
|
@ -360,15 +360,13 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
||||
@Nullable CmcdData.Factory cmcdDataFactory = null;
|
||||
if (cmcdConfiguration != null) {
|
||||
cmcdDataFactory =
|
||||
new CmcdData.Factory(
|
||||
cmcdConfiguration,
|
||||
trackSelection,
|
||||
max(0, bufferedDurationUs),
|
||||
/* playbackRate= */ loadingInfo.playbackSpeed,
|
||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_SS,
|
||||
/* isLive= */ manifest.isLive,
|
||||
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
|
||||
/* isBufferEmpty= */ queue.isEmpty())
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_SS)
|
||||
.setTrackSelection(trackSelection)
|
||||
.setBufferedDurationUs(max(0, bufferedDurationUs))
|
||||
.setPlaybackRate(loadingInfo.playbackSpeed)
|
||||
.setIsLive(manifest.isLive)
|
||||
.setDidRebuffer(loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs))
|
||||
.setIsBufferEmpty(queue.isEmpty())
|
||||
.setChunkDurationUs(chunkEndTimeUs - chunkStartTimeUs)
|
||||
.setObjectType(CmcdData.Factory.getObjectType(trackSelection));
|
||||
|
||||
|
@ -35,6 +35,7 @@ import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
import androidx.media3.exoplayer.drm.DefaultDrmSessionManagerProvider;
|
||||
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
||||
@ -58,6 +59,7 @@ import androidx.media3.exoplayer.source.SequenceableLoader;
|
||||
import androidx.media3.exoplayer.source.SinglePeriodTimeline;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||
import androidx.media3.exoplayer.upstream.CmcdData;
|
||||
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
||||
@ -676,9 +678,19 @@ public final class SsMediaSource extends BaseMediaSource
|
||||
if (manifestLoader.hasFatalError()) {
|
||||
return;
|
||||
}
|
||||
DataSpec dataSpec =
|
||||
new DataSpec.Builder().setUri(manifestUri).setFlags(DataSpec.FLAG_ALLOW_GZIP).build();
|
||||
if (cmcdConfiguration != null) {
|
||||
CmcdData.Factory cmcdDataFactory =
|
||||
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_SS)
|
||||
.setObjectType(CmcdData.Factory.OBJECT_TYPE_MANIFEST);
|
||||
if (manifest != null) {
|
||||
cmcdDataFactory.setIsLive(manifest.isLive);
|
||||
}
|
||||
cmcdDataFactory.createCmcdData().addToDataSpec(dataSpec);
|
||||
}
|
||||
ParsingLoadable<SsManifest> loadable =
|
||||
new ParsingLoadable<>(
|
||||
manifestDataSource, manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
|
||||
new ParsingLoadable<>(manifestDataSource, dataSpec, C.DATA_TYPE_MANIFEST, manifestParser);
|
||||
manifestLoader.startLoading(
|
||||
loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user