Skip to content

feat(cmcd): upgrade to CMCD v2 using @svta/cml-cmcd CmcdReporter#7725

Open
littlespex wants to merge 54 commits into
video-dev:masterfrom
littlespex:issue/7723-cmcd-v2
Open

feat(cmcd): upgrade to CMCD v2 using @svta/cml-cmcd CmcdReporter#7725
littlespex wants to merge 54 commits into
video-dev:masterfrom
littlespex:issue/7723-cmcd-v2

Conversation

@littlespex
Copy link
Copy Markdown
Collaborator

@littlespex littlespex commented Feb 12, 2026

Summary

Implements CMCD v2 support by migrating from @svta/common-media-library to the dedicated @svta/cml-* packages and fully delegating CMCD encoding to CmcdReporter from @svta/cml-cmcd.

Resolves #7723.

Changes

  • Migrate dependencies: Replace @svta/common-media-library with @svta/cml-cmcd, @svta/cml-id3, @svta/cml-utils, and @svta/cml-structured-field-values
  • CMCD v2 data fields: Add version, st (stream type), sta (player state), sn (sequence number) to CMCD output. Inner list encoding for br, tb, bl, mtp, nor per v2 spec
  • CmcdReporter integration: Delegate all CMCD request encoding to CmcdReporter.createRequestReport(), removing manual appendCmcdHeaders/appendCmcdQuery calls and the createData() method
  • Event-mode reporting: Support v2 event targets for play state changes (PLAY_STATE), fatal errors (ERROR), and bitrate changes (BITRATE_CHANGE)
  • Configuration: Add version, enabledKeys, and eventTargets to CMCDControllerConfig. Default enabledKeys to CMCD_V1_KEYS (v1) or CMCD_KEYS (v2)
  • Exported types: Re-export CMCD v2 types (CmcdVersion, CmcdPlayerState, CmcdStreamType, etc.) from exports-named.ts
  • Tests: Comprehensive unit test coverage for v1 defaults, v2 fields (query and headers modes), stream type detection, event reporting, and reporter lifecycle

Behavioral notes

  • v=1 is omitted from output per CMCD spec (it is the default)
  • sn (sequence number) is automatically included in v2 output, filtered out for v1
  • nor uses root-relative paths (via url.origin as baseUrl) instead of path-relative
  • Inner list values (bl, tb) are guarded against NaN to prevent Structured Fields serialization errors

Test plan

  • All 912 unit tests pass
  • Manual verification with a CMCD v1 configuration (query and headers modes)
  • Manual verification with a CMCD v2 configuration (query, headers, and event modes)
  • Verify no source map warnings in downstream bundlers (Vite/Nuxt)

claude and others added 6 commits February 8, 2026 17:45
…d v2 version support

Replace the monolithic @svta/common-media-library package with the scoped
@svta/cml-cmcd@2.1.0 and @svta/cml-utils@1.3.0 packages for CMCD
functionality. The old package is retained for non-CMCD imports (ID3, UTF8).

Add a `version` option to CMCDControllerConfig (defaults to 1 for backwards
compatibility) that controls CMCD encoding version. When set to 2, the
controller uses CMCD v2 Structured Field Value encoding via the new library.

Key changes:
- Update all CMCD imports to use @svta/cml-cmcd single entry point
- Update uuid import to use @svta/cml-utils
- Update tsconfig moduleResolution to "bundler" for exports field support
- Adapt CMCD data fields (br, bl, mtp, tb, nor) to v2 array types
- Pass version through to CmcdEncodeOptions for version-aware encoding

https://claude.ai/code/session_01FmnN6xNSm9Qo17tp52ag3U
Phase 3: Add new CMCD v2 data fields to the controller:
- Stream type (st): Detect VOD/LIVE/LOW_LATENCY from level details
  based on live flag, canBlockReload, and canSkipUntil properties
- Player state (sta): Track player state transitions via media element
  events (waiting, playing, pause, seeking, ended) and hls.js ERROR
  events for fatal errors. Maps to CmcdPlayerState enum values.
- Both fields are only included when version >= 2

Phase 4: Integrate CmcdReporter for event-mode reporting:
- Add eventTargets config option (CmcdEventReportConfig[]) for v2
  event reporting endpoints
- Instantiate CmcdReporter when version >= 2 and eventTargets are
  configured, with session/content ID and transmission mode
- Record PLAY_STATE events on player state transitions
- Record ERROR events on fatal hls.js errors
- Record BITRATE_CHANGE events on level switches
- Stop and flush reporter on controller destroy

