diff --git a/package-lock.json b/package-lock.json index e1c0ec218..22c78f3f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1171,6 +1171,25 @@ "magic-string": "^0.25.7" } }, + "@rollup/plugin-strip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-strip/-/plugin-strip-2.0.1.tgz", + "integrity": "sha512-+JJInHt/90Ta/ofCH+YHrI6nyDKe9jVzwBkmnakjDUMD+2QUTPHy60jep+kaMm4ARKWavtfmPbQp7e+xxAHU7g==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "estree-walker": "^2.0.1", + "magic-string": "^0.25.7" + }, + "dependencies": { + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + } + } + }, "@rollup/plugin-virtual": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-2.0.3.tgz", diff --git a/package.json b/package.json index 796f220c8..a6bfda653 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ }, "devDependencies": { "@rollup/plugin-replace": "^2.3.4", + "@rollup/plugin-strip": "^2.0.1", "@videojs/generator-helpers": "~2.0.1", "d3": "^3.4.8", "es5-shim": "^4.5.13", diff --git a/scripts/rollup.config.js b/scripts/rollup.config.js index 1db948d0d..41cb5f7e6 100644 --- a/scripts/rollup.config.js +++ b/scripts/rollup.config.js @@ -3,6 +3,7 @@ const worker = require('rollup-plugin-worker-factory'); const {terser} = require('rollup-plugin-terser'); const createTestData = require('./create-test-data.js'); const replace = require('@rollup/plugin-replace'); +const strip = require('@rollup/plugin-strip'); const CI_TEST_TYPE = process.env.CI_TEST_TYPE || ''; @@ -57,6 +58,9 @@ const options = { } defaults.module.unshift('replace'); + defaults.module.unshift('strip'); + defaults.browser.unshift('strip'); + return defaults; }, primedPlugins(defaults) { @@ -71,6 +75,9 @@ const options = { output: {comments: 'some'}, compress: {passes: 2} }), + strip: strip({ + functions: ['TEST_ONLY_*'] + }), createTestData: createTestData() }); diff --git a/src/master-playlist-controller.js b/src/master-playlist-controller.js index f42344110..949fb2538 100644 --- a/src/master-playlist-controller.js +++ b/src/master-playlist-controller.js @@ -140,7 +140,6 @@ export class MasterPlaylistController extends videojs.EventTarget { bandwidth, externVhs, useCueTags, - maxPlaylistRetries, blacklistDuration, enableLowInitialPlaylist, sourceType, @@ -152,6 +151,12 @@ export class MasterPlaylistController extends videojs.EventTarget { throw new Error('A non-empty playlist URL or JSON manifest string is required'); } + let { maxPlaylistRetries } = options; + + if (maxPlaylistRetries === null || typeof maxPlaylistRetries === 'undefined') { + maxPlaylistRetries = Infinity; + } + Vhs = externVhs; this.experimentalBufferBasedABR = Boolean(experimentalBufferBasedABR); diff --git a/src/playlist-selectors.js b/src/playlist-selectors.js index c1ed22639..2dabc0815 100644 --- a/src/playlist-selectors.js +++ b/src/playlist-selectors.js @@ -149,7 +149,7 @@ export const comparePlaylistResolution = function(left, right) { * currently detected bandwidth, accounting for some amount of * bandwidth variance */ -export const simpleSelector = function( +export let simpleSelector = function( master, playerBandwidth, playerWidth, @@ -313,6 +313,16 @@ export const simpleSelector = function( return null; }; +export const TEST_ONLY_SIMPLE_SELECTOR = (newSimpleSelector) => { + const oldSimpleSelector = simpleSelector; + + simpleSelector = newSimpleSelector; + + return function resetSimpleSelector() { + simpleSelector = oldSimpleSelector; + }; +}; + // Playlist Selectors /** diff --git a/test/master-playlist-controller.test.js b/test/master-playlist-controller.test.js index 5fe28396c..ae4e69898 100644 --- a/test/master-playlist-controller.test.js +++ b/test/master-playlist-controller.test.js @@ -5796,6 +5796,70 @@ QUnit.test('true if duration < 30', function(assert) { assert.ok(mpc.shouldSwitchToMedia_(nextPlaylist), 'should switch'); }); +QUnit.test('maxPlaylistRetries defaults to Infinity when no value or null/undefined is provided', function(assert) { + const playerNull = createPlayer({ + html5: { + vhs: { + maxPlaylistRetries: null + } + } + }); + + const playerUndefined = createPlayer({ + html5: { + vhs: { + maxPlaylistRetries: undefined + } + } + }); + + const playerNoValue = createPlayer(); + + playerNull.src({ + src: 'manifest/master.m3u8', + type: 'application/vnd.apple.mpegurl' + }); + + this.clock.tick(1); + + playerUndefined.src({ + src: 'manifest/master.m3u8', + type: 'application/vnd.apple.mpegurl' + }); + + this.clock.tick(1); + + playerNoValue.src({ + src: 'manifest/master.m3u8', + type: 'application/vnd.apple.mpegurl' + }); + + this.clock.tick(1); + + assert.equal(playerNull.tech_.vhs.masterPlaylistController_.maxPlaylistRetries, Infinity, 'maxPlaylistRetries defaults to Infinity when null is provided as the option value'); + assert.equal(playerUndefined.tech_.vhs.masterPlaylistController_.maxPlaylistRetries, Infinity, 'maxPlaylistRetries defaults to Infinity when undefined is provided as the option value'); + assert.equal(playerNoValue.tech_.vhs.masterPlaylistController_.maxPlaylistRetries, Infinity, 'maxPlaylistRetries defaults to Infinity when no value is provided'); +}); + +QUnit.test('maxPlaylistRetries is set when zero is passed as the option\'s value', function(assert) { + const player = createPlayer({ + html5: { + vhs: { + maxPlaylistRetries: 0 + } + } + }); + + player.src({ + src: 'manifest/master.m3u8', + type: 'application/vnd.apple.mpegurl' + }); + + this.clock.tick(1); + + assert.equal(player.tech_.vhs.masterPlaylistController_.maxPlaylistRetries, 0, 'maxPlaylistRetries was set to zero'); +}); + QUnit.test('true duration < 16 with experimentalBufferBasedABR', function(assert) { const mpc = this.masterPlaylistController; const nextPlaylist = {id: 'foo', endList: true}; diff --git a/test/playlist-selectors.test.js b/test/playlist-selectors.test.js index 9db0f2006..c7fddb987 100644 --- a/test/playlist-selectors.test.js +++ b/test/playlist-selectors.test.js @@ -1,6 +1,7 @@ import { module, test } from 'qunit'; import document from 'global/document'; import { + TEST_ONLY_SIMPLE_SELECTOR, simpleSelector, movingAverageBandwidthSelector, minRebufferMaxBandwidthSelector, @@ -64,6 +65,50 @@ test('Exponential moving average has a configurable decay parameter', function(a assert.equal(playlist.attributes.BANDWIDTH, 50, 'selected the middle playlist'); }); +test('Calling exponential moving average wont decay average unless new bandwidth data was provided', function(assert) { + let playlist; + const simSel = simpleSelector; + const bandwidthAverages = []; + + const resetSimpleSelector = TEST_ONLY_SIMPLE_SELECTOR((...args) => { + // second argument to simpleSelector is the average + bandwidthAverages.push(args[1]); + return simSel(...args); + }); + + this.vhs.playlists.master.playlists = [ + { attributes: { BANDWIDTH: 1 } }, + { attributes: { BANDWIDTH: 50 } }, + { attributes: { BANDWIDTH: 100 } } + ]; + + const fiftyPercentDecay = movingAverageBandwidthSelector(0.50); + + this.vhs.systemBandwidth = 50 * Config.BANDWIDTH_VARIANCE + 1; + playlist = fiftyPercentDecay.call(this.vhs); + assert.equal(playlist.attributes.BANDWIDTH, 50, 'selected the middle playlist'); + + this.vhs.systemBandwidth = 1000 * Config.BANDWIDTH_VARIANCE + 1; + playlist = fiftyPercentDecay.call(this.vhs); + assert.equal(playlist.attributes.BANDWIDTH, 100, 'selected the top playlist'); + + // using the systemBandwidth values above, 50->1000 + // we decay into 1000 after 50 iterations + let i = 50; + + while (i--) { + playlist = fiftyPercentDecay.call(this.vhs); + } + + assert.equal( + bandwidthAverages[bandwidthAverages.length - 1], + bandwidthAverages[1], + 'bandwidth should only change when we get new bandwidth data' + ); + + resetSimpleSelector(); +}); + test( 'minRebufferMaxBandwidthSelector picks highest rendition without rebuffering', function(assert) {