From c25c7f08dd22ef07a9cf5b32eb4f0ef43cc39a5e Mon Sep 17 00:00:00 2001 From: Casey Occhialini <1508707+littlespex@users.noreply.github.com> Date: Fri, 6 Feb 2026 22:53:20 -0800 Subject: [PATCH 01/10] chore: update CML dependencies --- package-lock.json | 92 +++++++++++++++++++++++------------------------ package.json | 12 +++---- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2375bbefe3..b456ac78d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,12 @@ "license": "BSD-3-Clause", "dependencies": { "@svta/cml-608": "1.0.1", - "@svta/cml-cmcd": "2.0.0", - "@svta/cml-cmsd": "1.0.3", - "@svta/cml-dash": "1.0.3", - "@svta/cml-id3": "1.0.3", - "@svta/cml-request": "1.0.4", - "@svta/cml-xml": "1.1.1", + "@svta/cml-cmcd": "2.1.0", + "@svta/cml-cmsd": "1.0.4", + "@svta/cml-dash": "1.0.4", + "@svta/cml-id3": "1.0.4", + "@svta/cml-request": "1.0.6", + "@svta/cml-xml": "1.1.2", "bcp-47-match": "^2.0.3", "bcp-47-normalize": "^2.3.0", "codem-isoboxer": "0.3.10", @@ -2548,100 +2548,100 @@ } }, "node_modules/@svta/cml-cmcd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@svta/cml-cmcd/-/cml-cmcd-2.0.0.tgz", - "integrity": "sha512-6Eigwk8tXdsKPGUcDzAsjzxq58lDj56XrG2IGDIOOLG1tEFmtFUajtoJTWk69FLHEwbNM9clDtfGXQ8Pr8+7/A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@svta/cml-cmcd/-/cml-cmcd-2.1.0.tgz", + "integrity": "sha512-BX7E6AjzqJG6NLl0zGQOEWsgsodimU8wIQPtVivZoJYRDJ2HF6CdmkVNoTaQGeiu355TnGllpZf6e+VfzUxnKg==", "license": "Apache-2.0", "engines": { "node": ">=20" }, "peerDependencies": { - "@svta/cml-structured-field-values": "1.1.0", - "@svta/cml-utils": "1.2.0" + "@svta/cml-structured-field-values": "1.1.1", + "@svta/cml-utils": "1.3.0" } }, "node_modules/@svta/cml-cmsd": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@svta/cml-cmsd/-/cml-cmsd-1.0.3.tgz", - "integrity": "sha512-8DYYwVrrJMpY6CbnPQHDWgsoip9ewEURAzlLOWwOuIgS6xaf4kzYep0QBKZwKlWKTq+BRXQHNk7Ns3QaKzbw7Q==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@svta/cml-cmsd/-/cml-cmsd-1.0.4.tgz", + "integrity": "sha512-4284bLITFLayFOE7aloXS5WkhlOJjIrw50pOwEdK5wWO02vGjcQmx0vrkc4MCOh2lmpgvuzrNfyaFlTl9JyH2A==", "license": "Apache-2.0", "engines": { "node": ">=20" }, "peerDependencies": { - "@svta/cml-cta": "1.0.3", - "@svta/cml-structured-field-values": "1.1.0", - "@svta/cml-utils": "1.2.0" + "@svta/cml-cta": "1.0.4", + "@svta/cml-structured-field-values": "1.1.1", + "@svta/cml-utils": "1.3.0" } }, "node_modules/@svta/cml-cta": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@svta/cml-cta/-/cml-cta-1.0.3.tgz", - "integrity": "sha512-BoFuFPwCv1h6D2l75GSf1oZzHMK6XmV6Cfe2SDuq0ZfOjzoQ+0vjd8btKk53Gom+sjw6A/54aogFkinriS5rWA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@svta/cml-cta/-/cml-cta-1.0.4.tgz", + "integrity": "sha512-lNIrX9jnM/31q+O/p3QsR0IvRMa2OrHuNeaw86mDBrb3oyJcBOOWGQcCr2yLcf6EuBgbzQmswDEQecmlWRL9dg==", "license": "Apache-2.0", "peer": true, "engines": { "node": ">=20" }, "peerDependencies": { - "@svta/cml-structured-field-values": "1.1.0", - "@svta/cml-utils": "1.2.0" + "@svta/cml-structured-field-values": "1.1.1", + "@svta/cml-utils": "1.3.0" } }, "node_modules/@svta/cml-dash": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@svta/cml-dash/-/cml-dash-1.0.3.tgz", - "integrity": "sha512-x6szXcB7uGq0grbFn2X0zP8Oxlh/R3mmgNtzpnm3Wpa0mXxiaBnLX+ULsuF37JYgMDlIT93e/gi21PJoXQxvzg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@svta/cml-dash/-/cml-dash-1.0.4.tgz", + "integrity": "sha512-1opr9SMb8fCN6FpApTF+T9DCu8gvMNwIi5oi2i2IBXLysszT+f++cDcfNq5E+1AJq4N0uoreKcW4PKwTQ2XDFw==", "license": "Apache-2.0", "engines": { "node": ">=20" }, "peerDependencies": { - "@svta/cml-utils": "1.2.0" + "@svta/cml-utils": "1.3.0" } }, "node_modules/@svta/cml-id3": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@svta/cml-id3/-/cml-id3-1.0.3.tgz", - "integrity": "sha512-nWqDFJndPwT01rHlzlNQkkRSi10nKW6AmhZ+ejrWjaPMNV+GxykmVH13kATKYhluW5iLyyzfQPIIdEWBFmGMvw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@svta/cml-id3/-/cml-id3-1.0.4.tgz", + "integrity": "sha512-v2IU+91SDrDXga6yZITqyBHa2Y1RVGT1ts6BHg3/YOHOCBAXy4o5gq9SQSRlHFvw6jzhJ3JdSpjSaXs3PPBwDw==", "license": "Apache-2.0", "engines": { "node": ">=20" }, "peerDependencies": { - "@svta/cml-utils": "1.2.0" + "@svta/cml-utils": "1.3.0" } }, "node_modules/@svta/cml-request": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@svta/cml-request/-/cml-request-1.0.4.tgz", - "integrity": "sha512-gGhvtiV4fXolVdYQ8daDOpsKfPc1y88cBLCZOHaYIVWNO5nXGhWKiCI3Daez6JFawyDCIyRqtrZo3f8jmujgQA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@svta/cml-request/-/cml-request-1.0.6.tgz", + "integrity": "sha512-+vCTcNN8Y8sFNSxIIFsIFckUcLKFd7v2H5Gg5ej2iKz2Z7MX5U/jxX4/McBDVA7g8AEqR6snHg7TbpUShD5hRA==", "license": "Apache-2.0", "engines": { "node": ">=20" }, "peerDependencies": { - "@svta/cml-utils": "1.2.0", - "@svta/cml-xml": "1.1.1" + "@svta/cml-utils": "1.3.0", + "@svta/cml-xml": "1.1.2" } }, "node_modules/@svta/cml-structured-field-values": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@svta/cml-structured-field-values/-/cml-structured-field-values-1.1.0.tgz", - "integrity": "sha512-MecTsJA0sRITLI87WGrAQ80Ir9+eAanJ7LyhW0fNM6o0x0vezrEYCNdLmqE8iyVYg0Fi2NwCV8tIVHmZPEfiqQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@svta/cml-structured-field-values/-/cml-structured-field-values-1.1.1.tgz", + "integrity": "sha512-04zNbiY2HrOMAQ2F0iZ4yDbKaAg3UybtTARNv930bhIY00vmmd8Elt0jHB+Q3y/hC3I35QU8SGrORKT5LE2sOQ==", "license": "Apache-2.0", "peer": true, "engines": { "node": ">=20" }, "peerDependencies": { - "@svta/cml-utils": "1.2.0" + "@svta/cml-utils": "1.3.0" } }, "node_modules/@svta/cml-utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@svta/cml-utils/-/cml-utils-1.2.0.tgz", - "integrity": "sha512-2oCminPTrIGox4zsemHcq4dVUwKX6Ln/OYQsXGHUvV8gTel1glHSfBsNWndyJPFTAQqsld7TFqI7lXOIrFvFQA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@svta/cml-utils/-/cml-utils-1.3.0.tgz", + "integrity": "sha512-TaVyR899SZpjvw0RgA+/wRnij6pf4r0l+W1qHie9H/cPGNJBsQG1sKLx8inmKHwvZzcPt2SP/fv9MwMyqAbC+Q==", "license": "Apache-2.0", "peer": true, "engines": { @@ -2649,15 +2649,15 @@ } }, "node_modules/@svta/cml-xml": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@svta/cml-xml/-/cml-xml-1.1.1.tgz", - "integrity": "sha512-yrCYUO/k0KnRPFLv//RsbsUf1qLpEF7bV4toh2+sgU52Kp0kI5Hs4I+qZXqhVqJMUwS/D083A2nDV8z0fNinYQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@svta/cml-xml/-/cml-xml-1.1.2.tgz", + "integrity": "sha512-4BCzRc1IpqfA94dsZXnfJ/8KsXaFYjJRbrMafgGT8tXwrKeR7ln6wO4L2FZ7b+D1e18zSHIyEqlU9MqKyMmnYg==", "license": "Apache-2.0", "engines": { "node": ">=20" }, "peerDependencies": { - "@svta/cml-utils": "1.2.0" + "@svta/cml-utils": "1.3.0" } }, "node_modules/@types/body-parser": { diff --git a/package.json b/package.json index 3298ffe8a0..5a7e1007a9 100644 --- a/package.json +++ b/package.json @@ -90,12 +90,12 @@ }, "dependencies": { "@svta/cml-608": "1.0.1", - "@svta/cml-cmcd": "2.0.0", - "@svta/cml-cmsd": "1.0.3", - "@svta/cml-dash": "1.0.3", - "@svta/cml-id3": "1.0.3", - "@svta/cml-request": "1.0.4", - "@svta/cml-xml": "1.1.1", + "@svta/cml-cmcd": "2.1.0", + "@svta/cml-cmsd": "1.0.4", + "@svta/cml-dash": "1.0.4", + "@svta/cml-id3": "1.0.4", + "@svta/cml-request": "1.0.6", + "@svta/cml-xml": "1.1.2", "bcp-47-match": "^2.0.3", "bcp-47-normalize": "^2.3.0", "codem-isoboxer": "0.3.10", From 59d3713ddd84ede871d1330666de6f801654a5aa Mon Sep 17 00:00:00 2001 From: Casey Occhialini <1508707+littlespex@users.noreply.github.com> Date: Fri, 6 Feb 2026 22:55:06 -0800 Subject: [PATCH 02/10] fix: update common media request --- src/streaming/controllers/CmcdController.js | 10 ++++------ src/streaming/vo/CommonMediaRequest.js | 9 ++++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/streaming/controllers/CmcdController.js b/src/streaming/controllers/CmcdController.js index f7dfaf936a..57f3a2fe02 100644 --- a/src/streaming/controllers/CmcdController.js +++ b/src/streaming/controllers/CmcdController.js @@ -295,7 +295,7 @@ function CmcdController() { // For RESPONSE_RECEIVED, merge request CMCD data and response metrics if (event === Constants.CMCD_REPORTING_EVENTS.RESPONSE_RECEIVED && response) { - cmcdData = { ...cmcdData, ...response.request.cmcd }; + cmcdData = { ...cmcdData, ...response.request.customData?.cmcd }; cmcdData = _addCmcdResponseReceivedData(response, cmcdData); } @@ -324,13 +324,13 @@ function CmcdController() { ...cmcdModel.calculateCmcdDataForRequest(request), ...cmcdModel.updateMsdData(Constants.CMCD_REPORTING_MODE.REQUEST), }; - - request.cmcd = cmcdData; //TODO: wrong because cmcdData only has data from model, not complete data with reporter + cmcdReporter.update(cmcdData); const decorated = cmcdReporter.applyRequestReport(request); request.url = decorated.url; request.headers = decorated.headers; + request.cmcd = decorated.customData?.cmcd || {}; _triggerCMCDDataGeneratedEvent(request) @@ -504,7 +504,6 @@ function CmcdController() { const requestType = commonMediaRequest.customData.request.type; if (!cmcdModel.isIncludedInRequestFilter(requestType)) { - commonMediaRequest.cmcd = commonMediaRequest.customData.request.cmcd; return commonMediaRequest; } @@ -516,8 +515,7 @@ function CmcdController() { ...commonMediaRequest, url: request.url, headers: request.headers, - customData: { request }, - cmcd: request.cmcd, + customData: { ...commonMediaRequest.customData, cmcd: request.cmcd }, }; return commonMediaRequest; diff --git a/src/streaming/vo/CommonMediaRequest.js b/src/streaming/vo/CommonMediaRequest.js index 96a78884b0..02c837cec3 100644 --- a/src/streaming/vo/CommonMediaRequest.js +++ b/src/streaming/vo/CommonMediaRequest.js @@ -3,25 +3,24 @@ class CommonMediaRequest { * @param {Object} params * @param {string} params.url * @param {string} params.method + * @param {BodyInit} [params.body] * @param {string} [params.responseType] * @param {Object} [params.headers] * @param {RequestCredentials} [params.credentials] * @param {RequestMode} [params.mode] * @param {number} [params.timeout] - * @param {Cmcd} [params.cmcd] - * @param {any} [params.customData] + * @param {Object} [params.customData] */ constructor(params) { this.url = params.url; this.method = params.method; + this.body = params.body !== undefined ? params.body : null; this.responseType = params.responseType !== undefined ? params.responseType : null; this.headers = params.headers !== undefined ? params.headers : {}; this.credentials = params.credentials !== undefined ? params.credentials : null; this.mode = params.mode !== undefined ? params.mode : null; this.timeout = params.timeout !== undefined ? params.timeout : 0; - this.cmcd = params.cmcd !== undefined ? params.cmcd : null; - this.customData = params.customData !== undefined ? params.customData : null; - this.body = params.body !== undefined ? params.body : null; + this.customData = params.customData !== undefined ? params.customData : {}; } } From b4fb749f7056450784ff5cbfd56385e9b0f17919 Mon Sep 17 00:00:00 2001 From: Casey Occhialini <1508707+littlespex@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:08:18 -0800 Subject: [PATCH 03/10] fix: update resourceTiming properties to use performance.now() --- src/streaming/net/HTTPLoader.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/streaming/net/HTTPLoader.js b/src/streaming/net/HTTPLoader.js index ccce901597..5641d1b70a 100644 --- a/src/streaming/net/HTTPLoader.js +++ b/src/streaming/net/HTTPLoader.js @@ -297,7 +297,8 @@ function HTTPLoader(cfg) { } const _updateResourceTimingInfo = function () { - commonMediaResponse.resourceTiming.responseEnd = Date.now(); + commonMediaResponse.resourceTiming.responseEnd = performance.now(); + commonMediaResponse.resourceTiming.duration = commonMediaResponse.resourceTiming.responseEnd - commonMediaResponse.resourceTiming.startTime; // If enabled the ResourceTimingApi we add the corresponding information to the request object. // These values are more accurate and can be used by the ThroughputController later @@ -314,7 +315,7 @@ function HTTPLoader(cfg) { httpRequest.customData.onabort = _onabort; httpRequest.customData.ontimeout = _ontimeout; - httpResponse.resourceTiming.startTime = Date.now(); + httpResponse.resourceTiming.startTime = performance.now(); loader.load(httpRequest, httpResponse); resolve(); }); @@ -386,7 +387,7 @@ function HTTPLoader(cfg) { const loaderInformation = _getLoader(requestObject); const loader = loaderInformation.loader; requestObject.fileLoaderType = loaderInformation.fileLoaderType; - + requestObject.headers = requestObject.headers || {}; _updateRequestUrlAndHeaders(requestObject); if (requestObject.range) { @@ -409,7 +410,7 @@ function HTTPLoader(cfg) { commonMediaResponse = new CommonMediaResponse({ request: commonMediaRequest, resourceTiming: { - startTime: Date.now(), + startTime: performance.now(), encodedBodySize: 0 }, status: 0 From 4c343ccdeacc08de52d8dd7cb6cea0b80a6fafe5 Mon Sep 17 00:00:00 2001 From: Casey Occhialini <1508707+littlespex@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:25:43 -0800 Subject: [PATCH 04/10] fix: update cmcd data formatting --- src/streaming/controllers/CmcdController.js | 6 +- src/streaming/models/CmcdModel.js | 168 ++++++-------------- 2 files changed, 54 insertions(+), 120 deletions(-) diff --git a/src/streaming/controllers/CmcdController.js b/src/streaming/controllers/CmcdController.js index 57f3a2fe02..13fff1be3b 100644 --- a/src/streaming/controllers/CmcdController.js +++ b/src/streaming/controllers/CmcdController.js @@ -306,7 +306,7 @@ function CmcdController() { /** * Applies CMCD data to a request by decorating its URL and/or headers. - * Delegates to CmcdReporter.applyRequestReport() which handles + * Delegates to CmcdReporter.createRequestReport() which handles * transmission mode (query vs header) internally. * * @param {object} request - The request object with at least { url, type }. @@ -325,9 +325,7 @@ function CmcdController() { ...cmcdModel.updateMsdData(Constants.CMCD_REPORTING_MODE.REQUEST), }; - cmcdReporter.update(cmcdData); - - const decorated = cmcdReporter.applyRequestReport(request); + const decorated = cmcdReporter.createRequestReport(request, cmcdData); request.url = decorated.url; request.headers = decorated.headers; request.cmcd = decorated.customData?.cmcd || {}; diff --git a/src/streaming/models/CmcdModel.js b/src/streaming/models/CmcdModel.js index e5c862589f..a29b5831a5 100644 --- a/src/streaming/models/CmcdModel.js +++ b/src/streaming/models/CmcdModel.js @@ -157,29 +157,17 @@ function CmcdModel() { } if (nextRequest) { - if (cmcdConfig.getVersion() === 2) { - if (request.url !== nextRequest.url) { - const relativeUrl = Utils.getRelativeUrl(request.url, nextRequest.url); - const params = nextRequest.range ? { r: nextRequest.range } : undefined; - data.nor = [toCmcdValue(relativeUrl, params)]; - } - } else { - if (request.url !== nextRequest.url) { - data.nor = encodeURIComponent(Utils.getRelativeUrl(request.url, nextRequest.url)); - } else if (nextRequest.range) { - data.nrr = nextRequest.range; - } + if (request.url !== nextRequest.url) { + const relativeUrl = Utils.getRelativeUrl(request.url, nextRequest.url); + const params = nextRequest.range ? { r: nextRequest.range } : undefined; + data.nor = [toCmcdValue(relativeUrl, params)]; } } if (encodedBitrate) { - if (cmcdConfig.getVersion() === 2) { - const videoBr = mediaType === Constants.VIDEO ? encodedBitrate : null; - const audioBr = mediaType === Constants.AUDIO ? encodedBitrate : null; - data.br = _toInnerList(videoBr, audioBr) || [toCmcdValue(encodedBitrate, {})]; - } else { - data.br = encodedBitrate; - } + const videoBr = mediaType === Constants.VIDEO ? encodedBitrate : null; + const audioBr = mediaType === Constants.AUDIO ? encodedBitrate : null; + data.br = _toInnerList(videoBr, audioBr) || [toCmcdValue(encodedBitrate, {})]; } if (ot) { @@ -191,13 +179,9 @@ function CmcdModel() { } if (!isNaN(mtp)) { - if (cmcdConfig.getVersion() === 2) { - const videoMtp = mediaType === Constants.VIDEO ? mtp : null; - const audioMtp = mediaType === Constants.AUDIO ? mtp : null; - data.mtp = _toInnerList(videoMtp, audioMtp) || [toCmcdValue(mtp, {})]; - } else { - data.mtp = mtp; - } + const videoMtp = mediaType === Constants.VIDEO ? mtp : null; + const audioMtp = mediaType === Constants.AUDIO ? mtp : null; + data.mtp = _toInnerList(videoMtp, audioMtp) || [toCmcdValue(mtp, {})]; } if (!isNaN(dl)) { @@ -205,43 +189,27 @@ function CmcdModel() { } if (!isNaN(bl)) { - if (cmcdConfig.getVersion() === 2) { - const videoBl = mediaType === Constants.VIDEO ? bl : null; - const audioBl = mediaType === Constants.AUDIO ? bl : null; - data.bl = _toInnerList(videoBl, audioBl) || [toCmcdValue(bl, {})]; - } else { - data.bl = bl; - } + const videoBl = mediaType === Constants.VIDEO ? bl : null; + const audioBl = mediaType === Constants.AUDIO ? bl : null; + data.bl = _toInnerList(videoBl, audioBl) || [toCmcdValue(bl, {})]; } if (!isNaN(tb) && isFinite(tb)) { - if (cmcdConfig.getVersion() === 2) { - const videoTb = mediaType === Constants.VIDEO ? tb : null; - const audioTb = mediaType === Constants.AUDIO ? tb : null; - data.tb = _toInnerList(videoTb, audioTb) || [toCmcdValue(tb, {})]; - } else { - data.tb = tb; - } + const videoTb = mediaType === Constants.VIDEO ? tb : null; + const audioTb = mediaType === Constants.AUDIO ? tb : null; + data.tb = _toInnerList(videoTb, audioTb) || [toCmcdValue(tb, {})]; } if (tpb !== null && !isNaN(tpb)) { - if (cmcdConfig.getVersion() === 2) { - const videoTpb = mediaType === Constants.VIDEO ? tpb : null; - const audioTpb = mediaType === Constants.AUDIO ? tpb : null; - data.tpb = _toInnerList(videoTpb, audioTpb) || [toCmcdValue(tpb, {})]; - } else { - data.tpb = tpb; - } + const videoTpb = mediaType === Constants.VIDEO ? tpb : null; + const audioTpb = mediaType === Constants.AUDIO ? tpb : null; + data.tpb = _toInnerList(videoTpb, audioTpb) || [toCmcdValue(tpb, {})]; } if (pb !== null && !isNaN(pb)) { - if (cmcdConfig.getVersion() === 2) { - const videoPb = mediaType === Constants.VIDEO ? pb : null; - const audioPb = mediaType === Constants.AUDIO ? pb : null; - data.pb = _toInnerList(videoPb, audioPb) || [toCmcdValue(pb, {})]; - } else { - data.pb = pb; - } + const videoPb = mediaType === Constants.VIDEO ? pb : null; + const audioPb = mediaType === Constants.AUDIO ? pb : null; + data.pb = _toInnerList(videoPb, audioPb) || [toCmcdValue(pb, {})]; } if (_bufferLevelStarved[mediaType]) { @@ -250,13 +218,9 @@ function CmcdModel() { } if (_rebufferingDuration[mediaType]) { - if (cmcdConfig.getVersion() === 2) { - const videoBsd = mediaType === Constants.VIDEO ? _rebufferingDuration[mediaType] : null; - const audioBsd = mediaType === Constants.AUDIO ? _rebufferingDuration[mediaType] : null; - data.bsd = _toInnerList(videoBsd, audioBsd) || [toCmcdValue(_rebufferingDuration[mediaType], {})]; - } else { - data.bsd = _rebufferingDuration[mediaType]; - } + const videoBsd = mediaType === Constants.VIDEO ? _rebufferingDuration[mediaType] : null; + const audioBsd = mediaType === Constants.AUDIO ? _rebufferingDuration[mediaType] : null; + data.bsd = _toInnerList(videoBsd, audioBsd) || [toCmcdValue(_rebufferingDuration[mediaType], {})]; delete _rebufferingDuration[mediaType]; } @@ -601,16 +565,13 @@ function CmcdModel() { data.pr = _playbackRate; } - const cmcdVersion = cmcdConfig.getVersion(); - if (cmcdVersion === 2) { - let ltc = playbackController.getCurrentLiveLatency() * 1000; - if (!isNaN(ltc)) { - data.ltc = ltc; - } + let ltc = playbackController.getCurrentLiveLatency() * 1000; + if (!isNaN(ltc)) { + data.ltc = ltc; + } - if (typeof document !== 'undefined' && document.hidden) { - data.bg = true; - } + if (typeof document !== 'undefined' && document.hidden) { + data.bg = true; } if (mediaType && _shouldIncludeDroppedFrames(mediaType)) { @@ -715,15 +676,12 @@ function CmcdModel() { } function updateMsdData(mode) { - const cmcdVersion = cmcdConfig.getVersion(); const data = {}; const msd = _calculateMsd(); - if (cmcdVersion === 2) { - if (!_msdSent[mode] && msd !== null && !isNaN(msd)) { - data.msd = msd; - _msdSent[mode] = true; - } + if (!_msdSent[mode] && msd !== null && !isNaN(msd)) { + data.msd = msd; + _msdSent[mode] = true; } return data; @@ -869,22 +827,14 @@ function CmcdModel() { const currentVideoBitrate = videoRep ? videoRep.bitrateInKbit : 0; const currentAudioBitrate = audioRep ? audioRep.bitrateInKbit : 0; - const isV2 = cmcdConfig.getVersion() === 2; // Calculate aggregated bitrate - if (isV2) { - const abValues = _toInnerList( - currentVideoBitrate > 0 ? Math.round(currentVideoBitrate) : null, - currentAudioBitrate > 0 ? Math.round(currentAudioBitrate) : null - ); - if (abValues) { - data.ab = abValues; - } - } else { - const aggregatedBitrate = currentVideoBitrate + currentAudioBitrate; - if (aggregatedBitrate > 0) { - data.ab = Math.round(aggregatedBitrate); - } + const abValues = _toInnerList( + currentVideoBitrate > 0 ? Math.round(currentVideoBitrate) : null, + currentAudioBitrate > 0 ? Math.round(currentAudioBitrate) : null + ); + if (abValues) { + data.ab = abValues; } // Calculate top aggregated bitrate @@ -892,37 +842,23 @@ function CmcdModel() { const allAudioReps = activeStream.getRepresentationsByType(Constants.AUDIO) || []; const topVideoBitrate = allVideoReps.reduce((max, rep) => Math.max(max, rep.bitrateInKbit), 0); const topAudioBitrate = allAudioReps.reduce((max, rep) => Math.max(max, rep.bitrateInKbit), 0); - if (isV2) { - const tabValues = _toInnerList( - topVideoBitrate > 0 ? Math.round(topVideoBitrate) : null, - topAudioBitrate > 0 ? Math.round(topAudioBitrate) : null - ); - if (tabValues) { - data.tab = tabValues; - } - } else { - const topAggregatedBitrate = topVideoBitrate + topAudioBitrate; - if (topAggregatedBitrate > 0) { - data.tab = Math.round(topAggregatedBitrate); - } + const tabValues = _toInnerList( + topVideoBitrate > 0 ? Math.round(topVideoBitrate) : null, + topAudioBitrate > 0 ? Math.round(topAudioBitrate) : null + ); + if (tabValues) { + data.tab = tabValues; } // Calculate lowest aggregated bitrate const lowestVideoBitrate = allVideoReps.length > 0 ? Math.min(...allVideoReps.map(rep => rep.bitrateInKbit)) : 0; const lowestAudioBitrate = allAudioReps.length > 0 ? Math.min(...allAudioReps.map(rep => rep.bitrateInKbit)) : 0; - if (isV2) { - const labValues = _toInnerList( - lowestVideoBitrate > 0 ? Math.round(lowestVideoBitrate) : null, - lowestAudioBitrate > 0 ? Math.round(lowestAudioBitrate) : null - ); - if (labValues) { - data.lab = labValues; - } - } else { - const lowestAggregatedBitrate = lowestVideoBitrate + lowestAudioBitrate; - if (lowestAggregatedBitrate > 0) { - data.lab = Math.round(lowestAggregatedBitrate); - } + const labValues = _toInnerList( + lowestVideoBitrate > 0 ? Math.round(lowestVideoBitrate) : null, + lowestAudioBitrate > 0 ? Math.round(lowestAudioBitrate) : null + ); + if (labValues) { + data.lab = labValues; } return data; From 89ffd86b873143238bc4064355d67d1239873d73 Mon Sep 17 00:00:00 2001 From: Casey Occhialini <1508707+littlespex@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:31:32 -0800 Subject: [PATCH 05/10] fix: remove redundant rr values --- src/streaming/controllers/CmcdController.js | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/streaming/controllers/CmcdController.js b/src/streaming/controllers/CmcdController.js index 13fff1be3b..340758b0eb 100644 --- a/src/streaming/controllers/CmcdController.js +++ b/src/streaming/controllers/CmcdController.js @@ -243,7 +243,7 @@ function CmcdController() { }); }); } - + function _onStateChange(state) { // Update CmcdReporter with the new player state if (cmcdReporter) { @@ -534,25 +534,7 @@ function CmcdController() { function _addCmcdResponseReceivedData(response, cmcdData){ const responseData = {}; - const request = response.request.customData.request; - const requestType = request.type; - - if (requestType === HTTPRequest.MEDIA_SEGMENT_TYPE){ - responseData.rc = response.status; - } - - if (request.startDate && request.firstByteDate){ - responseData.ttfb = request.firstByteDate - request.startDate; - } - - if (request.endDate && request.startDate){ - responseData.ttlb = request.endDate - request.startDate - } - if (request.url) { - responseData.url = request.url.split('?')[0] - } - if (response.headers){ try { const cmsdStaticHeader = response.headers['cmsd-static']; From d72349a65d5db09e06d65c2752a3cb108730e357 Mon Sep 17 00:00:00 2001 From: Casey Occhialini <1508707+littlespex@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:59:21 -0800 Subject: [PATCH 06/10] refactor: simplify cmcd reporting --- src/streaming/controllers/CmcdController.js | 66 +++++++++++-------- src/streaming/models/CmcdModel.js | 54 +++++---------- .../streaming/streaming.models.CmcdModel.js | 31 ++------- 3 files changed, 64 insertions(+), 87 deletions(-) diff --git a/src/streaming/controllers/CmcdController.js b/src/streaming/controllers/CmcdController.js index 340758b0eb..b968e601c4 100644 --- a/src/streaming/controllers/CmcdController.js +++ b/src/streaming/controllers/CmcdController.js @@ -249,11 +249,7 @@ function CmcdController() { if (cmcdReporter) { cmcdReporter.update({ sta: state }); } - _onEventChange(Constants.CMCD_REPORTING_EVENTS.PLAY_STATE); - } - - function _onEventChange(event, response){ - triggerCmcdEventMode(event, response); + triggerCmcdEventMode(Constants.CMCD_REPORTING_EVENTS.PLAY_STATE); } function _onPeriodSwitchComplete() { @@ -281,27 +277,26 @@ function CmcdController() { } } - _onEventChange(Constants.CMCD_REPORTING_EVENTS.ERROR); + triggerCmcdEventMode(Constants.CMCD_REPORTING_EVENTS.ERROR); } - function triggerCmcdEventMode(event, response) { + function triggerCmcdEventMode(event) { if (!cmcdReporter) { return; } _rebuildReporterIfNeeded(); - let cmcdData = cmcdModel.triggerCmcdEventMode(); + const cmcdData = cmcdModel.getEventModeData(); - // For RESPONSE_RECEIVED, merge request CMCD data and response metrics - if (event === Constants.CMCD_REPORTING_EVENTS.RESPONSE_RECEIVED && response) { - cmcdData = { ...cmcdData, ...response.request.customData?.cmcd }; - cmcdData = _addCmcdResponseReceivedData(response, cmcdData); + // Route MSD through update() for the reporter's internal send-once tracking + const msdData = cmcdModel.calculateMsd(); + if (msdData.msd !== undefined) { + cmcdReporter.update(msdData); } - // Update reporter with calculated data and record the event - cmcdReporter.update(cmcdData); - cmcdReporter.recordEvent(event); + // Pass event-mode data as transient per-event data (not persisted) + cmcdReporter.recordEvent(event, cmcdData); } /** @@ -320,10 +315,13 @@ function CmcdController() { _rebuildReporterIfNeeded(); try { - const cmcdData = { - ...cmcdModel.calculateCmcdDataForRequest(request), - ...cmcdModel.updateMsdData(Constants.CMCD_REPORTING_MODE.REQUEST), - }; + const cmcdData = cmcdModel.calculateCmcdDataForRequest(request); + + // Route MSD through update() for the reporter's internal send-once tracking + const msdData = cmcdModel.calculateMsd(); + if (msdData.msd !== undefined) { + cmcdReporter.update(msdData); + } const decorated = cmcdReporter.createRequestReport(request, cmcdData); request.url = decorated.url; @@ -528,30 +526,46 @@ function CmcdController() { if (requestType === HTTPRequest.CMCD_EVENT) { return response; } - _onEventChange(Constants.CMCD_REPORTING_EVENTS.RESPONSE_RECEIVED, response) + _handleResponseReceived(response); return response; } - function _addCmcdResponseReceivedData(response, cmcdData){ - const responseData = {}; + function _handleResponseReceived(response) { + if (!cmcdReporter) { + return; + } + + _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); + } + + // Collect dash.js-specific additional data + const additionalData = {}; - if (response.headers){ + if (response.headers) { try { const cmsdStaticHeader = response.headers['cmsd-static']; if (cmsdStaticHeader) { - responseData.cmsds = btoa(cmsdStaticHeader); + additionalData.cmsds = btoa(cmsdStaticHeader); } const cmsdDynamicHeader = response.headers['cmsd-dynamic']; if (cmsdDynamicHeader) { - responseData.cmsdd = btoa(cmsdDynamicHeader); + additionalData.cmsdd = btoa(cmsdDynamicHeader); } } catch (e) { logger.warn('Failed to base64 encode CMSD headers, ignoring.', e); } } - return {...cmcdData, ...responseData}; + cmcdReporter.recordResponseReceived(response, { ...eventData, ...additionalData }); } function getCmcdParametersFromManifest() { diff --git a/src/streaming/models/CmcdModel.js b/src/streaming/models/CmcdModel.js index a29b5831a5..af7a959713 100644 --- a/src/streaming/models/CmcdModel.js +++ b/src/streaming/models/CmcdModel.js @@ -60,15 +60,10 @@ function CmcdModel() { _playbackStartedTime, _isSeeking, streamProcessors, - _msdSent = { - [Constants.CMCD_REPORTING_MODE.EVENT]: false, - [Constants.CMCD_REPORTING_MODE.REQUEST]: false - }, _rebufferingStartTime = {}, _rebufferingDuration = {}, _streamType, - _streamingFormat, - _playbackRate; + _streamingFormat; let context = this.context; @@ -76,7 +71,7 @@ function CmcdModel() { cmcdConfig = CmcdConfigAccessor(context).getInstance(); resetInitialSettings(); } - + function setConfig(config) { if (!config) { return; @@ -205,7 +200,7 @@ function CmcdModel() { const audioTpb = mediaType === Constants.AUDIO ? tpb : null; data.tpb = _toInnerList(videoTpb, audioTpb) || [toCmcdValue(tpb, {})]; } - + if (pb !== null && !isNaN(pb)) { const videoPb = mediaType === Constants.VIDEO ? pb : null; const audioPb = mediaType === Constants.AUDIO ? pb : null; @@ -310,7 +305,7 @@ function CmcdModel() { if (!streamProcessors || streamProcessors.length === 0) { return null; } - + const streamProcessor = streamProcessors.find(sp => sp.getType() === mediaType); const bitrate = streamProcessor?.getRepresentationController()?.getCurrentRepresentation()?.bitrateInKbit; @@ -553,17 +548,10 @@ function CmcdModel() { function getGenericCmcdData(mediaType) { const data = {}; - data.ts = Date.now(); - - if (_streamType) { - data.st = _streamType; - } - if (_streamingFormat) { - data.sf = _streamingFormat; - } - if (_playbackRate !== undefined && _playbackRate !== 1) { - data.pr = _playbackRate; - } + // Note: ts, st, sf, pr are handled by CmcdReporter: + // - ts: auto-generated by recordEvent() / recordResponseReceived() + // - st, sf: persisted via cmcdReporter.update() in _onManifestLoaded + // - pr: persisted via cmcdReporter.update() in _onPlaybackRateChanged let ltc = playbackController.getCurrentLiveLatency() * 1000; if (!isNaN(ltc)) { @@ -590,10 +578,9 @@ function CmcdModel() { mediaType === Constants.OTHER; } - function triggerCmcdEventMode(){ + function getEventModeData(){ const cmcdData = { ...getGenericCmcdData(), - ...updateMsdData(Constants.CMCD_REPORTING_MODE.EVENT), ..._getAggregatedBitrateData(), ..._getEncodedBitrateData(), ..._getBufferLevelData(), @@ -616,11 +603,6 @@ function CmcdModel() { _rebufferingDuration = {}; _streamType = undefined; _streamingFormat = undefined; - _playbackRate = undefined; - _msdSent = { - [Constants.CMCD_REPORTING_MODE.EVENT]: false, - [Constants.CMCD_REPORTING_MODE.REQUEST]: false - } _updateStreamProcessors(); } @@ -675,13 +657,12 @@ function CmcdModel() { } } - function updateMsdData(mode) { + function calculateMsd() { const data = {}; const msd = _calculateMsd(); - if (!_msdSent[mode] && msd !== null && !isNaN(msd)) { + if (msd !== null && !isNaN(msd)) { data.msd = msd; - _msdSent[mode] = true; } return data; @@ -689,7 +670,6 @@ function CmcdModel() { function onPlaybackRateChanged(data) { if (data.playbackRate !== undefined) { - _playbackRate = data.playbackRate; return { pr: data.playbackRate }; } return null; @@ -819,8 +799,8 @@ function CmcdModel() { const activeStream = playbackController.getStreamController()?.getActiveStream(); if (!activeStream) { return data; - } - + } + // Get current representations const videoRep = activeStream.getCurrentRepresentationForType(Constants.VIDEO); const audioRep = activeStream.getCurrentRepresentationForType(Constants.AUDIO); @@ -863,7 +843,7 @@ function CmcdModel() { return data; } - + function getLastMediaTypeRequest() { return _lastMediaTypeRequest; } @@ -882,12 +862,12 @@ function CmcdModel() { onPlaybackSeeked, wasPlaying, onBufferLevelStateChanged, - updateMsdData, + calculateMsd, resetInitialSettings, getCmcdParametersFromManifest, onPlaybackRateChanged, onManifestLoaded, - triggerCmcdEventMode, + getEventModeData, isIncludedInRequestFilter, getLastMediaTypeRequest }; @@ -898,4 +878,4 @@ function CmcdModel() { } CmcdModel.__dashjs_factory_name = 'CmcdModel'; -export default FactoryMaker.getSingletonFactory(CmcdModel); \ No newline at end of file +export default FactoryMaker.getSingletonFactory(CmcdModel); diff --git a/test/unit/test/streaming/streaming.models.CmcdModel.js b/test/unit/test/streaming/streaming.models.CmcdModel.js index d5994ace18..90847ffb81 100644 --- a/test/unit/test/streaming/streaming.models.CmcdModel.js +++ b/test/unit/test/streaming/streaming.models.CmcdModel.js @@ -178,42 +178,25 @@ describe('CmcdModel', function () { }); }); - describe('updateMsdData', function () { - it('should return MSD data for version 2', function () { - settings.update({ - streaming: { - cmcd: { - version: 2 - } - } - }); - + describe('calculateMsd', function () { + it('should return MSD data when playback has started', function () { cmcdModel.onPlaybackStarted(); cmcdModel.onPlaybackPlaying(); - const msdData = cmcdModel.updateMsdData(Constants.CMCD_REPORTING_MODE.REQUEST); + const msdData = cmcdModel.calculateMsd(); expect(msdData).to.have.property('msd').that.is.a('number'); }); - it('should not return MSD data for version 1', function () { - settings.update({ - streaming: { - cmcd: { - version: 1 - } - } - }); - - const msdData = cmcdModel.updateMsdData(Constants.CMCD_REPORTING_MODE.REQUEST); + it('should return empty object when playback has not started', function () { + const msdData = cmcdModel.calculateMsd(); expect(Object.keys(msdData)).to.have.length(0); }); }); - describe('triggerCmcdEventMode', function () { + describe('getEventModeData', function () { it('should return event mode CMCD data', function () { - const eventData = cmcdModel.triggerCmcdEventMode(); + const eventData = cmcdModel.getEventModeData(); expect(eventData).to.exist; - expect(eventData.ts).to.be.a('number'); }); }); From 527289f608974005b851fb87ecb6ae6998058cc4 Mon Sep 17 00:00:00 2001 From: Casey Occhialini <1508707+littlespex@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:39:53 -0800 Subject: [PATCH 07/10] fix: test mock requests missing parameters --- src/streaming/controllers/CmcdController.js | 2 + .../streaming.controllers.CmcdController.js | 68 +++++-------------- .../streaming/streaming.models.CmcdModel.js | 11 +-- 3 files changed, 24 insertions(+), 57 deletions(-) diff --git a/src/streaming/controllers/CmcdController.js b/src/streaming/controllers/CmcdController.js index b968e601c4..c892fdd8b6 100644 --- a/src/streaming/controllers/CmcdController.js +++ b/src/streaming/controllers/CmcdController.js @@ -500,6 +500,7 @@ function CmcdController() { const requestType = commonMediaRequest.customData.request.type; if (!cmcdModel.isIncludedInRequestFilter(requestType)) { + commonMediaRequest.cmcd = commonMediaRequest.customData.request.cmcd; return commonMediaRequest; } @@ -511,6 +512,7 @@ function CmcdController() { ...commonMediaRequest, url: request.url, headers: request.headers, + cmcd: request.cmcd, customData: { ...commonMediaRequest.customData, cmcd: request.cmcd }, }; diff --git a/test/unit/test/streaming/streaming.controllers.CmcdController.js b/test/unit/test/streaming/streaming.controllers.CmcdController.js index e224fc0a32..1c09df5a24 100644 --- a/test/unit/test/streaming/streaming.controllers.CmcdController.js +++ b/test/unit/test/streaming/streaming.controllers.CmcdController.js @@ -309,6 +309,7 @@ describe('CmcdController', function () { const mockResponse = { status: 200, request: { + url: 'http://test.url/video.m4s', customData: { request: { type: HTTPRequest.MEDIA_SEGMENT_TYPE, @@ -663,6 +664,7 @@ describe('CmcdController', function () { const mockResponse = { status: 200, request: { + url: 'http://test.url/video.m4s', customData: { request: { type: HTTPRequest.MEDIA_SEGMENT_TYPE, @@ -673,6 +675,11 @@ describe('CmcdController', function () { } }, cmcd: { sid: 'session-id' }, + }, + resourceTiming: { + startTime: currentTime - 1000, + responseStart: currentTime - 500, + duration: 1000 } }; @@ -720,6 +727,7 @@ describe('CmcdController', function () { 'cmsd-dynamic': cmsdDynamicHeaderValue }, request: { + url: 'http://test.url/video.m4s', customData: { request: { type: HTTPRequest.MEDIA_SEGMENT_TYPE, @@ -761,6 +769,7 @@ describe('CmcdController', function () { const mockResponse = { status: 200, request: { + url: 'http://test.url/video.m4s', customData: { request: { type: HTTPRequest.MEDIA_SEGMENT_TYPE, @@ -811,58 +820,6 @@ describe('CmcdController', function () { expect(urlLoaderMock.load.called).to.be.false; }); - it('should send all available keys if enabledKeys is not defined', () => { - settings.update({ - streaming: { - cmcd: { - version: 2, - sid: 'session-id', - targets: [{ - url: 'https://cmcd.response.collector/api', - enabled: true, - includeOnRequests: ['segment'], - events: ['rr'], - timeInterval: 0 - }] - } - } - }); - cmcdController.initialize(); - - let currentTime = new Date(Date.now()); - const mockResponse = { - status: 200, - request: { - customData: { - request: { - type: HTTPRequest.MEDIA_SEGMENT_TYPE, - url: 'http://test.url/video.m4s', - startDate: currentTime - 1000, - firstByteDate: currentTime - 500, - endDate: new Date() - } - }, - cmcd: { sid: 'session-id' }, - } - }; - - const interceptor = cmcdController.getCmcdResponseInterceptors()[0]; - interceptor(mockResponse); - - expect(urlLoaderMock.load.calledOnce).to.be.true; - const requestSent = urlLoaderMock.load.firstCall.args[0].request; - expect(requestSent.url).to.equal('https://cmcd.response.collector/api'); - expect(requestSent.method).to.equal(HTTPRequest.POST); - - const metrics = decodeCmcd(decodeURIComponent(requestSent.body)); - expect(metrics).to.have.property('rc'); - expect(metrics).to.have.property('sid', 'session-id'); - expect(metrics).to.have.property('url', 'http://test.url/video.m4s'); - expect(metrics).to.have.property('ttfb'); - expect(metrics).to.have.property('ttlb'); - expect(metrics).to.have.property('v'); - }); - it('should send a response report with response mode available keys', () => { settings.update({ streaming: { @@ -885,6 +842,7 @@ describe('CmcdController', function () { const mockResponse = { status: 200, request: { + url: 'http://test.url/video.m4s', customData: { request: { type: HTTPRequest.MEDIA_SEGMENT_TYPE, @@ -895,6 +853,11 @@ describe('CmcdController', function () { } }, cmcd: { sid: 'session-id' }, + }, + resourceTiming: { + startTime: currentTime - 1000, + responseStart: currentTime - 500, + duration: 1000 } }; @@ -931,6 +894,7 @@ describe('CmcdController', function () { const mockResponse = { status: 200, request: { + url: 'http://test.url/video.m4s', customData: { request: { type: HTTPRequest.MEDIA_SEGMENT_TYPE, diff --git a/test/unit/test/streaming/streaming.models.CmcdModel.js b/test/unit/test/streaming/streaming.models.CmcdModel.js index 90847ffb81..a364fd251f 100644 --- a/test/unit/test/streaming/streaming.models.CmcdModel.js +++ b/test/unit/test/streaming/streaming.models.CmcdModel.js @@ -7,6 +7,7 @@ import DashMetricsMock from '../../mocks/DashMetricsMock.js'; import PlaybackControllerMock from '../../mocks/PlaybackControllerMock.js'; import ThroughputControllerMock from '../../mocks/ThroughputControllerMock.js'; import ServiceDescriptionControllerMock from '../../mocks/ServiceDescriptionControllerMock.js'; +import { SfItem } from '@svta/cml-structured-field-values'; import {expect} from 'chai'; import sinon from 'sinon'; @@ -100,7 +101,7 @@ describe('CmcdModel', function () { const data = cmcdModel.calculateCmcdDataForRequest(request); expect(data).to.exist; expect(data.ot).to.equal('v'); // video object type - expect(data.br).to.equal(1000); // bitrate in kbps + expect(data.br).to.deep.equal([new SfItem(1000, { v: true })]); // bitrate in kbps expect(data.d).to.equal(4000); // duration in ms }); @@ -172,7 +173,7 @@ describe('CmcdModel', function () { } } }); - + const isIncluded = cmcdModel.isIncludedInRequestFilter(HTTPRequest.MEDIA_SEGMENT_TYPE); expect(isIncluded).to.be.false; }); @@ -182,7 +183,7 @@ describe('CmcdModel', function () { it('should return MSD data when playback has started', function () { cmcdModel.onPlaybackStarted(); cmcdModel.onPlaybackPlaying(); - + const msdData = cmcdModel.calculateMsd(); expect(msdData).to.have.property('msd').that.is.a('number'); }); @@ -219,7 +220,7 @@ describe('CmcdModel', function () { cmcdModel.onPlaybackPlaying(); const data = cmcdModel.calculateCmcdDataForRequest(request); - expect(data.bsd).to.equal(500); + expect(data.bsd).to.deep.equal([new SfItem(500, { v: true })]); const data2 = cmcdModel.calculateCmcdDataForRequest(request); expect(data2.bsd).to.not.exist; @@ -270,4 +271,4 @@ describe('CmcdModel', function () { expect(result).to.deep.equal({}); }); }); -}); \ No newline at end of file +}); From ea7b76722dca6a8208353e12658897944f60bda9 Mon Sep 17 00:00:00 2001 From: Casey Occhialini <1508707+littlespex@users.noreply.github.com> Date: Mon, 9 Feb 2026 16:37:37 -0800 Subject: [PATCH 08/10] chore: update cml cmcd version --- package-lock.json | 8 ++++---- package.json | 2 +- src/streaming/controllers/CmcdController.js | 6 ++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index b456ac78d5..e1f48208b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "BSD-3-Clause", "dependencies": { "@svta/cml-608": "1.0.1", - "@svta/cml-cmcd": "2.1.0", + "@svta/cml-cmcd": "2.1.1", "@svta/cml-cmsd": "1.0.4", "@svta/cml-dash": "1.0.4", "@svta/cml-id3": "1.0.4", @@ -2548,9 +2548,9 @@ } }, "node_modules/@svta/cml-cmcd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@svta/cml-cmcd/-/cml-cmcd-2.1.0.tgz", - "integrity": "sha512-BX7E6AjzqJG6NLl0zGQOEWsgsodimU8wIQPtVivZoJYRDJ2HF6CdmkVNoTaQGeiu355TnGllpZf6e+VfzUxnKg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@svta/cml-cmcd/-/cml-cmcd-2.1.1.tgz", + "integrity": "sha512-cEkULGNbZ2QhOGBby9KwqkjdznJRhPXDEUZM8pmwEg33lKGM0bCCK8kBTpVjs5+jP+6M5g4ua5nrgYDOFYCR3w==", "license": "Apache-2.0", "engines": { "node": ">=20" diff --git a/package.json b/package.json index 5a7e1007a9..19e1bd3392 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ }, "dependencies": { "@svta/cml-608": "1.0.1", - "@svta/cml-cmcd": "2.1.0", + "@svta/cml-cmcd": "2.1.1", "@svta/cml-cmsd": "1.0.4", "@svta/cml-dash": "1.0.4", "@svta/cml-id3": "1.0.4", diff --git a/src/streaming/controllers/CmcdController.js b/src/streaming/controllers/CmcdController.js index c892fdd8b6..5b0ae25d9f 100644 --- a/src/streaming/controllers/CmcdController.js +++ b/src/streaming/controllers/CmcdController.js @@ -169,8 +169,7 @@ function CmcdController() { // Reset flag only after confirming we will rebuild reporterNeedsRebuild = false; - cmcdReporter.stop(); - cmcdReporter.flush(); + cmcdReporter.stop(true); cmcdReporter = _createCmcdReporter(); cmcdReporter.start(); } @@ -586,8 +585,7 @@ function CmcdController() { eventBus.off(MediaPlayerEvents.PLAYBACK_WAITING, _onPlaybackWaiting, instance); if (cmcdReporter) { - cmcdReporter.stop(); - cmcdReporter.flush(); + cmcdReporter.stop(true); cmcdReporter = null; } From 6eff5394756b4b71337c2b55f1e0e7bf46492cd3 Mon Sep 17 00:00:00 2001 From: cotid-qualabs Date: Tue, 10 Feb 2026 11:57:53 -0300 Subject: [PATCH 09/10] should not send report if events are undefined --- src/streaming/cmcd/config/CmcdPropertyMap.js | 9 ++------- .../streaming.controllers.CmcdController.js | 17 +++-------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/streaming/cmcd/config/CmcdPropertyMap.js b/src/streaming/cmcd/config/CmcdPropertyMap.js index 19cf3b3de7..ce6615b42c 100644 --- a/src/streaming/cmcd/config/CmcdPropertyMap.js +++ b/src/streaming/cmcd/config/CmcdPropertyMap.js @@ -329,13 +329,8 @@ const CmcdPropertyMap = { { path: 'settings.streaming.cmcd.targets[{targetIndex}].enabledKeys', priority: 1, - type: 'array' - }, - { - path: 'settings.streaming.cmcd.enabledKeys', - priority: 2, type: 'array', - default: Constants.CMCD_KEYS + default: [] } ] }, @@ -351,7 +346,7 @@ const CmcdPropertyMap = { path: 'settings.streaming.cmcd.targets[{targetIndex}].events', priority: 1, type: 'array', - default: Object.values(Constants.CMCD_REPORTING_EVENTS) + default: [] } ] }, diff --git a/test/unit/test/streaming/streaming.controllers.CmcdController.js b/test/unit/test/streaming/streaming.controllers.CmcdController.js index 1c09df5a24..421c93d8ee 100644 --- a/test/unit/test/streaming/streaming.controllers.CmcdController.js +++ b/test/unit/test/streaming/streaming.controllers.CmcdController.js @@ -104,7 +104,7 @@ describe('CmcdController', function () { expect(metrics).to.have.property('e', 'ps'); }); - it('should send all available keys and events if they are undefined', () => { + it('should not send any event if they are undefined', () => { settings.update({ streaming: { cmcd: { @@ -121,18 +121,7 @@ describe('CmcdController', function () { eventBus.trigger(MediaPlayerEvents.PLAYBACK_PLAYING); - expect(urlLoaderMock.load.calledOnce).to.be.true; - const requestSent = urlLoaderMock.load.firstCall.args[0].request; - expect(requestSent.url).to.equal('https://cmcd.event.collector/api'); - expect(requestSent.method).to.equal(HTTPRequest.POST); - expect(requestSent.body).to.be.a('string'); - - const metrics = decodeCmcd(decodeURIComponent(requestSent.body)); - expect(metrics).to.have.property('e', 'ps'); - expect(metrics).to.have.property('sta', 'p'); - expect(metrics).to.have.property('ts'); - expect(metrics).to.have.property('sid'); - expect(metrics).to.have.property('v'); + expect(urlLoaderMock.load.called).to.be.false; }); it('should send a report with event mode available keys', () => { @@ -288,7 +277,7 @@ describe('CmcdController', function () { expect(metrics2).to.have.property('sn', 1); }); - it('should send mandatory keys if enabled keys is empty', () => { + it('should send mandatory keys if enabled keys is not defined', () => { settings.update({ streaming: { cmcd: { From 78ae27e9a4630abb48a2b81274b42b9162e43e6a Mon Sep 17 00:00:00 2001 From: cotid-qualabs Date: Tue, 10 Feb 2026 12:06:47 -0300 Subject: [PATCH 10/10] fix unit tests --- .../test/streaming/streaming.controllers.CmcdController.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit/test/streaming/streaming.controllers.CmcdController.js b/test/unit/test/streaming/streaming.controllers.CmcdController.js index 421c93d8ee..c03ae76ae9 100644 --- a/test/unit/test/streaming/streaming.controllers.CmcdController.js +++ b/test/unit/test/streaming/streaming.controllers.CmcdController.js @@ -255,6 +255,7 @@ describe('CmcdController', function () { targets: [{ url: 'https://cmcd.event.collector/api', enabled: true, + enabledKeys: ['sn'], events: ['ps'] }] } @@ -548,6 +549,7 @@ describe('CmcdController', function () { targets: [{ url: 'https://cmcd.event.collector/api', enabled: true, + enabledKeys: ['ab', 'tab', 'lab'], events: ['ps'], timeInterval: 0 }] @@ -873,6 +875,7 @@ describe('CmcdController', function () { url: 'https://cmcd.response.collector/api', enabled: true, includeOnRequests: ['segment'], + enabledKeys: ['sn'], events: ['rr'] }] }