Skip to content

Commit

Permalink
Implement logging support for Common Media Client Data (CMCD)
Browse files Browse the repository at this point in the history
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: #8699

#minor-release

PiperOrigin-RevId: 539021056
  • Loading branch information
rohitjoins authored and tof-tof committed Jun 9, 2023
1 parent 499fd09 commit b7e7153
Show file tree
Hide file tree
Showing 23 changed files with 1,445 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
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;
Expand Down Expand Up @@ -367,6 +368,14 @@ public DefaultMediaSourceFactory setLiveMaxSpeed(float maxSpeed) {
return this;
}

@CanIgnoreReturnValue
@Override
public DefaultMediaSourceFactory setCmcdConfigurationFactory(
CmcdConfiguration.Factory cmcdConfigurationFactory) {
delegateFactoryLoader.setCmcdConfigurationFactory(checkNotNull(cmcdConfigurationFactory));
return this;
}

@CanIgnoreReturnValue
@Override
public DefaultMediaSourceFactory setDrmSessionManagerProvider(
Expand Down Expand Up @@ -548,6 +557,7 @@ private static final class DelegateFactoryLoader {
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;

Expand Down Expand Up @@ -577,6 +587,9 @@ public MediaSource.Factory getMediaSourceFactory(@C.ContentType int contentType)
}

mediaSourceFactory = mediaSourceFactorySupplier.get();
if (cmcdConfigurationFactory != null) {
mediaSourceFactory.setCmcdConfigurationFactory(cmcdConfigurationFactory);
}
if (drmSessionManagerProvider != null) {
mediaSourceFactory.setDrmSessionManagerProvider(drmSessionManagerProvider);
}
Expand All @@ -597,6 +610,13 @@ public void setDataSourceFactory(DataSource.Factory dataSourceFactory) {
}
}

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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -65,6 +66,18 @@ interface Factory {
@SuppressWarnings("deprecation")
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}.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/*
* 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);
}
}

0 comments on commit b7e7153

Please sign in to comment.