Add unit tests for v2 version encoding, stream type detection (VOD,
LIVE, LOW_LATENCY), and player state inclusion.

https://claude.ai/code/session_01FmnN6xNSm9Qo17tp52ag3U
Phase 6: Re-export CMCD types and constants from exports-named.ts for
ESM consumers: CmcdObjectType, CmcdStreamType, CmcdStreamingFormat,
CmcdPlayerState, CmcdEventType, CmcdHeaderField, CMCD_V1, CMCD_V2,
and type exports for Cmcd, CmcdEncodeOptions, CmcdEventReportConfig,
CmcdVersion. Add @svta/cml-cmcd, @svta/cml-utils, and
@svta/cml-structured-field-values to api-extractor bundledPackages
so external types are inlined in the rolled-up dist/hls.d.ts.

Phase 7: Add tests for:
- v2 fragment data includes version, stream type, and player state
- v2 headers mode includes v2 fields in CMCD headers
- Reporter is not created without eventTargets or for v1
- Reporter is created with v2 + eventTargets
- Reporter.stop(true) is called on destroy
- Play state events are recorded on state transitions
- Error events are recorded on fatal hls.js errors
- Duplicate player state events are deduplicated

Phase 8: Verified TypeScript type-check, all Rollup build configs
(full, fullEsm, light), and api-extractor declaration bundling.

https://claude.ai/code/session_01FmnN6xNSm9Qo17tp52ag3U
Comment thread src/controller/cmcd-controller.ts Outdated
Comment thread src/controller/id3-track-controller.ts Outdated
Comment thread src/controller/cmcd-controller.ts
Comment thread src/controller/cmcd-controller.ts Outdated
@robwalch robwalch linked an issue Feb 14, 2026 that may be closed by this pull request
@robwalch robwalch added the CMCD label Feb 16, 2026
littlespex and others added 4 commits February 23, 2026 13:08
…ooks

CmcdReporter sends CMCD v2 event reports via a requester function that
defaults to bare fetch(), bypassing customer auth headers and credentials
configured via xhrSetup/fetchSetup. Add a createCmcdRequester adapter
that routes through the same setup hooks applied to media/playlist requests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add end-to-end tests for CMCD v2 covering query mode, header mode, event
mode, key filtering, and version comparison (v1 vs v2). Fix eventTargets
enabledKeys mapping bug in CMCDController where includeKeys was not being
mapped to the library's enabledKeys parameter, causing event reports to
have no enabled keys.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread tests/e2e/cmcd.ts
Comment thread tests/mocks/cmcd-request-collector.ts Outdated
littlespex and others added 2 commits February 25, 2026 20:09
… option

Remove createCmcdRequester function and allow users to pass a custom
loader via cmcd.loader config. When not provided, CmcdReporter uses
its default fetch-based requester.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The browserNoActivityTimeout and client.mocha.timeout overrides were
added during an earlier iteration of the CMCD e2e tests. The e2e suite
now sets its own describe-level timeout, so the karma-level overrides
aren't needed.
littlespex and others added 2 commits May 19, 2026 21:19
Defers to hls.mainForwardBufferInfo for VIDEO/MUXED so reported bl matches
the player's authoritative forward-buffer view (handles alt-audio vs muxed
SourceBuffer selection, backward-seek/paused maxBufferHole=0, gap skipping,
and live/interstitial buffer-before-seek). Audio path keeps the direct
SourceBuffer query pending video-dev#7858 but mirrors the paused -> hole=0 rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@robwalch robwalch added this to the 1.7.0 milestone May 20, 2026
@robwalch robwalch self-requested a review May 20, 2026 16:14
Comment thread tests/unit/utils/utf8.ts
Comment thread src/controller/cmcd-controller.ts Outdated
Comment thread src/controller/cmcd-controller.ts Outdated
littlespex and others added 3 commits May 21, 2026 11:33
Resolves Rob's review feedback on video-dev#7725.

getObjectType now accepts an optional Level | LevelSwitchingData and
resolves main-fragment ot by (1) variant audioCodec/videoCodec, then
(2) Fragment.elementaryStreams when parsed, otherwise undefined. The
hls.audioTracks.length heuristic is removed — it misclassified
muxed fMP4 with alt audio renditions, audio-only main playlists, and
video-only variants.

getBufferLength and getTopBandwidth now branch on Fragment.type
instead of CmcdObjectType, so audio-only main playlists correctly
draw from hls.levels / mainForwardBufferInfo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The br/tb/bl block in applyFragmentData was gated on ot ∈ {VIDEO,AUDIO,MUXED}.
For single-rendition streams with no master playlist (muxed-fmp4, mp3 audio-
only), the variant carries no codec metadata, and the fragment's
elementaryStreams aren't populated until after the request fires — so
getObjectType returns undefined and the gate skipped these fields entirely.

