Skip to content

Commit ffe08a7

Browse files
JoaquinBChjmt-qualabssebastianpiq
authored
Feature: CMCD v2 LTC and MSD keys (Dash-Industry-Forum#4603)
* Add CMCDv2 key: live stream latency * Send ltc in every CMCD request * Implement CMCD MSD key * Implement custom headers for the cmcd v2 keys * Implement version setting for cmcd * Add cmcd version managment * Add cmcd v2 keys unit tests * Remove unnecessary blank spaces * Remove unnecessary blank spaces * Add missing semicolons * Feature/cmcd v2 fixes (Dash-Industry-Forum#28) fix: fixed autoplay mpd calculation and version moved to top level cmcd config * Revert "Feature/cmcd v2 fixes (Dash-Industry-Forum#28)" (Dash-Industry-Forum#29) This reverts commit fbb9b30. * fix: autoplay mpd calculation and version moved to top level cmcd config --------- Co-authored-by: Juan Manuel Tomás <[email protected]> Co-authored-by: Sebastian Piquerez <[email protected]> Co-authored-by: jmt-qualabs <[email protected]> Co-authored-by: Sebastian Piquerez <[email protected]>
1 parent 35699f3 commit ffe08a7

File tree

7 files changed

+215
-18
lines changed

7 files changed

+215
-18
lines changed

index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,8 @@ declare namespace dashjs {
12811281
rtpSafetyFactor?: number,
12821282
mode?: 'query' | 'header',
12831283
enabledKeys?: Array<string>,
1284-
includeInRequests?: Array<string>
1284+
includeInRequests?: Array<string>,
1285+
version?: number
12851286
},
12861287
cmsd?: {
12871288
enabled?: boolean,

src/core/Settings.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ import Events from './events/Events.js';
308308
* rtpSafetyFactor: 5,
309309
* mode: Constants.CMCD_MODE_QUERY,
310310
* enabledKeys: ['br', 'd', 'ot', 'tb' , 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su' , 'bs', 'rtp' , 'cid', 'pr', 'sf', 'sid', 'st', 'v']
311-
* includeInRequests: ['segment', 'mpd']
311+
* includeInRequests: ['segment', 'mpd'],
312+
* version: 1
312313
* },
313314
* cmsd: {
314315
* enabled: false,
@@ -882,6 +883,10 @@ import Events from './events/Events.js';
882883
* Specifies which HTTP GET requests shall carry parameters.
883884
*
884885
* If not specified this value defaults to ['segment'].
886+
* @property {number} [version=1]
887+
* The version of the CMCD to use.
888+
*
889+
* If not specified this value defaults to 1.
885890
*/
886891

887892
/**
@@ -1325,7 +1330,8 @@ function Settings() {
13251330
rtpSafetyFactor: 5,
13261331
mode: Constants.CMCD_MODE_QUERY,
13271332
enabledKeys: Constants.CMCD_AVAILABLE_KEYS,
1328-
includeInRequests: ['segment', 'mpd']
1333+
includeInRequests: ['segment', 'mpd'],
1334+
version: 1
13291335
},
13301336
cmsd: {
13311337
enabled: false,

src/streaming/MediaPlayer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2482,7 +2482,7 @@ function MediaPlayer() {
24822482
textController.initialize();
24832483
gapController.initialize();
24842484
catchupController.initialize();
2485-
cmcdModel.initialize();
2485+
cmcdModel.initialize(autoPlay);
24862486
cmsdModel.initialize();
24872487
contentSteeringController.initialize();
24882488
segmentBaseController.initialize();

src/streaming/constants/Constants.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,20 @@ export default {
216216
CMCD_MODE_HEADER: 'header',
217217

218218
/**
219-
* @constant {string} CMCD_AVAILABLE_KEYS specifies all the availables keys for CMCD metrics.
219+
* @constant {string} CMCD_AVAILABLE_KEYS specifies all the available keys for CMCD metrics.
220220
* @memberof Constants#
221221
* @static
222222
*/
223223
CMCD_AVAILABLE_KEYS: ['br', 'd', 'ot', 'tb', 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su', 'bs', 'rtp', 'cid', 'pr', 'sf', 'sid', 'st', 'v'],
224+
/**
225+
* @constant {string} CMCD_AVAILABLE_KEYS_V2 specifies all the available keys for CMCD version 2 metrics.
226+
* @memberof Constants#
227+
* @static
228+
*/
229+
CMCD_V2_AVAILABLE_KEYS: ['msd', 'ltc'],
224230

225231
/**
226-
* @constant {string} CMCD_AVAILABLE_REQUESTS specifies all the availables requests type for CMCD metrics.
232+
* @constant {string} CMCD_AVAILABLE_REQUESTS specifies all the available requests type for CMCD metrics.
227233
* @memberof Constants#
228234
* @static
229235
*/

src/streaming/models/CmcdModel.js

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ import {CmcdStreamType} from '@svta/common-media-library/cmcd/CmcdStreamType';
4444
import {CmcdStreamingFormat} from '@svta/common-media-library/cmcd/CmcdStreamingFormat';
4545
import {encodeCmcd} from '@svta/common-media-library/cmcd/encodeCmcd';
4646
import {toCmcdHeaders} from '@svta/common-media-library/cmcd/toCmcdHeaders';
47-
48-
const CMCD_VERSION = 1;
47+
import {CmcdHeaderField} from '@svta/common-media-library/cmcd/CmcdHeaderField';
48+
const DEFAULT_CMCD_VERSION = 1;
4949
const DEFAULT_INCLUDE_IN_REQUESTS = 'segment';
5050
const RTP_SAFETY_FACTOR = 5;
5151

@@ -64,7 +64,9 @@ function CmcdModel() {
6464
_lastMediaTypeRequest,
6565
_isStartup,
6666
_bufferLevelStarved,
67-
_initialMediaRequestsDone;
67+
_initialMediaRequestsDone,
68+
_playbackStartedTime,
69+
_msdSent;
6870

6971
let context = this.context;
7072
let eventBus = EventBus(context).getInstance();
@@ -77,12 +79,19 @@ function CmcdModel() {
7779
_resetInitialSettings();
7880
}
7981

80-
function initialize() {
82+
function initialize(autoPlay) {
8183
eventBus.on(MediaPlayerEvents.PLAYBACK_RATE_CHANGED, _onPlaybackRateChanged, instance);
8284
eventBus.on(MediaPlayerEvents.MANIFEST_LOADED, _onManifestLoaded, instance);
8385
eventBus.on(MediaPlayerEvents.BUFFER_LEVEL_STATE_CHANGED, _onBufferLevelStateChanged, instance);
8486
eventBus.on(MediaPlayerEvents.PLAYBACK_SEEKED, _onPlaybackSeeked, instance);
8587
eventBus.on(MediaPlayerEvents.PERIOD_SWITCH_COMPLETED, _onPeriodSwitchComplete, instance);
88+
if (autoPlay) {
89+
eventBus.on(MediaPlayerEvents.MANIFEST_LOADING_STARTED, _onPlaybackStarted, instance);
90+
}
91+
else {
92+
eventBus.on(MediaPlayerEvents.PLAYBACK_STARTED, _onPlaybackStarted, instance);
93+
}
94+
eventBus.on(MediaPlayerEvents.PLAYBACK_PLAYING, _onPlaybackPlaying, instance);
8695
}
8796

8897
function setConfig(config) {
@@ -124,13 +133,29 @@ function CmcdModel() {
124133
_isStartup = {};
125134
_initialMediaRequestsDone = {};
126135
_lastMediaTypeRequest = undefined;
136+
_playbackStartedTime = undefined;
137+
_msdSent = false;
127138
_updateStreamProcessors();
128139
}
129140

130141
function _onPeriodSwitchComplete() {
131142
_updateStreamProcessors();
132143
}
133144

145+
function _onPlaybackStarted() {
146+
if (!_playbackStartedTime) {
147+
_playbackStartedTime = Date.now();
148+
}
149+
}
150+
151+
function _onPlaybackPlaying() {
152+
if (!_playbackStartedTime || internalData.msd) {
153+
return;
154+
}
155+
156+
internalData.msd = Date.now() - _playbackStartedTime;
157+
}
158+
134159
function _updateStreamProcessors() {
135160
if (!playbackController) {
136161
return;
@@ -195,7 +220,8 @@ function CmcdModel() {
195220
if (isCmcdEnabled()) {
196221
const cmcdData = getCmcdData(request);
197222
const filteredCmcdData = _applyWhitelist(cmcdData);
198-
const headers = toCmcdHeaders(filteredCmcdData)
223+
const options = _createCmcdV2HeadersCustomMap();
224+
const headers = toCmcdHeaders(filteredCmcdData, options);
199225

200226
eventBus.trigger(MetricsReportingEvents.CMCD_DATA_GENERATED, {
201227
url: request.url,
@@ -219,8 +245,8 @@ function CmcdModel() {
219245

220246
function _canBeEnabled(cmcdParametersFromManifest) {
221247
if (Object.keys(cmcdParametersFromManifest).length) {
222-
if (!cmcdParametersFromManifest.version) {
223-
logger.error(`version parameter must be defined.`);
248+
if (parseInt(cmcdParametersFromManifest.version) !== 1) {
249+
logger.error(`version parameter must be defined in 1.`);
224250
return false;
225251
}
226252
if (!cmcdParametersFromManifest.keys) {
@@ -257,15 +283,17 @@ function CmcdModel() {
257283

258284
function _checkAvailableKeys(cmcdParametersFromManifest) {
259285
const defaultAvailableKeys = Constants.CMCD_AVAILABLE_KEYS;
286+
const defaultV2AvailableKeys = Constants.CMCD_V2_AVAILABLE_KEYS;
260287
const enabledCMCDKeys = cmcdParametersFromManifest.version ? cmcdParametersFromManifest.keys : settings.get().streaming.cmcd.enabledKeys;
261-
const invalidKeys = enabledCMCDKeys.filter(k => !defaultAvailableKeys.includes(k));
288+
const cmcdVersion = settings.get().streaming.cmcd.version;
289+
const invalidKeys = enabledCMCDKeys.filter(k => !defaultAvailableKeys.includes(k) && !(cmcdVersion === 2 && defaultV2AvailableKeys.includes(k)));
262290

263291
if (invalidKeys.length === enabledCMCDKeys.length && enabledCMCDKeys.length > 0) {
264-
logger.error(`None of the keys are implemented.`);
292+
logger.error(`None of the keys are implemented for CMCD version ${cmcdVersion}.`);
265293
return false;
266294
}
267295
invalidKeys.map((k) => {
268-
logger.warn(`key parameter ${k} is not implemented.`);
296+
logger.warn(`key parameter ${k} is not implemented for CMCD version ${cmcdVersion}.`);
269297
});
270298

271299
return true;
@@ -497,7 +525,7 @@ function CmcdModel() {
497525
let cid = settings.get().streaming.cmcd.cid ? settings.get().streaming.cmcd.cid : internalData.cid;
498526
cid = cmcdParametersFromManifest.contentID ? cmcdParametersFromManifest.contentID : cid;
499527

500-
data.v = CMCD_VERSION;
528+
data.v = settings.get().streaming.cmcd.version ?? DEFAULT_CMCD_VERSION;
501529

502530
data.sid = settings.get().streaming.cmcd.sid ? settings.get().streaming.cmcd.sid : internalData.sid;
503531
data.sid = cmcdParametersFromManifest.sessionID ? cmcdParametersFromManifest.sessionID : data.sid;
@@ -520,9 +548,33 @@ function CmcdModel() {
520548
data.sf = internalData.sf;
521549
}
522550

551+
if (data.v === 2) {
552+
let ltc = playbackController.getCurrentLiveLatency() * 1000;
553+
if (!isNaN(ltc)) {
554+
data.ltc = ltc;
555+
}
556+
const msd = internalData.msd;
557+
if (!_msdSent && !isNaN(msd)) {
558+
data.msd = msd;
559+
_msdSent = true;
560+
}
561+
}
562+
563+
564+
523565
return data;
524566
}
525567

568+
function _createCmcdV2HeadersCustomMap() {
569+
const cmcdVersion = settings.get().streaming.cmcd.version;
570+
return cmcdVersion === 1 ? {} : {
571+
customHeaderMap: {
572+
[CmcdHeaderField.REQUEST]: ['ltc'],
573+
[CmcdHeaderField.SESSION]: ['msd']
574+
}
575+
};
576+
}
577+
526578
function _getBitrateByRequest(request) {
527579
try {
528580
return parseInt(request.bandwidth / 1000);
@@ -658,7 +710,7 @@ function CmcdModel() {
658710
playbackRate = 1;
659711
}
660712
let { bandwidth, mediaType, representation, duration } = request;
661-
const mediaInfo = representation.mediaInfo
713+
const mediaInfo = representation.mediaInfo;
662714

663715
if (!mediaInfo) {
664716
return NaN;
@@ -688,6 +740,8 @@ function CmcdModel() {
688740
eventBus.off(MediaPlayerEvents.MANIFEST_LOADED, _onManifestLoaded, this);
689741
eventBus.off(MediaPlayerEvents.BUFFER_LEVEL_STATE_CHANGED, _onBufferLevelStateChanged, instance);
690742
eventBus.off(MediaPlayerEvents.PLAYBACK_SEEKED, _onPlaybackSeeked, instance);
743+
eventBus.off(MediaPlayerEvents.PLAYBACK_STARTED, _onPlaybackStarted, instance);
744+
eventBus.off(MediaPlayerEvents.PLAYBACK_PLAYING, _onPlaybackPlaying, instance);
691745

692746
_resetInitialSettings();
693747
}

test/unit/mocks/PlaybackControllerMock.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ class PlaybackControllerMock {
133133
this.lowLatencyEnabled = value;
134134
}
135135

136+
getCurrentLiveLatency() {
137+
return 15;
138+
}
139+
136140
}
137141

138142

test/unit/test/streaming/streaming.models.CmcdModel.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,70 @@ describe('CmcdModel', function () {
715715
});
716716
});
717717

718+
719+
describe('getHeadersParameters() return CMCD v2 data correctly', () => {
720+
it('getHeadersParameters() should return cmcd v2 data if version is 2', function () {
721+
const REQUEST_TYPE = HTTPRequest.MEDIA_SEGMENT_TYPE;
722+
const MEDIA_TYPE = 'video';
723+
724+
let request = {
725+
type: REQUEST_TYPE,
726+
mediaType: MEDIA_TYPE
727+
};
728+
729+
settings.update({
730+
streaming: {
731+
cmcd: {
732+
version: 2,
733+
enabledKeys: ['br', 'd', 'ot', 'tb', 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su', 'bs', 'rtp', 'cid', 'pr', 'sf', 'sid', 'st', 'v', 'msd', 'ltc', 'msd', 'ltc'],
734+
}
735+
}
736+
});
737+
738+
let headers = cmcdModel.getHeaderParameters(request);
739+
let metrics = decodeCmcd(headers[REQUEST_HEADER_NAME]);
740+
expect(metrics).to.have.property('ltc');
741+
742+
eventBus.trigger(MediaPlayerEvents.PLAYBACK_STARTED);
743+
eventBus.trigger(MediaPlayerEvents.PLAYBACK_PLAYING);
744+
745+
headers = cmcdModel.getHeaderParameters(request);
746+
metrics = decodeCmcd(headers[SESSION_HEADER_NAME]);
747+
expect(metrics).to.have.property('msd');
748+
});
749+
750+
it('getHeadersParameters() should not return cmcd v2 data if the cmcd version is 1', function () {
751+
const REQUEST_TYPE = HTTPRequest.MEDIA_SEGMENT_TYPE;
752+
const MEDIA_TYPE = 'video';
753+
754+
let request = {
755+
type: REQUEST_TYPE,
756+
mediaType: MEDIA_TYPE
757+
};
758+
759+
settings.update({
760+
streaming: {
761+
cmcd: {
762+
enabled: true,
763+
version: 1
764+
},
765+
enabledKeys: ['br', 'd', 'ot', 'tb', 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su', 'bs', 'rtp', 'cid', 'pr', 'sf', 'sid', 'st', 'v', 'msd', 'ltc', 'msd', 'ltc'],
766+
}
767+
});
768+
769+
let headers = cmcdModel.getHeaderParameters(request);
770+
let metrics = decodeCmcd(headers[REQUEST_HEADER_NAME]);
771+
expect(metrics).to.not.have.property('ltc');
772+
773+
eventBus.trigger(MediaPlayerEvents.PLAYBACK_STARTED);
774+
eventBus.trigger(MediaPlayerEvents.PLAYBACK_PLAYING);
775+
776+
headers = cmcdModel.getHeaderParameters(request);
777+
metrics = decodeCmcd(headers[REQUEST_HEADER_NAME]);
778+
expect(metrics).to.not.have.property('msd');
779+
});
780+
});
781+
718782
})
719783
})
720784

@@ -1396,6 +1460,68 @@ describe('CmcdModel', function () {
13961460
});
13971461
});
13981462

1463+
describe('getQueryParameter() return CMCD v2 data correctly', () => {
1464+
it('getQueryParameter() should return cmcd v2 data if the cmcd version is 2', function () {
1465+
const REQUEST_TYPE = HTTPRequest.MEDIA_SEGMENT_TYPE;
1466+
const MEDIA_TYPE = 'video';
1467+
1468+
let request = {
1469+
type: REQUEST_TYPE,
1470+
mediaType: MEDIA_TYPE
1471+
};
1472+
1473+
settings.update({
1474+
streaming: {
1475+
cmcd: {
1476+
version: 2,
1477+
enabledKeys: ['br', 'd', 'ot', 'tb', 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su', 'bs', 'rtp', 'cid', 'pr', 'sf', 'sid', 'st', 'v', 'msd', 'ltc', 'msd', 'ltc'],
1478+
}
1479+
}
1480+
});
1481+
let parameters = cmcdModel.getQueryParameter(request);
1482+
let metrics = decodeCmcd(parameters.value);
1483+
expect(metrics).to.have.property('ltc');
1484+
1485+
eventBus.trigger(MediaPlayerEvents.PLAYBACK_STARTED);
1486+
eventBus.trigger(MediaPlayerEvents.PLAYBACK_PLAYING);
1487+
1488+
parameters = cmcdModel.getQueryParameter(request);
1489+
metrics = decodeCmcd(parameters.value);
1490+
expect(metrics).to.have.property('msd');
1491+
});
1492+
1493+
it('getQueryParameter() sould not return cmcd v2 data if the cmcd version is 1', function () {
1494+
const REQUEST_TYPE = HTTPRequest.MEDIA_SEGMENT_TYPE;
1495+
const MEDIA_TYPE = 'video';
1496+
1497+
let request = {
1498+
type: REQUEST_TYPE,
1499+
mediaType: MEDIA_TYPE
1500+
};
1501+
1502+
settings.update({
1503+
streaming: {
1504+
cmcd: {
1505+
enabled: true,
1506+
version: 1,
1507+
enabledKeys: ['br', 'd', 'ot', 'tb', 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su', 'bs', 'rtp', 'cid', 'pr', 'sf', 'sid', 'st', 'v', 'msd', 'ltc', 'msd', 'ltc'],
1508+
}
1509+
}
1510+
});
1511+
1512+
let parameters = cmcdModel.getQueryParameter(request);
1513+
let metrics = decodeCmcd(parameters.value);
1514+
expect(metrics).to.not.have.property('ltc');
1515+
1516+
eventBus.trigger(MediaPlayerEvents.PLAYBACK_STARTED);
1517+
eventBus.trigger(MediaPlayerEvents.PLAYBACK_PLAYING);
1518+
1519+
parameters = cmcdModel.getQueryParameter(request);
1520+
metrics = decodeCmcd(parameters.value);
1521+
expect(metrics).to.not.have.property('msd');
1522+
});
1523+
});
1524+
13991525
describe('applyParametersFromMpd', () => {
14001526
it('should ignore service description cmcd configuration when applyParametersFromMpd is false', function () {
14011527
const REQUEST_TYPE = HTTPRequest.MEDIA_SEGMENT_TYPE;

0 commit comments

Comments
 (0)