diff --git a/samples/advanced/cmcd-v2-network-interceptors.html b/samples/advanced/cmcd-v2-network-interceptors.html index 93acf29972..40869a2b9c 100644 --- a/samples/advanced/cmcd-v2-network-interceptors.html +++ b/samples/advanced/cmcd-v2-network-interceptors.html @@ -80,12 +80,11 @@ includeInRequests: ['segment', 'mpd'], targets: [ { - cmcdMode: 'response', enabled: true, - url: 'http://localhost:3003/response-mode', - enabledKeys: ['ot', 'rc', 'msd'], + url: 'http://localhost:3003/report', + enabledKeys: ['e', 'rc', 'url'], mode: CMCD_MODE_QUERY, - includeOnRequests: ['mpd', 'segment'], + events: ['rr'] }, ] } @@ -97,7 +96,7 @@ /* Callback before report */ player.addRequestInterceptor((request) => { - if (request.customData.request.type == 'CmcdResponse' && request.url.includes('http://localhost:3003/response-mode')) { + if (request.customData.request.type == 'CmcdEvent' && request.url.includes('rr') && request.url.includes('http://localhost:3003/report')) { let customKey = 'synchronization-leader-sid'; let customKeyValue = '123' let { cmcd } = request; @@ -115,7 +114,7 @@ /* Callback after server response */ player.addResponseInterceptor((response) => { request = response.request.customData.request; - if (request.type == 'CmcdResponse' && request.url.includes('http://localhost:3003/response-mode')) { + if (request.type == 'CmcdEvent' && request.url.includes('rr') && request.url.includes('http://localhost:3003/report')) { console.log(request.cmcd); } return Promise.resolve(response); @@ -123,7 +122,7 @@ const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, ...rest) { - if (url.includes('response-mode')) { + if (url.includes('report')) { const queryString = decodeURIComponent(url.split('CMCD=')[1]); const data = getKeysForQueryMode(queryString); var keys = Object.keys(data); diff --git a/samples/advanced/cmcd-v2.html b/samples/advanced/cmcd-v2.html index a3797c5fa1..5a9cf1f03f 100644 --- a/samples/advanced/cmcd-v2.html +++ b/samples/advanced/cmcd-v2.html @@ -55,16 +55,15 @@ enabledKeys: ['br', 'd', 'ot', 'tb' , 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su' , 'bs', 'rtp' , 'cid', 'pr', 'sf', 'sid', 'st', 'v', 'msd'], targets: [ { - cmcdMode: 'response', enabled: true, - url: 'http://localhost:3001/cmcd/response-mode', - enabledKeys: ['ot', 'rc', 'msd'], + url: 'http://localhost:3001/cmcd/response-received', + enabledKeys: ['url', 'rc', 'msd'], includeOnRequests: ['mpd', 'segment'], + events: ['rr'], mode: CMCD_MODE_QUERY, }, { enabled: true, - cmcdMode: 'event', url: 'http://localhost:3002/cmcd/event-mode', timeInterval: 10, enabledKeys: ['e', 'msd', 'sta'], @@ -73,21 +72,18 @@ }, { enabled: true, - cmcdMode: 'event', url: 'http://localhost:3003/cmcd/event-mode', timeInterval: 6, mode: CMCD_MODE_HEADER, }, { enabled: true, - cmcdMode: 'response', - url: 'http://localhost:3004/cmcd/response-body-mode', + url: 'http://localhost:3004/cmcd/event-body-mode', mode: CMCD_MODE_BODY, batchSize: 3, }, { enabled: true, - cmcdMode: 'event', url: 'http://localhost:3005/cmcd/event-body-mode', mode: CMCD_MODE_BODY, batchTimer: 3, diff --git a/src/core/Settings.js b/src/core/Settings.js index 6a2a4c63d3..278b76de16 100644 --- a/src/core/Settings.js +++ b/src/core/Settings.js @@ -1410,7 +1410,7 @@ function Settings() { rtp: null, rtpSafetyFactor: 5, mode: Constants.CMCD_MODE_QUERY, - enabledKeys: null, + enabledKeys: Constants.CMCD_KEYS, includeInRequests: ['segment', 'mpd'], version: 1, targets: [] diff --git a/src/streaming/constants/Constants.js b/src/streaming/constants/Constants.js index 935269530c..ac7a5cf1ec 100644 --- a/src/streaming/constants/Constants.js +++ b/src/streaming/constants/Constants.js @@ -34,6 +34,7 @@ import { CmcdEventType } from '@svta/common-media-library/cmcd/CmcdEventType'; import { CMCD_DEFAULT_TIME_INTERVAL } from '@svta/common-media-library/cmcd/CMCD_DEFAULT_TIME_INTERVAL'; import { CMCD_PARAM } from '@svta/common-media-library/cmcd/CMCD_PARAM'; import { CMCD_QUERY } from '@svta/common-media-library/cmcd/CMCD_QUERY'; +import { CMCD_KEYS } from '@svta/common-media-library/cmcd/CMCD_KEYS'; /** * Constants declaration @@ -247,6 +248,13 @@ export default { */ CMCD_REPORTING_MODE: CmcdReportingMode, + /** + * @constant {string} CMCD_KEYS specifies all the available keys for CMCD. + * @memberof Constants# + * @static + */ + CMCD_KEYS: CMCD_KEYS, + /** * @constant {string} CMCD_REPORTING_EVENTS specifies all the available events for CMCD event mode. * @memberof Constants# diff --git a/src/streaming/controllers/CmcdBatchController.js b/src/streaming/controllers/CmcdBatchController.js index a2cb4f4a02..13e729665a 100644 --- a/src/streaming/controllers/CmcdBatchController.js +++ b/src/streaming/controllers/CmcdBatchController.js @@ -99,12 +99,7 @@ function CmcdBatchController() { httpRequest.method = HTTPRequest.POST; httpRequest.body = cmcdData; httpRequest.headers = Constants.CMCD_CONTENT_TYPE_HEADER - - if (target.cmcdMode === Constants.CMCD_REPORTING_MODE.EVENT) { - httpRequest.type = HTTPRequest.CMCD_EVENT; - } else if (target.cmcdMode === Constants.CMCD_REPORTING_MODE.RESPONSE) { - httpRequest.type = HTTPRequest.CMCD_RESPONSE; - } + httpRequest.type = HTTPRequest.CMCD_EVENT; _sendBatchReport(httpRequest) .then((response) => { diff --git a/src/streaming/controllers/CmcdController.js b/src/streaming/controllers/CmcdController.js index 29af4495ce..d2c57ed82c 100644 --- a/src/streaming/controllers/CmcdController.js +++ b/src/streaming/controllers/CmcdController.js @@ -152,8 +152,7 @@ function CmcdController() { function _initializeEventModeTimeInterval() { const targets = settings.get().streaming.cmcd.targets; - const eventModeTargets = targets.filter((target) => target.cmcdMode === Constants.CMCD_REPORTING_MODE.EVENT); - eventModeTargets.forEach(({ timeInterval, events }) => { + targets.forEach(({ timeInterval, events }) => { if (!events || !events.includes(Constants.CMCD_REPORTING_EVENTS.TIME_INTERVAL)) { return; } @@ -176,9 +175,9 @@ function CmcdController() { _onEventChange(Constants.CMCD_REPORTING_EVENTS.PLAY_STATE); } - function _onEventChange(state){ + function _onEventChange(state, response){ cmcdModel.onEventChange(state); - triggerCmcdEventMode(state); + triggerCmcdEventMode(state, response); } function _onPeriodSwitchComplete() { @@ -228,47 +227,54 @@ function CmcdController() { } } - function triggerCmcdEventMode(event){ + function triggerCmcdEventMode(event, response){ const targets = settings.get().streaming.cmcd.targets; - const eventModeTargets = targets.filter((target) => target.cmcdMode === Constants.CMCD_REPORTING_MODE.EVENT); - if (eventModeTargets.length === 0) { + if (targets.length === 0) { return; } - const cmcdData = cmcdModel.triggerCmcdEventMode(event); + let cmcdData = cmcdModel.triggerCmcdEventMode(event); + if (event === Constants.CMCD_REPORTING_EVENTS.RESPONSE_RECEIVED) { + cmcdData = {...cmcdData, ...response.request.cmcd} + cmcdData = _addCmcdResponseReceivedData(response, cmcdData); + } - eventModeTargets.forEach(targetSettings => { - if (targetSettings.enabled) { + targets.forEach(targetSettings => { + if (!isCmcdEnabled(targetSettings)){ + return; + } - if (targetSettings.events?.length === 0) { - logger.warn('CMCD Event Mode is enabled, but the "events" setting is empty. No event-specific CMCD data will be sent.'); - } + const requestType = response?.request.customData.request.type; + if (requestType && !cmcdModel.isIncludedInRequestFilter(requestType, targetSettings.includeOnRequests)){ + return; + } - let events = targetSettings.events ? targetSettings.events : Object.values(Constants.CMCD_REPORTING_EVENTS); + if (targetSettings.events?.length === 0) { + logger.warn('CMCD Event Mode is enabled, but the "events" setting is empty. No event-specific CMCD data will be sent.'); + } - if (!events.includes(event)) { - return; - } + let events = targetSettings.events ? targetSettings.events : Object.values(Constants.CMCD_REPORTING_EVENTS); - let httpRequest = new CmcdReportRequest(); + if (!events.includes(event)) { + return; + } - httpRequest.url = targetSettings.url; - httpRequest.type = HTTPRequest.CMCD_EVENT; - httpRequest.method = HTTPRequest.GET; + let httpRequest = new CmcdReportRequest(); - const sequenceNumber = _getNextSequenceNumber(targetSettings); - let cmcd = {...cmcdData, sn: sequenceNumber} - httpRequest.cmcd = cmcd; + httpRequest.url = targetSettings.url; + httpRequest.type = HTTPRequest.CMCD_EVENT; + httpRequest.method = HTTPRequest.GET; - if (isCmcdEnabled(targetSettings)) { - _updateRequestWithCmcd(httpRequest, cmcd, targetSettings) - if ((targetSettings.batchSize || targetSettings.batchTimer) && httpRequest.body){ - cmcdBatchController.addReport(targetSettings, httpRequest.body) - } else { - _sendCmcdDataReport(httpRequest); - } - } + const sequenceNumber = _getNextSequenceNumber(targetSettings); + let cmcd = {...cmcdData, sn: sequenceNumber} + httpRequest.cmcd = cmcd; + + _updateRequestWithCmcd(httpRequest, cmcd, targetSettings) + if ((targetSettings.batchSize || targetSettings.batchTimer) && httpRequest.body){ + cmcdBatchController.addReport(targetSettings, httpRequest.body) + } else { + _sendCmcdDataReport(httpRequest); } }); } @@ -299,8 +305,8 @@ function CmcdController() { if (isIncludedFilters) { const cmcdParameters = cmcdModel.getCmcdParametersFromManifest(); const cmcdModeSetting = targetSettings ? targetSettings.mode : settings.get().streaming.cmcd.mode; - const cmcdMode = cmcdParameters.mode ? cmcdParameters.mode : cmcdModeSetting; - switch (cmcdMode) { + const mode = cmcdParameters.mode ? cmcdParameters.mode : cmcdModeSetting; + switch (mode) { case Constants.CMCD_MODE_QUERY: request.url = Utils.removeQueryParameterFromUrl(request.url, Constants.CMCD_QUERY_KEY); const additionalQueryParameter = _getAdditionalQueryParameter(request, cmcdData, targetSettings); @@ -311,7 +317,7 @@ function CmcdController() { request.headers = Object.assign(request.headers, getHeaderParameters(request, cmcdData, targetSettings)); break; case Constants.CMCD_MODE_BODY: - if (request.type === HTTPRequest.CMCD_RESPONSE || request.type === HTTPRequest.CMCD_EVENT) { + if (request.type === HTTPRequest.CMCD_EVENT) { request.body = getJsonParameters(request, cmcdData, targetSettings); request.method = HTTPRequest.POST; request.headers = request.headers || {}; @@ -480,7 +486,7 @@ function CmcdController() { (cmcdParametersFromManifest.version ? cmcdParametersFromManifest.keys : settings.get().streaming.cmcd.enabledKeys); return { - reportingMode: targetSettings?.cmcdMode, + reportingMode: targetSettings ? Constants.CMCD_REPORTING_MODE.EVENT : Constants.CMCD_REPORTING_MODE.REQUEST, version: settings.get().streaming.cmcd.version ?? Constants.CMCD_DEFAULT_VERSION, filter: enabledKeys ? (key) => enabledKeys.includes(key) : undefined, } @@ -558,86 +564,56 @@ function CmcdController() { } function getCmcdResponseInterceptors(){ - return [_cmcdResponseModeInterceptor]; + return [_cmcdResponseReceivedInterceptor]; } - function _cmcdResponseModeInterceptor(response){ - const requestType = response.request.customData.request.type; - - let cmcdData = { - ...response.request.cmcd, - }; - - cmcdData = _addCmcdResponseModeData(response, cmcdData); - const targets = settings.get().streaming.cmcd.targets; - const responseModeTargets = targets.filter((target) => target.cmcdMode === Constants.CMCD_REPORTING_MODE.RESPONSE); - responseModeTargets.forEach(targetSettings => { - if (targetSettings.enabled && cmcdModel.isIncludedInRequestFilter(requestType, targetSettings.includeOnRequests)){ - let httpRequest = new CmcdReportRequest(); - httpRequest.url = targetSettings.url; - httpRequest.type = HTTPRequest.CMCD_RESPONSE; - httpRequest.method = HTTPRequest.GET; - - const sequenceNumber = _getNextSequenceNumber(targetSettings); - let cmcd = {...cmcdData, sn: sequenceNumber} - httpRequest.cmcd = cmcd; - - if (isCmcdEnabled(targetSettings)) { - _updateRequestWithCmcd(httpRequest, cmcd, targetSettings) - if ((targetSettings.batchSize || targetSettings.batchTimer) && httpRequest.body){ - cmcdBatchController.addReport(targetSettings, httpRequest.body) - } else { - _sendCmcdDataReport(httpRequest); - } - } - } - }); - + function _cmcdResponseReceivedInterceptor(response){ + _onEventChange(Constants.CMCD_REPORTING_EVENTS.RESPONSE_RECEIVED, response) return response; } - function _addCmcdResponseModeData(response, cmcdData){ - const responseModeData = {}; + function _addCmcdResponseReceivedData(response, cmcdData){ + const responseData = {}; const request = response.request.customData.request; const requestType = request.type; if (requestType === HTTPRequest.MEDIA_SEGMENT_TYPE){ - responseModeData.rc = response.status; + responseData.rc = response.status; } if (request.startDate && request.firstByteDate){ - responseModeData.ttfb = request.firstByteDate - request.startDate; + responseData.ttfb = request.firstByteDate - request.startDate; } if (request.endDate && request.startDate){ - responseModeData.ttlb = request.endDate - request.startDate + responseData.ttlb = request.endDate - request.startDate } if (request.url) { - responseModeData.url = request.url.split('?')[0] + responseData.url = request.url.split('?')[0] } if (response.headers){ try { const cmsdStaticHeader = response.headers['cmsd-static']; if (cmsdStaticHeader) { - responseModeData.cmsds = btoa(cmsdStaticHeader); + responseData.cmsds = btoa(cmsdStaticHeader); } const cmsdDynamicHeader = response.headers['cmsd-dynamic']; if (cmsdDynamicHeader) { - responseModeData.cmsdd = btoa(cmsdDynamicHeader); + responseData.cmsdd = btoa(cmsdDynamicHeader); } } catch (e) { logger.warn('Failed to base64 encode CMSD headers, ignoring.', e); } } - return {...cmcdData, ...responseModeData}; + return {...cmcdData, ...responseData}; } function _getTargetKey(target) { - return `${target.url}_${target.cmcdMode}_${target.mode}`; + return `${target.url}_${target.mode}`; } function _getNextSequenceNumber(target) { diff --git a/src/streaming/vo/metrics/HTTPRequest.js b/src/streaming/vo/metrics/HTTPRequest.js index 94bd8afe63..17bb754f95 100644 --- a/src/streaming/vo/metrics/HTTPRequest.js +++ b/src/streaming/vo/metrics/HTTPRequest.js @@ -180,7 +180,6 @@ HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE = 'FragmentInfoSegment'; HTTPRequest.DVB_REPORTING_TYPE = 'DVBReporting'; HTTPRequest.LICENSE = 'license'; HTTPRequest.CONTENT_STEERING_TYPE = 'ContentSteering'; -HTTPRequest.CMCD_RESPONSE = 'CmcdResponse'; HTTPRequest.CMCD_EVENT = 'CmcdEvent'; HTTPRequest.OTHER_TYPE = 'other'; diff --git a/test/unit/test/streaming/streaming.controllers.CmcdBatchController.js b/test/unit/test/streaming/streaming.controllers.CmcdBatchController.js index f9f869b950..d27854c8c0 100644 --- a/test/unit/test/streaming/streaming.controllers.CmcdBatchController.js +++ b/test/unit/test/streaming/streaming.controllers.CmcdBatchController.js @@ -48,7 +48,7 @@ describe('CmcdBatchController', function () { const target = { url: 'http://test.com/report', batchSize: 2, - cmcdMode: Constants.CMCD_REPORTING_MODE.RESPONSE + cmcdMode: Constants.CMCD_REPORTING_MODE.EVENT }; const cmcdData1 = 'ot%3Dm%2Csid%3D%session1'; const cmcdData2 = 'ot%3Da%2Csid%3D%session2'; @@ -62,7 +62,7 @@ describe('CmcdBatchController', function () { expect(request.url).to.equal(target.url); expect(request.method).to.equal(HTTPRequest.POST); expect(request.body).to.equal(cmcdData1 + '\n' + cmcdData2); - expect(request.type).to.equal(HTTPRequest.CMCD_RESPONSE); + expect(request.type).to.equal(HTTPRequest.CMCD_EVENT); }); }); @@ -178,7 +178,7 @@ describe('CmcdBatchController', function () { const target1 = { url: 'http://test.com/report', batchSize: 2, - cmcdMode: Constants.CMCD_REPORTING_MODE.RESPONSE + cmcdMode: Constants.CMCD_REPORTING_MODE.EVENT }; const target2 = { @@ -207,7 +207,7 @@ describe('CmcdBatchController', function () { expect(urlLoaderMock.load.calledTwice).to.be.true; const secondRequest = urlLoaderMock.load.getCall(1).args[0].request; - expect(secondRequest.type).to.equal(HTTPRequest.CMCD_RESPONSE); + expect(secondRequest.type).to.equal(HTTPRequest.CMCD_EVENT); expect(secondRequest.body).to.equal(cmcdData1 + '\n' + cmcdData3); }); }); diff --git a/test/unit/test/streaming/streaming.controllers.CmcdController.js b/test/unit/test/streaming/streaming.controllers.CmcdController.js index 166436440d..6b5bee851e 100644 --- a/test/unit/test/streaming/streaming.controllers.CmcdController.js +++ b/test/unit/test/streaming/streaming.controllers.CmcdController.js @@ -1909,6 +1909,46 @@ describe('CmcdController', function () { const metrics2 = decodeCmcd(cmcdString2); expect(metrics2).to.have.property('sn', 2); }); + + it('should send mandatory keys if enabled keys is empty', () => { + settings.update({ + streaming: { + cmcd: { + version: 2, + targets: [{ + url: 'https://cmcd.event.collector/api', + enabled: true, + mode: 'query', + enabledKeys: [], + }] + } + } + }); + + const mockResponse = { + status: 200, + request: { + customData: { + request: { + type: HTTPRequest.MEDIA_SEGMENT_TYPE, + url: 'http://test.url/video.m4s' + } + }, + cmcd: { sid: 'session-id' } + } + }; + + const interceptor = cmcdController.getCmcdResponseInterceptors()[0]; + interceptor(mockResponse); + + expect(urlLoaderMock.load.called).to.be.true; + const requestSent = urlLoaderMock.load.firstCall.args[0].request; + const url = new URL(requestSent.url); + const cmcdString = url.searchParams.get('CMCD'); + const metrics = decodeCmcd(cmcdString); + expect(metrics).to.have.property('ts'); + expect(metrics).to.have.property('v'); + }); }); describe('Event Mode player state events', () => { @@ -2199,10 +2239,10 @@ describe('CmcdController', function () { targets: [{ url: 'https://cmcd.response.collector/api', enabled: true, - cmcdMode: 'response', mode: 'query', includeOnRequests: ['segment'], - enabledKeys: ['rc', 'ttfb', 'ttlb', 'url', 'sid'] + enabledKeys: ['rc', 'ttfb', 'ttlb', 'url', 'sid'], + events: ['rr'] }] } } @@ -2250,10 +2290,10 @@ describe('CmcdController', function () { targets: [{ url: 'https://cmcd.response.collector/api', enabled: true, - cmcdMode: 'response', mode: 'query', includeOnRequests: ['segment'], - enabledKeys: ['cmsdd', 'cmsds'] + enabledKeys: ['cmsdd', 'cmsds'], + events: ['rr'] }] } } @@ -2292,42 +2332,6 @@ describe('CmcdController', function () { expect(metrics).to.have.property('cmsdd', btoa(cmsdDynamicHeaderValue)); }); - it('should not send a report if enabled keys is empty', () => { - settings.update({ - streaming: { - cmcd: { - version: 2, - targets: [{ - url: 'https://cmcd.response.collector/api', - enabled: true, - cmcdMode: 'response', - mode: 'query', - enabledKeys: [], - includeOnRequests: ['mpd'] - }] - } - } - }); - - const mockResponse = { - status: 200, - request: { - customData: { - request: { - type: HTTPRequest.MEDIA_SEGMENT_TYPE, - url: 'http://test.url/video.m4s' - } - }, - cmcd: { sid: 'session-id' } - } - }; - - const interceptor = cmcdController.getCmcdResponseInterceptors()[0]; - interceptor(mockResponse); - - expect(urlLoaderMock.load.called).to.be.false; - }); - it('should not send a report if the target is disabled', () => { settings.update({ streaming: { @@ -2336,9 +2340,9 @@ describe('CmcdController', function () { targets: [{ url: 'https://cmcd.response.collector/api', enabled: false, - cmcdMode: 'response', mode: 'query', - includeOnRequests: ['segment'] + includeOnRequests: ['segment'], + events: ['rr'] }] } } @@ -2371,9 +2375,9 @@ describe('CmcdController', function () { targets: [{ url: 'https://cmcd.response.collector/api', enabled: true, - cmcdMode: 'response', mode: 'query', - includeOnRequests: ['segment'] + includeOnRequests: ['segment'], + events: ['rr'] }] } } @@ -2406,10 +2410,10 @@ describe('CmcdController', function () { targets: [{ url: 'https://cmcd.response.collector/api', enabled: true, - cmcdMode: 'response', mode: 'header', includeOnRequests: ['segment'], - enabledKeys: ['rc', 'sid'] + enabledKeys: ['rc', 'sid'], + events: ['rr'] }] } } @@ -2452,9 +2456,9 @@ describe('CmcdController', function () { targets: [{ url: 'https://cmcd.response.collector/api', enabled: true, - cmcdMode: 'response', mode: 'query', includeOnRequests: ['segment'], + events: ['rr'] }] } } @@ -2503,10 +2507,10 @@ describe('CmcdController', function () { targets: [{ url: 'https://cmcd.response.collector/api', enabled: true, - cmcdMode: 'response', mode: 'query', includeOnRequests: ['segment'], - enabledKeys: ['rc', 'e'] + enabledKeys: ['rc', 'e', 'd'], + events: ['rr'] }] } } @@ -2540,7 +2544,8 @@ describe('CmcdController', function () { const cmcdString = url.searchParams.get('CMCD'); const metrics = decodeCmcd(cmcdString); expect(metrics).to.have.property('rc'); - expect(metrics).to.not.have.property('e'); + expect(metrics).to.have.property('e'); + expect(metrics).to.not.have.property('d'); }); it('should increment the sn key for each response report', () => { @@ -2551,9 +2556,9 @@ describe('CmcdController', function () { targets: [{ url: 'https://cmcd.response.collector/api', enabled: true, - cmcdMode: 'response', mode: 'query', - includeOnRequests: ['segment'] + includeOnRequests: ['segment'], + events: ['rr'] }] } }