Widen the gate to also run when ot is null and frag.type is 'main' or
'audio'. getBufferLength and getTopBandwidth already branch on frag.type,
not ot, so they return correct values regardless of ot resolution.

Also tighten the loose-equality MUXED check to strict equality.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bl was only written to per-request CMCD payloads in applyFragmentData,
never to the reporter's persistent data (this.data) or recordEvent
payloads. Since events are built as { ...this.data, ...perEventData,
e, ts, sn }, bl never appeared on TIME_INTERVAL, PLAY_STATE,
BITRATE_CHANGE, or other event reports.

Register listeners on BUFFER_APPENDED and BUFFER_FLUSHED that call
reporter.update({ bl: [...] }) using a no-fragment buffer-length
helper — min(main, audio) when both buffer sources exist, otherwise
whichever is available. This makes bl available on all subsequent
event reports while keeping per-request bl unchanged (fragment-
specific value still flows through applyFragmentData).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@robwalch robwalch self-requested a review May 21, 2026 22:23
Comment thread src/controller/cmcd-controller.ts Outdated
Comment thread src/controller/cmcd-controller.ts Outdated
littlespex and others added 3 commits May 21, 2026 16:18
…sed streams

CODECS in a STREAM-INF describes the variant plus any alternate renditions it
references, so an audioCodec value can belong to an alt audio track rather than
the main variant. Only infer MUXED/AUDIO from the variant's audio+video codecs
when no audio media options exist; videoCodec always implies VIDEO.

Also reorder getObjectType so parsed fragment.elementaryStreams take precedence
over variant codec hints — the parsed segment is ground truth when available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two TIME_INTERVAL events with sn=0 fired back-to-back at the start of
playback. The CMCDController created the reporter in its constructor
(firing the initial t event via start()) and then immediately tore it
down and recreated it on MANIFEST_LOADING (firing another t event with
sn=0 from the new reporter's fresh counter).

Move reporter creation out of the constructor — it now lives only in
onManifestLoading, giving one reporter per master manifest. Construct
CMCDController before PlaylistLoader so its MANIFEST_LOADING listener
registers first and the reporter exists when the manifest request
applies CMCD data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread src/hls.ts
Comment thread src/controller/cmcd-controller.ts Outdated
littlespex and others added 5 commits May 22, 2026 15:25
Per CTA-5004-B, sta should be present from the first CMCD request and
should reflect the actual playback context. Previously the controller
defaulted to STARTING ("s") at construction, which inflated the msd
metric (measured from STARTING → PLAYING) by counting time before play
was instructed.

Changes:

- Listen to MEDIA_ATTACHING (sync from hls.attachMedia) instead of
  MEDIA_ATTACHED (async from buffer-controller). This lets us read
  media.autoplay before the first manifest URL is built when attachMedia
  is called before loadSource.
- On MEDIA_ATTACHING, set state to STARTING when media.autoplay is true,
  otherwise PRELOADING ("d").
- In onManifestLoading, when no media is attached yet (load-before-attach
  order), set state to PRELOADING so the first manifest still carries
  sta.
- Add a 'play' media-event listener that transitions PRELOADING →
  STARTING on the first play() call. Subsequent play events (post-pause)
  are no-ops since this.initialized is set by onPlaying.
- Guard onSeeking/onSeeked on this.initialized: per spec, SEEKING is
  "after starting", and STARTING/PRELOADING should persist through
  pre-playback seeks. Mirrors the existing onWaiting guard for
  REBUFFERING.

Tests:

- Unit: new "initial sta matrix (attach order × autoplay)" describe
  covering the 4 attach-order/autoplay combinations.
- E2E: new "Group 6: initial sta matrix" covering all 6 combinations of
  autoplay × autoStartLoad × attach order against a real stream.
- Existing e2e manifest-mode tests restored to assert sta=s (autoplay=T,
  attach→load matches the default test setup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add ratechange listener; reporter.update({ pr }) auto-fires PLAYBACK_RATE
- Drop local sta dedup guard and explicit recordEvent calls in
  setPlayerState and onLevelSwitching; rely on library auto-fire and br
  write-through
- Replace CmcdRequestCollector mock with CmcdReportRecorder in e2e tests

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Development

Successfully merging this pull request may close these issues.

CMCD Version 2 (CTA-5004) Support

3 participants