Skip to content

Commit

Permalink
Feature: CMCD v2 LTC and MSD keys (Dash-Industry-Forum#4603)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
5 people authored Nov 14, 2024
1 parent 35699f3 commit ffe08a7
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 18 deletions.
3 changes: 2 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,8 @@ declare namespace dashjs {
rtpSafetyFactor?: number,
mode?: 'query' | 'header',
enabledKeys?: Array<string>,
includeInRequests?: Array<string>
includeInRequests?: Array<string>,
version?: number
},
cmsd?: {
enabled?: boolean,
Expand Down
10 changes: 8 additions & 2 deletions src/core/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,8 @@ import Events from './events/Events.js';
* rtpSafetyFactor: 5,
* mode: Constants.CMCD_MODE_QUERY,
* enabledKeys: ['br', 'd', 'ot', 'tb' , 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su' , 'bs', 'rtp' , 'cid', 'pr', 'sf', 'sid', 'st', 'v']
* includeInRequests: ['segment', 'mpd']
* includeInRequests: ['segment', 'mpd'],
* version: 1
* },
* cmsd: {
* enabled: false,
Expand Down Expand Up @@ -882,6 +883,10 @@ import Events from './events/Events.js';
* Specifies which HTTP GET requests shall carry parameters.
*
* If not specified this value defaults to ['segment'].
* @property {number} [version=1]
* The version of the CMCD to use.
*
* If not specified this value defaults to 1.
*/

/**
Expand Down Expand Up @@ -1325,7 +1330,8 @@ function Settings() {
rtpSafetyFactor: 5,
mode: Constants.CMCD_MODE_QUERY,
enabledKeys: Constants.CMCD_AVAILABLE_KEYS,
includeInRequests: ['segment', 'mpd']
includeInRequests: ['segment', 'mpd'],
version: 1
},
cmsd: {
enabled: false,
Expand Down
2 changes: 1 addition & 1 deletion src/streaming/MediaPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2482,7 +2482,7 @@ function MediaPlayer() {
textController.initialize();
gapController.initialize();
catchupController.initialize();
cmcdModel.initialize();
cmcdModel.initialize(autoPlay);
cmsdModel.initialize();
contentSteeringController.initialize();
segmentBaseController.initialize();
Expand Down
10 changes: 8 additions & 2 deletions src/streaming/constants/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,20 @@ export default {
CMCD_MODE_HEADER: 'header',

/**
* @constant {string} CMCD_AVAILABLE_KEYS specifies all the availables keys for CMCD metrics.
* @constant {string} CMCD_AVAILABLE_KEYS specifies all the available keys for CMCD metrics.
* @memberof Constants#
* @static
*/
CMCD_AVAILABLE_KEYS: ['br', 'd', 'ot', 'tb', 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su', 'bs', 'rtp', 'cid', 'pr', 'sf', 'sid', 'st', 'v'],
/**
* @constant {string} CMCD_AVAILABLE_KEYS_V2 specifies all the available keys for CMCD version 2 metrics.
* @memberof Constants#
* @static
*/
CMCD_V2_AVAILABLE_KEYS: ['msd', 'ltc'],

/**
* @constant {string} CMCD_AVAILABLE_REQUESTS specifies all the availables requests type for CMCD metrics.
* @constant {string} CMCD_AVAILABLE_REQUESTS specifies all the available requests type for CMCD metrics.
* @memberof Constants#
* @static
*/
Expand Down
78 changes: 66 additions & 12 deletions src/streaming/models/CmcdModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ import {CmcdStreamType} from '@svta/common-media-library/cmcd/CmcdStreamType';
import {CmcdStreamingFormat} from '@svta/common-media-library/cmcd/CmcdStreamingFormat';
import {encodeCmcd} from '@svta/common-media-library/cmcd/encodeCmcd';
import {toCmcdHeaders} from '@svta/common-media-library/cmcd/toCmcdHeaders';

const CMCD_VERSION = 1;
import {CmcdHeaderField} from '@svta/common-media-library/cmcd/CmcdHeaderField';
const DEFAULT_CMCD_VERSION = 1;
const DEFAULT_INCLUDE_IN_REQUESTS = 'segment';
const RTP_SAFETY_FACTOR = 5;

Expand All @@ -64,7 +64,9 @@ function CmcdModel() {
_lastMediaTypeRequest,
_isStartup,
_bufferLevelStarved,
_initialMediaRequestsDone;
_initialMediaRequestsDone,
_playbackStartedTime,
_msdSent;

let context = this.context;
let eventBus = EventBus(context).getInstance();
Expand All @@ -77,12 +79,19 @@ function CmcdModel() {
_resetInitialSettings();
}

function initialize() {
function initialize(autoPlay) {
eventBus.on(MediaPlayerEvents.PLAYBACK_RATE_CHANGED, _onPlaybackRateChanged, instance);
eventBus.on(MediaPlayerEvents.MANIFEST_LOADED, _onManifestLoaded, instance);
eventBus.on(MediaPlayerEvents.BUFFER_LEVEL_STATE_CHANGED, _onBufferLevelStateChanged, instance);
eventBus.on(MediaPlayerEvents.PLAYBACK_SEEKED, _onPlaybackSeeked, instance);
eventBus.on(MediaPlayerEvents.PERIOD_SWITCH_COMPLETED, _onPeriodSwitchComplete, instance);
if (autoPlay) {
eventBus.on(MediaPlayerEvents.MANIFEST_LOADING_STARTED, _onPlaybackStarted, instance);
}
else {
eventBus.on(MediaPlayerEvents.PLAYBACK_STARTED, _onPlaybackStarted, instance);
}
eventBus.on(MediaPlayerEvents.PLAYBACK_PLAYING, _onPlaybackPlaying, instance);
}

function setConfig(config) {
Expand Down Expand Up @@ -124,13 +133,29 @@ function CmcdModel() {
_isStartup = {};
_initialMediaRequestsDone = {};
_lastMediaTypeRequest = undefined;
_playbackStartedTime = undefined;
_msdSent = false;
_updateStreamProcessors();
}

function _onPeriodSwitchComplete() {
_updateStreamProcessors();
}

function _onPlaybackStarted() {
if (!_playbackStartedTime) {
_playbackStartedTime = Date.now();
}
}

function _onPlaybackPlaying() {
if (!_playbackStartedTime || internalData.msd) {
return;
}

internalData.msd = Date.now() - _playbackStartedTime;
}

function _updateStreamProcessors() {
if (!playbackController) {
return;
Expand Down Expand Up @@ -195,7 +220,8 @@ function CmcdModel() {
if (isCmcdEnabled()) {
const cmcdData = getCmcdData(request);
const filteredCmcdData = _applyWhitelist(cmcdData);
const headers = toCmcdHeaders(filteredCmcdData)
const options = _createCmcdV2HeadersCustomMap();
const headers = toCmcdHeaders(filteredCmcdData, options);

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

function _canBeEnabled(cmcdParametersFromManifest) {
if (Object.keys(cmcdParametersFromManifest).length) {
if (!cmcdParametersFromManifest.version) {
logger.error(`version parameter must be defined.`);
if (parseInt(cmcdParametersFromManifest.version) !== 1) {
logger.error(`version parameter must be defined in 1.`);
return false;
}
if (!cmcdParametersFromManifest.keys) {
Expand Down Expand Up @@ -257,15 +283,17 @@ function CmcdModel() {

function _checkAvailableKeys(cmcdParametersFromManifest) {
const defaultAvailableKeys = Constants.CMCD_AVAILABLE_KEYS;
const defaultV2AvailableKeys = Constants.CMCD_V2_AVAILABLE_KEYS;
const enabledCMCDKeys = cmcdParametersFromManifest.version ? cmcdParametersFromManifest.keys : settings.get().streaming.cmcd.enabledKeys;
const invalidKeys = enabledCMCDKeys.filter(k => !defaultAvailableKeys.includes(k));
const cmcdVersion = settings.get().streaming.cmcd.version;
const invalidKeys = enabledCMCDKeys.filter(k => !defaultAvailableKeys.includes(k) && !(cmcdVersion === 2 && defaultV2AvailableKeys.includes(k)));

if (invalidKeys.length === enabledCMCDKeys.length && enabledCMCDKeys.length > 0) {
logger.error(`None of the keys are implemented.`);
logger.error(`None of the keys are implemented for CMCD version ${cmcdVersion}.`);
return false;
}
invalidKeys.map((k) => {
logger.warn(`key parameter ${k} is not implemented.`);
logger.warn(`key parameter ${k} is not implemented for CMCD version ${cmcdVersion}.`);
});

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

data.v = CMCD_VERSION;
data.v = settings.get().streaming.cmcd.version ?? DEFAULT_CMCD_VERSION;

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

if (data.v === 2) {
let ltc = playbackController.getCurrentLiveLatency() * 1000;
if (!isNaN(ltc)) {
data.ltc = ltc;
}
const msd = internalData.msd;
if (!_msdSent && !isNaN(msd)) {
data.msd = msd;
_msdSent = true;
}
}



return data;
}

function _createCmcdV2HeadersCustomMap() {
const cmcdVersion = settings.get().streaming.cmcd.version;
return cmcdVersion === 1 ? {} : {
customHeaderMap: {
[CmcdHeaderField.REQUEST]: ['ltc'],
[CmcdHeaderField.SESSION]: ['msd']
}
};
}

function _getBitrateByRequest(request) {
try {
return parseInt(request.bandwidth / 1000);
Expand Down Expand Up @@ -658,7 +710,7 @@ function CmcdModel() {
playbackRate = 1;
}
let { bandwidth, mediaType, representation, duration } = request;
const mediaInfo = representation.mediaInfo
const mediaInfo = representation.mediaInfo;

if (!mediaInfo) {
return NaN;
Expand Down Expand Up @@ -688,6 +740,8 @@ function CmcdModel() {
eventBus.off(MediaPlayerEvents.MANIFEST_LOADED, _onManifestLoaded, this);
eventBus.off(MediaPlayerEvents.BUFFER_LEVEL_STATE_CHANGED, _onBufferLevelStateChanged, instance);
eventBus.off(MediaPlayerEvents.PLAYBACK_SEEKED, _onPlaybackSeeked, instance);
eventBus.off(MediaPlayerEvents.PLAYBACK_STARTED, _onPlaybackStarted, instance);
eventBus.off(MediaPlayerEvents.PLAYBACK_PLAYING, _onPlaybackPlaying, instance);

_resetInitialSettings();
}
Expand Down
4 changes: 4 additions & 0 deletions test/unit/mocks/PlaybackControllerMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ class PlaybackControllerMock {
this.lowLatencyEnabled = value;
}

getCurrentLiveLatency() {
return 15;
}

}


Expand Down
126 changes: 126 additions & 0 deletions test/unit/test/streaming/streaming.models.CmcdModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,70 @@ describe('CmcdModel', function () {
});
});


describe('getHeadersParameters() return CMCD v2 data correctly', () => {
it('getHeadersParameters() should return cmcd v2 data if version is 2', function () {
const REQUEST_TYPE = HTTPRequest.MEDIA_SEGMENT_TYPE;
const MEDIA_TYPE = 'video';

let request = {
type: REQUEST_TYPE,
mediaType: MEDIA_TYPE
};

settings.update({
streaming: {
cmcd: {
version: 2,
enabledKeys: ['br', 'd', 'ot', 'tb', 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su', 'bs', 'rtp', 'cid', 'pr', 'sf', 'sid', 'st', 'v', 'msd', 'ltc', 'msd', 'ltc'],
}
}
});

let headers = cmcdModel.getHeaderParameters(request);
let metrics = decodeCmcd(headers[REQUEST_HEADER_NAME]);
expect(metrics).to.have.property('ltc');

eventBus.trigger(MediaPlayerEvents.PLAYBACK_STARTED);
eventBus.trigger(MediaPlayerEvents.PLAYBACK_PLAYING);

headers = cmcdModel.getHeaderParameters(request);
metrics = decodeCmcd(headers[SESSION_HEADER_NAME]);
expect(metrics).to.have.property('msd');
});

it('getHeadersParameters() should not return cmcd v2 data if the cmcd version is 1', function () {
const REQUEST_TYPE = HTTPRequest.MEDIA_SEGMENT_TYPE;
const MEDIA_TYPE = 'video';

let request = {
type: REQUEST_TYPE,
mediaType: MEDIA_TYPE
};

settings.update({
streaming: {
cmcd: {
enabled: true,
version: 1
},
enabledKeys: ['br', 'd', 'ot', 'tb', 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su', 'bs', 'rtp', 'cid', 'pr', 'sf', 'sid', 'st', 'v', 'msd', 'ltc', 'msd', 'ltc'],
}
});

let headers = cmcdModel.getHeaderParameters(request);
let metrics = decodeCmcd(headers[REQUEST_HEADER_NAME]);
expect(metrics).to.not.have.property('ltc');

eventBus.trigger(MediaPlayerEvents.PLAYBACK_STARTED);
eventBus.trigger(MediaPlayerEvents.PLAYBACK_PLAYING);

headers = cmcdModel.getHeaderParameters(request);
metrics = decodeCmcd(headers[REQUEST_HEADER_NAME]);
expect(metrics).to.not.have.property('msd');
});
});

})
})

Expand Down Expand Up @@ -1396,6 +1460,68 @@ describe('CmcdModel', function () {
});
});

describe('getQueryParameter() return CMCD v2 data correctly', () => {
it('getQueryParameter() should return cmcd v2 data if the cmcd version is 2', function () {
const REQUEST_TYPE = HTTPRequest.MEDIA_SEGMENT_TYPE;
const MEDIA_TYPE = 'video';

let request = {
type: REQUEST_TYPE,
mediaType: MEDIA_TYPE
};

settings.update({
streaming: {
cmcd: {
version: 2,
enabledKeys: ['br', 'd', 'ot', 'tb', 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su', 'bs', 'rtp', 'cid', 'pr', 'sf', 'sid', 'st', 'v', 'msd', 'ltc', 'msd', 'ltc'],
}
}
});
let parameters = cmcdModel.getQueryParameter(request);
let metrics = decodeCmcd(parameters.value);
expect(metrics).to.have.property('ltc');

eventBus.trigger(MediaPlayerEvents.PLAYBACK_STARTED);
eventBus.trigger(MediaPlayerEvents.PLAYBACK_PLAYING);

parameters = cmcdModel.getQueryParameter(request);
metrics = decodeCmcd(parameters.value);
expect(metrics).to.have.property('msd');
});

it('getQueryParameter() sould not return cmcd v2 data if the cmcd version is 1', function () {
const REQUEST_TYPE = HTTPRequest.MEDIA_SEGMENT_TYPE;
const MEDIA_TYPE = 'video';

let request = {
type: REQUEST_TYPE,
mediaType: MEDIA_TYPE
};

settings.update({
streaming: {
cmcd: {
enabled: true,
version: 1,
enabledKeys: ['br', 'd', 'ot', 'tb', 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su', 'bs', 'rtp', 'cid', 'pr', 'sf', 'sid', 'st', 'v', 'msd', 'ltc', 'msd', 'ltc'],
}
}
});

let parameters = cmcdModel.getQueryParameter(request);
let metrics = decodeCmcd(parameters.value);
expect(metrics).to.not.have.property('ltc');

eventBus.trigger(MediaPlayerEvents.PLAYBACK_STARTED);
eventBus.trigger(MediaPlayerEvents.PLAYBACK_PLAYING);

parameters = cmcdModel.getQueryParameter(request);
metrics = decodeCmcd(parameters.value);
expect(metrics).to.not.have.property('msd');
});
});

describe('applyParametersFromMpd', () => {
it('should ignore service description cmcd configuration when applyParametersFromMpd is false', function () {
const REQUEST_TYPE = HTTPRequest.MEDIA_SEGMENT_TYPE;
Expand Down

0 comments on commit ffe08a7

Please sign in to comment.