Skip to content

Commit

Permalink
Add further CMCD parameters (#3508)
Browse files Browse the repository at this point in the history
* Implemented further CMCD parameters

- Implemented CMCD object type 'tt'
- Implemented metrics nor and nrr
- Implemented unit tests for CMCD parameters nor and nrr
- Added CMCD query parameters to license requests

* Implemented cmcd parameter rtp

* Add JSDoc

* Implemented requested changes

* Updated CMCD unit tests

- updated tests for nrr and nor
- added test for rtp
- updated logic for nrr and nor

* Add RTP value to JSdoc and index.d.ts

* Use underscore notation for private function in CmcdModel.js

* Add rtpSafetyFactor to settings

* Fix typo

* Fix rtpSafetyFactor

* Fix CMCD reporting for DRM content

* Add CMCD tooltips in reference UI

* Add unit tests for static rtp value

Co-authored-by: dsilhavy <daniel.silhavy@fokus.fraunhofer.de>
  • Loading branch information
FritzHeiden and dsilhavy authored Jan 27, 2021
1 parent 14fbd66 commit 4f25e04
Show file tree
Hide file tree
Showing 17 changed files with 266 additions and 11 deletions.
4 changes: 3 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ declare namespace dashjs {
cmcd?: {
enabled?: boolean,
sid?: string,
cid?: string
cid?: string,
rtp?: number,
rtpSafetyFactor?: number
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions samples/advanced/cmcd.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
player = dashjs.MediaPlayer().create();
video = document.querySelector("video");
player.initialize();
player.setTextDefaultEnabled(true);
version = player.getVersion();

player.on(CMCD_DATA_GENERATED, handleCmcdDataGeneratedEvent);
Expand All @@ -37,6 +38,27 @@
player.attachView(video);
player.attachSource(url);

var TTMLRenderingDiv = document.querySelector("#ttml-rendering-div");
player.attachTTMLRenderingDiv(TTMLRenderingDiv);
}

function handleCmcdDataGeneratedEvent(event) {
log("type: " + event.mediaType);
log("file: " + event.url.split("/").pop())
var keys = Object.keys(event.cmcdData);
keys = keys.sort();
for (var key of keys) {
log(key.padEnd(4) + ": " + event.cmcdData[key]);
}
log("");
}

function log(msg) {
msg = msg.length > 200 ? msg.substring(0, 200) + "..." : msg; /* to avoid repeated wrapping with large objects */
var tracePanel = document.getElementById("trace");
tracePanel.innerHTML += msg + "\n";
tracePanel.scrollTop = tracePanel.scrollHeight;
console.log(msg);
}

function handleCmcdDataGeneratedEvent(event) {
Expand Down Expand Up @@ -76,6 +98,7 @@
<div>
<video controls="true">
</video>
<div id="ttml-rendering-div"></div>
<textarea id="trace" placeholder="Sent CMCD data will be displayed here"></textarea>
</div>
<script>
Expand Down
2 changes: 2 additions & 0 deletions samples/dash-if-reference-player/app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,8 @@ app.controller('DashController', ['$scope', '$window', 'sources', 'contributors'

config.streaming.cmcd.sid = $scope.cmcdSessionId ? $scope.cmcdSessionId : null;
config.streaming.cmcd.cid = $scope.cmcdContentId ? $scope.cmcdContentId : null;
config.streaming.cmcd.rtp = $scope.cmcdRtp ? $scope.cmcdRtp : null;
config.streaming.cmcd.rtpSafetyFactor = $scope.cmcdRtpSafetyFactor ? $scope.cmcdRtpSafetyFactor : null;

$scope.player.updateSettings(config);

Expand Down
15 changes: 12 additions & 3 deletions samples/dash-if-reference-player/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,13 @@
<input type="text" class="form-control" placeholder="mandatory session id" ng-model="cmcdSessionId">
<label class="options-label">Content ID:</label>
<input type="text" class="form-control" placeholder="content id" ng-model="cmcdContentId">
<label class="options-label" data-toggle="tooltip" data-placement="right"
title="A static value to be used as RTP parameter">Requested maximum throughput (rtp):</label>
<input type="text" class="form-control" placeholder="rtp in kbps" ng-model="cmcdRtp">
<label class="options-label" data-toggle="tooltip" data-placement="right"
title="This value is used as a factor for the rtp value calculation: rtp = minBandwidth * rtpSafetyFactor. If not specified this value defaults to 5. Note that this value is only used when no static rtp value is defined.">RTP
safety factor:</label>
<input type="text" class="form-control" placeholder="Default 5" ng-model="cmcdRtpSafetyFactor">
</div>
</div>

Expand Down Expand Up @@ -698,9 +705,9 @@
</div>
</div>
</div>

</div>

<!-- ERROR MODAL -->
<div class="modal fade" id="errorModal" tabindex="-1" role="dialog" aria-labelledby="errorModalLabel"
aria-hidden="true">
Expand Down Expand Up @@ -745,7 +752,9 @@ <h5 class="modal-title" id="errorModalLabel">Error {{errorType}}</h5>
<div id="conformance-violations">
<h4>Conformance Violations </h4>
<ul class="list-unstyled" ng-repeat="conformanceViolation in conformanceViolations">
<li><span class="label label-{{conformanceViolation.level}}">{{conformanceViolation.level}}</span> : {{conformanceViolation.event.message}} </li>
<li><span class="label label-{{conformanceViolation.level}}">{{conformanceViolation.level}}</span> :
{{conformanceViolation.event.message}}
</li>
</ul>
</div>
</div>
Expand Down
16 changes: 14 additions & 2 deletions src/core/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* cmcd: {
* enabled: false,
* sid: null,
* cid: null
* cid: null,
* rtp: null,
* rtpSafetyFactor: 5
* }
* }
* }
Expand Down Expand Up @@ -466,6 +468,14 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* A unique string to identify the current content.
*
* If not specified it will be a hash of the MPD url.
* @property {number} [rtp]
* The requested maximum throughput that the client considers sufficient for delivery of the asset.
*
* If not specified this value will be dynamically calculated in the CMCDModel based on the current buffer level.
* @property {number} [rtpSafetyFactor]
* This value is used as a factor for the rtp value calculation: rtp = minBandwidth * rtpSafetyFactor
*
* If not specified this value defaults to 5. Note that this value is only used when no static rtp value is defined.
*/

/**
Expand Down Expand Up @@ -627,7 +637,9 @@ function Settings() {
cmcd: {
enabled: false,
sid: null,
cid: null
cid: null,
rtp: null,
rtpSafetyFactor: 5
}
}
};
Expand Down
28 changes: 27 additions & 1 deletion src/dash/DashHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,31 @@ function DashHandler(config) {
return request;
}

/**
* This function returns the next segment request without modifying any internal variables. Any class (e.g CMCD Model) that needs information about the upcoming request should use this method.
* @param {object} mediaInfo
* @param {object} representation
* @return {FragmentRequest|null}
*/
function getNextSegmentRequestIdempotent(mediaInfo, representation) {
let request = null;
let indexToRequest = segmentIndex + 1;
const segment = segmentsController.getSegmentByIndex(
representation,
indexToRequest,
lastSegment ? lastSegment.mediaStartTime : -1
);
if (!segment) return null;
request = getRequestForSegment(mediaInfo, segment);
return request;
}

/**
* Main function to get the next segment request.
* @param {object} mediaInfo
* @param {object} representation
* @return {FragmentRequest|null}
*/
function getNextSegmentRequest(mediaInfo, representation) {
let request = null;

Expand Down Expand Up @@ -448,7 +473,8 @@ function DashHandler(config) {
isMediaFinished: isMediaFinished,
reset: reset,
resetIndex: resetIndex,
setMimeType: setMimeType
setMimeType: setMimeType,
getNextSegmentRequestIdempotent
};

setup();
Expand Down
3 changes: 2 additions & 1 deletion src/streaming/MediaPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2085,7 +2085,8 @@ function MediaPlayer() {
eventBus: eventBus,
events: Events,
BASE64: BASE64,
constants: Constants
constants: Constants,
cmcdModel: cmcdModel
});
return protectionController;
}
Expand Down
19 changes: 19 additions & 0 deletions src/streaming/StreamProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,24 @@ function StreamProcessor(config) {
scheduleController.processMediaRequest(request);
}

/**
* Probe the next request. This is used in the CMCD model to get information about the upcoming request. Note: No actual request is performed here.
* @return {FragmentRequest|null}
*/
function probeNextRequest() {
const representationInfo = getRepresentationInfo();

const representation = representationController && representationInfo ?
representationController.getRepresentationForQuality(representationInfo.quality) : null;

let request = indexHandler.getNextSegmentRequestIdempotent(
getMediaInfo(),
representation
);

return request;
}

function findNextRequest(seekTarget, requestToReplace) {
const representationInfo = getRepresentationInfo();
const hasSeekTarget = !isNaN(seekTarget);
Expand Down Expand Up @@ -765,6 +783,7 @@ function StreamProcessor(config) {
getInitRequest: getInitRequest,
getFragmentRequest: getFragmentRequest,
finalisePlayList: finalisePlayList,
probeNextRequest: probeNextRequest,
reset: reset
};

Expand Down
90 changes: 88 additions & 2 deletions src/streaming/models/CmcdModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import MetricsReportingEvents from '../metrics/MetricsReportingEvents';
import FactoryMaker from '../../core/FactoryMaker';
import Debug from '../../core/Debug';
import Settings from '../../core/Settings';
import Constants from '../../streaming/constants/Constants';
import {HTTPRequest} from '../vo/metrics/HTTPRequest';
import DashManifestModel from '../../dash/models/DashManifestModel';
import Utils from '../../core/Utils';
Expand All @@ -58,6 +59,7 @@ const STREAM_TYPES = {
VOD: 'v',
LIVE: 'l'
};
const RTP_SAFETY_FACTOR = 5;

function CmcdModel() {

Expand All @@ -68,6 +70,7 @@ function CmcdModel() {
abrController,
dashMetrics,
playbackController,
streamProcessors,
_isStartup,
_bufferLevelStarved,
_initialMediaRequestsDone;
Expand All @@ -88,6 +91,7 @@ function CmcdModel() {
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);
}

function setConfig(config) {
Expand Down Expand Up @@ -118,6 +122,21 @@ function CmcdModel() {
_bufferLevelStarved = {};
_isStartup = {};
_initialMediaRequestsDone = {};
_updateStreamProcessors();
}

function _onPeriodSwitchComplete() {
_updateStreamProcessors();
}

function _updateStreamProcessors() {
if (!playbackController) return;
const streamController = playbackController.getStreamController();
if (!streamController) return;
if (typeof streamController.getActiveStream !== 'function') return;
const activeStream = streamController.getActiveStream();
if (!activeStream) return;
streamProcessors = activeStream.getProcessors();
}

function getQueryParameter(request) {
Expand Down Expand Up @@ -157,6 +176,8 @@ function CmcdModel() {
return _getCmcdDataForInitSegment(request);
} else if (request.type === HTTPRequest.OTHER_TYPE || request.type === HTTPRequest.XLINK_EXPANSION_TYPE) {
return _getCmcdDataForOther(request);
} else if (request.type === HTTPRequest.LICENSE) {
return _getCmcdDataForLicense(request);
}

return cmcdData;
Expand All @@ -165,6 +186,14 @@ function CmcdModel() {
}
}

function _getCmcdDataForLicense(request) {
const data = _getGenericCmcdData(request);

data.ot = OBJECT_TYPES.ENCRYPTION_KEY;

return data;
}

function _getCmcdDataForMpd() {
const data = _getGenericCmcdData();

Expand All @@ -177,13 +206,40 @@ function CmcdModel() {
const data = _getGenericCmcdData();
const encodedBitrate = _getBitrateByRequest(request);
const d = _getObjectDurationByRequest(request);
const ot = request.mediaType === 'video' ? `${OBJECT_TYPES.VIDEO}` : request.mediaType === 'audio' ? `${OBJECT_TYPES.AUDIO}` : request.mediaType === 'fragmentedText' ? `${OBJECT_TYPES.CAPTION}` : null;
const mtp = _getMeasuredThroughputByType(request.mediaType);
const dl = _getDeadlineByType(request.mediaType);
const bl = _getBufferLevelByType(request.mediaType);
const tb = _getTopBitrateByType(request.mediaType);
const pr = internalData.pr;

const nextRequest = _probeNextRequest(request.mediaType);

let ot;
if (request.mediaType === Constants.VIDEO) ot = OBJECT_TYPES.VIDEO;
if (request.mediaType === Constants.AUDIO) ot = OBJECT_TYPES.AUDIO;
if (request.mediaType === Constants.FRAGMENTED_TEXT) {
if (request.mediaInfo.mimeType === 'application/mp4') {
ot = OBJECT_TYPES.ISOBMFF_TEXT_TRACK;
} else {
ot = OBJECT_TYPES.CAPTION;
}
}

let rtp = settings.get().streaming.cmcd.rtp;
if (!rtp) {
rtp = _calculateRtp(request);
}
data.rtp = rtp;

if (nextRequest) {
if (request.url !== nextRequest.url) {
let url = new URL(nextRequest.url);
data.nor = url.pathname;
} else if (nextRequest.range) {
data.nrr = nextRequest.range;
}
}

if (encodedBitrate) {
data.br = encodedBitrate;
}
Expand Down Expand Up @@ -414,7 +470,7 @@ function CmcdModel() {
if (!cmcdData) {
return null;
}
const keys = Object.keys(cmcdData).sort((a, b) =>a.localeCompare(b));
const keys = Object.keys(cmcdData).sort((a, b) => a.localeCompare(b));
const length = keys.length;

let cmcdString = keys.reduce((acc, key, index) => {
Expand All @@ -440,6 +496,36 @@ function CmcdModel() {
}
}

function _probeNextRequest(mediaType) {
if (!streamProcessors || streamProcessors.length === 0) return;
for (let streamProcessor of streamProcessors) {
if (streamProcessor.getType() === mediaType) {
return streamProcessor.probeNextRequest();
}
}
}

function _calculateRtp(request) {
// Get the values we need
let playbackRate = playbackController.getPlaybackRate();
if (!playbackRate) playbackRate = 1;
let { quality, mediaType, mediaInfo, duration } = request;
let currentBufferLevel = _getBufferLevelByType(mediaType);
if (currentBufferLevel === 0) currentBufferLevel = 500;
let bitrate = mediaInfo.bitrateList[quality].bandwidth;

// Calculate RTP
let segmentSize = bitrate * duration / 1000; // Calculate file size in kilobits
let timeToLoad = currentBufferLevel * playbackRate / 1000; // Calculate time available to load file in seconds
let minBandwidth = segmentSize / timeToLoad; // Calculate the exact bandwidth required
let rtpSafetyFactor = settings.get().streaming.cmcd.rtpSafetyFactor && !isNaN(settings.get().streaming.cmcd.rtpSafetyFactor) ? settings.get().streaming.cmcd.rtpSafetyFactor : RTP_SAFETY_FACTOR;
let maxBandwidth = minBandwidth * rtpSafetyFactor; // Include a safety buffer

let rtp = (parseInt(maxBandwidth / 100) + 1) * 100; // Round to the next multiple of 100

return rtp;
}

function reset() {
eventBus.off(MediaPlayerEvents.PLAYBACK_RATE_CHANGED, _onPlaybackRateChanged, this);
eventBus.off(MediaPlayerEvents.MANIFEST_LOADED, _onManifestLoaded, this);
Expand Down
3 changes: 2 additions & 1 deletion src/streaming/protection/Protection.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ function Protection() {
debug: config.debug,
events: config.events,
BASE64: config.BASE64,
constants: config.constants
constants: config.constants,
cmcdModel: config.cmcdModel
});
config.capabilities.setEncryptedMediaSupported(true);
}
Expand Down
Loading

0 comments on commit 4f25e04

Please sign in to comment.