refactor(cmcd): vendor @svta/cml-cmcd, replace custom encoder + state machine#16
Open
littlespex wants to merge 168 commits into
Open
refactor(cmcd): vendor @svta/cml-cmcd, replace custom encoder + state machine#16littlespex wants to merge 168 commits into
littlespex wants to merge 168 commits into
Conversation
Spec the migration from shaka's custom CMCD integration to @svta/cml-cmcd via a vendored closure-port in third_party/cml-cmcd/. CmcdManager becomes a thin adapter over CmcdReporter; encoding, validation, and state tracking move upstream to CML as the single source of truth. Three-phase plan: vendor + delegate encoding, dedupe constants, then swap state machine for CmcdReporter. Vendored port is transitional and deletes when shaka adopts TypeScript (shaka-project#8262). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Compared the spec against hls.js shaka-project#7725 and dash.js shaka-project#4816 (the two open PRs migrating to @svta/cml-cmcd) and addressed 11 gaps: Correctness: - Inner-list array encoding for v2 keys (br, tb, bl, mtp, nor) - NaN guards on bl/tb/mtp before encoding - Distinguish reporter.update() from reporter.recordEvent() - Add reporter.start()/stop(flush)/flush() lifecycle - Event-mode transport is POST + body, not query/headers Design clarifications: - Player-state deduplication in adapter - 'nor' becomes root-relative (intentional spec-conformance change) - 'v=1' omitted from output - Public re-exports: CmcdEventType, CmcdPlayerState - Per-target eventTargets[] field shape enumeration - Reporter chosen always-on (unified request + event mode path) Plus: architecture diagram refresh, CML API confirmations from source, Phase 1 reframed for the 'nor' change, Phase 3 adds demo updates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan-vs-spec attribution was wrong: the enum-name guesses for pe/pc/t/c/b live in plan.md:86 (the Task 0.3 description), not spec.md:530-534 (which lists only two-letter codes). Also expanded the requester misnomer callout to cover both spec.md:449 and spec.md:511. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…c lists The "Update spec.md immediately" section mixed plan.md and spec.md items under a spec-only heading and double-listed the requester misnomer. Split into separate "Update plan.md" and "Update spec.md" subsections; combined the two requester entries; added line-ref specificity to each spec.md item. Also added a quoted spec assertion to gap #3 for symmetry with the other gap entries. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Reclassify CmcdStreamingFormat finding: shaka's 'ld'/'lh' are non-spec (from an old unreleased CMCD draft); drop in Phase 1 instead of filing upstream. No CML changes required. - Apply 5 spec/plan doc fixes from cml-version.md (interval rename, recordResponseReceived shape, requester as positional arg, no Blob body, plan.md:86 enum-name guesses). - Update plan.md Phase 1 to include the LL value drop as a wire-format change alongside nor URL relativization (new Task 1.10b). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the third_party/cml-cmcd/ directory with LICENSE, NOTICE, and SUMMARY.txt copied from CML at tag cmcd-v2.3.0 (commit 22390e35dfbbe1e53d15648d3aace99cdf71f9dd). Source files in subsequent commits. CML and shaka-player both Apache 2.0; no license-compatibility issues. This vendored port is transitional — once shaka migrates to TypeScript (issue shaka-project#8262), the directory deletes and `goog.require('cml.cmcd.X')` becomes `import { X } from '@svta/cml-cmcd'`.
Adds 20 closure typedef files under cml.cmcd.* corresponding to CML's type-only TypeScript modules in libs/cmcd/src/. Each file declares a single namespaced @typedef with no runtime body. Skipped from this commit (will land in Phase 1 sub-phase C alongside CmcdReporter): CmcdReporterConfig, CmcdReportConfig, CmcdRequestReport, CmcdRequestReportConfig, CmcdEventReportConfig — all reporter-only. Translation notes: - TypeScript intersection types (`A & B`) cannot be expressed in closure typedefs; the inheriting types (CmcdResponse, CmcdEvent, Cmcd) inline the resolved superset of fields directly. All members are optional, matching CML's "no key is mandatory" wire format. - `keyof T` and `ValueOf<T>` constructs widen to `string` / `*` respectively. Effective key sets are enumerated by the constants ported in Task 1.4 (CMCD_KEYS, CMCD_REQUEST_KEYS, etc.). - Structured-field `SfItem<T, ...>` wrappers cannot be statically expressed; CmcdObjectTypeList widens to `Array<*>` and CmcdFormatter return widens to `*`. Wire output is unaffected. - Type-only TS imports erase; cross-typedef references use closure namespace lookup via `goog.require`.
Adds 8 closure @enum {string} files under cml.cmcd.*: - cmcd_object_type.js - cmcd_streaming_format.js (DASH/HLS/SMOOTH/OTHER — no LL variants) - cmcd_stream_type.js - cmcd_player_state.js - cmcd_event_type.js - cmcd_reporting_mode.js - cmcd_transmission_mode.js - cmcd_header_field.js Enum string values verified verbatim against CML cmcd-v2.3.0 source (`libs/cmcd/src/Cmcd*.ts`). For enum files that also expose individual named-constant exports (CmcdEventType, CmcdReportingMode, CmcdTransmissionMode, CmcdHeaderField), each `export const X = 'literal'` becomes a separate `goog.provide`d `cml.cmcd.X` constant alongside the enum so consumers can `goog.require` either form.
Adds 15 constant-module files under cml.cmcd.* corresponding to CML's data-only constant modules: - cmcd_default_time_interval.js - cmcd_event_keys.js - cmcd_header_map_const.js (the data; CmcdHeaderField enum is separate) - cmcd_inner_list_keys.js (Set instance) - cmcd_key_types.js (CMCD_KEY_TYPES + CMCD_V1_KEY_TYPE_OVERRIDES) - cmcd_keys.js (composed from request/response/event/v1 lists) - cmcd_mime_type.js (application/cmcd) - cmcd_param.js (the CMCD query param name) - cmcd_request_keys.js - cmcd_response_keys.js - cmcd_string_length_limits.js (+CMCD_CUSTOM_KEY_VALUE_MAX_LENGTH) - cmcd_token_values.js - cmcd_v1_const.js (the numeric `CMCD_V1 = 1` constant) - cmcd_v1_keys.js - cmcd_v2_const.js (the numeric `CMCD_V2 = 2` constant) Naming note: TS files `CmcdV1.ts` (typedef) and `CMCD_V1.ts` (numeric const) both map to `cmcd_v1.js` under the strict "TS filename → snake_case" rule. Disambiguated by suffixing the numeric-const file with `_const` (likewise `cmcd_v2_const.js`). Same disambiguation for `CMCD_HEADER_MAP.ts` vs the `CmcdHeaderMap` typedef → `cmcd_header_map_const.js`. Deferred: CMCD_FORMATTER_MAP.ts is intentionally NOT ported in this commit. It defines runtime functions that depend on `SfItem` from `@svta/cml-structured-field-values` (not in the vendoring scope) and `urlToRelativePath` from `@svta/cml-utils` — both of which require infrastructure that lands with the encoders in Task 1.6. Its only consumer is `prepareCmcdData.ts` (also Task 1.6), so deferring is self-contained. Values verified verbatim against CML cmcd-v2.3.0 source.
Adds third_party/cml-cmcd/cml_utils.js providing `cml.cmcd.uuid` as a thin wrapper around `crypto.randomUUID()`. Replaces CML's transitive dependency on `@svta/cml-utils` for the one runtime call site in `CmcdReporter`'s default-`sid` codepath. The shaka adapter (Phase 3) always sets `sid` explicitly when building a `CmcdReporterConfig`, so this codepath is dead at runtime and Closure ADVANCED will strip it. The shim exists purely so the vendored `CmcdReporter` source stays verbatim with upstream — making per-bump diffs trivial. `crypto.randomUUID()` is polyfilled in browsers without native support by `lib/polyfill/random_uuid.js`.
- Add the 44 third_party/cml-cmcd/*.js files to build/types/core, pulling Task 1.9's build integration forward from after Task 1.8. Resolves the 'complete' build check being red between sub-phases. As a side effect this enables Closure type-checking for the port, which surfaced three dangling goog.requires in the typedef- intersection files (cmcd.js, cmcd_event.js, cmcd_response.js) that were unused because those typedefs inline the resolved superset of fields rather than referencing the parent typedef. Removed. - Document the _const filename disambiguation pattern in third_party/cml-cmcd/SUMMARY.txt. - Backfill circular-require widening rationale into cmcd.js, cmcd_event.js, cmcd_response.js to match cmcd_request.js. `python3 build/check.py` now passes clean (lint, complete, spell, type-check all green).
Sub-phase B prerequisite. The CMCD encoders pull in three runtime imports from CML's sibling packages: - `urlToRelativePath` from `@svta/cml-utils` (used by `CMCD_FORMATTER_MAP`'s `nor` formatter to relativize URLs). - `SfItem` / `SfToken` constructors from `@svta/cml-structured-field-values` (used by `prepareCmcdData`, `CMCD_FORMATTER_MAP`, and `toCmcdValue` to wrap typed structured field values). - `encodeSfDict` (the RFC 8941 §4.1 dictionary serializer that `encodePreparedCmcd` and `toPreparedCmcdHeaders` route every CMCD byte through). Surface from the encoders' POV is small (4 callables), but `encodeSfDict` pulls in the structured-field-values serializer's transitive closure (~13 internal helpers, ~600 LoC vendored verbatim). Since CMCD wire output is impossible without a structured-fields encoder, vendoring is preferable to npm-dependency adoption per AGENTS.md. Same per-bump diff stability strategy as `cml_utils.js`. - Extend `cml_utils.js` with `urlToRelativePath` (vendored verbatim). - Add new `cml_sfv.js` exposing `SfItem`, `SfToken`, `encodeSfDict`. Internal helpers prefixed `cml.cmcd.SfvImpl_*_` to mark them shim-private; not intended as a public surface. - Wire `cml_sfv.js` into `build/types/core` alphabetically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sub-phase A deferred this constant because its body imports `SfItem` from `@svta/cml-structured-field-values` and `urlToRelativePath` from `@svta/cml-utils`. With both shimmed in the prior commit, port the constant verbatim from CML. Filename `cmcd_formatter_map_const.js` follows sub-phase A's `_const` disambiguation pattern: the typedef `CmcdFormatterMap` already lives at `cmcd_formatter_map.js`, so the constant lands as `cmcd_formatter_map_const.js`. The `roundValue`/`toRounded`/`hundredValue`/`toHundred`/`toUrlSafe`/ `nor` helpers from upstream become module-private functions prefixed `cml.cmcd.CMCD_FORMATTER_MAP_*_`. Algorithm logic mirrors upstream `CMCD_FORMATTER_MAP.ts` exactly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sub-phase B Task 1.6. Port the 11 encoder files from spec § "Encoders":
- `append_cmcd_headers.js` (`appendCmcdHeaders`)
- `append_cmcd_query.js` (`appendCmcdQuery`)
- `encode_cmcd.js` (`encodeCmcd`)
- `encode_prepared_cmcd.js` (`encodePreparedCmcd`)
- `ensure_headers.js` (`ensureHeaders`)
- `prepare_cmcd_data.js` (`prepareCmcdData`)
- `to_cmcd_headers.js` (`toCmcdHeaders`)
- `to_cmcd_query.js` (`toCmcdQuery`)
- `to_cmcd_url.js` (`toCmcdUrl`)
- `to_cmcd_value.js` (`toCmcdValue`)
- `to_prepared_cmcd_headers.js` (`toPreparedCmcdHeaders`)
Algorithm logic preserved verbatim against upstream cmcd-v2.3.0.
The encoders pull in seven CMCD support modules that the spec marked
as "tooling-only Excluded" but that `prepareCmcdData` and
`toPreparedCmcdHeaders` actually call at runtime — port them too:
- `is_cmcd_custom_key.js` (`isCmcdCustomKey`)
- `is_cmcd_event_key.js` (`isCmcdEventKey`)
- `is_cmcd_request_key.js` (`isCmcdRequestKey`)
- `is_cmcd_response_received_key.js` (`isCmcdResponseReceivedKey`)
- `is_cmcd_v1_key.js` (`isCmcdV1Key`)
- `is_token_field.js` (`isTokenField`)
- `is_valid.js` (`isValid`)
- `group_cmcd_headers.js` (`groupCmcdHeaders`)
`groupCmcdHeaders` is shared between encoders and decoders; we vendor
the encoder-side without the decoder counterparts.
Closure type adjustments:
- `Object.entries(...)` returns `Array<[?, ?]>`; cast keys back to
`string` at index sites (`prepare_cmcd_data.js`,
`to_prepared_cmcd_headers.js`).
- `prepareCmcdData(obj, options)` re-binds `options || {}` to a typed
local `opts` so Closure does not narrow it to the empty-object type.
- `first.params.r` widens through the SfItem `params` typedef via
inline cast.
- TS generic `toCmcdValue<V, P>` erases to a single-shape JSDoc.
Build wiring: append all 19 entries to `build/types/core`
alphabetically. `python3 build/check.py` passes clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sub-phase B Task 1.7. Port the two version-resolution helpers from spec § "Helpers": - `up_convert_to_v2.js` (`upConvertToV2`) — wraps v1 scalar values in inner-list arrays and `nor` strings in arrays for v2 output. - `resolve_version.js` (`resolveVersion`) — picks an explicit version override, falls back to payload `v`, defaults to `CMCD_V1`. `resolveVersion`'s upstream signature uses `CmcdValidationOptions`, which lives among the validation typedefs that spec § "Excluded validators" omits. Inline the option shape (only the `version` field is read) instead of vendoring the typedef. `SUMMARY.txt` updated to note the included predicates, the `urlToRelativePath` cml-utils addition, and the new `cml_sfv.js` shim covering the structured-field-values encoder surface. Build wiring: append both new entries to `build/types/core` alphabetically. `python3 build/check.py` passes clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ish cml_sfv - The phase 1A polish commit (e6cb256) dropped 'dangling' goog.requires in cmcd.js, cmcd_event.js, cmcd_response.js. These should have been converted to goog.requireType instead — the typedefs reference cml.cmcd.CmcdObjectTypeList in JSDoc only (no runtime use). Restoring as requireType fixes 42 JSC_MISSING_REQUIRE_TYPE_IN_PROVIDES_FILE errors in build/build.py. - Drop misleading @template V, P from cml_sfv.js SfItem class JSDoc (the V/P params are erased to * — documented in to_cmcd_value.js). - Simplify encodeSfDict's `(options && options.whitespace)` to `options.whitespace` — options has a default value, so the && guard is dead code and diverged from upstream. Verified: python3 build/check.py and python3 build/build.py both pass clean (0 errors).
Ports the 5 deferred typedefs that sub-phase A held back because they are consumed only by `CmcdReporter` (sub-phase C): - `CmcdReportConfig.ts` -> `cmcd_report_config.js` - `CmcdEventReportConfig.ts` -> `cmcd_event_report_config.js` - `CmcdRequestReportConfig.ts` -> `cmcd_request_report_config.js` - `CmcdReporterConfig.ts` -> `cmcd_reporter_config.js` - `CmcdRequestReport.ts` -> `cmcd_request_report.js` TS interface inheritance (`extends`) collapses to inlined record fields per the sub-phase A precedent. Generic parameters (`HttpRequest<D>`, `CmcdRequestReport<D>`) erase; `HttpRequest`'s structural shape is inlined into `CmcdRequestReport` since `@svta/cml-utils` is type-only here. Files wired into `build/types/core` alphabetically among the existing `cml-cmcd/` block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final vendored-port file for sub-phase C: ports `CmcdReporter.ts`
(~459 lines) to `cmcd_reporter.js` as a Closure ES6 class. With this
commit, the entire `third_party/cml-cmcd/` port is complete; sub-phase
D will start delegating shaka's `CmcdManager` through CML.
Constructor signature `(config, requester)` preserved as positional
args (per `cml-version.md` Task 0.4 / 0.8 verification — NOT a single
config object). Public surface exposed: `start`, `stop(flush)`,
`flush`, `update(partialState)`, `recordEvent(type, data)`,
`createRequestReport(request, data)`, `recordResponseReceived(response,
data)`. Internal helpers (`processEventTargets_`, `sendEventReport_`,
`recordTargetEvent_`, `resetSession_`) preserved verbatim.
TS-to-Closure idioms applied:
- TS class private fields (`private foo`) → Closure trailing-underscore
fields with `@private`
- `keyof` / `ValueOf<>` generics erase; widen to `string` / `*`
- Type intersections (`CmcdReporterConfigNormalized = CmcdReporterConfig
& {sid, eventTargets}`) → inline record fields per sub-phase A
precedent
- Generic method signatures (`<R extends HttpRequest>`) erase
- Module-scope helpers (`createEncodingOptions`,
`createCmcdReporterConfig`) namespaced under `cml.cmcd.CmcdReporter_*_`
Shim surface extension:
- `cml.cmcd.defaultRequester` added to `cml_utils.js`. Wraps `fetch` in
the same dead-codepath pattern as `cml.cmcd.uuid` — shaka adapter
always supplies a custom requester via NetworkingEngine, so this is
Closure-strippable.
Conformance whitelist extensions (one entry each):
- `setInterval` and `window.setInterval` → `cmcd_reporter.js`. Upstream
uses `setInterval` directly in `start()`; replacing with
`shaka.util.Timer` would break the verbatim-port property and the
per-bump diff stability.
- `fetch` → `cml_utils.js` (for the dead-codepath `defaultRequester`
shim only).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…porter - cmcd_reporter_config.js and cmcd_request_report_config.js: typedef field 'transmissionMode' was widened to plain 'string' with an incorrect comment about avoiding a circular goog.require. CmcdTransmissionMode has zero requires, so there's no cycle. Tighten to cml.cmcd.CmcdTransmissionMode with goog.requireType. - cmcd_reporter.js: lines 391 and 411 used '||' falsy fallback while other sites in the same file use the faithful '!= null ?' form. Unify to '!= null ?' for consistency with upstream's '??' semantics. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a "Current status" section to plan.md surfacing where the refactor stands at session-end: Phase 0 + Phase 1 sub-phases A/B/C complete, the vendored port at third_party/cml-cmcd/ structurally done, sub-phase D the next resume point. Includes CML pin + reclone command, three architectural decisions worth surfacing in the eventual Phase 1 PR description (setInterval/fetch whitelist, defaultRequester relocation), and a hygiene note about two existing git stashes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace shaka.util.CmcdManager.serialize / toQuery static method bodies with one-line delegations to cml.cmcd.encodeCmcd, and thread an optional CmcdEncodeOptions through serialize / toQuery / toHeaders so callers can pass a baseUrl. CML's `nor` formatter uses options.baseUrl to produce root-relative URLs. Drop urlToRelativePath (and its 9 unit tests). data.nor is now set to the absolute next-segment URL in getDataForSegment_; CML relativizes it during encode against new URL(requestUri).origin. This is the first of Phase 1's two intentional wire-format changes — `nor` becomes root-relative instead of path-relative, matching CTA-5004-B and CML's spec-conformant output. Sub-phase E will catalog the full diff. Add a small static helper getEncodeOptions_(uri) that derives baseUrl from a URL's origin (with offline:/parse-error guards). Used at all four CMCD-encoding call sites: appendSrcData, appendTextTrackData, sendCmcdRequest_, applyCmcdDataToRequest_. appendQueryToUri keeps its goog.Uri-based body: cml.cmcd.appendCmcdQuery takes a data object instead of a pre-encoded query string, so this shaka method is retained as an adapter for now. Phase 3 removes these adapter methods entirely as part of the CmcdReporter rewrite. Per Task 1.10 of plans/cmcd-cml-refactor/plan.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CTA-5004 and CTA-5004-B define only 'd', 'h', 's', 'o' for the sf key. shaka's 'ld' (LOW_LATENCY_DASH) and 'lh' (LOW_LATENCY_HLS) come from an old unreleased draft and are not in either spec. CML correctly omits them. Wire change: LL DASH now emits sf=d, LL HLS now emits sf=h. setLowLatency body simplified to a single field assignment — the LL flag no longer mutates this.sf_, since both LL DASH and DASH share 'd'. The flag is preserved for any external readers; sf_ is set once at manifest-load time in getStreamFormat_, which itself drops its this.lowLatency_ branches. This is the second of Phase 1's two intentional wire-format changes, alongside `nor` URL relativization (committed previously). Phase 2's alias re-export becomes a straightforward identity map once shaka's enum is a strict subset of CML's CmcdStreamingFormat. No tests asserted sf=ld or sf=lh (verified via grep over test/), so the test-file delta from this change is empty. Per Task 1.10b of plans/cmcd-cml-refactor/plan.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update plan.md "Current status" section: sub-phase D (Tasks 1.10 + 1.10b) ✅ complete after this session, sub-phase E is the resume point. Add a "Sub-phase D landing notes" subsection capturing the three concrete deliverables (encoder delegation, urlToRelativePath deletion, two intentional wire-format changes) and the new private helper getEncodeOptions_(uri). Replace the old "Resuming work — sub-phase D prep" subsection with "sub-phase E prep": baseline-capture flow now uses `git checkout 0f69e7f -- ...` (since sub-phase D is committed across d8d614c + adfdfab) rather than the plan's literal git stash flow which assumed uncommitted changes. Pre-flag the likely (c)-class divergence in toHeaders (CML auto-adds v=2 per shard; shaka's old encoder didn't), so sub-phase E starts with the highest-priority item identified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three (c)-class bugs surfaced by sub-phase E diff testing of the
encoder delegation from Task 1.10:
C1. Event-mode encoded output dropped event-only keys (e, ts, cen).
cml.cmcd.prepareCmcdData defaults reportingMode to CMCD_REQUEST_MODE
when not specified, and the request-mode filter (isCmcdRequestKey)
excludes event-only keys. Fix: thread reportingMode through
getEncodeOptions_ — CMCD_EVENT_MODE for sendCmcdRequest_ (the
out-of-band event/response-received path), CMCD_REQUEST_MODE for
applyCmcdDataToRequest_ / appendSrcData / appendTextTrackData.
sendCmcdRequest_ also stops passing baseURL: target URLs are
typically a different origin from segment URLs, so baseUrl-based
nor relativization against the collector URL is meaningless. CML's
own reporter omits baseUrl in event mode for the same reason.
C2. toHeaders emitted v=2 in every shard. shaka groups data into 4
shards and previously called serialize per shard; once serialize
delegated to encodeCmcd, each shard ran prepareCmcdData and got
v=2 auto-appended. Fix: prepareCmcdData once on the full data
(auto-adding v=2 once into the prepared output), bucket the
prepared keys into shaka's headerMap, then encodePreparedCmcd per
shard so re-preparation is skipped.
C3. V1 configurations were getting V2 filter behavior because
prepareCmcdData defaults to V2 when no version is supplied. Tests
that emitted V1-only keys (e.g. nrr) saw them filtered out. Fix:
thread this.config_.version through getEncodeOptions_.
Per Task 1.12 of plans/cmcd-cml-refactor/plan.md (sub-phase E (c)-class
classifications C1, C2, C3 from diff-test-classification.md).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sub-phase E classifications (a)/(b) per plan.md Task 1.12 — tests now
match CML's spec-conformant wire output:
(a)/(b) string vs token formatting (~73 sites)
Old: e="ps", e="m", e="pe", e="pc", e="t", e="um", e="rr",
sta="s", sta="p", sta="r", sta="a", sta="d", sta="e",
sta="w", sta="k"
New: e=ps, e=m, e=pe, e=pc, e=t, e=um, e=rr,
sta=s, sta=p, sta=r, sta=a, sta=d, sta=e, sta=w, sta=k
CML treats `e` and `sta` values as RFC 8941 SFV tokens, not
strings. shaka's old encoder emitted everything as quoted strings
for non-token-shaped values; the SFV-aware emitter is correct
per CTA-5004-B.
(b) v=2 auto-added in V2 output (8 sites)
CML's prepareCmcdData unconditionally appends `v=2` when version
is V2 (per CTA-5004-B § 4.1, V2 output MUST include `v`). Old
shaka emitted `v` only when explicitly present in `data`. Tests
that previously asserted `not.toContain('v=2')` (in cases where
the user filtered `v` out via includeKeys) now assert
`toContain('v=2')` — CML's auto-add takes precedence over
includeKeys for the version field. Tests with no explicit `v`
in `data` (escape test, single-key header test) gained `,v=2`
in expected.
(b) ts no longer in request-mode output (1 site)
Old shaka set data.ts = Date.now() in applyRequestSegmentData
and emitted it in request-mode CMCD; per CTA-5004-B § 4.1 `ts`
is event-mode-only. CML's request-mode filter correctly drops
it. Inverted "includes ts for segment requests" to "does not
include ts for segment requests".
(a)/(b) nor V2 inner-list format and event-mode no-baseUrl (1 site)
Old: nor="next-seg.m4v" (V1 string format)
New: nor=("https://test.com/next-seg.m4v") (V2 inner-list with
absolute URL because event-mode reports skip baseUrl when the
collector is at a different origin from the segment).
(b) urlToRelativePath unit tests already removed in d8d614c (Task
1.10) since the helper is gone.
After these updates, all 124 CMCD-filtered tests pass; the broader
--quick suite reports 2995 success / 0 fail.
Per Task 1.12 of plans/cmcd-cml-refactor/plan.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update plan.md "Current status" section: sub-phase E (Tasks 1.11 + 1.12) ✅ complete after this session, sub-phase F (Tasks 1.13-1.14: demo smoke + Phase 1 PR) is the resume point. Add a "Sub-phase E landing notes" subsection capturing the three (c)-class adapter fixes (reportingMode threading, prepare-once for header shards, version threading) and the five (a)/(b)-class wire-format alignments updated in test assertions (nor V2 inner-list, v=2 always-present, ts request-mode drop, e/sta token vs string, event-mode nor-stays-absolute). Replace "Resuming work — sub-phase E prep" with "sub-phase F prep": the PR description scaffolding now lives in the three landing-notes sections and "Key architectural decisions" subsection. Phase 1 ships three intentional wire-format changes — nor root-relative, 'ld'/'lh' dropped, V2 SFV-conformant encoding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sub-phase F (Task 1.13: demo smoke test) ✅ complete — Phase 1 of the
CMCD CML refactor is now fully done. End-to-end verification on
bbb-dark-truths/dash.mpd via Claude Preview-driven local HTTP server,
both transmission modes:
Query mode: ?CMCD=cid="…",ot=m,sf=d,sid="…",sn=N,su,v=2
(v=2 always present, ts absent in request mode, sf=d
for DASH (no 'ld'), ot/sf/st as bare SFV tokens, cid/sid
as quoted strings, su as bare-token boolean)
Header mode: CMCD-Object: ot=m
CMCD-Request: mtp=N,sn=N,su
CMCD-Session: cid="…",sf=d,sid="…",v=2
(v=2 only in CMCD-Session, not in Object/Request/Status —
the C2 sub-phase E fix verified end-to-end)
Zero JS console errors during the demo session. Cross-origin storage.
googleapis.com blocks CMCD-* via CORS preflight; smoke test stripped
the headers in a request filter so segment fetch could complete. Real
deployments configure their own CDN to allow CMCD-* preflight — this
is browser-spec behavior, not a shaka issue.
Plan changes:
- Mark Phase 1F ✅ in the status table; resume point is now Phase 2.
- Replace "Phase 1 ships as PR after this" with a "PR strategy
(revised 2026-04-28)" note in Plan structure: per user direction,
per-phase PRs are deprecated. Single PR off feat/cmcd-cml-refactor
after Phase 3 lands. Sub-phase boundaries still gate work
internally (build/check.py + test.py per sub-phase).
- Replace the "sub-phase F prep" subsection with a "Phase 2 prep"
subsection. Phase 2 is pure dedupe (Tasks 2.1-2.6: map shaka enums
to CML, replace internal references, alias-export StreamingFormat).
- Add a "Sub-phase F landing notes" subsection parallel to D and E,
capturing the smoke-test sample wire output for the eventual all-
phases PR description.
New file:
- `plans/cmcd-cml-refactor/phase-1-pr-draft.md` — Phase 1 portion of
the PR description scaffolding. Originally intended as a temporary
scratch file (per Task 1.12 step 5), now persisted as scaffolding
that Phase 2 + 3 sections will accrete to before the all-phases PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2 of the CMCD CML refactor: replace shaka.util.CmcdManager's
internal enums and key arrays with cml.cmcd.* equivalents. Pure dedupe;
no behavioral change.
Migrated (1:1 value match):
shaka.util.CmcdManager.ObjectType → cml.cmcd.CmcdObjectType
shaka.util.CmcdManager.StreamType → cml.cmcd.CmcdStreamType
shaka.util.CmcdManager.Version.VERSION_2 → cml.cmcd.CMCD_V2 (= 2)
shaka.util.CmcdManager.CmcdMode → cml.cmcd.CmcdReportingMode
shaka.util.CmcdManager.CmcdKeys.V1Keys → cml.cmcd.CMCD_V1_KEYS
Migrated (set differs but consolidation is permissive):
shaka.util.CmcdManager.CmcdKeys.V2RequestModeKeys ∪ V2CommonKeys
→ cml.cmcd.CMCD_REQUEST_KEYS
shaka.util.CmcdManager.CmcdKeys.V2EventModeKeys ∪ V2CommonKeys
→ CMCD_REQUEST_KEYS
∪ CMCD_RESPONSE_KEYS
∪ CMCD_EVENT_KEYS
(mirrors CML's
`isCmcdEventKey`)
shaka.util.CmcdManager.CmcdKeys.CmcdV2Events
→ Object.values(
cml.cmcd.CmcdEventType)
(CML's 17 events
— superset of shaka's 10)
shaka.util.CmcdManager.CmcdKeys.CmcdV2PlayStates
→ Object.values(
cml.cmcd.CmcdPlayerState)
(10 values, exact match)
Inlined / dropped:
shaka.util.CmcdManager.CmcdV2Constants.TIME_INTERVAL_DEFAULT_VALUE
inlined as `10`. CML's CMCD_DEFAULT_TIME_INTERVAL is `30`; Phase 3
will switch to CmcdReporter, which uses the CML default natively.
shaka.util.CmcdManager.CmcdV2Keys.TIMESTAMP — inlined as the literal
'ts' (CML doesn't expose a dedicated constant for the timestamp
key name).
shaka.util.CmcdManager.CmcdV2Keys.TIME_INTERVAL_EVENT
→ cml.cmcd.CMCD_EVENT_TIME_INTERVAL
StreamingFormat: literal `@enum` definition retained — Closure's
clutz TypeScript-defs generator and shaka's generateExterns.js both
reject `@export`ed enums whose RHS is a non-`ObjectExpression`. The
4 values match cml.cmcd.CmcdStreamingFormat exactly (DASH/HLS/SMOOTH/
OTHER → 'd'/'h'/'s'/'o'); a new unit test asserts the value-identity
so Phase 3 can rely on it.
Internal type annotations referencing the now-deleted shaka enums
(`@private {(shaka.util.CmcdManager.StreamingFormat|undefined)}`,
return type of `getStreamFormat_`) updated to refer to
`cml.cmcd.CmcdStreamingFormat` directly. The public-facing
`shaka.util.CmcdManager.StreamingFormat` symbol stays.
`getStreamFormat_` now returns `cml.cmcd.CmcdStreamingFormat.DASH/HLS`
directly. Public callers see the same string values; this also keeps
the `cml.cmcd.CmcdStreamingFormat` `goog.require` in active use.
Net −74 LoC in cmcd_manager.js; +12 LoC for the value-identity test.
build/check.py exits 0; build/test.py --filter Cmcd: 125/125 pass
(the new value-identity test included); full --quick suite: 2996/2996
pass; demo smoke test (DASH bbb-dark-truths) confirms wire format
unchanged across 19 segment requests.
Per Tasks 2.1-2.5 of plans/cmcd-cml-refactor/plan.md. Task 2.6 (open
Phase 2 PR) deferred per single-branch / single-PR strategy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ject#10268) - Centralize HLS metadata access and parsing (X- attribute lookup, start/end times, CUE, DASH overlay TopLeft/Size) into shared helpers. - Extract helpers for the overlay CSS positioning, ad-break time offset and interstitial id, removing duplication between the static/video/overlay paths. - Cache the start-time-sorted interstitial list (rebuilt only on change) to avoid re-sorting on every getCurrentInterstitial_ call.
… and Xbox (shaka-project#10275) Add unit tests covering version parsing, device identity, browser engine and version-specific behavior for the six device classes, using real user agent strings from the vendor specifications. While adding the TiVoOS test it became apparent that shaka.device.TiVoOS was only listed in build/types/devices, so the device was never required from the uncompiled entry points and therefore never registered in uncompiled/debug builds (nor available to the tests). Add the missing goog.require to shaka-player.uncompiled.js and transmuxer_worker.uncompiled.js. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
completeXhrWith_ already awaits response.clone().text() before firing onload/onloadend, so completion was already deferred past send() return. The extra queueMicrotask was a redundant second microtask hop and the only queueMicrotask usage in the repo. Replace with await Promise.resolve() to keep the never-fire-synchronously-inside-send() guarantee for the generic case where clone() could throw and skip the earlier await. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…level (shaka-project#10277) Read EssentialProperty and SupplementalProperty descriptors at MPD and Period level, and enforce the MPD-level EssentialProperty conformance rule from ISO/IEC 23009-1 clause 5.8.4.8. EssentialProperty descriptors at MPD level are grouped by their `@id` and, when a whole group uses only schemes the parser does not recognize, the presentation is terminated with the new error code DASH_UNSUPPORTED_ESSENTIAL_PROPERTY (4063). SupplementalProperty descriptors remain non-essential and are ignored when unrecognized. --------- Co-authored-by: Wojciech Tyczyński <tykus160@gmail.com>
…lution (shaka-project#10269) ## Description The symptom noticed on Playstation 4 Pro was that all 4K streams were being dropped even though this device is capable of 4K. ## Explanation of changes Playstation may have 'Apple Computer, Inc' as the navigator.vendor and so `isWebkitSTB_` defaults to true. Even though `isPlayStation_` also evaluates to true, `isWebkitSTB_` takes over due to import order in `shaka-player.uncompiled.js` The device name at runtime was: 'WebKit STB' This specific bug was caused by the call to `device.detectMaxHardwareResolution` which was using the WebKit STB implementation which returns 1920*1080 as the max resolution. There are other device methods that will now use the correct implementation.
…ct#10270) Centralize NAL emulation-prevention-byte removal in Uint8ArrayUtils (shared with ExpGolomb), unify CEA-608 stream naming and SEI NALU-type detection, and streamline the 608/708 decoders, MP4 parser, and ClosedCaptionParser. Includes correctness fixes, single-pass scans, and comment/typing cleanups.
…#10133) Expose the parsed media segment timestamp through the existing segmentappended event. The media-to-presentation offset is given by (event.start - event.mediaTimestamp). This can be used to map external media timestamps onto the presentation timeline. Co-authored-by: ChatGPT Codex <chatgpt-codex-connector[bot]@users.noreply.github.com>
This PR adds a PES header-only method that extracts the first PTS/DTS without concatenating PES payloads in the common case, and switches MediaSourceEngine to use it — avoids the per-PES Uint8Array allocation when the first TS packet already holds the full PES header (falls back to concatenating the PES range otherwise)
…haka-project#10131) ## Problem `JsonUtils.jsonToMpd()` in `lib/dash/json_utils.js` does not XML-escape string values when building the MPD XML string. The `emit()` helper calls `StringUtils.htmlUnescape()` on both attribute values (line 38) and text content (lines 61, 120) — the wrong direction. `htmlUnescape()` converts HTML entities back to raw characters but does **not** escape raw `<`, `>`, or `"` characters. This means any JSON string value that contains XML metacharacters is embedded verbatim into the XML output and interpreted as markup by the parser. **Example — BaseURL injection:** ```json { "Period": [{ "AdaptationSet": [{ "BaseURL": "</BaseURL><BaseURL>http://attacker.example/segments/" }] }] } ``` `emit('BaseURL', {}, ...)` produces: ```xml <BaseURL></BaseURL><BaseURL>http://attacker.example/segments/</BaseURL> ``` `dash_parser.js` collects **all** `BaseURL` children via `TXml.findChildren(elem, 'BaseURL')` and adds every resolved URI to `lastCalculatedBaseUris_`. An attacker who controls the manifest URL can redirect segment requests to an arbitrary server. The same injection path applies to `ContentProtection` and other child elements, and to attribute values containing `"`. ## Fix Replace the three `htmlUnescape()` calls with a local `xmlEscape()` function that encodes `&`, `<`, `>`, and `"` as XML character references. Remove the now-unused `StringUtils` import. ## Tests Added two regression tests to `test/dash/json_utils_unit.js`: - Text content injection: verifies that a `BaseURL` value with `</BaseURL><BaseURL>http://attacker.example/` produces exactly **one** `BaseURL` element with the literal string as content, not two parsed elements. - Attribute value injection: verifies that a `Period` `id` attribute containing `"` does not break out of the attribute context.
The launcher for Edge on Linux no longer works in the GitHub-maintained VMs. (It still works in our lab.) Remove this from the test matrix for PRs.
…-project#10289) In lib/util/player_configuration.js, the default robustness is set to SW_SECURE_DECODE for Video, and SW_SECURE_CRYPTO for Audio. However, Android robustness is unset. This leads to unspecified behavior and is explicitly warned against in Chrome via the console, and unset robustness is also mentioned in the EME spec. This now sets both audio and video robustness to SW_SECURE_CRYPTO on Android. We don't use DECODE because of the below: https://source.chromium.org/chromium/chromium/src/+/main:components/cdm/renderer/widevine_key_system_info.cc;l=182
…change (shaka-project#10287) The composite `mute_volume` element introduced in shaka-project#10214 replaced the separate `mute` and `volume` entries in the default controlPanelElements. However, the Smart TV and mobile configs still filtered the now-nonexistent `volume` entry, so the composite element survived and rendered the volume slider on devices where it was previously hidden. Replace `mute_volume` with the standalone `mute` button on Smart TV and mobile (where volume is controlled by the remote / hardware buttons), keeping the mute control while hiding the volume slider, and drop the dead `volume` filter entry. Fixes the `UI controls controls-button-panel has default elements` unit test failure on Tizen (deviceType TV).
Now that the App Engine deployments and version archive are being shut down, replace both App Engine deployments with shims. The shaka-player-demo deployment becomes the default service: a Flask app that branches on the request Host and path and forwards old appspot URLs to their equivalents on GitHub Pages. The /demo/ paths render an interstitial whose JavaScript reads the URL fragment, translates the outdated asset/license/certificate/drmSystem/adTagUri parameters into the current assetBase64 format, and forwards the user on. All other paths are HTTP redirects that preserve the path, so direct requests for a library file or image resolve to the best equivalent. The Doodle-style poster service is removed; poster.jpg, audioOnly.gif, and time.txt now redirect to static replacements. The demo-version-index deployment becomes a single static turn-down page pointing users at Google Hosted Libraries, jsDelivr, UNPKG, and npm; its gcloud-driven generator and template are removed. Both deployments move to the latest supported App Engine Python runtime (python314) to maximize time before a forced upgrade. Fixes shaka-project#9984 Co-authored-by: Claude Code (Claude Opus 4.8) <noreply@anthropic.com>
The "nor is a valid string or string-list" integration spec, plus its
siblings, hard-waited for 2 segment CMCD reports via
waitForSegments({count: 2}). On slow CI browsers (Opera/Edge on macOS)
the second report often did not arrive within REQUEST_TIMEOUT, so the
recorder rejected before the spec could validate the report(s) it had,
failing intermittently while passing on Chrome/Firefox/Safari/Linux.
Add an opt-in rejectOnTimeout option to CmcdReportRecorder (defaults to
true, preserving the strict behavior). The five segment-wait sites, none
of which assert anything about the report count, opt into the soft wait
so they validate whatever arrived instead of failing on under-delivery.
Also run detachRecorder() and eventManager.release() in a finally in the
integration afterEach, so a destroy() rejection cannot leak the
recorder's global fetch / HttpFetchPlugin.fetch_ patch into later specs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…10288) Follow-up to shaka-project#9596, which added a generated typedef for the `shaka` namespace to the externs but left the hand-maintained `shakaNamespaceType` typedef in place in the tests. This generates a standalone externs file containing only the `shaka` namespace typedef and includes it when type-checking the tests, so the tests can refer to the whole library namespace in a type-safe way. The hand-maintained `shakaNamespaceType` typedef is then removed in favor of the generated `shaka` type. Closes shaka-project#6762 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Merges all shims into one. The index shim was just serving a single HTML, and we had an undocumented service running that forwarded to error docs. This merges both into the main shim, so we have a single deployment for compatibility. This also made it easier to clean up resources on GCP (old container images etc generated by AppEngine) because we can just delete everything and do a single redeploy to get into a minimal good state. Issue shaka-project#9984
shaka-project#10298) CEA-608 paint-on and roll-up captions are painted to the screen as their bytes arrive, one character at a time. Shaka decoded both modes but emitted each caption as a single batch, so the whole line or window appeared at once instead of progressively, unlike a native decoder. Each decoded character now carries its presentation timestamp, and paint-on and roll-up cues reveal their text character by character using per-nested-cue start times (the same mechanism WebVTT karaoke already relies on). Pop-on is left unchanged, since it is meant to appear all at once. To make the reveal land on time without polling, the UITextDisplayer now schedules each update at the next cue (or nested-cue) boundary instead of on a fixed interval, bounded by a safety period so a stall or a missed event can never leave the captions stale. The textDisplayer.captionsUpdatePeriod configuration is no longer needed and is now deprecated and ignored.
…0302) Leftover from a first iteration that had a separate frame drop config typed as `shaka.extern.DroppedFrameProtectionConfig`. This was later added to the ABR config's advanced prop but we accidentally left its type in.
…ka-project#10295) Mp4CeaParser assumed the samples described across multiple trun boxes were contiguous in the mdat and only seeked to the first trun's data offset. When a track's runs are interleaved with another track's data in a shared mdat (as in Apple HLS streams, which place the CEA-608 'c608' track's runs between the video runs), the second and later samples were read from the neighboring track's bytes. On the raw CEA-608 ('c608') path this fed video bytes into the 608 decoder; stray pairs that happened to pass the parity check were decoded as characters, rendering garbage (e.g. "úX") plus a stack of empty rows above the real caption. The SEI path had the same latent assumption and could misread NAL units. Both `parseC608Mdat_` and `parseMdat_` now read each run starting from its own trun data offset; parseMdat_ additionally bounds the NAL walk by each sample size so it never reads past a run into another track's data. The now-unused getSampleData_ helper is removed. Adds regression tests with crafted segments whose runs are non-contiguous, for both the RAW608 and SEI paths.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces shaka's custom CMCD wire-format encoding and state machine with a vendored Closure port of
@svta/cml-cmcd(CML) underthird_party/cml-cmcd/. Implementation in three sequential phases offfeat/cmcd-cml-refactor:norURL relativization,'ld'/'lh'dropped, V2 SFV-conformant encoding).cml.cmcd.*equivalents. Pure dedupe; no behavior change.lib/util/cmcd_manager.js(1580 → ~700 LoC) as a thin adapter aroundcml.cmcd.CmcdReporter. Deletes the state machine, sequence-number tracking, event timing, and mode-selection logic. Ships experimental v2 config renames (targets→eventTargets, per-targettimeInterval→interval) and adds publicEventType/PlayerStatere-exports. Three additional wire-format alignments inherited from CML.Phases 1 + 2 are behavior-preserving for shaka's public API, modulo documented intentional wire-format changes that align shaka with CTA-5004 / CTA-5004-B and CML's spec-conformant output. Phase 3 is the load-bearing behavioral change — the manager's external surface stays compatible, but the internal state machine, sequence-number scope, and event-mode dispatch path all flow through CML's reporter.
What's vendored
third_party/cml-cmcd/mirrors the CML repo'slibs/cmcd/src/plus two shim files (cml_utils.js,cml_sfv.js), pinned atv2.3.0/ commit22390e35dfbbe1e53d15648d3aace99cdf71f9dd**.When shaka migrates to TypeScript (#8262), the vendored directory deletes and each
goog.require('cml.cmcd.X')becomesimport { X } from '@svta/cml-cmcd'— no other code changes.Architectural decisions in the port
These are judgment calls during sub-phase B+C; please weigh in:
setIntervalwhitelisted inbuild/conformance.textprotoforthird_party/cml-cmcd/cmcd_reporter.js. The reporter callssetIntervaldirectly for periodic time-interval event reporting. Alternatives considered: (a) patching the vendored reporter to useshaka.util.Timer(breaks verbatim parity, complicates per-bump diff workflow), (b) filing a CML upstream PR for injectable-timer support (delays Phase 1). Whitelist is pragmatic but means Phase 3 tests can't inject a fake timer — will needjasmine.clock()for interval testing.fetchwhitelisted inbuild/conformance.textprotoforthird_party/cml-cmcd/cml_utils.js. Used bycml.cmcd.defaultRequester— dead code at runtime because the shaka adapter always supplies a customrequesterviaNetworkingEngine. Closure ADVANCED is expected to strip the dead path; the conformance check runs first.defaultRequesterrelocated fromCmcdReporter.tsmodule scope intocml_utils.jsascml.cmcd.defaultRequester. Done to centralize thefetchwhitelist scope to a single file. Per-bump CML diff workflow needs to knowdefaultRequesterlives here, not in the reporter — otherwise CML 2.4.0+ bumps will look like the function vanished.Wire-format changes (intentional)
Three changes; all align shaka with CTA-5004-B and CML's spec-conformant output. Test assertions updated to match.
1.
norURLs become root-relative + V2 inner-list format.nor="next-seg.m4v"(path-relative against request URL).nor=("next-seg.m4v")(RFC 8941 inner-list, root-relative against request origin per CTA-5004-B § 4.1).cmcd_manager.jsno longer pre-relativizesdata.nor; CML'snorformatter relativizes againstoptions.baseUrl(request URL's origin). For event-mode reports sent to a different-origin collector,norstays absolute.2.
'ld'and'lh'StreamingFormatvalues dropped.CTA-5004 / CTA-5004-B define only
'd'(DASH),'h'(HLS),'s'(Smooth),'o'(Other).'ld'and'lh'were from an old unreleased CMCD draft and are non-spec. Wire change: low-latency DASH content now emitssf=d; low-latency HLS now emitssf=h.setLowLatency()no longer mutatessf_; the LL flag is preserved for any external readers but has no effect on the encodedsfvalue.3. V2 SFV-conformant encoding.
CML uses RFC 8941 Structured Field Values for V2 output; old shaka used a JSON-stringify-shaped quoting rule. Concrete differences:
e(event type) andsta(player state) values. Old:e="ps",sta="s". New:e=ps,sta=s. Per CTA-5004-B these values are spec-defined single-character tokens; SFV tokens don't take quotes.v=2always present in V2 output. Per CTA-5004-B § 4.1, V2 output MUST includev. Old shaka emittedvonly when explicitly present in input data. CML'sprepareCmcdDataenforces this, even when the user filtersvout viaincludeKeys.tsno longer in request-mode output. Per CTA-5004-Btsis event-mode only. Old shaka emittedts=<timestamp>in request-mode CMCD; CML's request-mode filter correctly drops it. (Event-mode and response-received reports continue to includets.)Behavior preserved otherwise
The state machine, sequence numbers (
cmcdSequenceNumbers_per-target counters), event timing, request/response routing, and the publicshaka.util.CmcdManagerAPI are unchanged. ThesetLowLatency,setMediaElement,configure,reset,applyRequestData,applyResponseData,appendSrcData,appendTextTrackDataentry points retain their existing signatures and semantics. Phase 2 dedupes constants/enums; Phase 3 rewrites the state machine as a thin adapter aroundCmcdReporterand is the load-bearing behavioral change.Implementation summary
shaka.util.CmcdManagerstatic encoders now delegate:serialize(data, options)→cml.cmcd.encodeCmcd(data, options).toQuery(data, options)→cml.cmcd.encodeCmcd(data, options)(preserves shaka's "raw value, noCMCD=prefix" contract — CML'stoCmcdQueryreturns the prefixed form, which would break callers).toHeaders(data, options)→cml.cmcd.prepareCmcdData(data, options)once on the full input, then bucketed into shaka's existing 4-shardheaderMap, thencml.cmcd.encodePreparedCmcdper shard. Calling the high-levelencodeCmcdper shard would re-runprepareCmcdDataand re-addv=2to every non-empty shard.appendQueryToUri(uri, query)retained as agoog.Uri-based adapter — CML'sappendCmcdQuery(url, cmcd, options)takes a data object, not a pre-encoded query string, so direct delegation isn't possible. Phase 3 deletes call sites entirely.urlToRelativePathdeleted; the helper and its 9 unit tests are gone.A new private
getEncodeOptions_(uri, version, reportingMode)static helper buildscml.cmcd.CmcdEncodeOptionsobjects at the four CMCD-encoding call sites:appendSrcData,appendTextTrackData,sendCmcdRequest_(event/response path),applyCmcdDataToRequest_(request path). It threadsthis.config_.versionand the reporting mode (CMCD_REQUEST_MODEfor request paths,CMCD_EVENT_MODEforsendCmcdRequest_).Verification (all three phases)
python3 build/check.py --forceexits 0 (lint, conformance, types, spelling).python3 build/all.py --forceexits 0 (full bundle build: dash/hls/compiled/ui/experimental, debug + release).python3 build/test.py --filter Cmcd— 57 / 57 pass (Phase 3 deletes ~125 wire-format tests now in CML, adds adapter glue + smoke).python3 build/test.py --quick— 2927 / 2927 pass (no regressions outside CMCD; 4 environmental skips on this branch are unrelated to this work).bbb-dark-truths/dash.mpd— V1+query, V2+query, V2+headers (withunload→configure({useHeaders: true})→loadcycle) all emit correct CMCD output. Sequence numbers 0-based, reset onsidchange.v=2only inCMCD-Sessionshard. Public re-exports (EventType,PlayerState,StreamingFormat) all readable at runtime from the compiled bundle. Zero CMCD-related console errors.