* Migrate CMCD from @svta/common-media-library to @svta/cml-cmcd and add 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
* Add CMCD v2 data fields and CmcdReporter event-mode reporting
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
* Export CMCD v2 types and add comprehensive test coverage
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
* fix: remove @svta/common-media-library
* feat: update cmcd-controller to use CML's CmcdReporter
* chore: update CML packages
* fix: build system cleanup
* fix: export clean up
* fix: api-extractor rebuild
* revert: build config change
* fix: address code review comment about muxed buffers
* feat(cmcd): route event report requests through xhrSetup/fetchSetup hooks
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>
* feat(cmcd): add CMCD v2 e2e tests and upgrade @svta/cml-cmcd to 2.2.0
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>
* fix: return 204 for event mode responses
* refactor(cmcd): use CmcdReporter default requester, add loader config 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>
* chore: update api file
* chore: update CMCD package for revision B changes
* fix(cmcd): address review feedback on controller, tests, and docs
- Swap native 'ended' listener for hls Events.MEDIA_ENDED
- Reset + recreate reporter on MANIFEST_LOADING so loadSource() on an
existing Hls instance behaves like a fresh CMCD session
- Replace this.reporter! assertions with if (this.reporter) blocks
- Use hls.latestLevelDetails in getStreamType
- Tighten low-latency detection to require details.partList (TODO #7729)
- Document version, eventTargets (with child properties), and loader
config options in docs/API.md
- Extend setupEach test harness with a streamController.getLevelDetails
mock to support latestLevelDetails lookup
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update @svta/cml-cmcd to 2.3.1
* chore: update CML versions
* refactor(cmcd): use addEventListener/removeEventListener helpers
Replace direct media.addEventListener/removeEventListener calls with the
shared helpers from utils/event-listener-helper. The helpers minify
better and guard against duplicate listener registration, matching the
pattern used by base-stream-controller, buffer-controller, and
gap-controller.
* fix(cmcd): resolve SEEKING state when seek lands while paused
After a seek, the native 'seeked' event fires but no 'playing' or
'pause' event follows when the media was already paused. Add an
'onSeeked' handler that transitions SEEKING -> PAUSED in that case.
PLAYING and REBUFFERING transitions are still handled by the existing
'playing' and 'waiting' listeners.
* chore(cmcd): drop unused karma timeout config
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.
* fix(cmcd): use mainForwardBufferInfo for buffer length
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 #7858 but mirrors the paused -> hole=0 rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(cmcd): add failing tests for getBufferLength buffer-info sources
* fix(cmcd): use hls.audioForwardBufferInfo in getBufferLength (resolves#7858 TODO)
* refactor(cmcd): drop unused audioBuffer field and BUFFER_CREATED wiring
* fix(cmcd): derive object type from variant codecs / elementary streams
Resolves Rob's review feedback on #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>
* fix(cmcd): emit br/tb/bl for single-rendition fragments
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>
* fix(cmcd): keep bl fresh on event reports via buffer-info listener
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>
* fix(cmcd): gate variant-codec ot inference on audioTracks; prefer parsed 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>
* fix(cmcd): defer reporter creation to MANIFEST_LOADING, one per session
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>
* chore: fix lint issue
* chore: fix lint issue
* chore: fix lint issue
* fix(cmcd): set initial sta on MEDIA_ATTACHING; default to PRELOADING
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>
* feat(cmcd): adopt cml-cmcd 2.4.0 auto-fire and recorder helper
- 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>
* chore: update cml-cmcd to version 2.4.0
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Fix interstitial asset events not firing when attaching primary early for playout-limit change
* Add log message helper for timeline position updates
* Interstitials improvements
- Add interstitialPlayer to InterstitialsManager and simplify interface
- Fix and cover expected playback state in interstitial event callbacks (where currentTime is derived from transitional controller state)
- Fix live start in append-in-place interstitial seeking to second start position when primary starts buffering after starting in interstitial
- Simplify playout-limit handling in asset player
- Add padding to flush front buffer call (prevents start of audio segment from being removed in Safari)
- Fix an issue where buffering an interstitial in place gets stuck when the video buffer is full, but the audio buffer is not (use combined buffer in stream-controller when there is no alt-audio source)
- Fix typos (Interstitals > Interstitials)
* Add `interstitialsManager.interstitialPlayer.assetPlayers` tests - verify playingIndex player is present for relevant asset events
* Fix seeking into and over multiple EXT-X-GAP segments
Fixes#6814
* Add `hls.loadLevelObj` to API - replaces internal use of `hls.levels[hls.loadLevel]`
* Flush MSE rendering pipeline when crossing video buffer holes with audio
Resolves#5631
* Don't wait to nudge when stalling with waiting event
Fixes#6169
* Add `hls.inFlightFragments` to Hls API. Used to determine if gap-controller should wait for in-flight media segments before seeking over gaps.
* Changes to support returning CMAF based KLV data to your HLS client
This leverages the existing metadata callbacks for ID3 tags
Not sure this is the best way, but it works for my simple testing
* Run through prettier
* Add enableEmsgKLVMetadata option (default false)
* Commiting API change for new config option enableEmsgKLVMetadata
* Added details of the new config enableEmsgKLVMetadata (default false) and exposing of MetadataSchema along with changes to the FRAG_PARSING_METADATA event
* Add `preferManagedMediaSource` API.md doc entry, and only emit BUFFER_FLUSHED with removed ranges in ManagedMediaSource "bufferedchange" event
* Add BufferedChangeEvent TypeScript interface
based on w3c proposal https://github.com/w3c/media-source/issues/320
* Make initial variant selection based on preferred codec rather than the first variant in the multivariant playlist
* Fix `minAutoLevel` and `ttfb` usage in abandon rules check
* Fix filtering of invalid video-ranges
* Log reason for ignoring start candidates with a particular codec
* Add rendition helper and move SCORE logic into getStartCodecTier
* Add support for MediaCapabilities video configuration filtering
FIxes#5707
* Add support for MediaCapabilities audio configuration filtering
Related to #5704 and #4072
* Add __USE_MEDIA_CAPABILITIES__ build const to remove MediaCapabilities checks from light build
* MediaCapabilities: Assume a framerate of 30fps since decodingInfo will throw with Level default of 0.
* Select variant with alternate video codec on video append error
* Use complete media-parsed codec name when determining if changeType should be appended
* Add `firstAutoLevel` to API.md
* Fire STEERING_MANIFEST_LOADED event when loading a Content Steering Manifest
* Use steeringManifest instead of response inside STEERING_MANIFEST_LOADED content
* Fire STEERING_MANIFEST_LOADED between clonePathways and updatePathwayPriority
people should be using that over latest so that things don't break when we release v2 with potentially breaking changes. Main readme already used v1 url
* Relax TTFB timeout on manifest request
(Add support for LoadPolicy `maxTimeToFirstByteMs` of Infinity and similarly ignore value of 0)
* Add LoadPolicy API documentation