From 2723bc739a976fb6ef94c036ce3d039fa7416473 Mon Sep 17 00:00:00 2001 From: uncoolclub Date: Thu, 25 Jul 2024 13:36:03 +0900 Subject: [PATCH 01/16] feat(VideoNativeComponent.ts): add support for cmcd configuration in VideoSrc type to enable cmcd feature on android feat(video.ts): introduce CmcdMode enum and CmcdConfiguration type to define cmcd configuration options --- src/specs/VideoNativeComponent.ts | 10 ++++++++++ src/types/video.ts | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index 546bf97d8e..776033881a 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -40,6 +40,7 @@ export type VideoSrc = Readonly<{ cropEnd?: Float; metadata?: VideoMetadata; drm?: Drm; + cmcd?: NativeCmcdConfiguration; // android textTracksAllowChunklessPreparation?: boolean; // android }>; @@ -61,6 +62,15 @@ type Drm = Readonly<{ multiDrm?: WithDefault; // android }>; +type CmcdMode = WithDefault; +export type NativeCmcdConfiguration = Readonly<{ + mode?: CmcdMode; // default: MODE_QUERY_PARAMETER + request?: Headers; + session?: Headers; + object?: Headers; + status?: Headers; +}>; + type TextTracks = ReadonlyArray< Readonly<{ title: string; diff --git a/src/types/video.ts b/src/types/video.ts index eb075f9c1c..76be94ff7f 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -33,6 +33,7 @@ export type ReactVideoSourceProperties = { cropEnd?: number; metadata?: VideoMetadata; drm?: Drm; + cmcd?: Cmcd; // android textTracksAllowChunklessPreparation?: boolean; }; @@ -87,6 +88,27 @@ export type Drm = Readonly<{ /* eslint-enable @typescript-eslint/no-unused-vars */ }>; +export enum CmcdMode { + MODE_REQUEST_HEADER = 0, + MODE_QUERY_PARAMETER = 1, +} +/** + * Custom key names MUST carry a hyphenated prefix to ensure that there will not be a + * namespace collision with future revisions to this specification. Clients SHOULD + * use a reverse-DNS syntax when defining their own prefix. + * + * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf CTA-5004 Specification (Page 6, Section 3.1) + */ +export type CmcdData = Record<`${string}-${string}`, string | number>; +export type CmcdConfiguration = Readonly<{ + mode?: CmcdMode; // default: MODE_QUERY_PARAMETER + request?: CmcdData; + session?: CmcdData; + object?: CmcdData; + status?: CmcdData; +}>; +export type Cmcd = boolean | CmcdConfiguration; + export enum BufferingStrategyType { DEFAULT = 'Default', DISABLE_BUFFERING = 'DisableBuffering', From ebd60817efcab0a3eecc0cb076743730c6017d30 Mon Sep 17 00:00:00 2001 From: uncoolclub Date: Thu, 25 Jul 2024 13:36:25 +0900 Subject: [PATCH 02/16] feat(Video.tsx): add support for CMCD configuration in Video component to handle Content Management and Delivery (CMCD) headers for Android platform. --- src/Video.tsx | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Video.tsx b/src/Video.tsx index 49ca1e990b..429bc5d41d 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -16,7 +16,9 @@ import type { ImageResizeMode, } from 'react-native'; -import NativeVideoComponent from './specs/VideoNativeComponent'; +import NativeVideoComponent, { + NativeCmcdConfiguration, +} from './specs/VideoNativeComponent'; import type { OnAudioFocusChangedData, OnAudioTracksData, @@ -44,7 +46,7 @@ import { } from './utils'; import NativeVideoManager from './specs/NativeVideoManager'; import type {VideoSaveData} from './specs/NativeVideoManager'; -import {ViewType} from './types'; +import {CmcdMode, ViewType} from './types'; import type { OnLoadData, OnTextTracksData, @@ -142,6 +144,30 @@ const Video = forwardRef( setRestoreUserInterfaceForPIPStopCompletionHandler, ] = useState(); + const _cmcd = useMemo(() => { + if (Platform.OS !== 'android' || !source?.cmcd) { + return undefined; + } + + if (typeof source?.cmcd === 'boolean') { + return source?.cmcd ? {mode: CmcdMode.MODE_QUERY_PARAMETER} : undefined; + } + + const cmcd = source.cmcd; + + return { + mode: cmcd.mode ?? CmcdMode.MODE_QUERY_PARAMETER, + request: cmcd.request + ? generateHeaderForNative(cmcd.request) + : undefined, + session: cmcd.session + ? generateHeaderForNative(cmcd.session) + : undefined, + object: cmcd.object ? generateHeaderForNative(cmcd.object) : undefined, + status: cmcd.status ? generateHeaderForNative(cmcd.status) : undefined, + }; + }, [source?.cmcd]); + const src = useMemo(() => { if (!source) { return undefined; @@ -190,10 +216,11 @@ const Video = forwardRef( cropEnd: resolvedSource.cropEnd, metadata: resolvedSource.metadata, drm: _drm, + cmcd: _cmcd, textTracksAllowChunklessPreparation: resolvedSource.textTracksAllowChunklessPreparation, }; - }, [drm, source]); + }, [_cmcd, drm, source]); const _selectedTextTrack = useMemo(() => { if (!selectedTextTrack) { From 7d3cd847d7ab21f7577268b1e82e95c2f41ddcbb Mon Sep 17 00:00:00 2001 From: uncoolclub Date: Thu, 25 Jul 2024 13:36:42 +0900 Subject: [PATCH 03/16] feat(CMCDProps.kt): add CMCDProps class to handle CMCD related properties and parsing logic for React Native module --- .../com/brentvatne/common/api/CMCDProps.kt | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 android/src/main/java/com/brentvatne/common/api/CMCDProps.kt diff --git a/android/src/main/java/com/brentvatne/common/api/CMCDProps.kt b/android/src/main/java/com/brentvatne/common/api/CMCDProps.kt new file mode 100644 index 0000000000..7efed9e824 --- /dev/null +++ b/android/src/main/java/com/brentvatne/common/api/CMCDProps.kt @@ -0,0 +1,77 @@ +package com.brentvatne.common.api + +import androidx.media3.exoplayer.upstream.CmcdConfiguration +import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetInt +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableType + +class CMCDProps { + var cmcdObject: Array> = emptyArray() + var cmcdRequest: Array> = emptyArray() + var cmcdSession: Array> = emptyArray() + var cmcdStatus: Array> = emptyArray() + var mode: Int = CmcdConfiguration.MODE_QUERY_PARAMETER + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CMCDProps + + if (!cmcdObject.contentDeepEquals(other.cmcdObject)) return false + if (!cmcdRequest.contentDeepEquals(other.cmcdRequest)) return false + if (!cmcdSession.contentDeepEquals(other.cmcdSession)) return false + if (!cmcdStatus.contentDeepEquals(other.cmcdStatus)) return false + if (mode != other.mode) return false + + return true + } + + override fun hashCode(): Int { + var result = cmcdObject.contentHashCode() + result = 31 * result + cmcdRequest.contentHashCode() + result = 31 * result + cmcdSession.contentHashCode() + result = 31 * result + cmcdStatus.contentHashCode() + result = 31 * result + mode + return result + } + + companion object { + private const val PROP_CMCD_OBJECT = "object" + private const val PROP_CMCD_REQUEST = "request" + private const val PROP_CMCD_SESSION = "session" + private const val PROP_CMCD_STATUS = "status" + private const val PROP_CMCD_MODE = "mode" + + @JvmStatic + fun parse(src: ReadableMap?): CMCDProps? { + if (src == null) return null + + val cmcdProps = CMCDProps() + cmcdProps.cmcdObject = parseKeyValuePairs(src.getArray(PROP_CMCD_OBJECT)) + cmcdProps.cmcdRequest = parseKeyValuePairs(src.getArray(PROP_CMCD_REQUEST)) + cmcdProps.cmcdSession = parseKeyValuePairs(src.getArray(PROP_CMCD_SESSION)) + cmcdProps.cmcdStatus = parseKeyValuePairs(src.getArray(PROP_CMCD_STATUS)) + cmcdProps.mode = safeGetInt(src, PROP_CMCD_MODE, CmcdConfiguration.MODE_QUERY_PARAMETER) + + return cmcdProps + } + + private fun parseKeyValuePairs(array: ReadableArray?): Array> { + if (array == null) return emptyArray() + + return (0 until array.size()).mapNotNull { i -> + val item = array.getMap(i) + val key = item?.getString("key") + val value = when (item?.getType("value")) { + ReadableType.Number -> item.getDouble("value") + ReadableType.String -> item.getString("value") + else -> null + } + + if (key != null && value != null) Pair(key, value) else null + }.toTypedArray() + } + } +} From 3d49ccebd0c898f69a378e63678ee1460cc8020e Mon Sep 17 00:00:00 2001 From: uncoolclub Date: Thu, 25 Jul 2024 13:36:52 +0900 Subject: [PATCH 04/16] feat(CMCDConfig.kt): add CMCDConfig class to handle CMCD configuration for ExoPlayer with support for custom data and configuration options. --- .../com/brentvatne/exoplayer/CMCDConfig.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 android/src/main/java/com/brentvatne/exoplayer/CMCDConfig.kt diff --git a/android/src/main/java/com/brentvatne/exoplayer/CMCDConfig.kt b/android/src/main/java/com/brentvatne/exoplayer/CMCDConfig.kt new file mode 100644 index 0000000000..4103c156ca --- /dev/null +++ b/android/src/main/java/com/brentvatne/exoplayer/CMCDConfig.kt @@ -0,0 +1,41 @@ +package com.brentvatne.exoplayer + +import androidx.media3.common.MediaItem +import androidx.media3.exoplayer.upstream.CmcdConfiguration +import com.brentvatne.common.api.CMCDProps +import com.google.common.collect.ImmutableListMultimap + +class CMCDConfig(private val props: CMCDProps) { + fun toCmcdConfigurationFactory(): CmcdConfiguration.Factory = CmcdConfiguration.Factory(::createCmcdConfiguration) + + private fun createCmcdConfiguration(mediaItem: MediaItem): CmcdConfiguration = + CmcdConfiguration( + java.util.UUID.randomUUID().toString(), + mediaItem.mediaId, + object : CmcdConfiguration.RequestConfig { + override fun getCustomData(): ImmutableListMultimap = buildCustomData() + }, + props.mode + ) + + private fun buildCustomData(): ImmutableListMultimap = + ImmutableListMultimap.builder().apply { + addFormattedData(this, CmcdConfiguration.KEY_CMCD_OBJECT, props.cmcdObject) + addFormattedData(this, CmcdConfiguration.KEY_CMCD_REQUEST, props.cmcdRequest) + addFormattedData(this, CmcdConfiguration.KEY_CMCD_SESSION, props.cmcdSession) + addFormattedData(this, CmcdConfiguration.KEY_CMCD_STATUS, props.cmcdStatus) + }.build() + + private fun addFormattedData(builder: ImmutableListMultimap.Builder, key: String, dataArray: Array>) { + dataArray.forEach { (dataKey, dataValue) -> + builder.put(key, formatKeyValue(dataKey, dataValue)) + } + } + + private fun formatKeyValue(key: String, value: Any): String = + when (value) { + is String -> "$key=\"$value\"" + is Number -> "$key=$value" + else -> throw IllegalArgumentException("Unsupported value type: ${value::class.java}") + } +} From 2548b5a1363f969ef6db8e98852b930bf2216c75 Mon Sep 17 00:00:00 2001 From: uncoolclub Date: Thu, 25 Jul 2024 13:38:10 +0900 Subject: [PATCH 05/16] feat(ReactExoplayerViewManager.java): add support for CMCD configuration in ReactExoplayerViewManager to enable Content Management and Control Data (CMCD) for better video playback optimization. --- .../exoplayer/ReactExoplayerViewManager.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index e9ae70bc9b..f91ada5224 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -7,11 +7,11 @@ import android.util.Log; import androidx.annotation.NonNull; +import androidx.media3.exoplayer.upstream.CmcdConfiguration; import com.brentvatne.common.api.BufferConfig; import com.brentvatne.common.api.BufferingStrategy; import com.brentvatne.common.api.ControlsConfig; -import com.brentvatne.common.api.DRMProps; import com.brentvatne.common.api.ResizeMode; import com.brentvatne.common.api.SideLoadedTextTrackList; import com.brentvatne.common.api.Source; @@ -78,6 +78,9 @@ public class ReactExoplayerViewManager extends ViewGroupManager Date: Thu, 25 Jul 2024 13:38:31 +0900 Subject: [PATCH 06/16] feat(ReactExoplayerView.java): add support for setting CmcdConfiguration.Factory to customize CMCD configuration for media playback --- .../brentvatne/exoplayer/ReactExoplayerView.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 8ebba8c5bd..080c7fad7f 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -96,6 +96,7 @@ import androidx.media3.exoplayer.trackselection.TrackSelection; import androidx.media3.exoplayer.trackselection.TrackSelectionArray; import androidx.media3.exoplayer.upstream.BandwidthMeter; +import androidx.media3.exoplayer.upstream.CmcdConfiguration; import androidx.media3.exoplayer.upstream.DefaultAllocator; import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; import androidx.media3.exoplayer.util.EventLogger; @@ -269,6 +270,12 @@ public class ReactExoplayerView extends FrameLayout implements private String instanceId = String.valueOf(UUID.randomUUID()); + private CmcdConfiguration.Factory cmcdConfigurationFactory; + + public void setCmcdConfigurationFactory(CmcdConfiguration.Factory factory) { + this.cmcdConfigurationFactory = factory; + } + private void updateProgress() { if (player != null) { if (playerControlView != null && isPlayingAd() && controls) { @@ -1097,6 +1104,12 @@ private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessi } } + if (cmcdConfigurationFactory != null) { + mediaSourceFactory = mediaSourceFactory.setCmcdConfigurationFactory( + cmcdConfigurationFactory::createCmcdConfiguration + ); + } + MediaItem mediaItem = mediaItemBuilder.setStreamKeys(streamKeys).build(); MediaSource mediaSource = mediaSourceFactory .setDrmSessionManagerProvider(drmProvider) From 747c7aa078eaa6fdd78fb8ee7342c355b1346f6c Mon Sep 17 00:00:00 2001 From: uncoolclub Date: Thu, 25 Jul 2024 13:38:37 +0900 Subject: [PATCH 07/16] feat(Source.kt): add support for CMCD properties linked to the source to enhance functionality and data handling --- .../src/main/java/com/brentvatne/common/api/Source.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/brentvatne/common/api/Source.kt b/android/src/main/java/com/brentvatne/common/api/Source.kt index ba4f849605..b9ea8bf762 100644 --- a/android/src/main/java/com/brentvatne/common/api/Source.kt +++ b/android/src/main/java/com/brentvatne/common/api/Source.kt @@ -57,6 +57,11 @@ class Source { */ var textTracksAllowChunklessPreparation: Boolean = false + /** + * CMCD properties linked to the source + */ + var cmcdProps: CMCDProps? = null + override fun hashCode(): Int = Objects.hash(uriString, uri, startPositionMs, cropStartMs, cropEndMs, extension, metadata, headers) /** return true if this and src are equals */ @@ -68,7 +73,8 @@ class Source { cropEndMs == other.cropEndMs && startPositionMs == other.startPositionMs && extension == other.extension && - drmProps == other.drmProps + drmProps == other.drmProps && + cmcdProps == other.cmcdProps ) } @@ -131,6 +137,7 @@ class Source { private const val PROP_SRC_METADATA = "metadata" private const val PROP_SRC_HEADERS = "requestHeaders" private const val PROP_SRC_DRM = "drm" + private const val PROP_SRC_CMCD = "cmcd" private const val PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION = "textTracksAllowChunklessPreparation" @SuppressLint("DiscouragedApi") @@ -189,6 +196,7 @@ class Source { source.cropEndMs = safeGetInt(src, PROP_SRC_CROP_END, -1) source.extension = safeGetString(src, PROP_SRC_TYPE, null) source.drmProps = parse(safeGetMap(src, PROP_SRC_DRM)) + source.cmcdProps = CMCDProps.parse(safeGetMap(src, PROP_SRC_CMCD)) source.textTracksAllowChunklessPreparation = safeGetBool(src, PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION, true) val propSrcHeadersArray = safeGetArray(src, PROP_SRC_HEADERS) From 741b6e5d54aa5bffd7f1e5940ee410075f67e98f Mon Sep 17 00:00:00 2001 From: uncoolclub Date: Thu, 25 Jul 2024 14:16:58 +0900 Subject: [PATCH 08/16] docs(props.mdx): add documentation for configuring CMCD parameters in the component, including usage examples and default values --- docs/pages/component/props.mdx | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index c1603b8de6..f4d857d5b1 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -999,3 +999,51 @@ Adjust the volume. - **1.0 (default)** - Play at full volume - **0.0** - Mute the audio - **Other values** - Reduce volume + +### `cmcd` + + + +Configure CMCD (Common Media Client Data) parameters. CMCD is a standard for conveying client-side metrics and capabilities to servers, which can help improve streaming quality and performance. + +For detailed information about CMCD, please refer to the [CTA-5004 Final Specification](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf). + +- **false (default)** - Don't use CMCD +- **true** - Use default CMCD configuration +- **object** - Use custom CMCD configuration + +When providing an object, you can configure the following properties: + +| Property | Type | Description | +|----------|-------------------------|----------------------------------------------------| +| mode | CmcdMode | The mode for sending CMCD data | +| request | { [key: `string-string`]: string \| number } | Custom key-value pairs for the request object | +| session | { [key: `string-string`]: string \| number } | Custom key-value pairs for the session object | +| object | { [key: `string-string`]: string \| number } | Custom key-value pairs for the object metadata | +| status | { [key: `string-string`]: string \| number } | Custom key-value pairs for the status information | + +Note: The `mode` property defaults to `CmcdMode.MODE_QUERY_PARAMETER` if not specified. + +Example: + +```javascript +source={{ + uri: 'https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8', + cmcd: { + mode: CmcdMode.MODE_QUERY_PARAMETER, + request: { + 'com-custom-key': 'custom-value' + }, + session: { + sid: 'session-id' + }, + object: { + br: '3000', + d: '4000' + }, + status: { + rtp: '1200' + } + } + }} +``` \ No newline at end of file From 876b3aa89f56063df27b4f0889161c03a3a60715 Mon Sep 17 00:00:00 2001 From: uncoolclub Date: Thu, 25 Jul 2024 21:02:20 +0900 Subject: [PATCH 09/16] refactor(ReactExoplayerViewManager.java): remove unused PROP_CMCD and prevCmcdConfig variables to clean up code and improve readability --- .../com/brentvatne/exoplayer/ReactExoplayerViewManager.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index f91ada5224..c48daca07e 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -78,9 +78,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager Date: Thu, 25 Jul 2024 21:02:35 +0900 Subject: [PATCH 10/16] refactor(Video.tsx): simplify cmcd configuration logic for better readability and maintainability --- src/Video.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Video.tsx b/src/Video.tsx index 429bc5d41d..35b9be4dac 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -146,14 +146,20 @@ const Video = forwardRef( const _cmcd = useMemo(() => { if (Platform.OS !== 'android' || !source?.cmcd) { - return undefined; + return; } - if (typeof source?.cmcd === 'boolean') { - return source?.cmcd ? {mode: CmcdMode.MODE_QUERY_PARAMETER} : undefined; + const cmcd = source.cmcd; + + if (typeof cmcd === 'boolean') { + return cmcd ? {mode: CmcdMode.MODE_QUERY_PARAMETER} : undefined; } - const cmcd = source.cmcd; + if (typeof cmcd !== 'object' || Array.isArray(cmcd)) { + throw new Error( + 'Invalid CMCD configuration: Expected a boolean or an object.', + ); + } return { mode: cmcd.mode ?? CmcdMode.MODE_QUERY_PARAMETER, From 1c3672674b6814b5e04cbb8665640d533951a064 Mon Sep 17 00:00:00 2001 From: uncoolclub Date: Thu, 25 Jul 2024 21:23:41 +0900 Subject: [PATCH 11/16] docs(props.mdx): improve props documentation for clarity and consistency feat(props.mdx): add definitions for CmcdMode enum and CmcdData type to enhance understanding of CMCD data structure and usage --- docs/pages/component/props.mdx | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index f4d857d5b1..a68a1fdac6 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -1016,18 +1016,33 @@ When providing an object, you can configure the following properties: | Property | Type | Description | |----------|-------------------------|----------------------------------------------------| -| mode | CmcdMode | The mode for sending CMCD data | -| request | { [key: `string-string`]: string \| number } | Custom key-value pairs for the request object | -| session | { [key: `string-string`]: string \| number } | Custom key-value pairs for the session object | -| object | { [key: `string-string`]: string \| number } | Custom key-value pairs for the object metadata | -| status | { [key: `string-string`]: string \| number } | Custom key-value pairs for the status information | +| `mode` | `CmcdMode` | The mode for sending CMCD data | +| `request` | `CmcdData` | Custom key-value pairs for the request object | +| `session` | `CmcdData` | Custom key-value pairs for the session object | +| `object` | `CmcdData` | Custom key-value pairs for the object metadata | +| `status` | `CmcdData` | Custom key-value pairs for the status information | Note: The `mode` property defaults to `CmcdMode.MODE_QUERY_PARAMETER` if not specified. +#### `CmcdMode` +CmcdMode is an enum that defines how CMCD data should be sent: +- `CmcdMode.MODE_REQUEST_HEADER` (0) - Send CMCD data in the HTTP request headers. +- `CmcdMode.MODE_QUERY_PARAMETER` (1) - Send CMCD data as query parameters in the URL. + +#### `CmcdData` +CmcdData is a type representing custom key-value pairs for CMCD data. It's defined as: + +```typescript +type CmcdData = Record<`${string}-${string}`, string | number>; +``` + +Custom key names MUST include a hyphenated prefix to prevent namespace collisions. It's recommended to use a reverse-DNS syntax for custom prefixes. + Example: ```javascript -source={{ +