diff --git a/README.md b/README.md index 45b682b48..e420d1a7f 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,15 @@ When `enableLowInitialPlaylist` is set to true, it will be used to select the lowest bitrate playlist initially. This helps to decrease playback start time. This setting is `false` by default. +##### limitRenditionByPlayerDimensions +* Type: `boolean` +* can be used as an initialization option + +When `limitRenditionByPlayerDimensions` is set to true, rendition +selection logic will take into account the player size and rendition +resolutions when making a decision. +This setting is `true` by default. + ### Runtime Properties Runtime properties are attached to the tech object when HLS is in use. You can get a reference to the HLS source handler like this: diff --git a/src/config.js b/src/config.js index 1c812a01f..ed95983b7 100644 --- a/src/config.js +++ b/src/config.js @@ -2,6 +2,8 @@ export default { GOAL_BUFFER_LENGTH: 30, MAX_GOAL_BUFFER_LENGTH: 60, GOAL_BUFFER_LENGTH_RATE: 1, + // 0.5 MB/s + INITIAL_BANDWIDTH: 4194304, // A fudge factor to apply to advertised playlist bitrates to account for // temporary flucations in client bandwidth BANDWIDTH_VARIANCE: 1.2, diff --git a/src/playlist-selectors.js b/src/playlist-selectors.js index 6a707ad51..286930f0d 100644 --- a/src/playlist-selectors.js +++ b/src/playlist-selectors.js @@ -124,6 +124,8 @@ export const comparePlaylistResolution = function(left, right) { * Current width of the player element * @param {Number} playerHeight * Current height of the player element + * @param {Boolean} limitRenditionByPlayerDimensions + * True if the player width and height should be used during the selection, false otherwise * @return {Playlist} the highest bitrate playlist less than the * currently detected bandwidth, accounting for some amount of * bandwidth variance @@ -131,7 +133,8 @@ export const comparePlaylistResolution = function(left, right) { export const simpleSelector = function(master, playerBandwidth, playerWidth, - playerHeight) { + playerHeight, + limitRenditionByPlayerDimensions) { // convert the playlists to an intermediary representation to make comparisons easier let sortedPlaylistReps = master.playlists.map((playlist) => { let width; @@ -190,6 +193,17 @@ export const simpleSelector = function(master, (rep) => rep.bandwidth === highestRemainingBandwidthRep.bandwidth )[0]; + // if we're not going to limit renditions by player size, make an early decision. + if (limitRenditionByPlayerDimensions === false) { + let chosenRep = ( + bandwidthBestRep || + enabledPlaylistReps[0] || + sortedPlaylistReps[0] + ); + + return chosenRep ? chosenRep.playlist : null; + } + // filter out playlists without resolution information let haveResolution = bandwidthPlaylistReps.filter((rep) => rep.width && rep.height); @@ -261,7 +275,8 @@ export const lastBandwidthSelector = function() { return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), - parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10)); + parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), + this.limitRenditionByPlayerDimensions); }; /** @@ -294,7 +309,8 @@ export const movingAverageBandwidthSelector = function(decay) { return simpleSelector(this.playlists.master, average, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), - parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10)); + parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), + this.limitRenditionByPlayerDimensions); }; }; diff --git a/src/videojs-http-streaming.js b/src/videojs-http-streaming.js index 557ecc4c9..27e0f699c 100644 --- a/src/videojs-http-streaming.js +++ b/src/videojs-http-streaming.js @@ -44,9 +44,6 @@ const Hls = { xhr: xhrFactory() }; -// 0.5 MB/s -const INITIAL_BANDWIDTH = 4194304; - // Define getter/setters for config properites [ 'GOAL_BUFFER_LENGTH', @@ -335,6 +332,7 @@ class HlsHandler extends Component { setOptions_() { // defaults this.options_.withCredentials = this.options_.withCredentials || false; + this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true; if (typeof this.options_.blacklistDuration !== 'number') { this.options_.blacklistDuration = 5 * 60; @@ -343,23 +341,24 @@ class HlsHandler extends Component { // start playlist selection at a reasonable bandwidth for // broadband internet (0.5 MB/s) or mobile (0.0625 MB/s) if (typeof this.options_.bandwidth !== 'number') { - this.options_.bandwidth = INITIAL_BANDWIDTH; + this.options_.bandwidth = Config.INITIAL_BANDWIDTH; } // If the bandwidth number is unchanged from the initial setting // then this takes precedence over the enableLowInitialPlaylist option this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && - this.options_.bandwidth === INITIAL_BANDWIDTH; + this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src - ['withCredentials', 'bandwidth'].forEach((option) => { + ['withCredentials', 'limitRenditionByPlayerDimensions', 'bandwidth'].forEach((option) => { if (typeof this.source_[option] !== 'undefined') { this.options_[option] = this.source_[option]; } }); this.bandwidth = this.options_.bandwidth; + this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions; } /** * called when player.src gets called, handle a new source diff --git a/test/configuration.test.js b/test/configuration.test.js index 6ca0410db..e85110fba 100644 --- a/test/configuration.test.js +++ b/test/configuration.test.js @@ -23,6 +23,11 @@ const options = [{ default: false, test: true, alt: false +}, { + name: 'limitRenditionByPlayerDimensions', + default: true, + test: false, + alt: false }, { name: 'bandwidth', default: 4194304, diff --git a/test/playlist-selectors.test.js b/test/playlist-selectors.test.js index 2fb31cab9..f60b6056e 100644 --- a/test/playlist-selectors.test.js +++ b/test/playlist-selectors.test.js @@ -163,7 +163,39 @@ test('simpleSelector switches up even without resolution information', function( { attributes: { BANDWIDTH: 1000 } } ]; - const selectedPlaylist = simpleSelector(master, 2000, 1, 1); + const selectedPlaylist = simpleSelector(master, 2000, 1, 1, false); assert.equal(selectedPlaylist, master.playlists[1], 'selected the correct playlist'); }); + +// A set of playlists that were defined using non-traditional encoding. +// The resolutions were selected using a per-title encoding technique +// that ensures the resolution maximizes quality at a given bitrate. +const trickyPlaylists = [ + { attributes: { BANDWIDTH: 2362080, RESOLUTION: { width: 1280, height: 720 } } }, + { attributes: { BANDWIDTH: 1390830, RESOLUTION: { width: 1280, height: 720 } } }, + { attributes: { BANDWIDTH: 866114, RESOLUTION: { width: 1024, height: 576 } } }, + { attributes: { BANDWIDTH: 573028, RESOLUTION: { width: 768, height: 432 } } }, + { attributes: { BANDWIDTH: 3482070, RESOLUTION: { width: 1920, height: 1080 } } }, + { attributes: { BANDWIDTH: 6151620, RESOLUTION: { width: 1920, height: 1080 } } } +]; + +test('simpleSelector limits using resolution information when it exists', function(assert) { + let master = this.hls.playlists.master; + + master.playlists = trickyPlaylists; + + const selectedPlaylist = simpleSelector(master, Config.INITIAL_BANDWIDTH, 444, 790, true); + + assert.equal(selectedPlaylist, master.playlists[3], 'selected the playlist with the lowest bandwidth higher than player resolution'); +}); + +test('simpleSelector can not limit based on resolution information', function(assert) { + let master = this.hls.playlists.master; + + master.playlists = trickyPlaylists; + + const selectedPlaylist = simpleSelector(master, Config.INITIAL_BANDWIDTH, 444, 790, false); + + assert.equal(selectedPlaylist, master.playlists[4], 'selected a playlist based solely on bandwidth'); +});