From 0412a36564a09c0a237b91b4a78fed80336ea6b6 Mon Sep 17 00:00:00 2001 From: rohks Date: Wed, 12 Jul 2023 10:25:50 +0100 Subject: [PATCH] Add fields streaming format(sf), stream type(st) and version(v) Added these CMCD-Session fields to Common Media Client Data (CMCD) logging. #minor-release PiperOrigin-RevId: 547435498 --- RELEASENOTES.md | 2 + .../exoplayer/upstream/CmcdConfiguration.java | 36 +++++-- .../media3/exoplayer/upstream/CmcdLog.java | 99 ++++++++++++++++++- .../exoplayer/upstream/CmcdLogTest.java | 8 +- .../dash/DefaultDashChunkSource.java | 7 +- .../dash/DefaultDashChunkSourceTest.java | 6 +- .../media3/exoplayer/hls/HlsChunkSource.java | 7 +- .../exoplayer/hls/HlsChunkSourceTest.java | 6 +- .../smoothstreaming/DefaultSsChunkSource.java | 7 +- 9 files changed, 159 insertions(+), 19 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9ee75604ab..06b45e59e5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -32,6 +32,8 @@ * Allow `MediaItem` updates for all `MediaSource` classes provided by the library via `Player.replaceMediaItem(s)` (([#33](https://github.com/androidx/media/issues/33)),([#9978](https://github.com/google/ExoPlayer/issues/9978))). + * Add fields streaming format (sf), stream type (st) and version (v) to + Common Media Client Data (CMCD) logging. * Transformer: * Parse EXIF rotation data for image inputs. * Remove `TransformationRequest.HdrMode` annotation type and its diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdConfiguration.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdConfiguration.java index f3efc1af13..ad6d714481 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdConfiguration.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdConfiguration.java @@ -60,7 +60,10 @@ public final class CmcdConfiguration { KEY_BUFFER_LENGTH, KEY_CONTENT_ID, KEY_SESSION_ID, - KEY_MAXIMUM_REQUESTED_BITRATE + KEY_MAXIMUM_REQUESTED_BITRATE, + KEY_STREAMING_FORMAT, + KEY_STREAM_TYPE, + KEY_VERSION }) @Documented @Target(TYPE_USE) @@ -78,6 +81,9 @@ public final class CmcdConfiguration { 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"; + public static final String KEY_STREAMING_FORMAT = "sf"; + public static final String KEY_STREAM_TYPE = "st"; + public static final String KEY_VERSION = "v"; /** * Factory for {@link CmcdConfiguration} instances. @@ -201,7 +207,7 @@ public final class CmcdConfiguration { } /** - * Whether logging bitrate is allowed based on the {@linkplain RequestConfig request + * Returns whether logging bitrate is allowed based on the {@linkplain RequestConfig request * configuration}. */ public boolean isBitrateLoggingAllowed() { @@ -209,7 +215,7 @@ public final class CmcdConfiguration { } /** - * Whether logging buffer length is allowed based on the {@linkplain RequestConfig request + * Returns whether logging buffer length is allowed based on the {@linkplain RequestConfig request * configuration}. */ public boolean isBufferLengthLoggingAllowed() { @@ -217,7 +223,7 @@ public final class CmcdConfiguration { } /** - * Whether logging content ID is allowed based on the {@linkplain RequestConfig request + * Returns whether logging content ID is allowed based on the {@linkplain RequestConfig request * configuration}. */ public boolean isContentIdLoggingAllowed() { @@ -225,7 +231,7 @@ public final class CmcdConfiguration { } /** - * Whether logging session ID is allowed based on the {@linkplain RequestConfig request + * Returns whether logging session ID is allowed based on the {@linkplain RequestConfig request * configuration}. */ public boolean isSessionIdLoggingAllowed() { @@ -233,10 +239,26 @@ public final class CmcdConfiguration { } /** - * Whether logging maximum requested throughput is allowed based on the {@linkplain RequestConfig - * request configuration}. + * Returns whether logging maximum requested throughput is allowed based on the {@linkplain + * RequestConfig request configuration}. */ public boolean isMaximumRequestThroughputLoggingAllowed() { return requestConfig.isKeyAllowed(KEY_MAXIMUM_REQUESTED_BITRATE); } + + /** + * Returns whether logging streaming format is allowed based on the {@linkplain RequestConfig + * request configuration}. + */ + public boolean isStreamingFormatLoggingAllowed() { + return requestConfig.isKeyAllowed(KEY_STREAMING_FORMAT); + } + + /** + * Returns whether logging stream type is allowed based on the {@linkplain RequestConfig request + * configuration}. + */ + public boolean isStreamTypeLoggingAllowed() { + return requestConfig.isKeyAllowed(KEY_STREAM_TYPE); + } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdLog.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdLog.java index 82df4529f7..228f640325 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdLog.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/CmcdLog.java @@ -16,15 +16,21 @@ package androidx.media3.exoplayer.upstream; import static androidx.media3.common.util.Assertions.checkArgument; +import static java.lang.annotation.ElementType.TYPE_USE; import android.text.TextUtils; import androidx.annotation.Nullable; +import androidx.annotation.StringDef; import androidx.media3.common.C; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.exoplayer.trackselection.ExoTrackSelection; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * Represents the data for CMCD (Common Media Client Data) in adaptive streaming formats DASH, HLS, @@ -37,6 +43,35 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; @UnstableApi public final class CmcdLog { + /** Indicates the streaming format used for media content. */ + @Retention(RetentionPolicy.SOURCE) + @StringDef({STREAMING_FORMAT_DASH, STREAMING_FORMAT_HLS, STREAMING_FORMAT_SS}) + @Documented + @Target(TYPE_USE) + public @interface StreamingFormat {} + + /** Indicates the type of streaming for media content. */ + @Retention(RetentionPolicy.SOURCE) + @StringDef({STREAM_TYPE_VOD, STREAM_TYPE_LIVE}) + @Documented + @Target(TYPE_USE) + public @interface StreamType {} + + /** Represents the Dynamic Adaptive Streaming over HTTP (DASH) format. */ + public static final String STREAMING_FORMAT_DASH = "d"; + + /** Represents the HTTP Live Streaming (HLS) format. */ + public static final String STREAMING_FORMAT_HLS = "h"; + + /** Represents the Smooth Streaming (SS) format. */ + public static final String STREAMING_FORMAT_SS = "s"; + + /** Represents the Video on Demand (VOD) stream type. */ + public static final String STREAM_TYPE_VOD = "v"; + + /** Represents the Live Streaming stream type. */ + public static final String STREAM_TYPE_LIVE = "l"; + /** * Creates a new instance. * @@ -44,11 +79,17 @@ public final class CmcdLog { * @param trackSelection The {@linkplain ExoTrackSelection track selection}. * @param bufferedDurationUs The duration of media currently buffered from the current playback * position, in microseconds. + * @param streamingFormat The streaming format of the media content. Must be one of the allowed + * streaming formats specified by the {@link StreamingFormat} annotation. + * @param isLive {@code true} if the media content is being streamed live, {@code false} + * otherwise. */ public static CmcdLog createInstance( CmcdConfiguration cmcdConfiguration, ExoTrackSelection trackSelection, - long bufferedDurationUs) { + long bufferedDurationUs, + @StreamingFormat String streamingFormat, + boolean isLive) { ImmutableMap<@CmcdConfiguration.HeaderKey String, String> customData = cmcdConfiguration.requestConfig.getCustomData(); int bitrateKbps = trackSelection.getSelectedFormat().bitrate / 1000; @@ -76,6 +117,12 @@ public final class CmcdLog { if (cmcdConfiguration.isSessionIdLoggingAllowed()) { cmcdSession.setSessionId(cmcdConfiguration.sessionId); } + if (cmcdConfiguration.isStreamingFormatLoggingAllowed()) { + cmcdSession.setStreamingFormat(streamingFormat); + } + if (cmcdConfiguration.isStreamTypeLoggingAllowed()) { + cmcdSession.setStreamType(isLive ? STREAM_TYPE_LIVE : STREAM_TYPE_VOD); + } CmcdLog.CmcdStatus.Builder cmcdStatus = new CmcdLog.CmcdStatus.Builder() @@ -290,6 +337,8 @@ public final class CmcdLog { public static final class Builder { @Nullable private String contentId; @Nullable private String sessionId; + @Nullable private String streamingFormat; + @Nullable private String streamType; @Nullable private String customData; /** @@ -314,6 +363,20 @@ public final class CmcdLog { return this; } + /** Sets the {@link CmcdSession#streamingFormat}. The default value is {@code null}. */ + @CanIgnoreReturnValue + public Builder setStreamingFormat(@Nullable @StreamingFormat String streamingFormat) { + this.streamingFormat = streamingFormat; + return this; + } + + /** Sets the {@link CmcdSession#streamType}. The default value is {@code null}. */ + @CanIgnoreReturnValue + public Builder setStreamType(@Nullable @StreamType String streamType) { + this.streamType = streamType; + return this; + } + /** Sets the {@link CmcdSession#customData}. The default value is {@code null}. */ @CanIgnoreReturnValue public CmcdSession.Builder setCustomData(@Nullable String customData) { @@ -326,6 +389,13 @@ public final class CmcdLog { } } + /** + * The version of this specification used for interpreting the defined key names and values. If + * this key is omitted, the client and server MUST interpret the values as being defined by + * version 1. Client SHOULD omit this field if the version is 1. + */ + public static final int VERSION = 1; + /** * A GUID identifying the current content, or {@code null} if unset. * @@ -342,6 +412,19 @@ public final class CmcdLog { */ @Nullable public final String sessionId; + /** + * The streaming format that defines the current request. d = MPEG DASH, h = HTTP Live Streaming + * (HLS), s = Smooth Streaming and o = other. If the streaming format being requested is + * unknown, then this key MUST NOT be used. + */ + @Nullable public final String streamingFormat; + + /** + * Type of stream. v = all segments are available – e.g., VOD and l = segments become available + * over time – e.g., LIVE. + */ + @Nullable public final String streamType; + /** * Custom data where the values of the keys are expected to be invariant over the life of the * session, or {@code null} if unset. @@ -354,6 +437,8 @@ public final class CmcdLog { private CmcdSession(Builder builder) { this.contentId = builder.contentId; this.sessionId = builder.sessionId; + this.streamingFormat = builder.streamingFormat; + this.streamType = builder.streamType; this.customData = builder.customData; } @@ -374,6 +459,18 @@ public final class CmcdLog { headerValue.append( Util.formatInvariant("%s=\"%s\",", CmcdConfiguration.KEY_SESSION_ID, sessionId)); } + if (!TextUtils.isEmpty(this.streamingFormat)) { + headerValue.append( + Util.formatInvariant( + "%s=%s,", CmcdConfiguration.KEY_STREAMING_FORMAT, streamingFormat)); + } + if (!TextUtils.isEmpty(this.streamType)) { + headerValue.append( + Util.formatInvariant("%s=%s,", CmcdConfiguration.KEY_STREAM_TYPE, streamType)); + } + if (VERSION != 1) { + headerValue.append(Util.formatInvariant("%s=%d,", CmcdConfiguration.KEY_VERSION, VERSION)); + } if (!TextUtils.isEmpty(customData)) { headerValue.append(Util.formatInvariant("%s,", customData)); } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdLogTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdLogTest.java index ae5f559d8b..b9a193137c 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdLogTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/upstream/CmcdLogTest.java @@ -60,7 +60,11 @@ public class CmcdLogTest { .thenReturn(new Format.Builder().setPeakBitrate(840_000).build()); CmcdLog cmcdLog = CmcdLog.createInstance( - cmcdConfiguration, trackSelection, /* bufferedDurationUs= */ 1_760_000); + cmcdConfiguration, + trackSelection, + /* bufferedDurationUs= */ 1_760_000, + CmcdLog.STREAMING_FORMAT_DASH, + true); ImmutableMap<@CmcdConfiguration.HeaderKey String, String> requestHeaders = cmcdLog.getHttpRequestHeaders(); @@ -72,7 +76,7 @@ public class CmcdLogTest { "CMCD-Request", "bl=1800,key2=\"stringValue\"", "CMCD-Session", - "cid=\"mediaId\",sid=\"sessionId\"", + "cid=\"mediaId\",sid=\"sessionId\",sf=d,st=l", "CMCD-Status", "rtp=1700"); } diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java index 6d1d877778..2284c3c53e 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java @@ -374,7 +374,12 @@ public class DefaultDashChunkSource implements DashChunkSource { CmcdLog cmcdLog = cmcdConfiguration == null ? null - : CmcdLog.createInstance(cmcdConfiguration, trackSelection, bufferedDurationUs); + : CmcdLog.createInstance( + cmcdConfiguration, + trackSelection, + bufferedDurationUs, + CmcdLog.STREAMING_FORMAT_DASH, + manifest.dynamic); RepresentationHolder representationHolder = updateSelectedBaseUrl(trackSelection.getSelectedIndex()); diff --git a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DefaultDashChunkSourceTest.java b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DefaultDashChunkSourceTest.java index a2ca922600..ad85683a3c 100644 --- a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DefaultDashChunkSourceTest.java +++ b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DefaultDashChunkSourceTest.java @@ -318,7 +318,7 @@ public class DefaultDashChunkSourceTest { "CMCD-Request", "bl=0", "CMCD-Session", - "cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\""); + "cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=d,st=v"); } @Test @@ -363,7 +363,7 @@ public class DefaultDashChunkSourceTest { "CMCD-Request", "bl=0", "CMCD-Session", - "cid=\"mediaIdcontentIdSuffix\"", + "cid=\"mediaIdcontentIdSuffix\",sf=d,st=v", "CMCD-Status", "rtp=3500"); } @@ -409,7 +409,7 @@ public class DefaultDashChunkSourceTest { "CMCD-Request", "bl=0,key2=\"stringValue\"", "CMCD-Session", - "cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",key3=1", + "cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=d,st=v,key3=1", "CMCD-Status", "key4=5.0"); } diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsChunkSource.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsChunkSource.java index ad0631321a..a0c32d1894 100644 --- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsChunkSource.java +++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsChunkSource.java @@ -485,7 +485,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; CmcdLog cmcdLog = cmcdConfiguration == null ? null - : CmcdLog.createInstance(cmcdConfiguration, trackSelection, bufferedDurationUs); + : CmcdLog.createInstance( + cmcdConfiguration, + trackSelection, + bufferedDurationUs, + CmcdLog.STREAMING_FORMAT_HLS, + !playlist.hasEndTag); // Check if the media segment or its initialization segment are fully encrypted. @Nullable diff --git a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsChunkSourceTest.java b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsChunkSourceTest.java index 6ff21c3d8b..41c3fa9380 100644 --- a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsChunkSourceTest.java +++ b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsChunkSourceTest.java @@ -214,7 +214,7 @@ public class HlsChunkSourceTest { "CMCD-Request", "bl=0", "CMCD-Session", - "cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\""); + "cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=h,st=v"); } @Test @@ -260,7 +260,7 @@ public class HlsChunkSourceTest { "CMCD-Request", "bl=0", "CMCD-Session", - "cid=\"mediaIdcontentIdSuffix\"", + "cid=\"mediaIdcontentIdSuffix\",sf=h,st=v", "CMCD-Status", "rtp=4000"); } @@ -307,7 +307,7 @@ public class HlsChunkSourceTest { "CMCD-Request", "bl=0,key2=\"stringValue\"", "CMCD-Session", - "cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",key3=1", + "cid=\"mediaId\",sid=\"" + cmcdConfiguration.sessionId + "\",sf=h,st=v,key3=1", "CMCD-Status", "key4=5.0"); } diff --git a/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSource.java b/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSource.java index ceeb2fbfb0..1f5babe94a 100644 --- a/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSource.java +++ b/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSource.java @@ -284,7 +284,12 @@ public class DefaultSsChunkSource implements SsChunkSource { CmcdLog cmcdLog = cmcdConfiguration == null ? null - : CmcdLog.createInstance(cmcdConfiguration, trackSelection, bufferedDurationUs); + : CmcdLog.createInstance( + cmcdConfiguration, + trackSelection, + bufferedDurationUs, + CmcdLog.STREAMING_FORMAT_SS, + manifest.isLive); out.chunk = newMediaChunk(