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
|
* `RenderersFactory.createSecondaryRenderer` can be implemented to provide
|
||||||
secondary renderers for pre-warming. Pre-warming enables quicker media
|
secondary renderers for pre-warming. Pre-warming enables quicker media
|
||||||
item transitions during playback.
|
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:
|
* Transformer:
|
||||||
* Update parameters of `VideoFrameProcessor.registerInputStream` and
|
* Update parameters of `VideoFrameProcessor.registerInputStream` and
|
||||||
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.
|
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.
|
||||||
|
@ -16,7 +16,9 @@
|
|||||||
package androidx.media3.exoplayer.upstream;
|
package androidx.media3.exoplayer.upstream;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
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.checkState;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
@ -46,6 +48,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.regex.Pattern;
|
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. */
|
/** 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";
|
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
|
* 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
|
* 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 static final Pattern CUSTOM_KEY_NAME_PATTERN = Pattern.compile(".*-.*");
|
||||||
|
|
||||||
private final CmcdConfiguration cmcdConfiguration;
|
private final CmcdConfiguration cmcdConfiguration;
|
||||||
private final ExoTrackSelection trackSelection;
|
|
||||||
private final long bufferedDurationUs;
|
|
||||||
private final float playbackRate;
|
|
||||||
private final @CmcdData.StreamingFormat String streamingFormat;
|
private final @CmcdData.StreamingFormat String streamingFormat;
|
||||||
private final boolean isLive;
|
@Nullable private ExoTrackSelection trackSelection;
|
||||||
private final boolean didRebuffer;
|
private long bufferedDurationUs;
|
||||||
private final boolean isBufferEmpty;
|
private float playbackRate;
|
||||||
|
@Nullable private Boolean isLive;
|
||||||
|
private boolean didRebuffer;
|
||||||
|
private boolean isBufferEmpty;
|
||||||
private long chunkDurationUs;
|
private long chunkDurationUs;
|
||||||
@Nullable private @CmcdData.ObjectType String objectType;
|
@Nullable private @CmcdData.ObjectType String objectType;
|
||||||
@Nullable private String nextObjectRequest;
|
@Nullable private String nextObjectRequest;
|
||||||
@ -112,41 +118,15 @@ public final class CmcdData {
|
|||||||
/**
|
/**
|
||||||
* Creates an instance.
|
* Creates an instance.
|
||||||
*
|
*
|
||||||
* @param cmcdConfiguration The {@link CmcdConfiguration} for this chunk source.
|
* @param cmcdConfiguration The {@link CmcdConfiguration} for this source.
|
||||||
* @param trackSelection The {@linkplain ExoTrackSelection track selection}.
|
* @param streamingFormat The streaming format of the media content.
|
||||||
* @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.
|
|
||||||
*/
|
*/
|
||||||
public Factory(
|
public Factory(
|
||||||
CmcdConfiguration cmcdConfiguration,
|
CmcdConfiguration cmcdConfiguration, @CmcdData.StreamingFormat String streamingFormat) {
|
||||||
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);
|
|
||||||
this.cmcdConfiguration = cmcdConfiguration;
|
this.cmcdConfiguration = cmcdConfiguration;
|
||||||
this.trackSelection = trackSelection;
|
this.bufferedDurationUs = C.TIME_UNSET;
|
||||||
this.bufferedDurationUs = bufferedDurationUs;
|
this.playbackRate = C.RATE_UNSET;
|
||||||
this.playbackRate = playbackRate;
|
|
||||||
this.streamingFormat = streamingFormat;
|
this.streamingFormat = streamingFormat;
|
||||||
this.isLive = isLive;
|
|
||||||
this.didRebuffer = didRebuffer;
|
|
||||||
this.isBufferEmpty = isBufferEmpty;
|
|
||||||
this.chunkDurationUs = C.TIME_UNSET;
|
this.chunkDurationUs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +140,6 @@ public final class CmcdData {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static @CmcdData.ObjectType String getObjectType(ExoTrackSelection trackSelection) {
|
public static @CmcdData.ObjectType String getObjectType(ExoTrackSelection trackSelection) {
|
||||||
checkArgument(trackSelection != null);
|
|
||||||
@TrackType
|
@TrackType
|
||||||
int trackType = MimeTypes.getTrackType(trackSelection.getSelectedFormat().sampleMimeType);
|
int trackType = MimeTypes.getTrackType(trackSelection.getSelectedFormat().sampleMimeType);
|
||||||
if (trackType == C.TRACK_TYPE_UNKNOWN) {
|
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
|
* Sets the duration of current media chunk being requested, in microseconds.
|
||||||
* is {@link C#TIME_UNSET}.
|
*
|
||||||
|
* <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.
|
* @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
|
* Sets the object type of the current object being requested.
|
||||||
* types specified by the {@link CmcdData.ObjectType} annotation.
|
|
||||||
*
|
*
|
||||||
* <p>Default is {@code null}.
|
* <p>Default is {@code null}.
|
||||||
*/
|
*/
|
||||||
@ -226,32 +209,146 @@ public final class CmcdData {
|
|||||||
return this;
|
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() {
|
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 =
|
ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String> customData =
|
||||||
cmcdConfiguration.requestConfig.getCustomData();
|
cmcdConfiguration.requestConfig.getCustomData();
|
||||||
for (String headerKey : customData.keySet()) {
|
for (String headerKey : customData.keySet()) {
|
||||||
validateCustomDataListFormat(customData.get(headerKey));
|
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();
|
CmcdObject.Builder cmcdObject = new CmcdObject.Builder();
|
||||||
if (!getIsInitSegment()) {
|
|
||||||
if (cmcdConfiguration.isBitrateLoggingAllowed()) {
|
if (cmcdConfiguration.isBitrateLoggingAllowed()) {
|
||||||
cmcdObject.setBitrateKbps(bitrateKbps);
|
cmcdObject.setBitrateKbps(bitrateKbps);
|
||||||
}
|
}
|
||||||
if (cmcdConfiguration.isTopBitrateLoggingAllowed()) {
|
if (cmcdConfiguration.isTopBitrateLoggingAllowed()) {
|
||||||
TrackGroup trackGroup = trackSelection.getTrackGroup();
|
cmcdObject.setTopBitrateKbps(topBitrateKbps);
|
||||||
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 (isMediaObjectType && cmcdConfiguration.isObjectDurationLoggingAllowed()) {
|
||||||
}
|
|
||||||
if (cmcdConfiguration.isObjectDurationLoggingAllowed()) {
|
|
||||||
cmcdObject.setObjectDurationMs(Util.usToMs(chunkDurationUs));
|
cmcdObject.setObjectDurationMs(Util.usToMs(chunkDurationUs));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (cmcdConfiguration.isObjectTypeLoggingAllowed()) {
|
if (cmcdConfiguration.isObjectTypeLoggingAllowed()) {
|
||||||
cmcdObject.setObjectType(objectType);
|
cmcdObject.setObjectType(objectType);
|
||||||
}
|
}
|
||||||
@ -260,17 +357,17 @@ public final class CmcdData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CmcdRequest.Builder cmcdRequest = new CmcdRequest.Builder();
|
CmcdRequest.Builder cmcdRequest = new CmcdRequest.Builder();
|
||||||
if (!getIsInitSegment() && cmcdConfiguration.isBufferLengthLoggingAllowed()) {
|
if (isMediaObjectType) {
|
||||||
|
if (cmcdConfiguration.isBufferLengthLoggingAllowed()) {
|
||||||
cmcdRequest.setBufferLengthMs(Util.usToMs(bufferedDurationUs));
|
cmcdRequest.setBufferLengthMs(Util.usToMs(bufferedDurationUs));
|
||||||
}
|
}
|
||||||
if (cmcdConfiguration.isMeasuredThroughputLoggingAllowed()
|
|
||||||
&& trackSelection.getLatestBitrateEstimate() != C.RATE_UNSET_INT) {
|
|
||||||
cmcdRequest.setMeasuredThroughputInKbps(
|
|
||||||
Util.ceilDivide(trackSelection.getLatestBitrateEstimate(), 1000));
|
|
||||||
}
|
|
||||||
if (cmcdConfiguration.isDeadlineLoggingAllowed()) {
|
if (cmcdConfiguration.isDeadlineLoggingAllowed()) {
|
||||||
cmcdRequest.setDeadlineMs(Util.usToMs((long) (bufferedDurationUs / playbackRate)));
|
cmcdRequest.setDeadlineMs(Util.usToMs((long) (bufferedDurationUs / playbackRate)));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (cmcdConfiguration.isMeasuredThroughputLoggingAllowed()) {
|
||||||
|
cmcdRequest.setMeasuredThroughputInKbps(latestBitrateEstimateKbps);
|
||||||
|
}
|
||||||
if (cmcdConfiguration.isStartupLoggingAllowed()) {
|
if (cmcdConfiguration.isStartupLoggingAllowed()) {
|
||||||
cmcdRequest.setStartup(didRebuffer || isBufferEmpty);
|
cmcdRequest.setStartup(didRebuffer || isBufferEmpty);
|
||||||
}
|
}
|
||||||
@ -294,8 +391,8 @@ public final class CmcdData {
|
|||||||
if (cmcdConfiguration.isStreamingFormatLoggingAllowed()) {
|
if (cmcdConfiguration.isStreamingFormatLoggingAllowed()) {
|
||||||
cmcdSession.setStreamingFormat(streamingFormat);
|
cmcdSession.setStreamingFormat(streamingFormat);
|
||||||
}
|
}
|
||||||
if (cmcdConfiguration.isStreamTypeLoggingAllowed()) {
|
if (isLive != null && cmcdConfiguration.isStreamTypeLoggingAllowed()) {
|
||||||
cmcdSession.setStreamType(isLive ? STREAM_TYPE_LIVE : STREAM_TYPE_VOD);
|
cmcdSession.setStreamType(checkNotNull(isLive) ? STREAM_TYPE_LIVE : STREAM_TYPE_VOD);
|
||||||
}
|
}
|
||||||
if (cmcdConfiguration.isPlaybackRateLoggingAllowed()) {
|
if (cmcdConfiguration.isPlaybackRateLoggingAllowed()) {
|
||||||
cmcdSession.setPlaybackRate(playbackRate);
|
cmcdSession.setPlaybackRate(playbackRate);
|
||||||
@ -306,8 +403,7 @@ public final class CmcdData {
|
|||||||
|
|
||||||
CmcdStatus.Builder cmcdStatus = new CmcdStatus.Builder();
|
CmcdStatus.Builder cmcdStatus = new CmcdStatus.Builder();
|
||||||
if (cmcdConfiguration.isMaximumRequestThroughputLoggingAllowed()) {
|
if (cmcdConfiguration.isMaximumRequestThroughputLoggingAllowed()) {
|
||||||
cmcdStatus.setMaximumRequestedThroughputKbps(
|
cmcdStatus.setMaximumRequestedThroughputKbps(requestedMaximumThroughputKbps);
|
||||||
cmcdConfiguration.requestConfig.getRequestedMaximumThroughputKbps(bitrateKbps));
|
|
||||||
}
|
}
|
||||||
if (cmcdConfiguration.isBufferStarvationLoggingAllowed()) {
|
if (cmcdConfiguration.isBufferStarvationLoggingAllowed()) {
|
||||||
cmcdStatus.setBufferStarvation(didRebuffer);
|
cmcdStatus.setBufferStarvation(didRebuffer);
|
||||||
@ -324,8 +420,14 @@ public final class CmcdData {
|
|||||||
cmcdConfiguration.dataTransmissionMode);
|
cmcdConfiguration.dataTransmissionMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean getIsInitSegment() {
|
private static boolean isManifestObjectType(@Nullable @ObjectType String objectType) {
|
||||||
return objectType != null && objectType.equals(OBJECT_TYPE_INIT_SEGMENT);
|
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) {
|
private void validateCustomDataListFormat(List<String> customDataList) {
|
||||||
@ -360,7 +462,8 @@ public final class CmcdData {
|
|||||||
Factory.OBJECT_TYPE_INIT_SEGMENT,
|
Factory.OBJECT_TYPE_INIT_SEGMENT,
|
||||||
Factory.OBJECT_TYPE_AUDIO_ONLY,
|
Factory.OBJECT_TYPE_AUDIO_ONLY,
|
||||||
Factory.OBJECT_TYPE_VIDEO_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
|
@Documented
|
||||||
@Target(TYPE_USE)
|
@Target(TYPE_USE)
|
||||||
@ -528,10 +631,8 @@ public final class CmcdData {
|
|||||||
public final long objectDurationMs;
|
public final long objectDurationMs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The media type of the current object being requested , or {@code null} if unset. Must be one
|
* The media type of the current object being requested. Must be one of the allowed object types
|
||||||
* of the allowed object types specified by the {@link ObjectType} annotation.
|
* specified by the {@link ObjectType} annotation.
|
||||||
*
|
|
||||||
* <p>If the object type being requested is unknown, then this key MUST NOT be used.
|
|
||||||
*/
|
*/
|
||||||
@Nullable public final @ObjectType String objectType;
|
@Nullable public final @ObjectType String objectType;
|
||||||
|
|
||||||
@ -607,8 +708,13 @@ public final class CmcdData {
|
|||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setBufferLengthMs(long bufferLengthMs) {
|
public Builder setBufferLengthMs(long bufferLengthMs) {
|
||||||
checkArgument(bufferLengthMs >= 0 || bufferLengthMs == C.TIME_UNSET);
|
if (bufferLengthMs == C.TIME_UNSET) {
|
||||||
|
this.bufferLengthMs = bufferLengthMs;
|
||||||
|
} else if (bufferLengthMs >= 0) {
|
||||||
this.bufferLengthMs = ((bufferLengthMs + 50) / 100) * 100;
|
this.bufferLengthMs = ((bufferLengthMs + 50) / 100) * 100;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -621,10 +727,13 @@ public final class CmcdData {
|
|||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setMeasuredThroughputInKbps(long measuredThroughputInKbps) {
|
public Builder setMeasuredThroughputInKbps(long measuredThroughputInKbps) {
|
||||||
checkArgument(
|
if (measuredThroughputInKbps == C.RATE_UNSET_INT) {
|
||||||
measuredThroughputInKbps >= 0 || measuredThroughputInKbps == C.RATE_UNSET_INT);
|
this.measuredThroughputInKbps = measuredThroughputInKbps;
|
||||||
|
} else if (measuredThroughputInKbps >= 0) {
|
||||||
this.measuredThroughputInKbps = ((measuredThroughputInKbps + 50) / 100) * 100;
|
this.measuredThroughputInKbps = ((measuredThroughputInKbps + 50) / 100) * 100;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -637,8 +746,13 @@ public final class CmcdData {
|
|||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setDeadlineMs(long deadlineMs) {
|
public Builder setDeadlineMs(long deadlineMs) {
|
||||||
checkArgument(deadlineMs >= 0 || deadlineMs == C.TIME_UNSET);
|
if (deadlineMs == C.TIME_UNSET) {
|
||||||
|
this.deadlineMs = deadlineMs;
|
||||||
|
} else if (deadlineMs >= 0) {
|
||||||
this.deadlineMs = ((deadlineMs + 50) / 100) * 100;
|
this.deadlineMs = ((deadlineMs + 50) / 100) * 100;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,23 +36,48 @@ import org.junit.runner.RunWith;
|
|||||||
public class CmcdDataTest {
|
public class CmcdDataTest {
|
||||||
|
|
||||||
@Test
|
@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 =
|
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||||
mediaItem ->
|
mediaItem ->
|
||||||
new CmcdConfiguration(
|
new CmcdConfiguration(
|
||||||
"sessionId",
|
"sessionId",
|
||||||
mediaItem.mediaId,
|
mediaItem.mediaId,
|
||||||
new CmcdConfiguration.RequestConfig() {
|
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
|
@Override
|
||||||
public int getRequestedMaximumThroughputKbps(int throughputKbps) {
|
public int getRequestedMaximumThroughputKbps(int throughputKbps) {
|
||||||
return 2 * throughputKbps;
|
return 2 * throughputKbps;
|
||||||
@ -69,15 +94,14 @@ public class CmcdDataTest {
|
|||||||
when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L);
|
when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L);
|
||||||
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
||||||
CmcdData cmcdData =
|
CmcdData cmcdData =
|
||||||
new CmcdData.Factory(
|
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||||
cmcdConfiguration,
|
.setTrackSelection(trackSelection)
|
||||||
trackSelection,
|
.setObjectType(CmcdData.Factory.OBJECT_TYPE_AUDIO_ONLY)
|
||||||
/* bufferedDurationUs= */ 1_760_000,
|
.setBufferedDurationUs(1_760_000)
|
||||||
/* playbackRate= */ 2.0f,
|
.setPlaybackRate(2.0f)
|
||||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_DASH,
|
.setIsLive(true)
|
||||||
/* isLive= */ true,
|
.setDidRebuffer(true)
|
||||||
/* didRebuffer= */ true,
|
.setIsBufferEmpty(false)
|
||||||
/* isBufferEmpty= */ false)
|
|
||||||
.setChunkDurationUs(3_000_000)
|
.setChunkDurationUs(3_000_000)
|
||||||
.createCmcdData();
|
.createCmcdData();
|
||||||
|
|
||||||
@ -86,32 +110,23 @@ public class CmcdDataTest {
|
|||||||
assertThat(dataSpec.httpRequestHeaders)
|
assertThat(dataSpec.httpRequestHeaders)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
"CMCD-Object",
|
"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",
|
"CMCD-Request",
|
||||||
"bl=1800,dl=900,key-3=\"stringValue1,stringValue2\",mtp=500,su",
|
"bl=1800,dl=900,mtp=500,su",
|
||||||
"CMCD-Session",
|
"CMCD-Session",
|
||||||
"cid=\"mediaId\",pr=2.00,sf=d,sid=\"sessionId\",st=l",
|
"cid=\"mediaId\",pr=2.00,sf=d,sid=\"sessionId\",st=l",
|
||||||
"CMCD-Status",
|
"CMCD-Status",
|
||||||
"bs,key-4=\"stringValue3=stringValue4\",rtp=1700");
|
"bs,rtp=1700");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createInstance_populatesCmcdHttpQueryParameters() {
|
public void createInstance_audioObjectType_setsCorrectQueryParameters() {
|
||||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||||
mediaItem ->
|
mediaItem ->
|
||||||
new CmcdConfiguration(
|
new CmcdConfiguration(
|
||||||
"sessionId",
|
"sessionId",
|
||||||
mediaItem.mediaId,
|
mediaItem.mediaId,
|
||||||
new CmcdConfiguration.RequestConfig() {
|
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
|
@Override
|
||||||
public int getRequestedMaximumThroughputKbps(int throughputKbps) {
|
public int getRequestedMaximumThroughputKbps(int throughputKbps) {
|
||||||
return 2 * throughputKbps;
|
return 2 * throughputKbps;
|
||||||
@ -129,32 +144,148 @@ public class CmcdDataTest {
|
|||||||
when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L);
|
when(trackSelection.getLatestBitrateEstimate()).thenReturn(500_000L);
|
||||||
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
DataSpec dataSpec = new DataSpec.Builder().setUri(Uri.EMPTY).build();
|
||||||
CmcdData cmcdData =
|
CmcdData cmcdData =
|
||||||
new CmcdData.Factory(
|
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||||
cmcdConfiguration,
|
.setObjectType(CmcdData.Factory.OBJECT_TYPE_AUDIO_ONLY)
|
||||||
trackSelection,
|
.setTrackSelection(trackSelection)
|
||||||
/* bufferedDurationUs= */ 1_760_000,
|
.setBufferedDurationUs(1_760_000)
|
||||||
/* playbackRate= */ 2.0f,
|
.setPlaybackRate(2.0f)
|
||||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_DASH,
|
.setIsLive(true)
|
||||||
/* isLive= */ true,
|
.setDidRebuffer(true)
|
||||||
/* didRebuffer= */ true,
|
.setIsBufferEmpty(false)
|
||||||
/* isBufferEmpty= */ false)
|
|
||||||
.setChunkDurationUs(3_000_000)
|
.setChunkDurationUs(3_000_000)
|
||||||
.createCmcdData();
|
.createCmcdData();
|
||||||
|
|
||||||
dataSpec = cmcdData.addToDataSpec(dataSpec);
|
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))
|
assertThat(dataSpec.uri.getQueryParameter(CmcdConfiguration.CMCD_QUERY_PARAMETER_KEY))
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
"bl=1800,br=840,bs,cid=\"mediaId\",d=3000,dl=900,key-1=1,"
|
"bl=1800,br=840,bs,cid=\"mediaId\",d=3000,dl=900,mtp=500,ot=a,pr=2.00,"
|
||||||
+ "key-2=\"stringVälue1,stringVälue2\",mtp=500,pr=2.00,rtp=1700,sf=d,"
|
+ "rtp=1700,sf=d,sid=\"sessionId\",st=l,su,tb=1000");
|
||||||
+ "sid=\"sessionId\",st=l,su,tb=1000");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 =
|
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||||
mediaItem ->
|
mediaItem ->
|
||||||
new CmcdConfiguration(
|
new CmcdConfiguration(
|
||||||
@ -164,27 +295,94 @@ public class CmcdDataTest {
|
|||||||
@Override
|
@Override
|
||||||
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
public ImmutableListMultimap<@CmcdConfiguration.HeaderKey String, String>
|
||||||
getCustomData() {
|
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");
|
return ImmutableListMultimap.of("CMCD-Object", "key1=1");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
|
||||||
CmcdConfiguration cmcdConfiguration =
|
CmcdConfiguration cmcdConfiguration =
|
||||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
cmcdConfigurationFactory.createCmcdConfiguration(MediaItem.EMPTY);
|
||||||
ExoTrackSelection trackSelection = mock(ExoTrackSelection.class);
|
|
||||||
when(trackSelection.getSelectedFormat()).thenReturn(new Format.Builder().build());
|
|
||||||
|
|
||||||
assertThrows(
|
assertThrows(
|
||||||
IllegalStateException.class,
|
IllegalStateException.class,
|
||||||
() ->
|
() ->
|
||||||
new CmcdData.Factory(
|
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||||
cmcdConfiguration,
|
.setObjectType(CmcdData.Factory.OBJECT_TYPE_MANIFEST)
|
||||||
trackSelection,
|
|
||||||
/* bufferedDurationUs= */ 0,
|
|
||||||
/* playbackRate= */ 1.0f,
|
|
||||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_DASH,
|
|
||||||
/* isLive= */ true,
|
|
||||||
/* didRebuffer= */ true,
|
|
||||||
/* isBufferEmpty= */ false)
|
|
||||||
.createCmcdData());
|
.createCmcdData());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ import androidx.media3.common.util.Log;
|
|||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
|
import androidx.media3.datasource.DataSpec;
|
||||||
import androidx.media3.datasource.TransferListener;
|
import androidx.media3.datasource.TransferListener;
|
||||||
import androidx.media3.exoplayer.dash.PlayerEmsgHandler.PlayerEmsgCallback;
|
import androidx.media3.exoplayer.dash.PlayerEmsgHandler.PlayerEmsgCallback;
|
||||||
import androidx.media3.exoplayer.dash.manifest.AdaptationSet;
|
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.source.SequenceableLoader;
|
||||||
import androidx.media3.exoplayer.upstream.Allocator;
|
import androidx.media3.exoplayer.upstream.Allocator;
|
||||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||||
|
import androidx.media3.exoplayer.upstream.CmcdData;
|
||||||
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
|
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
|
||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
||||||
@ -1099,8 +1101,19 @@ public final class DashMediaSource extends BaseMediaSource {
|
|||||||
manifestUri = this.manifestUri;
|
manifestUri = this.manifestUri;
|
||||||
}
|
}
|
||||||
manifestLoadPending = false;
|
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(
|
startLoading(
|
||||||
new ParsingLoadable<>(dataSource, manifestUri, C.DATA_TYPE_MANIFEST, manifestParser),
|
new ParsingLoadable<>(dataSource, dataSpec, C.DATA_TYPE_MANIFEST, manifestParser),
|
||||||
manifestCallback,
|
manifestCallback,
|
||||||
loadErrorHandlingPolicy.getMinimumLoadableRetryCount(C.DATA_TYPE_MANIFEST));
|
loadErrorHandlingPolicy.getMinimumLoadableRetryCount(C.DATA_TYPE_MANIFEST));
|
||||||
}
|
}
|
||||||
|
@ -421,15 +421,13 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
CmcdData.Factory cmcdDataFactory =
|
CmcdData.Factory cmcdDataFactory =
|
||||||
cmcdConfiguration == null
|
cmcdConfiguration == null
|
||||||
? null
|
? null
|
||||||
: new CmcdData.Factory(
|
: new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_DASH)
|
||||||
cmcdConfiguration,
|
.setTrackSelection(trackSelection)
|
||||||
trackSelection,
|
.setBufferedDurationUs(max(0, bufferedDurationUs))
|
||||||
max(0, bufferedDurationUs),
|
.setPlaybackRate(loadingInfo.playbackSpeed)
|
||||||
/* playbackRate= */ loadingInfo.playbackSpeed,
|
.setIsLive(manifest.dynamic)
|
||||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_DASH,
|
.setDidRebuffer(loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs))
|
||||||
/* isLive= */ manifest.dynamic,
|
.setIsBufferEmpty(queue.isEmpty());
|
||||||
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
|
|
||||||
/* isBufferEmpty= */ queue.isEmpty());
|
|
||||||
lastChunkRequestRealtimeMs = SystemClock.elapsedRealtime();
|
lastChunkRequestRealtimeMs = SystemClock.elapsedRealtime();
|
||||||
|
|
||||||
RepresentationHolder representationHolder = updateSelectedBaseUrl(selectedTrackIndex);
|
RepresentationHolder representationHolder = updateSelectedBaseUrl(selectedTrackIndex);
|
||||||
@ -715,7 +713,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
* indexUri} is not {@code null}.
|
* indexUri} is not {@code null}.
|
||||||
* @param indexUri The URI pointing to index data. Can be {@code null} if {@code
|
* @param indexUri The URI pointing to index data. Can be {@code null} if {@code
|
||||||
* initializationUri} is not {@code null}.
|
* 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")
|
@RequiresNonNull("#1.chunkExtractor")
|
||||||
protected Chunk newInitializationChunk(
|
protected Chunk newInitializationChunk(
|
||||||
|
@ -507,20 +507,17 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
@Nullable CmcdData.Factory cmcdDataFactory = null;
|
@Nullable CmcdData.Factory cmcdDataFactory = null;
|
||||||
if (cmcdConfiguration != null) {
|
if (cmcdConfiguration != null) {
|
||||||
cmcdDataFactory =
|
cmcdDataFactory =
|
||||||
new CmcdData.Factory(
|
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_HLS)
|
||||||
cmcdConfiguration,
|
.setTrackSelection(trackSelection)
|
||||||
trackSelection,
|
.setBufferedDurationUs(max(0, bufferedDurationUs))
|
||||||
max(0, bufferedDurationUs),
|
.setPlaybackRate(loadingInfo.playbackSpeed)
|
||||||
/* playbackRate= */ loadingInfo.playbackSpeed,
|
.setIsLive(!playlist.hasEndTag)
|
||||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_HLS,
|
.setDidRebuffer(loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs))
|
||||||
/* isLive= */ !playlist.hasEndTag,
|
.setIsBufferEmpty(queue.isEmpty())
|
||||||
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
|
|
||||||
/* isBufferEmpty= */ queue.isEmpty())
|
|
||||||
.setObjectType(
|
.setObjectType(
|
||||||
getIsMuxedAudioAndVideo()
|
getIsMuxedAudioAndVideo()
|
||||||
? CmcdData.Factory.OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO
|
? CmcdData.Factory.OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO
|
||||||
: CmcdData.Factory.getObjectType(trackSelection));
|
: CmcdData.Factory.getObjectType(trackSelection));
|
||||||
|
|
||||||
long nextMediaSequence =
|
long nextMediaSequence =
|
||||||
segmentBaseHolder.partIndex == C.INDEX_UNSET
|
segmentBaseHolder.partIndex == C.INDEX_UNSET
|
||||||
? segmentBaseHolder.mediaSequence + 1
|
? segmentBaseHolder.mediaSequence + 1
|
||||||
|
@ -417,7 +417,10 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
drmSessionManagerProvider.get(mediaItem),
|
drmSessionManagerProvider.get(mediaItem),
|
||||||
loadErrorHandlingPolicy,
|
loadErrorHandlingPolicy,
|
||||||
playlistTrackerFactory.createTracker(
|
playlistTrackerFactory.createTracker(
|
||||||
hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory),
|
hlsDataSourceFactory,
|
||||||
|
loadErrorHandlingPolicy,
|
||||||
|
playlistParserFactory,
|
||||||
|
cmcdConfiguration),
|
||||||
elapsedRealTimeOffsetMs,
|
elapsedRealTimeOffsetMs,
|
||||||
allowChunklessPreparation,
|
allowChunklessPreparation,
|
||||||
metadataType,
|
metadataType,
|
||||||
|
@ -29,6 +29,7 @@ import androidx.media3.common.util.Assertions;
|
|||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
|
import androidx.media3.datasource.DataSpec;
|
||||||
import androidx.media3.datasource.HttpDataSource;
|
import androidx.media3.datasource.HttpDataSource;
|
||||||
import androidx.media3.exoplayer.hls.HlsDataSourceFactory;
|
import androidx.media3.exoplayer.hls.HlsDataSourceFactory;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Part;
|
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.LoadEventInfo;
|
||||||
import androidx.media3.exoplayer.source.MediaLoadData;
|
import androidx.media3.exoplayer.source.MediaLoadData;
|
||||||
import androidx.media3.exoplayer.source.MediaSourceEventListener.EventDispatcher;
|
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;
|
||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
||||||
import androidx.media3.exoplayer.upstream.Loader;
|
import androidx.media3.exoplayer.upstream.Loader;
|
||||||
@ -69,6 +72,7 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
private final HashMap<Uri, MediaPlaylistBundle> playlistBundles;
|
private final HashMap<Uri, MediaPlaylistBundle> playlistBundles;
|
||||||
private final CopyOnWriteArrayList<PlaylistEventListener> listeners;
|
private final CopyOnWriteArrayList<PlaylistEventListener> listeners;
|
||||||
private final double playlistStuckTargetDurationCoefficient;
|
private final double playlistStuckTargetDurationCoefficient;
|
||||||
|
@Nullable private final CmcdConfiguration cmcdConfiguration;
|
||||||
|
|
||||||
@Nullable private EventDispatcher eventDispatcher;
|
@Nullable private EventDispatcher eventDispatcher;
|
||||||
@Nullable private Loader initialPlaylistLoader;
|
@Nullable private Loader initialPlaylistLoader;
|
||||||
@ -86,15 +90,18 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
* @param dataSourceFactory A factory for {@link DataSource} instances.
|
* @param dataSourceFactory A factory for {@link DataSource} instances.
|
||||||
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}.
|
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}.
|
||||||
* @param playlistParserFactory An {@link HlsPlaylistParserFactory}.
|
* @param playlistParserFactory An {@link HlsPlaylistParserFactory}.
|
||||||
|
* @param cmcdConfiguration The {@link CmcdConfiguration}.
|
||||||
*/
|
*/
|
||||||
public DefaultHlsPlaylistTracker(
|
public DefaultHlsPlaylistTracker(
|
||||||
HlsDataSourceFactory dataSourceFactory,
|
HlsDataSourceFactory dataSourceFactory,
|
||||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
||||||
HlsPlaylistParserFactory playlistParserFactory) {
|
HlsPlaylistParserFactory playlistParserFactory,
|
||||||
|
@Nullable CmcdConfiguration cmcdConfiguration) {
|
||||||
this(
|
this(
|
||||||
dataSourceFactory,
|
dataSourceFactory,
|
||||||
loadErrorHandlingPolicy,
|
loadErrorHandlingPolicy,
|
||||||
playlistParserFactory,
|
playlistParserFactory,
|
||||||
|
cmcdConfiguration,
|
||||||
DEFAULT_PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT);
|
DEFAULT_PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +111,7 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
* @param dataSourceFactory A factory for {@link DataSource} instances.
|
* @param dataSourceFactory A factory for {@link DataSource} instances.
|
||||||
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}.
|
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}.
|
||||||
* @param playlistParserFactory An {@link HlsPlaylistParserFactory}.
|
* @param playlistParserFactory An {@link HlsPlaylistParserFactory}.
|
||||||
|
* @param cmcdConfiguration The {@link CmcdConfiguration}.
|
||||||
* @param playlistStuckTargetDurationCoefficient A coefficient to apply to the target duration of
|
* @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
|
* 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
|
* playlist is deemed stuck, a {@link PlaylistStuckException} is thrown via {@link
|
||||||
@ -113,10 +121,12 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
HlsDataSourceFactory dataSourceFactory,
|
HlsDataSourceFactory dataSourceFactory,
|
||||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
||||||
HlsPlaylistParserFactory playlistParserFactory,
|
HlsPlaylistParserFactory playlistParserFactory,
|
||||||
|
@Nullable CmcdConfiguration cmcdConfiguration,
|
||||||
double playlistStuckTargetDurationCoefficient) {
|
double playlistStuckTargetDurationCoefficient) {
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
this.playlistParserFactory = playlistParserFactory;
|
this.playlistParserFactory = playlistParserFactory;
|
||||||
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
||||||
|
this.cmcdConfiguration = cmcdConfiguration;
|
||||||
this.playlistStuckTargetDurationCoefficient = playlistStuckTargetDurationCoefficient;
|
this.playlistStuckTargetDurationCoefficient = playlistStuckTargetDurationCoefficient;
|
||||||
listeners = new CopyOnWriteArrayList<>();
|
listeners = new CopyOnWriteArrayList<>();
|
||||||
playlistBundles = new HashMap<>();
|
playlistBundles = new HashMap<>();
|
||||||
@ -133,10 +143,22 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
this.playlistRefreshHandler = Util.createHandlerForCurrentLooper();
|
this.playlistRefreshHandler = Util.createHandlerForCurrentLooper();
|
||||||
this.eventDispatcher = eventDispatcher;
|
this.eventDispatcher = eventDispatcher;
|
||||||
this.primaryPlaylistListener = primaryPlaylistListener;
|
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 =
|
ParsingLoadable<HlsPlaylist> multivariantPlaylistLoadable =
|
||||||
new ParsingLoadable<>(
|
new ParsingLoadable<>(
|
||||||
dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST),
|
dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST),
|
||||||
initialPlaylistUri,
|
dataSpec,
|
||||||
C.DATA_TYPE_MANIFEST,
|
C.DATA_TYPE_MANIFEST,
|
||||||
playlistParserFactory.createPlaylistParser());
|
playlistParserFactory.createPlaylistParser());
|
||||||
Assertions.checkState(initialPlaylistLoader == null);
|
Assertions.checkState(initialPlaylistLoader == null);
|
||||||
@ -762,12 +784,23 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
private void loadPlaylistImmediately(Uri playlistRequestUri) {
|
private void loadPlaylistImmediately(Uri playlistRequestUri) {
|
||||||
ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser =
|
ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser =
|
||||||
playlistParserFactory.createPlaylistParser(multivariantPlaylist, playlistSnapshot);
|
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 =
|
ParsingLoadable<HlsPlaylist> mediaPlaylistLoadable =
|
||||||
new ParsingLoadable<>(
|
new ParsingLoadable<>(
|
||||||
mediaPlaylistDataSource,
|
mediaPlaylistDataSource, dataSpec, C.DATA_TYPE_MANIFEST, mediaPlaylistParser);
|
||||||
playlistRequestUri,
|
|
||||||
C.DATA_TYPE_MANIFEST,
|
|
||||||
mediaPlaylistParser);
|
|
||||||
mediaPlaylistLoader.startLoading(
|
mediaPlaylistLoader.startLoading(
|
||||||
mediaPlaylistLoadable,
|
mediaPlaylistLoadable,
|
||||||
/* callback= */ this,
|
/* callback= */ this,
|
||||||
|
@ -21,6 +21,7 @@ import androidx.media3.common.C;
|
|||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.exoplayer.hls.HlsDataSourceFactory;
|
import androidx.media3.exoplayer.hls.HlsDataSourceFactory;
|
||||||
import androidx.media3.exoplayer.source.MediaSourceEventListener.EventDispatcher;
|
import androidx.media3.exoplayer.source.MediaSourceEventListener.EventDispatcher;
|
||||||
|
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@ -48,11 +49,13 @@ public interface HlsPlaylistTracker {
|
|||||||
* @param dataSourceFactory The {@link HlsDataSourceFactory} to use for playlist loading.
|
* @param dataSourceFactory The {@link HlsDataSourceFactory} to use for playlist loading.
|
||||||
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy} for playlist load errors.
|
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy} for playlist load errors.
|
||||||
* @param playlistParserFactory The {@link HlsPlaylistParserFactory} for playlist parsing.
|
* @param playlistParserFactory The {@link HlsPlaylistParserFactory} for playlist parsing.
|
||||||
|
* @param cmcdConfiguration The {@link CmcdConfiguration} to use for playlist loading.
|
||||||
*/
|
*/
|
||||||
HlsPlaylistTracker createTracker(
|
HlsPlaylistTracker createTracker(
|
||||||
HlsDataSourceFactory dataSourceFactory,
|
HlsDataSourceFactory dataSourceFactory,
|
||||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
||||||
HlsPlaylistParserFactory playlistParserFactory);
|
HlsPlaylistParserFactory playlistParserFactory,
|
||||||
|
@Nullable CmcdConfiguration cmcdConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Listener for primary playlist changes. */
|
/** Listener for primary playlist changes. */
|
||||||
|
@ -404,7 +404,8 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
new DefaultHlsPlaylistTracker(
|
new DefaultHlsPlaylistTracker(
|
||||||
dataType -> new DefaultHttpDataSource.Factory().createDataSource(),
|
dataType -> new DefaultHttpDataSource.Factory().createDataSource(),
|
||||||
new DefaultLoadErrorHandlingPolicy(),
|
new DefaultLoadErrorHandlingPolicy(),
|
||||||
new DefaultHlsPlaylistParserFactory());
|
new DefaultHlsPlaylistParserFactory(),
|
||||||
|
/* cmcdConfiguration= */ null);
|
||||||
AtomicInteger playlistChangedCounter = new AtomicInteger();
|
AtomicInteger playlistChangedCounter = new AtomicInteger();
|
||||||
AtomicReference<TimeoutException> audioPlaylistRefreshExceptionRef = new AtomicReference<>();
|
AtomicReference<TimeoutException> audioPlaylistRefreshExceptionRef = new AtomicReference<>();
|
||||||
defaultHlsPlaylistTracker.addListener(
|
defaultHlsPlaylistTracker.addListener(
|
||||||
@ -486,7 +487,8 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
new DefaultHlsPlaylistTracker(
|
new DefaultHlsPlaylistTracker(
|
||||||
dataType -> new DefaultHttpDataSource.Factory().createDataSource(),
|
dataType -> new DefaultHttpDataSource.Factory().createDataSource(),
|
||||||
new DefaultLoadErrorHandlingPolicy(),
|
new DefaultLoadErrorHandlingPolicy(),
|
||||||
new DefaultHlsPlaylistParserFactory());
|
new DefaultHlsPlaylistParserFactory(),
|
||||||
|
/* cmcdConfiguration= */ null);
|
||||||
List<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>();
|
List<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>();
|
||||||
AtomicInteger playlistCounter = new AtomicInteger();
|
AtomicInteger playlistCounter = new AtomicInteger();
|
||||||
AtomicReference<TimeoutException> primaryPlaylistChangeExceptionRef = new AtomicReference<>();
|
AtomicReference<TimeoutException> primaryPlaylistChangeExceptionRef = new AtomicReference<>();
|
||||||
@ -562,7 +564,8 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
new DefaultHlsPlaylistTracker(
|
new DefaultHlsPlaylistTracker(
|
||||||
dataType -> new DefaultHttpDataSource.Factory().createDataSource(),
|
dataType -> new DefaultHttpDataSource.Factory().createDataSource(),
|
||||||
new DefaultLoadErrorHandlingPolicy(),
|
new DefaultLoadErrorHandlingPolicy(),
|
||||||
new DefaultHlsPlaylistParserFactory());
|
new DefaultHlsPlaylistParserFactory(),
|
||||||
|
/* cmcdConfiguration= */ null);
|
||||||
List<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>();
|
List<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>();
|
||||||
AtomicInteger playlistCounter = new AtomicInteger();
|
AtomicInteger playlistCounter = new AtomicInteger();
|
||||||
AtomicReference<TimeoutException> playlistRefreshExceptionRef = new AtomicReference<>();
|
AtomicReference<TimeoutException> playlistRefreshExceptionRef = new AtomicReference<>();
|
||||||
@ -674,7 +677,8 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
new DefaultHlsPlaylistTracker(
|
new DefaultHlsPlaylistTracker(
|
||||||
dataType -> dataSourceFactory.createDataSource(),
|
dataType -> dataSourceFactory.createDataSource(),
|
||||||
new DefaultLoadErrorHandlingPolicy(),
|
new DefaultLoadErrorHandlingPolicy(),
|
||||||
new DefaultHlsPlaylistParserFactory());
|
new DefaultHlsPlaylistParserFactory(),
|
||||||
|
/* cmcdConfiguration= */ null);
|
||||||
|
|
||||||
List<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>();
|
List<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>();
|
||||||
AtomicInteger playlistCounter = new AtomicInteger();
|
AtomicInteger playlistCounter = new AtomicInteger();
|
||||||
|
@ -360,15 +360,13 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
|||||||
@Nullable CmcdData.Factory cmcdDataFactory = null;
|
@Nullable CmcdData.Factory cmcdDataFactory = null;
|
||||||
if (cmcdConfiguration != null) {
|
if (cmcdConfiguration != null) {
|
||||||
cmcdDataFactory =
|
cmcdDataFactory =
|
||||||
new CmcdData.Factory(
|
new CmcdData.Factory(cmcdConfiguration, CmcdData.Factory.STREAMING_FORMAT_SS)
|
||||||
cmcdConfiguration,
|
.setTrackSelection(trackSelection)
|
||||||
trackSelection,
|
.setBufferedDurationUs(max(0, bufferedDurationUs))
|
||||||
max(0, bufferedDurationUs),
|
.setPlaybackRate(loadingInfo.playbackSpeed)
|
||||||
/* playbackRate= */ loadingInfo.playbackSpeed,
|
.setIsLive(manifest.isLive)
|
||||||
/* streamingFormat= */ CmcdData.Factory.STREAMING_FORMAT_SS,
|
.setDidRebuffer(loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs))
|
||||||
/* isLive= */ manifest.isLive,
|
.setIsBufferEmpty(queue.isEmpty())
|
||||||
/* didRebuffer= */ loadingInfo.rebufferedSince(lastChunkRequestRealtimeMs),
|
|
||||||
/* isBufferEmpty= */ queue.isEmpty())
|
|
||||||
.setChunkDurationUs(chunkEndTimeUs - chunkStartTimeUs)
|
.setChunkDurationUs(chunkEndTimeUs - chunkStartTimeUs)
|
||||||
.setObjectType(CmcdData.Factory.getObjectType(trackSelection));
|
.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.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
|
import androidx.media3.datasource.DataSpec;
|
||||||
import androidx.media3.datasource.TransferListener;
|
import androidx.media3.datasource.TransferListener;
|
||||||
import androidx.media3.exoplayer.drm.DefaultDrmSessionManagerProvider;
|
import androidx.media3.exoplayer.drm.DefaultDrmSessionManagerProvider;
|
||||||
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
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.source.SinglePeriodTimeline;
|
||||||
import androidx.media3.exoplayer.upstream.Allocator;
|
import androidx.media3.exoplayer.upstream.Allocator;
|
||||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||||
|
import androidx.media3.exoplayer.upstream.CmcdData;
|
||||||
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
|
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
|
||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
||||||
@ -676,9 +678,19 @@ public final class SsMediaSource extends BaseMediaSource
|
|||||||
if (manifestLoader.hasFatalError()) {
|
if (manifestLoader.hasFatalError()) {
|
||||||
return;
|
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 =
|
ParsingLoadable<SsManifest> loadable =
|
||||||
new ParsingLoadable<>(
|
new ParsingLoadable<>(manifestDataSource, dataSpec, C.DATA_TYPE_MANIFEST, manifestParser);
|
||||||
manifestDataSource, manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
|
|
||||||
manifestLoader.startLoading(
|
manifestLoader.startLoading(
|
||||||
loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type));
|
loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user