diff --git a/README.md b/README.md index d581d4958..00d9bd0f9 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Video.js Compatibility: 7.x, 8.x - [enableLowInitialPlaylist](#enablelowinitialplaylist) - [limitRenditionByPlayerDimensions](#limitrenditionbyplayerdimensions) - [useDevicePixelRatio](#usedevicepixelratio) + - [customPixelRatio](#custompixelratio) - [allowSeeksWithinUnsafeLiveWindow](#allowseekswithinunsafelivewindow) - [customTagParsers](#customtagparsers) - [customTagMappers](#customtagmappers) @@ -404,6 +405,18 @@ This setting is `true` by default. If true, this will take the device pixel ratio into account when doing rendition switching. This means that if you have a player with the width of `540px` in a high density display with a device pixel ratio of 2, a rendition of `1080p` will be allowed. This setting is `false` by default. +##### customPixelRatio +* Type: `number` +* can be used as an initialization option. + +If set, this will take the initial player dimensions and multiply it by a custom ratio when the player automatically selects renditions. This means that if you have a player where the dimension is `540p`, with a custom pixel ratio of `2`, a rendition of `1080p` or a lower rendition closest to this value will be chosen. Additionally, if you have a player where the dimension is `540p`, with a custom pixel ratio of `0.5`, a rendition of `270p` or a lower rendition closest to this value will be chosen. When the custom pixel ratio is 0, the lowest available rendition will be selected. + +It is worth noting that if the player dimension multiplied by the custom pixel ratio is greater than any available rendition resolution, a rendition will be selected based on bandwidth, and the player dimension will be disregarded. + +`limitRenditionByPlayerDimensions` must be `true` in order for this feature to be enabled. This is the default value. + +If `useDevicePixelRatio` is set to `true`, the custom pixel ratio will be prioritized and overwrite any previous pixel ratio. + ##### allowSeeksWithinUnsafeLiveWindow * Type: `boolean` * can be used as a source option diff --git a/src/playlist-selectors.js b/src/playlist-selectors.js index 70dafd773..2d7431e2b 100644 --- a/src/playlist-selectors.js +++ b/src/playlist-selectors.js @@ -364,7 +364,11 @@ export const TEST_ONLY_SIMPLE_SELECTOR = (newSimpleSelector) => { * bandwidth variance */ export const lastBandwidthSelector = function() { - const pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1; + let pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1; + + if (!isNaN(this.customPixelRatio)) { + pixelRatio = this.customPixelRatio; + } return simpleSelector( this.playlists.main, @@ -399,7 +403,11 @@ export const movingAverageBandwidthSelector = function(decay) { } return function() { - const pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1; + let pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1; + + if (!isNaN(this.customPixelRatio)) { + pixelRatio = this.customPixelRatio; + } if (average < 0) { average = this.systemBandwidth; diff --git a/src/videojs-http-streaming.js b/src/videojs-http-streaming.js index e79579a5f..df14c4ea2 100644 --- a/src/videojs-http-streaming.js +++ b/src/videojs-http-streaming.js @@ -738,6 +738,7 @@ class VhsHandler extends Component { [ 'withCredentials', 'useDevicePixelRatio', + 'customPixelRatio', 'limitRenditionByPlayerDimensions', 'bandwidth', 'customTagParsers', @@ -761,6 +762,13 @@ class VhsHandler extends Component { this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions; this.useDevicePixelRatio = this.options_.useDevicePixelRatio; + + const customPixelRatio = this.options_.customPixelRatio; + + // Ensure the custom pixel ratio is a number greater than or equal to 0 + if (typeof customPixelRatio === 'number' && customPixelRatio >= 0) { + this.customPixelRatio = customPixelRatio; + } } // alias for public method to set options setOptions(options = {}) { diff --git a/test/configuration.test.js b/test/configuration.test.js index 28d16da4a..07ae9d1a2 100644 --- a/test/configuration.test.js +++ b/test/configuration.test.js @@ -31,7 +31,14 @@ const options = [{ default: false, test: true, alt: false -}, { +}, +{ + name: 'customPixelRatio', + default: undefined, + test: 1, + alt: 0.5 +}, +{ name: 'bandwidth', default: 4194304, test: 5, diff --git a/test/playlist-selectors.test.js b/test/playlist-selectors.test.js index f755c5625..e1018feb9 100644 --- a/test/playlist-selectors.test.js +++ b/test/playlist-selectors.test.js @@ -1,11 +1,13 @@ import { module, test } from 'qunit'; import document from 'global/document'; +import window from 'global/window'; import { TEST_ONLY_SIMPLE_SELECTOR, simpleSelector, movingAverageBandwidthSelector, minRebufferMaxBandwidthSelector, - lowestBitrateCompatibleVariantSelector + lowestBitrateCompatibleVariantSelector, + lastBandwidthSelector } from '../src/playlist-selectors'; import Config from '../src/config'; @@ -325,5 +327,116 @@ test('simpleSelector leastPixelDiffSelector selects least pixel diff resolution. assert.equal(pixelDiff, main.playlists[5], '1280w x 720h pixel diff higher bandwidth'); assert.equal(nonPixelDiff, main.playlists[5], '1280w x 720h resolution plus higher bandwidth'); +}); + +test('lastBandwidthSelector uses customPixelRatio to pick rendition', function(assert) { + let playlist; + const bandwidth = 20; + + const oldGetComputedStyle = window.getComputedStyle; + + // Mock a 540p player. + window.getComputedStyle = function() { + return { + width: 960, + height: 540 + }; + }; + + // Ensure system bandwith is greater than the rendition bandwidths. + this.vhs.systemBandwidth = bandwidth + 10; + // This is true by default. + this.vhs.limitRenditionByPlayerDimensions = true; + + this.vhs.playlists.main.playlists = [ + { attributes: { BANDWIDTH: bandwidth, RESOLUTION: { width: 480, height: 270 } } }, + { attributes: { BANDWIDTH: bandwidth, RESOLUTION: { width: 960, height: 540 } } }, + { attributes: { BANDWIDTH: bandwidth, RESOLUTION: { width: 1440, height: 810 } } }, + { attributes: { BANDWIDTH: bandwidth, RESOLUTION: { width: 1920, height: 1080 } } } + ]; + + // Picks the lowest possible rendition + this.vhs.customPixelRatio = 0; + playlist = lastBandwidthSelector.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 270, 'selected the lowest rendition'); + + this.vhs.customPixelRatio = 0.5; + playlist = lastBandwidthSelector.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 270, 'selected the rendition with 270p'); + + this.vhs.customPixelRatio = 1; + playlist = lastBandwidthSelector.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 540, 'selected the rendition with 540p'); + + this.vhs.customPixelRatio = 1.5; + playlist = lastBandwidthSelector.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 810, 'selected the rendition with 810p'); + + this.vhs.customPixelRatio = 2; + playlist = lastBandwidthSelector.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 1080, 'selected the rendition with 1080p'); + + // Since the customPixelRatio sets the player dimension higher than any available rendition, + // This value is entirely based on bandwidth. + this.vhs.customPixelRatio = 4; + playlist = lastBandwidthSelector.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 270, 'selected the rendition based on bandwidth'); + + window.getComputedStyle = oldGetComputedStyle; +}); + +test('movingAverageBandwidthSelector uses customPixelRatio to pick rendition', function(assert) { + let playlist; + const bandwidth = 20; + const selectionFunction = movingAverageBandwidthSelector(1); + const oldGetComputedStyle = window.getComputedStyle; + + // Mock a 540p player. + window.getComputedStyle = function() { + return { + width: 960, + height: 540 + }; + }; + + // Ensure system bandwith is greater than the rendition bandwidths. + this.vhs.systemBandwidth = bandwidth + 10; + // This is true by default. + this.vhs.limitRenditionByPlayerDimensions = true; + + this.vhs.playlists.main.playlists = [ + { attributes: { BANDWIDTH: bandwidth, RESOLUTION: { width: 480, height: 270 } } }, + { attributes: { BANDWIDTH: bandwidth, RESOLUTION: { width: 960, height: 540 } } }, + { attributes: { BANDWIDTH: bandwidth, RESOLUTION: { width: 1440, height: 810 } } }, + { attributes: { BANDWIDTH: bandwidth, RESOLUTION: { width: 1920, height: 1080 } } } + ]; + + // Picks the lowest possible rendition + this.vhs.customPixelRatio = 0; + playlist = selectionFunction.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 270, 'selected the lowest rendition'); + + this.vhs.customPixelRatio = 0.5; + playlist = selectionFunction.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 270, 'selected the rendition with 270p'); + + this.vhs.customPixelRatio = 1; + playlist = selectionFunction.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 540, 'selected the rendition with 540p'); + + this.vhs.customPixelRatio = 1.5; + playlist = selectionFunction.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 810, 'selected the rendition with 810p'); + + this.vhs.customPixelRatio = 2; + playlist = selectionFunction.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 1080, 'selected the rendition with 1080p'); + + // Since the customPixelRatio sets the player dimension higher than any available rendition, + // This value is entirely based on bandwidth. + this.vhs.customPixelRatio = 4; + playlist = selectionFunction.call(this.vhs); + assert.equal(playlist.attributes.RESOLUTION.height, 270, 'selected the rendition based on bandwidth'); + window.getComputedStyle = oldGetComputedStyle; });