Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add further CMCD parameters #3508

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