diff --git a/src/core/Settings.js b/src/core/Settings.js index df4fbf4e6c..647ab752fb 100644 --- a/src/core/Settings.js +++ b/src/core/Settings.js @@ -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, @@ -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. * @@ -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 */ /** @@ -1051,7 +1146,11 @@ function Settings() { active: true }, droppedFramesRule: { - active: true + active: true, + parameters: { + minimumSampleSize: 375, + droppedFramesPercentageThreshold: 0.15 + } }, abandonRequestsRule: { active: true, diff --git a/src/streaming/controllers/AbrController.js b/src/streaming/controllers/AbrController.js index febff46091..c72c2182ff 100644 --- a/src/streaming/controllers/AbrController.js +++ b/src/streaming/controllers/AbrController.js @@ -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 diff --git a/src/streaming/rules/abr/DroppedFramesRule.js b/src/streaming/rules/abr/DroppedFramesRule.js index b6c302e62a..4cd087c1c3 100644 --- a/src/streaming/rules/abr/DroppedFramesRule.js +++ b/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); } @@ -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;