Skip to content

Commit

Permalink
feat: Add settings portalScale and portalMinimum to cap resolution (
Browse files Browse the repository at this point in the history
#4388)

* test: add capability to scale portal size

* feat: add portalScale option and test for usage based on scaledWidth

* test: add portalMinimum setting

* feat: implement portalMinimum to maintain a specified bandwidth when the portal is scaled

* docs: update types and jsdoc

* fix: tests

* refactor: rollup portal settings into a block
  • Loading branch information
eirikbjornr committed Apr 23, 2024
1 parent e5c66cd commit d6a4415
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 65 deletions.
8 changes: 6 additions & 2 deletions index.d.ts
Expand Up @@ -1128,8 +1128,6 @@ declare namespace dashjs {
bandwidthSafetyFactor?: number;
useDefaultABRRules?: boolean;
useDeadTimeLatency?: boolean;
limitBitrateByPortal?: boolean;
usePixelRatioInLimitBitrateByPortal?: boolean;
maxBitrate?: {
audio?: number;
video?: number;
Expand All @@ -1155,6 +1153,12 @@ declare namespace dashjs {
video?: boolean;
},
fetchThroughputCalculationMode?: string;
limitBitrateByPortal?: {
enabled?: boolean;
usePixelRatio?: boolean;
scalingFactor?: number;
minimumBandwidthInKbit?: number;
}
},
cmcd?: {
enabled?: boolean,
Expand Down
44 changes: 32 additions & 12 deletions src/core/Settings.js
Expand Up @@ -215,15 +215,19 @@ import Events from './events/Events';
* bandwidthSafetyFactor: 0.9,
* useDefaultABRRules: true,
* useDeadTimeLatency: true,
* limitBitrateByPortal: false,
* usePixelRatioInLimitBitrateByPortal: false,
* maxBitrate: { audio: -1, video: -1 },
* minBitrate: { audio: -1, video: -1 },
* maxRepresentationRatio: { audio: 1, video: 1 },
* initialBitrate: { audio: -1, video: -1 },
* initialRepresentationRatio: { audio: -1, video: -1 },
* autoSwitchBitrate: { audio: true, video: true },
* fetchThroughputCalculationMode: Constants.ABR_FETCH_THROUGHPUT_CALCULATION_DOWNLOADED_DATA
* fetchThroughputCalculationMode: Constants.ABR_FETCH_THROUGHPUT_CALCULATION_DOWNLOADED_DATA,
* limitBitrateByPortal: {
* enabled: false,
* usePixelRatio: false,
* scalingFactor: 1,
* minimumBandwidthInBit: 0
* }
* },
* cmcd: {
* enabled: false,
Expand Down Expand Up @@ -604,6 +608,21 @@ import Events from './events/Events';
* Minimum number of throughput samples required to consider abandoning the download of the segment.
*/

/**
* @typedef {Object} ABRPortalSettings
* @property {boolean} [enabled=false]
* If true, the size of the video portal will limit the max chosen video resolution.
* @property {boolean} [usePixelRatio=false]
* Sets whether to take into account the device's pixel ratio when defining the portal dimensions.
* Useful on, for example, retina displays.
* @property {number} [scalingFactor=1]
* Scales the size of the video portal used to limit the max video resolution.
* The square root of this value is multiplied with the width and height of the media element.
* Accounts for pixel ratio if `usePixelRatio` is set.
* @property {number} [minimumBandwidthInBit=0]
* Limits the minimum bandwidth video playback quality can go down to depending on the portal size.
*/

/**
* @typedef {Object} AbrSettings
* @property {string} [movingAverageMethod="slidingWindow"]
Expand Down Expand Up @@ -642,12 +661,6 @@ import Events from './events/Events';
* If true, only the download portion will be considered part of the download bitrate and latency will be regarded as static.
*
* If false, the reciprocal of the whole transfer time will be used.
* @property {boolean} [limitBitrateByPortal=false]
* If true, the size of the video portal will limit the max chosen video resolution.
* @property {boolean} [usePixelRatioInLimitBitrateByPortal=false]
* Sets whether to take into account the device's pixel ratio when defining the portal dimensions.
*
* Useful on, for example, retina displays.
* @property {module:Settings~AudioVideoSettings} [maxBitrate={audio: -1, video: -1}]
* The maximum bitrate that the ABR algorithms will choose. This value is specified in kbps.
*
Expand Down Expand Up @@ -681,6 +694,9 @@ import Events from './events/Events';
* Algorithm to determine the throughput in case the Fetch API is used for low latency streaming.
*
* For details please check the samples section and FetchLoader.js.
*
* @property {module:Settings~ABRPortalSettings} limitBitrateByPortal
* Control the impact of the portal size on ABR.
*/

/**
Expand Down Expand Up @@ -1042,8 +1058,6 @@ function Settings() {
bandwidthSafetyFactor: 0.9,
useDefaultABRRules: true,
useDeadTimeLatency: true,
limitBitrateByPortal: false,
usePixelRatioInLimitBitrateByPortal: false,
maxBitrate: {
audio: -1,
video: -1
Expand All @@ -1068,7 +1082,13 @@ function Settings() {
audio: true,
video: true
},
fetchThroughputCalculationMode: Constants.ABR_FETCH_THROUGHPUT_CALCULATION_MOOF_PARSING
fetchThroughputCalculationMode: Constants.ABR_FETCH_THROUGHPUT_CALCULATION_MOOF_PARSING,
limitBitrateByPortal: {
enabled: false,
usePixelRatio: false,
scalingFactor: 1,
minimumBandwidthInBit: 0
}
},
cmcd: {
enabled: false,
Expand Down
25 changes: 20 additions & 5 deletions src/streaming/controllers/AbrController.js
Expand Up @@ -520,23 +520,38 @@ function AbrController() {
* @private
*/
function _checkPortalSize(idx, type, streamId) {
if (type !== Constants.VIDEO || !settings.get().streaming.abr.limitBitrateByPortal || !streamProcessorDict[streamId] || !streamProcessorDict[streamId][type]) {
if (!settings.get().streaming.abr.limitBitrateByPortal.enabled || type !== Constants.VIDEO || !streamProcessorDict[streamId] || !streamProcessorDict[streamId][type]) {
return idx;
}

if (!windowResizeEventCalled) {
setElementSize();
}

const streamInfo = streamProcessorDict[streamId][type].getStreamInfo();
const representation = adapter.getAdaptationForType(streamInfo.index, type, streamInfo).Representation_asArray;

const scalingFactor = settings.get().streaming.abr.limitBitrateByPortal.scalingFactor > 0
? Math.sqrt(settings.get().streaming.abr.limitBitrateByPortal.scalingFactor)
: 1;

const minimumBandwidthInBit = settings.get().streaming.abr.limitBitrateByPortal.minimumBandwidthInBit > 0
? settings.get().streaming.abr.limitBitrateByPortal.minimumBandwidthInBit
: 0;

const scaledWidth = elementWidth * scalingFactor;
const scaledHeight = elementHeight * scalingFactor;

let newIdx = idx;

if (elementWidth > 0 && elementHeight > 0) {
if (scaledWidth > 0 && scaledHeight > 0) {
while (
newIdx > 0 &&
representation[newIdx] &&
elementWidth < representation[newIdx].width &&
elementWidth - representation[newIdx - 1].width < representation[newIdx].width - elementWidth) {
scaledWidth < representation[newIdx].width &&
scaledWidth - representation[newIdx - 1].width < representation[newIdx].width - scaledWidth &&
representation[newIdx - 1].bandwidth >= minimumBandwidthInBit
) {
newIdx = newIdx - 1;
}

Expand Down Expand Up @@ -905,7 +920,7 @@ function AbrController() {

function setElementSize() {
if (videoModel) {
const hasPixelRatio = settings.get().streaming.abr.usePixelRatioInLimitBitrateByPortal && window.hasOwnProperty('devicePixelRatio');
const hasPixelRatio = settings.get().streaming.abr.limitBitrateByPortal.usePixelRatio && window.hasOwnProperty('devicePixelRatio');
const pixelRatio = hasPixelRatio ? window.devicePixelRatio : 1;
elementWidth = videoModel.getClientWidth() * pixelRatio;
elementHeight = videoModel.getClientHeight() * pixelRatio;
Expand Down
44 changes: 10 additions & 34 deletions test/unit/mocks/AdapterMock.js
Expand Up @@ -62,41 +62,17 @@ function AdapterMock() {
};

this.getAdaptationForType = function () {
const representation = [
{ width: 500, bandwidth: 1000000 },
{ width: 750, bandwidth: 2000000 },
{ width: 900, bandwidth: 3000000 },
{ width: 900, bandwidth: 4000000 },
{ width: 900, bandwidth: 5000000 }
]

return {
Representation: [
{
width: 500
},
{
width: 750
},
{
width: 900
},
{
width: 900
},
{
width: 900
}
],
Representation_asArray: [
{
width: 500
},
{
width: 750
},
{
width: 900
},
{
width: 900
},
{
width: 900
}
]
Representation: [...representation],
Representation_asArray: [...representation]
};
};

Expand Down
46 changes: 35 additions & 11 deletions test/unit/streaming.MediaPlayer.js
Expand Up @@ -463,35 +463,59 @@ describe('MediaPlayer', function () {
});

it('should configure bitrate according to playback area size', function () {
let limitBitrateByPortal = player.getSettings().streaming.abr.limitBitrateByPortal;
expect(limitBitrateByPortal).to.be.false; // jshint ignore:line
expect(player.getSettings().streaming.abr.limitBitrateByPortal.enabled).to.be.false; // jshint ignore:line

player.updateSettings({
'streaming': {
'abr': {
'limitBitrateByPortal': true
'limitBitrateByPortal': { enabled: true }
}
}
});

limitBitrateByPortal = player.getSettings().streaming.abr.limitBitrateByPortal;
expect(limitBitrateByPortal).to.be.true; // jshint ignore:line
expect(player.getSettings().streaming.abr.limitBitrateByPortal.enabled).to.be.true; // jshint ignore:line
});

it('should configure usePixelRatioInLimitBitrateByPortal', function () {
let UsePixelRatioInLimitBitrateByPortal = player.getSettings().streaming.abr.usePixelRatioInLimitBitrateByPortal;
expect(UsePixelRatioInLimitBitrateByPortal).to.be.false; // jshint ignore:line
it('should configure usePixelRatio in limitBitrateByPortal', function () {
expect(player.getSettings().streaming.abr.limitBitrateByPortal.usePixelRatio).to.be.false; // jshint ignore:line

player.updateSettings({
'streaming': {
'abr': {
'usePixelRatioInLimitBitrateByPortal': true
'limitBitrateByPortal': { usePixelRatio: true }
}
}
});

UsePixelRatioInLimitBitrateByPortal = player.getSettings().streaming.abr.usePixelRatioInLimitBitrateByPortal;
expect(UsePixelRatioInLimitBitrateByPortal).to.be.true; // jshint ignore:line
expect(player.getSettings().streaming.abr.limitBitrateByPortal.usePixelRatio).to.be.true; // jshint ignore:line
});

it('should configure scalingFactor in limitBitrateByPortal', function () {
expect(player.getSettings().streaming.abr.limitBitrateByPortal.scalingFactor).to.equal(1);

player.updateSettings({
'streaming': {
'abr': {
'limitBitrateByPortal': { scalingFactor: 0.5 }
}
}
});

expect(player.getSettings().streaming.abr.limitBitrateByPortal.scalingFactor).to.equal(0.5);
});

it('should configure minimumBandwidthInBit in limitBitrateByPortal', function () {
expect(player.getSettings().streaming.abr.limitBitrateByPortal.minimumBandwidthInBit).to.equal(0); // jshint ignore:line

player.updateSettings({
'streaming': {
'abr': {
'limitBitrateByPortal': { minimumBandwidthInBit: 100000 }
}
}
});

expect(player.getSettings().streaming.abr.limitBitrateByPortal.minimumBandwidthInBit).to.equal(100000); // jshint ignore:line
});

it('should configure initialRepresentationRatioFor', function () {
Expand Down
56 changes: 55 additions & 1 deletion test/unit/streaming.controllers.AbrController.js
Expand Up @@ -270,6 +270,60 @@ describe('AbrController', function () {
expect(minAllowedIndex).to.be.equal(0);
});

it('should return the appropriate max allowed index for the portal scale', function () {
const mediaInfo = streamProcessor.getMediaInfo();

mediaInfo.streamInfo = streamProcessor.getStreamInfo();
mediaInfo.representationCount = 5;
mediaInfo.type = Constants.VIDEO;
abrCtrl.updateTopQualityIndex(mediaInfo);

const s = { streaming: { abr: { limitBitrateByPortal: { enabled: true } } } };
const streamId = streamProcessor.getStreamInfo().id;
settings.update(s);

videoModelMock.width = 1000;

expect(abrCtrl.getMaxAllowedIndexFor(Constants.VIDEO, streamId)).to.be.equal(4);

videoModelMock.width = 800;

expect(abrCtrl.getMaxAllowedIndexFor(Constants.VIDEO, streamId)).to.be.equal(1);

s.streaming.abr.limitBitrateByPortal.scalingFactor = 2;
settings.update(s);

expect(abrCtrl.getMaxAllowedIndexFor(Constants.VIDEO, streamId)).to.be.equal(4);

s.streaming.abr.limitBitrateByPortal.scalingFactor = 0.2;
settings.update(s);

expect(abrCtrl.getMaxAllowedIndexFor(Constants.VIDEO, streamId)).to.be.equal(0);
})

it('should limit the min allowed bitrate for the portal scale', function () {
const mediaInfo = streamProcessor.getMediaInfo();

mediaInfo.streamInfo = streamProcessor.getStreamInfo();
mediaInfo.representationCount = 5;
mediaInfo.type = Constants.VIDEO;
abrCtrl.updateTopQualityIndex(mediaInfo);

const s = { streaming: { abr: { limitBitrateByPortal: { enabled: true } } } };
const streamId = streamProcessor.getStreamInfo().id;
settings.update(s);

s.streaming.abr.limitBitrateByPortal.scalingFactor = 0.2;
settings.update(s);

expect(abrCtrl.getMaxAllowedIndexFor(Constants.VIDEO, streamId)).to.equal(0);

s.streaming.abr.limitBitrateByPortal.minimumBandwidthInBit = 2000000;
settings.update(s);

expect(abrCtrl.getMaxAllowedIndexFor(Constants.VIDEO, streamId)).to.equal(1);
})

it('should configure initial bitrate for video type', function () {
domStorageMock.setSavedBitrateSettings(Constants.VIDEO, 50);

Expand All @@ -290,7 +344,7 @@ describe('AbrController', function () {
expect(bitrateInfo.bitrate).to.be.equal(3000000);
expect(bitrateInfo.qualityIndex).to.be.equal(2);

const s = { streaming: { abr: { limitBitrateByPortal: true } } };
const s = { streaming: { abr: { limitBitrateByPortal: { enabled: true } } } };
settings.update(s);

bitrateInfo = abrCtrl.getTopBitrateInfoFor(Constants.VIDEO);
Expand Down

0 comments on commit d6a4415

Please sign in to comment.