Commit 958c3596 by rohks Committed by Tofunmi Adigun-Hameed

Implement logging support for Common Media Client Data (CMCD)

Add support for including Common Media Client Data (CMCD) in the outgoing requests of adaptive streaming formats DASH, HLS, and SmoothStreaming.

API structure and API methods:
   *   CMCD logging is disabled by default, use `MediaSource.Factory.setCmcdConfigurationFactory(CmcdConfiguration.Factory cmcdConfigurationFactory)` to enable it.
   *   All keys are enabled by default, override `CmcdConfiguration.RequestConfig.isKeyAllowed(String key)` to filter out which keys are logged.
   *  Override `CmcdConfiguration.RequestConfig.getCustomData()` to enable custom key logging.

NOTE: Only the following fields have been implemented: `br`, `bl`, `cid`, `rtp`, and `sid`.

Issue: google/ExoPlayer#8699

#minor-release

PiperOrigin-RevId: 539021056
(cherry picked from commit b7e71538a3fc6892cf75f1e0799f339e63d2f1ec)
parent c79e13a8
Showing with 804 additions and 26 deletions
......@@ -39,6 +39,7 @@ import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.text.SubtitleDecoderFactory;
import com.google.android.exoplayer2.text.SubtitleExtractor;
import com.google.android.exoplayer2.ui.AdViewProvider;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
......@@ -369,6 +370,14 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
@CanIgnoreReturnValue
@Override
public DefaultMediaSourceFactory setCmcdConfigurationFactory(
CmcdConfiguration.Factory cmcdConfigurationFactory) {
delegateFactoryLoader.setCmcdConfigurationFactory(checkNotNull(cmcdConfigurationFactory));
return this;
}
@CanIgnoreReturnValue
@Override
public DefaultMediaSourceFactory setDrmSessionManagerProvider(
DrmSessionManagerProvider drmSessionManagerProvider) {
delegateFactoryLoader.setDrmSessionManagerProvider(
......@@ -548,6 +557,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
private final Map<Integer, MediaSource.Factory> mediaSourceFactories;
private DataSource.@MonotonicNonNull Factory dataSourceFactory;
@Nullable private CmcdConfiguration.Factory cmcdConfigurationFactory;
@Nullable private DrmSessionManagerProvider drmSessionManagerProvider;
@Nullable private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
......@@ -577,6 +587,9 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
}
mediaSourceFactory = mediaSourceFactorySupplier.get();
if (cmcdConfigurationFactory != null) {
mediaSourceFactory.setCmcdConfigurationFactory(cmcdConfigurationFactory);
}
if (drmSessionManagerProvider != null) {
mediaSourceFactory.setDrmSessionManagerProvider(drmSessionManagerProvider);
}
......@@ -597,6 +610,13 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
}
}
public void setCmcdConfigurationFactory(CmcdConfiguration.Factory cmcdConfigurationFactory) {
this.cmcdConfigurationFactory = cmcdConfigurationFactory;
for (MediaSource.Factory mediaSourceFactory : mediaSourceFactories.values()) {
mediaSourceFactory.setCmcdConfigurationFactory(cmcdConfigurationFactory);
}
}
public void setDrmSessionManagerProvider(DrmSessionManagerProvider drmSessionManagerProvider) {
this.drmSessionManagerProvider = drmSessionManagerProvider;
for (MediaSource.Factory mediaSourceFactory : mediaSourceFactories.values()) {
......
......@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener;
import java.io.IOException;
......@@ -66,6 +67,18 @@ public interface MediaSource {
Factory UNSUPPORTED = MediaSourceFactory.UNSUPPORTED;
/**
* Sets the {@link CmcdConfiguration.Factory} used to obtain a {@link CmcdConfiguration} for a
* {@link MediaItem}.
*
* @return This factory, for convenience.
*/
default Factory setCmcdConfigurationFactory(
CmcdConfiguration.Factory cmcdConfigurationFactory) {
// do nothing
return this;
}
/**
* Sets the {@link DrmSessionManagerProvider} used to obtain a {@link DrmSessionManager} for a
* {@link MediaItem}.
*
......
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.upstream;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.annotation.ElementType.TYPE_USE;
import androidx.annotation.Nullable;
import androidx.annotation.StringDef;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.common.collect.ImmutableMap;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.UUID;
/** Represents a configuration for the Common Media Client Data (CMCD) logging. */
public final class CmcdConfiguration {
/**
* Header keys SHOULD be allocated to one of the four defined header names based upon their
* expected level of variability:
*
* <ul>
* <li>CMCD-Object: keys whose values vary with the object being requested.
* <li>CMCD-Request: keys whose values vary with each request.
* <li>CMCD-Session: keys whose values are expected to be invariant over the life of the
* session.
* <li>CMCD-Status: keys whose values do not vary with every request or object.
* </ul>
*/
@Retention(RetentionPolicy.SOURCE)
@StringDef({KEY_CMCD_OBJECT, KEY_CMCD_REQUEST, KEY_CMCD_SESSION, KEY_CMCD_STATUS})
@Documented
@Target(TYPE_USE)
public @interface HeaderKey {}
/** Indicates that the annotated element represents a CMCD key. */
@Retention(RetentionPolicy.SOURCE)
@StringDef({
KEY_BITRATE,
KEY_BUFFER_LENGTH,
KEY_CONTENT_ID,
KEY_SESSION_ID,
KEY_MAXIMUM_REQUESTED_BITRATE
})
@Documented
@Target(TYPE_USE)
public @interface CmcdKey {}
/** Maximum length for ID fields. */
public static final int MAX_ID_LENGTH = 64;
public static final String KEY_CMCD_OBJECT = "CMCD-Object";
public static final String KEY_CMCD_REQUEST = "CMCD-Request";
public static final String KEY_CMCD_SESSION = "CMCD-Session";
public static final String KEY_CMCD_STATUS = "CMCD-Status";
public static final String KEY_BITRATE = "br";
public static final String KEY_BUFFER_LENGTH = "bl";
public static final String KEY_CONTENT_ID = "cid";
public static final String KEY_SESSION_ID = "sid";
public static final String KEY_MAXIMUM_REQUESTED_BITRATE = "rtp";
/**
* Factory for {@link CmcdConfiguration} instances.
*
* <p>Implementations must not make assumptions about which thread called their methods; and must
* be thread-safe.
*/
public interface Factory {
/**
* Creates a {@link CmcdConfiguration} based on the provided {@link MediaItem}.
*
* @param mediaItem The {@link MediaItem} from which to create the CMCD configuration.
* @return A {@link CmcdConfiguration} instance.
*/
CmcdConfiguration createCmcdConfiguration(MediaItem mediaItem);
/**
* The default factory implementation.
*
* <p>It creates a {@link CmcdConfiguration} by generating a random session ID and using the
* content ID from {@link MediaItem#mediaId} (or {@link MediaItem#DEFAULT_MEDIA_ID} if the media
* item does not have a {@link MediaItem#mediaId} defined).
*
* <p>It also utilises a default {@link RequestConfig} implementation that enables all available
* keys, provides empty custom data, and sets the maximum requested bitrate to {@link
* C#RATE_UNSET_INT}.
*/
CmcdConfiguration.Factory DEFAULT =
mediaItem ->
new CmcdConfiguration(
/* sessionId= */ UUID.randomUUID().toString(),
/* contentId= */ mediaItem.mediaId != null
? mediaItem.mediaId
: MediaItem.DEFAULT_MEDIA_ID,
new RequestConfig() {});
}
/**
* Represents configuration which can vary on each request.
*
* <p>Implementations must not make assumptions about which thread called their methods; and must
* be thread-safe.
*/
public interface RequestConfig {
/**
* Checks whether the specified key is allowed in CMCD logging. By default, all keys are
* allowed.
*
* @param key The key to check.
* @return Whether the key is allowed.
*/
default boolean isKeyAllowed(@CmcdKey String key) {
return true;
}
/**
* Retrieves the custom data associated with CMCD logging.
*
* <p>By default, no custom data is provided.
*
* <p>The key should belong to the {@link HeaderKey}. The value should consist of key-value
* pairs separated by commas. If the value contains one of the keys defined in the {@link
* CmcdKey} list, then this key should not be {@linkplain #isKeyAllowed(String) allowed},
* otherwise the key could be included twice in the produced log.
*
* <p>Example:
*
* <ul>
* <li>CMCD-Request:customField1=25400
* <li>CMCD-Object:customField2=3200,customField3=4004,customField4=v,customField5=6000
* <li>CMCD-Status:customField6,customField7=15000
* <li>CMCD-Session:customField8="6e2fb550-c457-11e9-bb97-0800200c9a66"
* </ul>
*
* @return An {@link ImmutableMap} containing the custom data.
*/
default ImmutableMap<@HeaderKey String, String> getCustomData() {
return ImmutableMap.of();
}
/**
* Returns the maximum throughput requested in kbps, or {@link C#RATE_UNSET_INT} if the maximum
* throughput is unknown in which case the maximum throughput will not be logged upstream.
*
* @param throughputKbps The throughput in kbps of the audio or video object being requested.
* @return The maximum throughput requested in kbps.
*/
default int getRequestedMaximumThroughputKbps(int throughputKbps) {
return C.RATE_UNSET_INT;
}
}
/**
* A GUID identifying the current playback session, or {@code null} if unset.
*
* <p>A playback session typically ties together segments belonging to a single media asset.
* Maximum length is 64 characters.
*/
@Nullable public final String sessionId;
/**
* A GUID identifying the current content, or {@code null} if unset.
*
* <p>This value is consistent across multiple different sessions and devices and is defined and
* updated at the discretion of the service provider. Maximum length is 64 characters.
*/
@Nullable public final String contentId;
/** Dynamic request specific configuration. */
public final RequestConfig requestConfig;
/** Creates an instance. */
public CmcdConfiguration(
@Nullable String sessionId, @Nullable String contentId, RequestConfig requestConfig) {
checkArgument(sessionId == null || sessionId.length() <= MAX_ID_LENGTH);
checkArgument(contentId == null || contentId.length() <= MAX_ID_LENGTH);
checkNotNull(requestConfig);
this.sessionId = sessionId;
this.contentId = contentId;
this.requestConfig = requestConfig;
}
/**
* Whether logging bitrate is allowed based on the {@linkplain RequestConfig request
* configuration}.
*/
public boolean isBitrateLoggingAllowed() {
return requestConfig.isKeyAllowed(KEY_BITRATE);
}
/**
* Whether logging buffer length is allowed based on the {@linkplain RequestConfig request
* configuration}.
*/
public boolean isBufferLengthLoggingAllowed() {
return requestConfig.isKeyAllowed(KEY_BUFFER_LENGTH);
}
/**
* Whether logging content ID is allowed based on the {@linkplain RequestConfig request
* configuration}.
*/
public boolean isContentIdLoggingAllowed() {
return requestConfig.isKeyAllowed(KEY_CONTENT_ID);
}
/**
* Whether logging session ID is allowed based on the {@linkplain RequestConfig request
* configuration}.
*/
public boolean isSessionIdLoggingAllowed() {
return requestConfig.isKeyAllowed(KEY_SESSION_ID);
}
/**
* Whether logging maximum requested throughput is allowed based on the {@linkplain RequestConfig
* request configuration}.
*/
public boolean isMaximumRequestThroughputLoggingAllowed() {
return requestConfig.isKeyAllowed(KEY_MAXIMUM_REQUESTED_BITRATE);
}
}
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.upstream;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link CmcdConfiguration}. */
@RunWith(AndroidJUnit4.class)
public class CmcdConfigurationTest {
private static final String TEST_CONTENT_ID = "contentId";
private static final String TEST_MEDIA_ID = "mediaId";
private static final String TEST_SESSION_ID = "sessionId";
private static final String LONG_INVALID_ID =
"9haaks0aousdjts41iczi1ilmkzxrbwf7hkuesvzt2ib44s8cmjtzfcmenzy3ozp67890qwertyuiopasd";
@Test
public void invalidSessionId_throwsError() {
assertThrows(
IllegalArgumentException.class,
() ->
new CmcdConfiguration(
/* sessionId= */ LONG_INVALID_ID,
/* contentId= */ null,
new CmcdConfiguration.RequestConfig() {}));
}
@Test
public void invalidContentId_throwsError() {
assertThrows(
IllegalArgumentException.class,
() ->
new CmcdConfiguration(
/* sessionId= */ null,
/* contentId= */ LONG_INVALID_ID,
new CmcdConfiguration.RequestConfig() {}));
}
@Test
public void nullRequestConfig_throwsError() {
assertThrows(
NullPointerException.class,
() ->
new CmcdConfiguration(
/* sessionId= */ null, /* contentId= */ null, /* requestConfig= */ null));
}
@Test
public void defaultFactory_createsInstance() {
CmcdConfiguration.Factory cmcdConfigurationFactory = CmcdConfiguration.Factory.DEFAULT;
MediaItem mediaItem = new MediaItem.Builder().setMediaId(TEST_MEDIA_ID).build();
CmcdConfiguration cmcdConfiguration =
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
assertThat(cmcdConfiguration.contentId).isEqualTo(TEST_MEDIA_ID);
assertThat(cmcdConfiguration.isBitrateLoggingAllowed()).isTrue();
assertThat(cmcdConfiguration.requestConfig.getCustomData()).isEmpty();
assertThat(
cmcdConfiguration.requestConfig.getRequestedMaximumThroughputKbps(
/* throughputKbps= */ 0))
.isEqualTo(C.RATE_UNSET_INT);
}
@Test
public void customFactory_createsInstance() {
CmcdConfiguration.Factory cmcdConfigurationFactory =
mediaItem ->
new CmcdConfiguration(
TEST_SESSION_ID,
TEST_CONTENT_ID,
new CmcdConfiguration.RequestConfig() {
@Override
public boolean isKeyAllowed(@CmcdConfiguration.CmcdKey String key) {
return key.equals("br") || key.equals("rtp");
}
@Override
public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> getCustomData() {
return new ImmutableMap.Builder<String, String>()
.put("CMCD-Object", "key1=value1")
.put("CMCD-Request", "key2=\"stringValue\"")
.buildOrThrow();
}
@Override
public int getRequestedMaximumThroughputKbps(int throughputKbps) {
return 2 * throughputKbps;
}
});
MediaItem mediaItem = new MediaItem.Builder().setMediaId(TEST_MEDIA_ID).build();
CmcdConfiguration cmcdConfiguration =
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
assertThat(cmcdConfiguration.sessionId).isEqualTo(TEST_SESSION_ID);
assertThat(cmcdConfiguration.contentId).isEqualTo(TEST_CONTENT_ID);
assertThat(cmcdConfiguration.isBitrateLoggingAllowed()).isTrue();
assertThat(cmcdConfiguration.isBufferLengthLoggingAllowed()).isFalse();
assertThat(cmcdConfiguration.isContentIdLoggingAllowed()).isFalse();
assertThat(cmcdConfiguration.isSessionIdLoggingAllowed()).isFalse();
assertThat(cmcdConfiguration.isMaximumRequestThroughputLoggingAllowed()).isTrue();
assertThat(cmcdConfiguration.requestConfig.getCustomData())
.containsExactly("CMCD-Object", "key1=value1", "CMCD-Request", "key2=\"stringValue\"");
assertThat(
cmcdConfiguration.requestConfig.getRequestedMaximumThroughputKbps(
/* throughputKbps= */ 100))
.isEqualTo(200);
}
}
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.upstream;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link CmcdLog}. */
@RunWith(AndroidJUnit4.class)
public class CmcdLogTest {
@Test
public void createInstance_populatesCmcdHeaders() {
CmcdConfiguration.Factory cmcdConfigurationFactory =
mediaItem ->
new CmcdConfiguration(
"sessionId",
mediaItem.mediaId,
new CmcdConfiguration.RequestConfig() {
@Override
public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> getCustomData() {
return new ImmutableMap.Builder<String, String>()
.put("CMCD-Object", "key1=value1")
.put("CMCD-Request", "key2=\"stringValue\"")
.buildOrThrow();
}
@Override
public int getRequestedMaximumThroughputKbps(int throughputKbps) {
return 2 * throughputKbps;
}
});
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().setPeakBitrate(840_000).build());
CmcdLog cmcdLog =
CmcdLog.createInstance(
cmcdConfiguration,
trackSelection,
/* playbackPositionUs= */ 1_000_000,
/* loadPositionUs= */ 2_760_000);
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> requestHeaders =
cmcdLog.getHttpRequestHeaders();
assertThat(requestHeaders)
.containsExactly(
"CMCD-Object",
"br=840,key1=value1",
"CMCD-Request",
"bl=1800,key2=\"stringValue\"",
"CMCD-Session",
"cid=\"mediaId\",sid=\"sessionId\"",
"CMCD-Status",
"rtp=1700");
}
}
......@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.source.chunk.ChunkSource;
import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerTrackEmsgHandler;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.upstream.TransferListener;
import java.util.List;
......@@ -53,6 +54,7 @@ public interface DashChunkSource extends ChunkSource {
* @param transferListener The transfer listener which should be informed of any data transfers.
* May be null if no listener is available.
* @param playerId The {@link PlayerId} of the player using this chunk source.
* @param cmcdConfiguration The {@link CmcdConfiguration} for this chunk source.
* @return The created {@link DashChunkSource}.
*/
DashChunkSource createDashChunkSource(
......@@ -68,7 +70,8 @@ public interface DashChunkSource extends ChunkSource {
List<Format> closedCaptionFormats,
@Nullable PlayerTrackEmsgHandler playerEmsgHandler,
@Nullable TransferListener transferListener,
PlayerId playerId);
PlayerId playerId,
@Nullable CmcdConfiguration cmcdConfiguration);
}
/**
......
......@@ -51,6 +51,7 @@ import com.google.android.exoplayer2.source.dash.manifest.Period;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.upstream.TransferListener;
......@@ -85,6 +86,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/* package */ final int id;
private final DashChunkSource.Factory chunkSourceFactory;
@Nullable private final TransferListener transferListener;
@Nullable private final CmcdConfiguration cmcdConfiguration;
private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final BaseUrlExclusionList baseUrlExclusionList;
......@@ -116,6 +118,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
int periodIndex,
DashChunkSource.Factory chunkSourceFactory,
@Nullable TransferListener transferListener,
@Nullable CmcdConfiguration cmcdConfiguration,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
......@@ -132,6 +135,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
this.periodIndex = periodIndex;
this.chunkSourceFactory = chunkSourceFactory;
this.transferListener = transferListener;
this.cmcdConfiguration = cmcdConfiguration;
this.drmSessionManager = drmSessionManager;
this.drmEventDispatcher = drmEventDispatcher;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
......@@ -791,7 +795,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
embeddedClosedCaptionTrackFormats,
trackPlayerEmsgHandler,
transferListener,
playerId);
playerId,
cmcdConfiguration);
ChunkSampleStream<DashChunkSource> stream =
new ChunkSampleStream<>(
trackGroupInfo.trackType,
......
......@@ -60,6 +60,7 @@ import com.google.android.exoplayer2.source.dash.manifest.Period;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
......@@ -104,6 +105,7 @@ public final class DashMediaSource extends BaseMediaSource {
private final DashChunkSource.Factory chunkSourceFactory;
@Nullable private final DataSource.Factory manifestDataSourceFactory;
private CmcdConfiguration.Factory cmcdConfigurationFactory;
private DrmSessionManagerProvider drmSessionManagerProvider;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
......@@ -161,6 +163,13 @@ public final class DashMediaSource extends BaseMediaSource {
@CanIgnoreReturnValue
@Override
public Factory setCmcdConfigurationFactory(CmcdConfiguration.Factory cmcdConfigurationFactory) {
this.cmcdConfigurationFactory = checkNotNull(cmcdConfigurationFactory);
return this;
}
@CanIgnoreReturnValue
@Override
public Factory setDrmSessionManagerProvider(
DrmSessionManagerProvider drmSessionManagerProvider) {
this.drmSessionManagerProvider =
......@@ -288,6 +297,11 @@ public final class DashMediaSource extends BaseMediaSource {
mediaItemBuilder.setUri(Uri.EMPTY);
}
mediaItem = mediaItemBuilder.build();
@Nullable
CmcdConfiguration cmcdConfiguration =
cmcdConfigurationFactory == null
? null
: cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
return new DashMediaSource(
mediaItem,
manifest,
......@@ -295,6 +309,7 @@ public final class DashMediaSource extends BaseMediaSource {
/* manifestParser= */ null,
chunkSourceFactory,
compositeSequenceableLoaderFactory,
cmcdConfiguration,
drmSessionManagerProvider.get(mediaItem),
loadErrorHandlingPolicy,
fallbackTargetLiveOffsetMs,
......@@ -319,6 +334,11 @@ public final class DashMediaSource extends BaseMediaSource {
if (!streamKeys.isEmpty()) {
manifestParser = new FilteringManifestParser<>(manifestParser, streamKeys);
}
@Nullable
CmcdConfiguration cmcdConfiguration =
cmcdConfigurationFactory == null
? null
: cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
return new DashMediaSource(
mediaItem,
......@@ -327,6 +347,7 @@ public final class DashMediaSource extends BaseMediaSource {
manifestParser,
chunkSourceFactory,
compositeSequenceableLoaderFactory,
cmcdConfiguration,
drmSessionManagerProvider.get(mediaItem),
loadErrorHandlingPolicy,
fallbackTargetLiveOffsetMs,
......@@ -371,6 +392,7 @@ public final class DashMediaSource extends BaseMediaSource {
private final DataSource.Factory manifestDataSourceFactory;
private final DashChunkSource.Factory chunkSourceFactory;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
@Nullable private final CmcdConfiguration cmcdConfiguration;
private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final BaseUrlExclusionList baseUrlExclusionList;
......@@ -414,6 +436,7 @@ public final class DashMediaSource extends BaseMediaSource {
@Nullable ParsingLoadable.Parser<? extends DashManifest> manifestParser,
DashChunkSource.Factory chunkSourceFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
@Nullable CmcdConfiguration cmcdConfiguration,
DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
long fallbackTargetLiveOffsetMs,
......@@ -426,6 +449,7 @@ public final class DashMediaSource extends BaseMediaSource {
this.manifestDataSourceFactory = manifestDataSourceFactory;
this.manifestParser = manifestParser;
this.chunkSourceFactory = chunkSourceFactory;
this.cmcdConfiguration = cmcdConfiguration;
this.drmSessionManager = drmSessionManager;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.fallbackTargetLiveOffsetMs = fallbackTargetLiveOffsetMs;
......@@ -505,6 +529,7 @@ public final class DashMediaSource extends BaseMediaSource {
periodIndex,
chunkSourceFactory,
mediaTransferListener,
cmcdConfiguration,
drmSessionManager,
drmEventDispatcher,
loadErrorHandlingPolicy,
......
......@@ -45,6 +45,8 @@ import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.CmcdLog;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
......@@ -52,6 +54,7 @@ import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
......@@ -112,7 +115,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
List<Format> closedCaptionFormats,
@Nullable PlayerTrackEmsgHandler playerEmsgHandler,
@Nullable TransferListener transferListener,
PlayerId playerId) {
PlayerId playerId,
@Nullable CmcdConfiguration cmcdConfiguration) {
DataSource dataSource = dataSourceFactory.createDataSource();
if (transferListener != null) {
dataSource.addTransferListener(transferListener);
......@@ -132,7 +136,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
enableEventMessageTrack,
closedCaptionFormats,
playerEmsgHandler,
playerId);
playerId,
cmcdConfiguration);
}
}
......@@ -144,6 +149,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
private final long elapsedRealtimeOffsetMs;
private final int maxSegmentsPerLoad;
@Nullable private final PlayerTrackEmsgHandler playerTrackEmsgHandler;
@Nullable private final CmcdConfiguration cmcdConfiguration;
protected final RepresentationHolder[] representationHolders;
......@@ -175,6 +181,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
* @param playerTrackEmsgHandler The {@link PlayerTrackEmsgHandler} instance to handle emsg
* messages targeting the player. Maybe null if this is not necessary.
* @param playerId The {@link PlayerId} of the player using this chunk source.
* @param cmcdConfiguration The {@link CmcdConfiguration} for this chunk source.
*/
public DefaultDashChunkSource(
ChunkExtractor.Factory chunkExtractorFactory,
......@@ -191,7 +198,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
boolean enableEventMessageTrack,
List<Format> closedCaptionFormats,
@Nullable PlayerTrackEmsgHandler playerTrackEmsgHandler,
PlayerId playerId) {
PlayerId playerId,
@Nullable CmcdConfiguration cmcdConfiguration) {
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.manifest = manifest;
this.baseUrlExclusionList = baseUrlExclusionList;
......@@ -203,6 +211,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs;
this.maxSegmentsPerLoad = maxSegmentsPerLoad;
this.playerTrackEmsgHandler = playerTrackEmsgHandler;
this.cmcdConfiguration = cmcdConfiguration;
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
......@@ -434,6 +443,13 @@ public class DefaultDashChunkSource implements DashChunkSource {
}
}
@Nullable
CmcdLog cmcdLog =
cmcdConfiguration == null
? null
: CmcdLog.createInstance(
cmcdConfiguration, trackSelection, playbackPositionUs, loadPositionUs);
long seekTimeUs = queue.isEmpty() ? loadPositionUs : C.TIME_UNSET;
out.chunk =
newMediaChunk(
......@@ -446,7 +462,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
segmentNum,
maxSegmentCount,
seekTimeUs,
nowPeriodTimeUs);
nowPeriodTimeUs,
cmcdLog);
}
@Override
......@@ -656,10 +673,13 @@ public class DefaultDashChunkSource implements DashChunkSource {
long firstSegmentNum,
int maxSegmentCount,
long seekTimeUs,
long nowPeriodTimeUs) {
long nowPeriodTimeUs,
@Nullable CmcdLog cmcdLog) {
Representation representation = representationHolder.representation;
long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum);
RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum);
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
if (representationHolder.chunkExtractor == null) {
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum);
int flags =
......@@ -670,6 +690,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
DataSpec dataSpec =
DashUtil.buildDataSpec(
representation, representationHolder.selectedBaseUrl.url, segmentUri, flags);
dataSpec = dataSpec.buildUpon().setHttpRequestHeaders(httpRequestHeaders).build();
return new SingleSampleMediaChunk(
dataSource,
dataSpec,
......@@ -709,6 +730,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
DataSpec dataSpec =
DashUtil.buildDataSpec(
representation, representationHolder.selectedBaseUrl.url, segmentUri, flags);
dataSpec = dataSpec.buildUpon().setHttpRequestHeaders(httpRequestHeaders).build();
long sampleOffsetUs = -representation.presentationTimeOffsetUs;
return new ContainerMediaChunk(
dataSource,
......
......@@ -211,6 +211,7 @@ public final class DashMediaPeriodTest {
periodIndex,
mock(DashChunkSource.Factory.class),
mock(TransferListener.class),
/* cmcdConfiguration= */ null,
DrmSessionManager.DRM_UNSUPPORTED,
new DrmSessionEventListener.EventDispatcher()
.withParameters(/* windowIndex= */ 0, mediaPeriodId),
......
......@@ -26,6 +26,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.analytics.PlayerId;
import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData;
......@@ -39,6 +40,7 @@ import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
......@@ -97,7 +99,8 @@ public class DefaultDashChunkSourceTest {
/* enableEventMessageTrack= */ false,
/* closedCaptionFormats= */ ImmutableList.of(),
/* playerTrackEmsgHandler= */ null,
PlayerId.UNSET);
PlayerId.UNSET,
/* cmcdConfiguration= */ null);
long nowInPeriodUs = Util.msToUs(nowMs - manifest.availabilityStartTimeMs);
ChunkHolder output = new ChunkHolder();
......@@ -146,7 +149,8 @@ public class DefaultDashChunkSourceTest {
/* enableEventMessageTrack= */ false,
/* closedCaptionFormats= */ ImmutableList.of(),
/* playerTrackEmsgHandler= */ null,
PlayerId.UNSET);
PlayerId.UNSET,
/* cmcdConfiguration= */ null);
ChunkHolder output = new ChunkHolder();
chunkSource.getNextChunk(
......@@ -171,7 +175,8 @@ public class DefaultDashChunkSourceTest {
}
};
List<Chunk> chunks = new ArrayList<>();
DashChunkSource chunkSource = createDashChunkSource(/* numberOfTracks= */ 1);
DashChunkSource chunkSource =
createDashChunkSource(/* numberOfTracks= */ 1, /* cmcdConfiguration= */ null);
ChunkHolder output = new ChunkHolder();
boolean requestReplacementChunk = true;
......@@ -228,7 +233,8 @@ public class DefaultDashChunkSourceTest {
FALLBACK_TYPE_TRACK, DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS);
}
};
DashChunkSource chunkSource = createDashChunkSource(/* numberOfTracks= */ 4);
DashChunkSource chunkSource =
createDashChunkSource(/* numberOfTracks= */ 4, /* cmcdConfiguration= */ null);
ChunkHolder output = new ChunkHolder();
List<Chunk> chunks = new ArrayList<>();
boolean requestReplacementChunk = true;
......@@ -269,7 +275,8 @@ public class DefaultDashChunkSourceTest {
return null;
}
};
DashChunkSource chunkSource = createDashChunkSource(/* numberOfTracks= */ 2);
DashChunkSource chunkSource =
createDashChunkSource(/* numberOfTracks= */ 2, /* cmcdConfiguration= */ null);
ChunkHolder output = new ChunkHolder();
chunkSource.getNextChunk(
/* playbackPositionUs= */ 0,
......@@ -288,7 +295,127 @@ public class DefaultDashChunkSourceTest {
assertThat(requestReplacementChunk).isFalse();
}
private DashChunkSource createDashChunkSource(int numberOfTracks) throws IOException {
@Test
public void getNextChunk_chunkSourceWithDefaultCmcdConfiguration_setsCmcdLoggingHeaders()
throws Exception {
CmcdConfiguration.Factory cmcdConfigurationFactory = CmcdConfiguration.Factory.DEFAULT;
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
CmcdConfiguration cmcdConfiguration =
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
DashChunkSource chunkSource = createDashChunkSource(/* numberOfTracks= */ 2, cmcdConfiguration);
ChunkHolder output = new ChunkHolder();
chunkSource.getNextChunk(
/* playbackPositionUs= */ 0,
/* loadPositionUs= */ 0,
/* queue= */ ImmutableList.of(),
output);
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
"br=700",
"CMCD-Request",
"bl=0",
"CMCD-Session",
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\"");
}
@Test
public void getNextChunk_chunkSourceWithCustomCmcdConfiguration_setsCmcdLoggingHeaders()
throws Exception {
CmcdConfiguration.Factory cmcdConfigurationFactory =
mediaItem -> {
CmcdConfiguration.RequestConfig cmcdRequestConfig =
new CmcdConfiguration.RequestConfig() {
@Override
public boolean isKeyAllowed(String key) {
return !key.equals(CmcdConfiguration.KEY_SESSION_ID);
}
@Override
public int getRequestedMaximumThroughputKbps(int throughputKbps) {
return 5 * throughputKbps;
}
};
return new CmcdConfiguration(
/* sessionId= */ "sessionId",
/* contentId= */ mediaItem.mediaId + "contentIdSuffix",
cmcdRequestConfig);
};
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
CmcdConfiguration cmcdConfiguration =
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
DashChunkSource chunkSource = createDashChunkSource(/* numberOfTracks= */ 2, cmcdConfiguration);
ChunkHolder output = new ChunkHolder();
chunkSource.getNextChunk(
/* playbackPositionUs= */ 0,
/* loadPositionUs= */ 0,
/* queue= */ ImmutableList.of(),
output);
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
"br=700",
"CMCD-Request",
"bl=0",
"CMCD-Session",
"cid=\"mediaIdcontentIdSuffix\"",
"CMCD-Status",
"rtp=3500");
}
@Test
public void
getNextChunk_chunkSourceWithCustomCmcdConfigurationAndCustomData_setsCmcdLoggingHeaders()
throws Exception {
CmcdConfiguration.Factory cmcdConfigurationFactory =
mediaItem -> {
CmcdConfiguration.RequestConfig cmcdRequestConfig =
new CmcdConfiguration.RequestConfig() {
@Override
public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> getCustomData() {
return new ImmutableMap.Builder<@CmcdConfiguration.HeaderKey String, String>()
.put(CmcdConfiguration.KEY_CMCD_OBJECT, "key1=value1")
.put(CmcdConfiguration.KEY_CMCD_REQUEST, "key2=\"stringValue\"")
.put(CmcdConfiguration.KEY_CMCD_SESSION, "key3=1")
.put(CmcdConfiguration.KEY_CMCD_STATUS, "key4=5.0")
.buildOrThrow();
}
};
return new CmcdConfiguration(
/* sessionId= */ "sessionId", /* contentId= */ mediaItem.mediaId, cmcdRequestConfig);
};
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
CmcdConfiguration cmcdConfiguration =
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
DashChunkSource chunkSource = createDashChunkSource(/* numberOfTracks= */ 2, cmcdConfiguration);
ChunkHolder output = new ChunkHolder();
chunkSource.getNextChunk(
/* playbackPositionUs= */ 0,
/* loadPositionUs= */ 0,
/* queue= */ ImmutableList.of(),
output);
assertThat(output.chunk.dataSpec.httpRequestHeaders)
.containsExactly(
"CMCD-Object",
"br=700,key1=value1",
"CMCD-Request",
"bl=0,key2=\"stringValue\"",
"CMCD-Session",
"cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",key3=1",
"CMCD-Status",
"key4=5.0");
}
private DashChunkSource createDashChunkSource(
int numberOfTracks, @Nullable CmcdConfiguration cmcdConfiguration) throws IOException {
Assertions.checkArgument(numberOfTracks < 6);
DashManifest manifest =
new DashManifestParser()
......@@ -330,7 +457,8 @@ public class DefaultDashChunkSourceTest {
/* enableEventMessageTrack= */ false,
/* closedCaptionFormats= */ ImmutableList.of(),
/* playerTrackEmsgHandler= */ null,
PlayerId.UNSET);
PlayerId.UNSET,
cmcdConfiguration);
}
private LoadErrorHandlingPolicy.LoadErrorInfo createFakeLoadErrorInfo(
......
......@@ -41,6 +41,8 @@ import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segmen
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.CmcdLog;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
......@@ -130,6 +132,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable private final List<Format> muxedCaptionFormats;
private final FullSegmentEncryptionKeyCache keyCache;
private final PlayerId playerId;
@Nullable private final CmcdConfiguration cmcdConfiguration;
private boolean isPrimaryTimestampSource;
private byte[] scratchSpace;
......@@ -170,7 +173,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable TransferListener mediaTransferListener,
TimestampAdjusterProvider timestampAdjusterProvider,
@Nullable List<Format> muxedCaptionFormats,
PlayerId playerId) {
PlayerId playerId,
@Nullable CmcdConfiguration cmcdConfiguration) {
this.extractorFactory = extractorFactory;
this.playlistTracker = playlistTracker;
this.playlistUrls = playlistUrls;
......@@ -178,6 +182,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.timestampAdjusterProvider = timestampAdjusterProvider;
this.muxedCaptionFormats = muxedCaptionFormats;
this.playerId = playerId;
this.cmcdConfiguration = cmcdConfiguration;
keyCache = new FullSegmentEncryptionKeyCache(KEY_CACHE_SIZE);
scratchSpace = Util.EMPTY_BYTE_ARRAY;
liveEdgeInPeriodTimeUs = C.TIME_UNSET;
......@@ -493,6 +498,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return;
}
@Nullable
CmcdLog cmcdLog =
cmcdConfiguration == null
? null
: CmcdLog.createInstance(
cmcdConfiguration, trackSelection, playbackPositionUs, loadPositionUs);
out.chunk =
HlsMediaChunk.createInstance(
extractorFactory,
......@@ -511,7 +523,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* mediaSegmentKey= */ keyCache.get(mediaSegmentKeyUri),
/* initSegmentKey= */ keyCache.get(initSegmentKeyUri),
shouldSpliceIn,
playerId);
playerId,
cmcdLog);
}
@Nullable
......
......@@ -30,6 +30,8 @@ import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
import com.google.android.exoplayer2.metadata.id3.PrivFrame;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.CmcdLog;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceUtil;
import com.google.android.exoplayer2.upstream.DataSpec;
......@@ -39,6 +41,7 @@ import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.UriUtil;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
......@@ -93,15 +96,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Nullable byte[] mediaSegmentKey,
@Nullable byte[] initSegmentKey,
boolean shouldSpliceIn,
PlayerId playerId) {
PlayerId playerId,
@Nullable CmcdLog cmcdLog) {
// Media segment.
HlsMediaPlaylist.SegmentBase mediaSegment = segmentBaseHolder.segmentBase;
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
DataSpec dataSpec =
new DataSpec.Builder()
.setUri(UriUtil.resolveToUri(mediaPlaylist.baseUri, mediaSegment.url))
.setPosition(mediaSegment.byteRangeOffset)
.setLength(mediaSegment.byteRangeLength)
.setFlags(segmentBaseHolder.isPreload ? FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED : 0)
.setHttpRequestHeaders(httpRequestHeaders)
.build();
boolean mediaSegmentEncrypted = mediaSegmentKey != null;
@Nullable
......
......@@ -42,6 +42,7 @@ import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener;
......@@ -67,6 +68,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsPlaylistTracker.Pla
private final HlsPlaylistTracker playlistTracker;
private final HlsDataSourceFactory dataSourceFactory;
@Nullable private final TransferListener mediaTransferListener;
@Nullable private final CmcdConfiguration cmcdConfiguration;
private final DrmSessionManager drmSessionManager;
private final DrmSessionEventListener.EventDispatcher drmEventDispatcher;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
......@@ -100,6 +102,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsPlaylistTracker.Pla
* and keys.
* @param mediaTransferListener The transfer listener to inform of any media data transfers. May
* be null if no listener is available.
* @param cmcdConfiguration The {@link CmcdConfiguration} for the period.
* @param drmSessionManager The {@link DrmSessionManager} to acquire {@link DrmSession
* DrmSessions} with.
* @param drmEventDispatcher A {@link DrmSessionEventListener.EventDispatcher} used to distribute
......@@ -119,6 +122,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsPlaylistTracker.Pla
HlsPlaylistTracker playlistTracker,
HlsDataSourceFactory dataSourceFactory,
@Nullable TransferListener mediaTransferListener,
@Nullable CmcdConfiguration cmcdConfiguration,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
......@@ -133,6 +137,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsPlaylistTracker.Pla
this.playlistTracker = playlistTracker;
this.dataSourceFactory = dataSourceFactory;
this.mediaTransferListener = mediaTransferListener;
this.cmcdConfiguration = cmcdConfiguration;
this.drmSessionManager = drmSessionManager;
this.drmEventDispatcher = drmEventDispatcher;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
......@@ -775,7 +780,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsPlaylistTracker.Pla
mediaTransferListener,
timestampAdjusterProvider,
muxedCaptionFormats,
playerId);
playerId,
cmcdConfiguration);
return new HlsSampleStreamWrapper(
uid,
trackType,
......
......@@ -50,6 +50,7 @@ import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParserFactory;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
......@@ -103,6 +104,7 @@ public final class HlsMediaSource extends BaseMediaSource
private HlsPlaylistParserFactory playlistParserFactory;
private HlsPlaylistTracker.Factory playlistTrackerFactory;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
@Nullable private CmcdConfiguration.Factory cmcdConfigurationFactory;
private DrmSessionManagerProvider drmSessionManagerProvider;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private boolean allowChunklessPreparation;
......@@ -300,6 +302,13 @@ public final class HlsMediaSource extends BaseMediaSource
@CanIgnoreReturnValue
@Override
public Factory setCmcdConfigurationFactory(CmcdConfiguration.Factory cmcdConfigurationFactory) {
this.cmcdConfigurationFactory = checkNotNull(cmcdConfigurationFactory);
return this;
}
@CanIgnoreReturnValue
@Override
public Factory setDrmSessionManagerProvider(
DrmSessionManagerProvider drmSessionManagerProvider) {
this.drmSessionManagerProvider =
......@@ -342,12 +351,18 @@ public final class HlsMediaSource extends BaseMediaSource
playlistParserFactory =
new FilteringHlsPlaylistParserFactory(playlistParserFactory, streamKeys);
}
@Nullable
CmcdConfiguration cmcdConfiguration =
cmcdConfigurationFactory == null
? null
: cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
return new HlsMediaSource(
mediaItem,
hlsDataSourceFactory,
extractorFactory,
compositeSequenceableLoaderFactory,
cmcdConfiguration,
drmSessionManagerProvider.get(mediaItem),
loadErrorHandlingPolicy,
playlistTrackerFactory.createTracker(
......@@ -368,6 +383,7 @@ public final class HlsMediaSource extends BaseMediaSource
private final MediaItem.LocalConfiguration localConfiguration;
private final HlsDataSourceFactory dataSourceFactory;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
@Nullable private final CmcdConfiguration cmcdConfiguration;
private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final boolean allowChunklessPreparation;
......@@ -385,6 +401,7 @@ public final class HlsMediaSource extends BaseMediaSource
HlsDataSourceFactory dataSourceFactory,
HlsExtractorFactory extractorFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
@Nullable CmcdConfiguration cmcdConfiguration,
DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
HlsPlaylistTracker playlistTracker,
......@@ -398,6 +415,7 @@ public final class HlsMediaSource extends BaseMediaSource
this.dataSourceFactory = dataSourceFactory;
this.extractorFactory = extractorFactory;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
this.cmcdConfiguration = cmcdConfiguration;
this.drmSessionManager = drmSessionManager;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.playlistTracker = playlistTracker;
......@@ -438,6 +456,7 @@ public final class HlsMediaSource extends BaseMediaSource
playlistTracker,
dataSourceFactory,
mediaTransferListener,
cmcdConfiguration,
drmSessionManager,
drmEventDispatcher,
loadErrorHandlingPolicy,
......
......@@ -84,6 +84,7 @@ public final class HlsMediaPeriodTest {
mockPlaylistTracker,
mockDataSourceFactory,
mock(TransferListener.class),
/* cmcdConfiguration= */ null,
mock(DrmSessionManager.class),
new DrmSessionEventListener.EventDispatcher()
.withParameters(/* windowIndex= */ 0, mediaPeriodId),
......
......@@ -37,6 +37,8 @@ import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.CmcdLog;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
......@@ -44,6 +46,7 @@ import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.FallbackSe
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.List;
......@@ -64,13 +67,19 @@ public class DefaultSsChunkSource implements SsChunkSource {
SsManifest manifest,
int streamElementIndex,
ExoTrackSelection trackSelection,
@Nullable TransferListener transferListener) {
@Nullable TransferListener transferListener,
@Nullable CmcdConfiguration cmcdConfiguration) {
DataSource dataSource = dataSourceFactory.createDataSource();
if (transferListener != null) {
dataSource.addTransferListener(transferListener);
}
return new DefaultSsChunkSource(
manifestLoaderErrorThrower, manifest, streamElementIndex, trackSelection, dataSource);
manifestLoaderErrorThrower,
manifest,
streamElementIndex,
trackSelection,
dataSource,
cmcdConfiguration);
}
}
......@@ -78,6 +87,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
private final int streamElementIndex;
private final ChunkExtractor[] chunkExtractors;
private final DataSource dataSource;
@Nullable private final CmcdConfiguration cmcdConfiguration;
private ExoTrackSelection trackSelection;
private SsManifest manifest;
......@@ -91,18 +101,21 @@ public class DefaultSsChunkSource implements SsChunkSource {
* @param streamElementIndex The index of the stream element in the manifest.
* @param trackSelection The track selection.
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @param cmcdConfiguration The {@link CmcdConfiguration} for this chunk source.
*/
public DefaultSsChunkSource(
LoaderErrorThrower manifestLoaderErrorThrower,
SsManifest manifest,
int streamElementIndex,
ExoTrackSelection trackSelection,
DataSource dataSource) {
DataSource dataSource,
@Nullable CmcdConfiguration cmcdConfiguration) {
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.manifest = manifest;
this.streamElementIndex = streamElementIndex;
this.trackSelection = trackSelection;
this.dataSource = dataSource;
this.cmcdConfiguration = cmcdConfiguration;
StreamElement streamElement = manifest.streamElements[streamElementIndex];
chunkExtractors = new ChunkExtractor[trackSelection.length()];
......@@ -265,6 +278,14 @@ public class DefaultSsChunkSource implements SsChunkSource {
int manifestTrackIndex = trackSelection.getIndexInTrackGroup(trackSelectionIndex);
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
@Nullable
CmcdLog cmcdLog =
cmcdConfiguration == null
? null
: CmcdLog.createInstance(
cmcdConfiguration, trackSelection, playbackPositionUs, loadPositionUs);
;
out.chunk =
newMediaChunk(
trackSelection.getSelectedFormat(),
......@@ -276,7 +297,8 @@ public class DefaultSsChunkSource implements SsChunkSource {
chunkSeekTimeUs,
trackSelection.getSelectionReason(),
trackSelection.getSelectionData(),
chunkExtractor);
chunkExtractor,
cmcdLog);
}
@Override
......@@ -320,8 +342,12 @@ public class DefaultSsChunkSource implements SsChunkSource {
long chunkSeekTimeUs,
@C.SelectionReason int trackSelectionReason,
@Nullable Object trackSelectionData,
ChunkExtractor chunkExtractor) {
DataSpec dataSpec = new DataSpec(uri);
ChunkExtractor chunkExtractor,
@Nullable CmcdLog cmcdLog) {
ImmutableMap<@CmcdConfiguration.HeaderKey String, String> httpRequestHeaders =
cmcdLog == null ? ImmutableMap.of() : cmcdLog.getHttpRequestHeaders();
DataSpec dataSpec =
new DataSpec.Builder().setUri(uri).setHttpRequestHeaders(httpRequestHeaders).build();
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
// To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs.
long sampleOffsetUs = chunkStartTimeUs;
......
......@@ -19,6 +19,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.chunk.ChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.upstream.TransferListener;
......@@ -37,6 +38,7 @@ public interface SsChunkSource extends ChunkSource {
* @param trackSelection The track selection.
* @param transferListener The transfer listener which should be informed of any data transfers.
* May be null if no listener is available.
* @param cmcdConfiguration The {@link CmcdConfiguration} for this chunk source.
* @return The created {@link SsChunkSource}.
*/
SsChunkSource createChunkSource(
......@@ -44,7 +46,8 @@ public interface SsChunkSource extends ChunkSource {
SsManifest manifest,
int streamElementIndex,
ExoTrackSelection trackSelection,
@Nullable TransferListener transferListener);
@Nullable TransferListener transferListener,
@Nullable CmcdConfiguration cmcdConfiguration);
}
/**
......
......@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.source.chunk.ChunkSampleStream;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.upstream.TransferListener;
......@@ -49,6 +50,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Nullable private final TransferListener transferListener;
private final LoaderErrorThrower manifestLoaderErrorThrower;
private final DrmSessionManager drmSessionManager;
@Nullable private final CmcdConfiguration cmcdConfiguration;
private final DrmSessionEventListener.EventDispatcher drmEventDispatcher;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher;
......@@ -66,6 +68,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
SsChunkSource.Factory chunkSourceFactory,
@Nullable TransferListener transferListener,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
@Nullable CmcdConfiguration cmcdConfiguration,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
......@@ -76,6 +79,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
this.chunkSourceFactory = chunkSourceFactory;
this.transferListener = transferListener;
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.cmcdConfiguration = cmcdConfiguration;
this.drmSessionManager = drmSessionManager;
this.drmEventDispatcher = drmEventDispatcher;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
......@@ -237,7 +241,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
int streamElementIndex = trackGroups.indexOf(selection.getTrackGroup());
SsChunkSource chunkSource =
chunkSourceFactory.createChunkSource(
manifestLoaderErrorThrower, manifest, streamElementIndex, selection, transferListener);
manifestLoaderErrorThrower,
manifest,
streamElementIndex,
selection,
transferListener,
cmcdConfiguration);
return new ChunkSampleStream<>(
manifest.streamElements[streamElementIndex].type,
null,
......
......@@ -50,6 +50,7 @@ import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.CmcdConfiguration;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
......@@ -84,6 +85,7 @@ public final class SsMediaSource extends BaseMediaSource
@Nullable private final DataSource.Factory manifestDataSourceFactory;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
@Nullable private CmcdConfiguration.Factory cmcdConfigurationFactory;
private DrmSessionManagerProvider drmSessionManagerProvider;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private long livePresentationDelayMs;
......@@ -198,6 +200,13 @@ public final class SsMediaSource extends BaseMediaSource
@CanIgnoreReturnValue
@Override
public Factory setCmcdConfigurationFactory(CmcdConfiguration.Factory cmcdConfigurationFactory) {
this.cmcdConfigurationFactory = checkNotNull(cmcdConfigurationFactory);
return this;
}
@CanIgnoreReturnValue
@Override
public Factory setDrmSessionManagerProvider(
DrmSessionManagerProvider drmSessionManagerProvider) {
this.drmSessionManagerProvider =
......@@ -246,6 +255,11 @@ public final class SsMediaSource extends BaseMediaSource
.setMimeType(MimeTypes.APPLICATION_SS)
.setUri(hasUri ? mediaItem.localConfiguration.uri : Uri.EMPTY)
.build();
@Nullable
CmcdConfiguration cmcdConfiguration =
cmcdConfigurationFactory == null
? null
: cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
return new SsMediaSource(
mediaItem,
manifest,
......@@ -253,6 +267,7 @@ public final class SsMediaSource extends BaseMediaSource
/* manifestParser= */ null,
chunkSourceFactory,
compositeSequenceableLoaderFactory,
cmcdConfiguration,
drmSessionManagerProvider.get(mediaItem),
loadErrorHandlingPolicy,
livePresentationDelayMs);
......@@ -276,6 +291,11 @@ public final class SsMediaSource extends BaseMediaSource
if (!streamKeys.isEmpty()) {
manifestParser = new FilteringManifestParser<>(manifestParser, streamKeys);
}
@Nullable
CmcdConfiguration cmcdConfiguration =
cmcdConfigurationFactory == null
? null
: cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
return new SsMediaSource(
mediaItem,
......@@ -284,6 +304,7 @@ public final class SsMediaSource extends BaseMediaSource
manifestParser,
chunkSourceFactory,
compositeSequenceableLoaderFactory,
cmcdConfiguration,
drmSessionManagerProvider.get(mediaItem),
loadErrorHandlingPolicy,
livePresentationDelayMs);
......@@ -315,6 +336,7 @@ public final class SsMediaSource extends BaseMediaSource
private final DataSource.Factory manifestDataSourceFactory;
private final SsChunkSource.Factory chunkSourceFactory;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
@Nullable private final CmcdConfiguration cmcdConfiguration;
private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final long livePresentationDelayMs;
......@@ -339,6 +361,7 @@ public final class SsMediaSource extends BaseMediaSource
@Nullable ParsingLoadable.Parser<? extends SsManifest> manifestParser,
SsChunkSource.Factory chunkSourceFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
@Nullable CmcdConfiguration cmcdConfiguration,
DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
long livePresentationDelayMs) {
......@@ -354,6 +377,7 @@ public final class SsMediaSource extends BaseMediaSource
this.manifestParser = manifestParser;
this.chunkSourceFactory = chunkSourceFactory;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
this.cmcdConfiguration = cmcdConfiguration;
this.drmSessionManager = drmSessionManager;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.livePresentationDelayMs = livePresentationDelayMs;
......@@ -401,6 +425,7 @@ public final class SsMediaSource extends BaseMediaSource
chunkSourceFactory,
mediaTransferListener,
compositeSequenceableLoaderFactory,
cmcdConfiguration,
drmSessionManager,
drmEventDispatcher,
loadErrorHandlingPolicy,
......
......@@ -67,6 +67,7 @@ public class SsMediaPeriodTest {
mock(SsChunkSource.Factory.class),
mock(TransferListener.class),
mock(CompositeSequenceableLoaderFactory.class),
/* cmcdConfiguration= */ null,
mock(DrmSessionManager.class),
new DrmSessionEventListener.EventDispatcher()
.withParameters(/* windowIndex= */ 0, mediaPeriodId),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment