Skip to content

Commit

Permalink
Refactor the DroppedFramesRule.js.js and allow parameter configuratio…
Browse files Browse the repository at this point in the history
…n via Settings.js (#4378)
  • Loading branch information
dsilhavy committed Jan 29, 2024
1 parent 015d0ff commit 0759671
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 57 deletions.
151 changes: 125 additions & 26 deletions src/core/Settings.js
Expand Up @@ -191,32 +191,44 @@ import Events from './events/Events.js';
* abr: {
* limitBitrateByPortal: false,
* usePixelRatioInLimitBitrateByPortal: false,
* rules: {
* rules: {
* throughputRule: {
* active: true
* },
* bolaRule: {
* active: true
* },
* active: true
* },
* bolaRule: {
* active: true
* },
* insufficientBufferRule: {
* active: true
* },
* switchHistoryRule: {
* active: true
* },
* droppedFramesRule: {
* active: true
* },
* abandonRequestsRule: {
* active: true
* },
* l2ARule: {
* active: false
* },
* loLPRule: {
* active: false
* }
*
* active: true,
* parameters: {
* throughputSafetyFactor: 0.7,
* segmentIgnoreCount: 2
* }
* },
* switchHistoryRule: {
* active: true
* },
* droppedFramesRule: {
* active: true,
* parameters: {
* minimumSampleSize: 375,
* droppedFramesPercentageThreshold: 0.15
* }
* },
* abandonRequestsRule: {
* active: true,
* parameters: {
* abandonDurationMultiplier: 1.8,
* minSegmentDownloadTimeThresholdInMs: 500,
* minThroughputSamplesThreshold: 6
* }
* },
* l2ARule: {
* active: false
* },
* loLPRule: {
* active: false
* }
* },
* throughput: {
* averageCalculationMode: Constants.THROUGHPUT_CALCULATION_MODES.EWMA,
Expand Down Expand Up @@ -624,7 +636,7 @@ import Events from './events/Events.js';
* Sets whether to take into account the device's pixel ratio when defining the portal dimensions.
*
* Useful on, for example, retina displays.
* @property {object} [activeRules={throughputRule: {active: true}, bolaRule: {active: true}, insufficientBufferRule: {active: true},switchHistoryRule: {active: true},droppedFramesRule: {active: true},abandonRequestsRule: {active: true}, l2ARule: {active: false}, loLPRule: {active: false}}]
* @property {module:Settings~AbrRules} [rules]
* Enable/Disable individual ABR rules. Note that if the throughputRule and the bolaRule are activated at the same time we switch to a dynamic mode.
* In the dynamic mode either ThroughputRule or BolaRule are active but not both at the same time.
*
Expand All @@ -645,7 +657,90 @@ import Events from './events/Events.js';
* Use -1 to let the player decide.
* @property {module:Settings~AudioVideoSettings} [autoSwitchBitrate={audio: true, video: true}]
* Indicates whether the player should enable ABR algorithms to switch the bitrate.
*/

/**
* @typedef {Object} AbrRules
* @property {module:Settings~ThroughputRule} [throughputRule]
* Configuration of the Throughput rule
* @property {module:Settings~BolaRule} [bolaRule]
* Configuration of the BOLA rule
* @property {module:Settings~InsufficientBufferRule} [insufficientBufferRule]
* Configuration of the Insufficient Buffer rule
* @property {module:Settings~SwitchHistoryRule} [switchHistoryRule]
* Configuration of the Switch History rule
* @property {module:Settings~DroppedFramesRule} [droppedFramesRule]
* Configuration of the Dropped Frames rule
* @property {module:Settings~AbandonRequestsRule} [abandonRequestsRule]
* Configuration of the Abandon Requests rule
* @property {module:Settings~L2ARule} [l2ARule]
* Configuration of the L2A rule
* @property {module:Settings~LoLPRule} [loLPRule]
* Configuration of the LoLP rule
*/

/**
* @typedef {Object} ThroughputRule
* @property {boolean} [active=true]
* Enable or disable the rule
*/

/**
* @typedef {Object} BolaRule
* @property {boolean} [active=true]
* Enable or disable the rule
*/

/**
* @typedef {Object} InsufficientBufferRule
* @property {boolean} [active=true]
* Enable or disable the rule
* @property {object} [parameters={throughputSafetyFactor=0.7, segmentIgnoreCount=2}]
* Configures the rule specific parameters.
*
* - throughputSafetyFactor: The safety factor that is applied to the derived throughput, see example in the Description.
* - segmentIgnoreCount: This rule is not taken into account until the first segmentIgnoreCount media segments have been appended to the buffer.
*/

/**
* @typedef {Object} SwitchHistoryRule
* @property {boolean} [active=true]
* Enable or disable the rule
*/

/**
* @typedef {Object} DroppedFramesRule
* @property {boolean} [active=true]
* Enable or disable the rule
* @property {object} [parameters={minimumSampleSize=375, droppedFramesPercentageThreshold=0.15}]
* Configures the rule specific parameters.
*
* - minimumSampleSize: Sum of rendered and dropped frames required for each Representation before the rule kicks in.
* - droppedFramesPercentageThreshold: Minimum percentage of dropped frames to trigger a quality down switch. Values are defined in the range of 0 - 1.
*/

/**
* @typedef {Object} AbandonRequestsRule
* @property {boolean} [active=true]
* Enable or disable the rule
* @property {object} [parameters={abandonDurationMultiplier=1.8, minSegmentDownloadTimeThresholdInMs=500, minThroughputSamplesThreshold=6}]
* Configures the rule specific parameters.
*
* - abandonDurationMultiplier: Factor to multiply with the segment duration to compare against the estimated remaining download time of the current segment. See code example above.
* - minSegmentDownloadTimeThresholdInMs: The AbandonRequestRule only kicks if the download time of the current segment exceeds this value.
* - minThroughputSamplesThreshold: Minimum throughput samples (equivalent to number of progress events) required before the AbandonRequestRule kicks in.
*/

/**
* @typedef {Object} L2ARule
* @property {boolean} [active=true]
* Enable or disable the rule
*/

/**
* @typedef {Object} LoLPRule
* @property {boolean} [active=true]
* Enable or disable the rule
*/

/**
Expand Down Expand Up @@ -1051,7 +1146,11 @@ function Settings() {
active: true
},
droppedFramesRule: {
active: true
active: true,
parameters: {
minimumSampleSize: 375,
droppedFramesPercentageThreshold: 0.15
}
},
abandonRequestsRule: {
active: true,
Expand Down
1 change: 0 additions & 1 deletion src/streaming/controllers/AbrController.js
Expand Up @@ -279,7 +279,6 @@ function AbrController() {
voRepresentations = _sortByCalculatedQualityRank(voRepresentations);

// Filter the list of options based on the provided settings
// We can not apply the filter before otherwise the absolute index would be wrong
voRepresentations = _filterByAllowedSettings(voRepresentations)

// Add an absolute index after filtering
Expand Down
59 changes: 29 additions & 30 deletions src/streaming/rules/abr/DroppedFramesRule.js
@@ -1,16 +1,15 @@
import FactoryMaker from '../../../core/FactoryMaker.js';
import SwitchRequest from '../SwitchRequest.js';
import Debug from '../../../core/Debug.js';
import Settings from '../../../core/Settings.js';

function DroppedFramesRule() {

const context = this.context;
const settings = Settings(context).getInstance();
let instance,
logger;

const DROPPED_PERCENTAGE_FORBID = 0.15;
const GOOD_SAMPLE_SIZE = 375; //Don't apply the rule until this many frames have been rendered(and counted under those indices).

function setup() {
logger = Debug(context).getInstance().getLogger(instance);
}
Expand All @@ -24,40 +23,40 @@ function DroppedFramesRule() {
}

const droppedFramesHistory = rulesContext.getDroppedFramesHistory();
if (!droppedFramesHistory) {
return switchRequest
}
const streamId = rulesContext.getStreamInfo().id;
const mediaInfo = rulesContext.getMediaInfo();
const abrController = rulesContext.getAbrController();
const droppedFramesHistoryData = droppedFramesHistory.getFrameHistory(streamId);

if (droppedFramesHistory) {
const dfh = droppedFramesHistory.getFrameHistory(streamId);

if (!dfh || Object.keys(dfh.length) === 0) {
return switchRequest;
}

let droppedFrames = 0;
let totalFrames = 0;
const representations = abrController.getPossibleVoRepresentations(mediaInfo, true);
let newRepresentation = null;

//No point in measuring dropped frames for the first index.
for (let i = 1; i < representations.length; i++) {
const currentRepresentation = representations[i];
if (currentRepresentation && dfh[currentRepresentation.id]) {
droppedFrames = dfh[currentRepresentation.id].droppedVideoFrames;
totalFrames = dfh[currentRepresentation.id].totalVideoFrames;
if (!droppedFramesHistoryData || Object.keys(droppedFramesHistoryData).length === 0) {
return switchRequest;
}

if (totalFrames > GOOD_SAMPLE_SIZE && droppedFrames / totalFrames > DROPPED_PERCENTAGE_FORBID) {
newRepresentation = representations[i - 1];
logger.debug('index: ' + newRepresentation.absoluteIndex + ' Dropped Frames: ' + droppedFrames + ' Total Frames: ' + totalFrames);
break;
}
let droppedFrames = 0;
let totalFrames = 0;
const representations = abrController.getPossibleVoRepresentations(mediaInfo, true);
let newRepresentation = null;

//No point in measuring dropped frames for the first index.
for (let i = 1; i < representations.length; i++) {
const currentRepresentation = representations[i];
if (currentRepresentation && droppedFramesHistoryData[currentRepresentation.id]) {
droppedFrames = droppedFramesHistoryData[currentRepresentation.id].droppedVideoFrames;
totalFrames = droppedFramesHistoryData[currentRepresentation.id].totalVideoFrames;

if (totalFrames > settings.get().streaming.abr.rules.droppedFramesRule.parameters.minimumSampleSize && droppedFrames / totalFrames > settings.get().streaming.abr.rules.droppedFramesRule.parameters.droppedFramesPercentageThreshold) {
newRepresentation = representations[i - 1];
logger.debug('index: ' + newRepresentation.absoluteIndex + ' Dropped Frames: ' + droppedFrames + ' Total Frames: ' + totalFrames);
break;
}
}
if (newRepresentation) {
switchRequest.representation = newRepresentation;
switchRequest.reason = { droppedFrames };
}
}
if (newRepresentation) {
switchRequest.representation = newRepresentation;
switchRequest.reason = { droppedFrames };
}

return switchRequest;
Expand Down

0 comments on commit 0759671

Please sign in to comment.