Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@
},
"dependencies": {
"@svta/cml-608": "1.0.2",
"@svta/cml-cmcd": "2.3.2",
"@svta/cml-cmcd": "2.4.0",
"@svta/cml-cmsd": "1.0.6",
"@svta/cml-dash": "1.0.6",
"@svta/cml-id3": "1.0.6",
"@svta/cml-request": "1.0.12",
"@svta/cml-request": "1.0.13",
"@svta/cml-xml": "1.1.4",
"bcp-47-match": "^2.0.3",
"codem-isoboxer": "0.3.10",
Expand Down
111 changes: 73 additions & 38 deletions src/streaming/controllers/CmcdController.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@ function CmcdController() {
logger,
mediaPlayerModel,
reporterNeedsRebuild,
urlLoader;
urlLoader,
_lastPtUpdateAt;


let context = this.context;
let eventBus = EventBus(context).getInstance();
let debug = Debug(context).getInstance();
const PT_UPDATE_THROTTLE_MS = 250;
const playbackStateMap = {
[MediaPlayerEvents.PLAYBACK_INITIALIZED]: Constants.CMCD_PLAYER_STATES.STARTING,
[MediaPlayerEvents.PLAYBACK_PAUSED]: Constants.CMCD_PLAYER_STATES.PAUSED,
Expand Down Expand Up @@ -134,6 +136,7 @@ function CmcdController() {

function _resetInitialSettings() {
reporterNeedsRebuild = false;
_lastPtUpdateAt = 0;
}

function _initializeEventBus(autoPlay) {
Expand All @@ -142,6 +145,7 @@ function CmcdController() {
eventBus.on(MediaPlayerEvents.BUFFER_LEVEL_STATE_CHANGED, _onBufferLevelStateChanged, instance);
eventBus.on(MediaPlayerEvents.PLAYBACK_SEEKED, _onPlaybackSeeked, instance);
eventBus.on(MediaPlayerEvents.PERIOD_SWITCH_COMPLETED, _onPeriodSwitchComplete, instance);
eventBus.on(MediaPlayerEvents.PLAYBACK_TIME_UPDATED, _onPlaybackTimeUpdated, instance);

if (autoPlay) {
eventBus.on(MediaPlayerEvents.MANIFEST_LOADING_STARTED, _onPlaybackStarted, instance);
Expand All @@ -165,12 +169,44 @@ function CmcdController() {
});
}

function _gatherContinuousMetrics() {
return {
...cmcdModel.getEventModeData(),
Comment thread
littlespex marked this conversation as resolved.
...cmcdModel.calculateMsd(),
};
}

function _onPlaybackStateChange(state) {
// Update CmcdReporter with the new player state
if (cmcdReporter) {
cmcdReporter.update({ sta: state });
if (!cmcdReporter) {
return;
}
_rebuildReporterIfNeeded();

// Single consolidated update() call:
// - Persists continuous metrics + sta into the reporter's data store
// - Auto-fires the PLAY_STATE event with the full enriched payload
// A separate recordEvent('ps', ...) would be dedup-suppressed under
// @svta/cml-cmcd 2.4.0 and silently drop its data argument.
cmcdReporter.update({ ..._gatherContinuousMetrics(), sta: state });
}

function _onPlaybackTimeUpdated(e) {
if (!cmcdReporter) {
return;
}
if (typeof e?.time !== 'number' || !isFinite(e.time) || e.time < 0) {
return;
}
triggerCmcdEventMode(Constants.CMCD_REPORTING_EVENTS.PLAY_STATE);
const now = Date.now();
if (now - _lastPtUpdateAt < PT_UPDATE_THROTTLE_MS) {
return;
}
// Honor any pending reporter rebuild before writing, otherwise pt
// lands on a reporter that's about to be discarded by the next
// state-change or response-received rebuild.
_rebuildReporterIfNeeded();
_lastPtUpdateAt = now;
cmcdReporter.update({ pt: Math.round(e.time * 1000) });
Comment thread
littlespex marked this conversation as resolved.
}

function _createCmcdReporter() {
Expand Down Expand Up @@ -250,15 +286,13 @@ function CmcdController() {
if (errorData.error?.data?.request?.type === HTTPRequest.CMCD_EVENT) {
return;
}
// Update CmcdReporter with the error code
if (cmcdReporter) {
const errorCode = errorData.error?.code || errorData.error?.data?.code;
if (errorCode) {
cmcdReporter.update({ ec: errorCode });
}
}

triggerCmcdEventMode(Constants.CMCD_REPORTING_EVENTS.ERROR);
const code = errorData.error?.code || errorData.error?.data?.code;
const transientData = code !== undefined && code !== null
? { ec: [String(code)] }
: {};

_recordNonStateEvent(Constants.CMCD_REPORTING_EVENTS.ERROR, transientData);
}

function _rebuildReporterIfNeeded() {
Expand All @@ -268,7 +302,7 @@ function CmcdController() {

// Only rebuild if manifest params are available and enabled.
// Without manifest params, the reporter config hasn't changed
// and rebuilding would unnecessarily reset sid and sn.
// and rebuilding would unnecessarily reset sid.
// IMPORTANT: Don't reset reporterNeedsRebuild until we actually rebuild,
// otherwise a race condition can occur where params aren't available yet
// and we never get another chance to rebuild.
Expand All @@ -286,26 +320,28 @@ function CmcdController() {
}

/**
* The handler that is triggered for CMCD event mode events (e.g., play, pause, error). Note that response recevived (rr) events are handled by getCmcdResponseReceivedInterceptors.
* @param event
* Records a non-state-change event (e.g., ERROR, custom). Continuous
* metrics are persisted into the reporter's data store via update();
* only ephemeral per-event payload (e.g., ec for ERROR) is passed
* through recordEvent()'s data argument.
*
* State-change events (ps, pr, c, b, bc) auto-fire from update() and
* MUST NOT be routed through this helper — a follow-up recordEvent()
* for the same event token is dedup-suppressed under
* @svta/cml-cmcd >= 2.4.0.
*
* @param event - Event token (e.g., Constants.CMCD_REPORTING_EVENTS.ERROR)
* @param transientData - Per-event payload that should NOT persist into
* the reporter's data store.
*/
function triggerCmcdEventMode(event) {
function _recordNonStateEvent(event, transientData = {}) {
if (!cmcdReporter) {
return;
}

_rebuildReporterIfNeeded();

const cmcdData = cmcdModel.getEventModeData();

// Route media start delay (MSD) through update() for the reporter's internal send-once tracking
const msdData = cmcdModel.calculateMsd();
if (msdData.msd !== undefined) {
cmcdReporter.update(msdData);
}

// Pass event-mode data as transient per-event data (not persisted)
cmcdReporter.recordEvent(event, cmcdData);
cmcdReporter.update(_gatherContinuousMetrics());
cmcdReporter.recordEvent(event, transientData);
}

/**
Expand Down Expand Up @@ -547,16 +583,14 @@ function CmcdController() {

_rebuildReporterIfNeeded();

// Collect event-mode data from the model
const eventData = cmcdModel.getEventModeData();

// Route MSD through update() for the reporter's internal send-once tracking
const msdData = cmcdModel.calculateMsd();
if (msdData.msd !== undefined) {
cmcdReporter.update(msdData);
}
// Persist continuous metrics into the reporter's data store via update().
// recordResponseReceived() will derive per-response fields and merge with
// whatever's in the store, so only ephemeral CMSD-derived data needs to
// ride the data argument.
cmcdReporter.update(_gatherContinuousMetrics());

// Collect dash.js-specific additional data
// Collect dash.js-specific additional data (CMSD headers are ephemeral
// to this response and must not leak into the persistent store).
const additionalData = {};

if (response.headers) {
Expand All @@ -576,7 +610,7 @@ function CmcdController() {
}

try {
cmcdReporter.recordResponseReceived(response, { ...eventData, ...additionalData });
cmcdReporter.recordResponseReceived(response, additionalData);
} catch (e) {
logger.error(e);
}
Expand All @@ -596,6 +630,7 @@ function CmcdController() {
eventBus.off(MediaPlayerEvents.BUFFER_LEVEL_STATE_CHANGED, _onBufferLevelStateChanged, instance);
eventBus.off(MediaPlayerEvents.PLAYBACK_SEEKED, _onPlaybackSeeked, instance);
eventBus.off(MediaPlayerEvents.PERIOD_SWITCH_COMPLETED, _onPeriodSwitchComplete, instance);
eventBus.off(MediaPlayerEvents.PLAYBACK_TIME_UPDATED, _onPlaybackTimeUpdated, instance);
eventBus.off(MediaPlayerEvents.PLAYBACK_STARTED, _onPlaybackStarted, instance);
eventBus.off(MediaPlayerEvents.MANIFEST_LOADING_STARTED, _onPlaybackStarted, instance);
eventBus.off(MediaPlayerEvents.ERROR, _onPlayerError, instance);
Expand Down
11 changes: 9 additions & 2 deletions src/streaming/models/CmcdModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,8 +567,15 @@ function CmcdModel() {
data.ltc = ltc;
}

if (typeof document !== 'undefined' && document.hidden) {
data.bg = true;
// Always report the current backgrounded state so transitions
// round-trip through the reporter's persistent store. Sending only
// bg:true (and omitting on visible) leaves a stale bg:true in the
// store after a hidden->visible transition, which then leaks into
// unrelated event-mode payloads. The library handles emission
// semantics: bg:false is stripped on non-`e=b` events and emitted
// as `?0` on `e=b` per the 2.4.0 carve-out.
if (typeof document !== 'undefined') {
data.bg = !!document.hidden;
}

if (mediaType && _shouldIncludeDroppedFrames(mediaType)) {
Expand Down
Loading
Loading