diff --git a/AUTHORS b/AUTHORS index f1a66ee7fc..87353cc20f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -44,6 +44,7 @@ Google Inc. <*@google.com> Itay Kinnrot Jaeseok Lee Jason Palmer +Jean-alexandre Iragne Jesper Haug Karsrud Johan Sundström Jonas Birmé diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 565b13a51c..c70b119a6a 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -64,6 +64,7 @@ Isaac Ramirez Jacob Trimble Jaeseok Lee Jason Palmer +Jean-alexandre Iragne Jeffrey Swan Jesper Haug Karsrud Jesse Gunsch diff --git a/demo/common/message_ids.js b/demo/common/message_ids.js index 4cb79b17ab..5d1c2405e6 100644 --- a/demo/common/message_ids.js +++ b/demo/common/message_ids.js @@ -174,6 +174,7 @@ shakaDemo.MessageIds = { BANDWIDTH_UPGRADE: 'DEMO_BANDWIDTH_UPGRADE', BUFFER_BEHIND: 'DEMO_BUFFER_BEHIND', BUFFERING_GOAL: 'DEMO_BUFFERING_GOAL', + CLEAR_BUFFER_SWITCH: 'DEMO_CLEAR_BUFFER_SWITCH', CLOCK_SYNC_URI: 'DEMO_CLOCK_SYNC_URI', CMCD_SECTION_HEADER: 'DEMO_CMCD_SECTION_HEADER', CONNECTION_TIMEOUT: 'DEMO_CONNECTION_TIMEOUT', @@ -269,6 +270,7 @@ shakaDemo.MessageIds = { RESTRICT_TO_ELEMENT_SIZE: 'DEMO_RESTRICT_TO_ELEMENT_SIZE', RESTRICT_TO_SCREEN_SIZE: 'DEMO_RESTRICT_TO_SCREEN_SIZE', RESTRICTIONS_SECTION_HEADER: 'DEMO_RESTRICTIONS_SECTION_HEADER', + SAFE_MARGIN_SWITCH: 'DEMO_SAFE_MARGIN_SWITCH', SAFE_SEEK_OFFSET: 'DEMO_SAFE_SEEK_OFFSET', SAFE_SKIP_DISTANCE: 'DEMO_SAFE_SKIP_DISTANCE', SEGMENT_RELATIVE_VTT_TIMING: 'DEMO_SEGMENT_RELATIVE_VTT_TIMING', diff --git a/demo/config.js b/demo/config.js index 58874c17f2..aaac157aa4 100644 --- a/demo/config.js +++ b/demo/config.js @@ -295,7 +295,12 @@ shakaDemo.Config = class { .addBoolInput_(MessageIds.RESTRICT_TO_SCREEN_SIZE, 'abr.restrictToScreenSize') .addBoolInput_(MessageIds.IGNORE_DEVICE_PIXEL_RATIO, - 'abr.ignoreDevicePixelRatio'); + 'abr.ignoreDevicePixelRatio') + .addBoolInput_(MessageIds.CLEAR_BUFFER_SWITCH, + 'abr.clearBufferSwitch') + .addNumberInput_(MessageIds.SAFE_MARGIN_SWITCH, + 'abr.safeMarginSwitch', + /* canBeDecimal= */ true); this.addRetrictionsSection_('abr', MessageIds.ADAPTATION_RESTRICTIONS_SECTION_HEADER); } diff --git a/demo/locales/en.json b/demo/locales/en.json index d15022912b..baf59f685c 100644 --- a/demo/locales/en.json +++ b/demo/locales/en.json @@ -39,6 +39,7 @@ "DEMO_CLEAR": "No DRM protection", "DEMO_CLEAR_KEY": "Clear Key DRM", "DEMO_CLOCK_SYNC_URI": "Clock Sync URI", + "DEMO_CLEAR_BUFFER_SWITCH": "Clear video buffer on abr rendition switch", "DEMO_CMCD_SECTION_HEADER": "CMCD", "DEMO_COMPILED_DEBUG": "Compiled (Debug)", "DEMO_COMPILED_RELEASE": "Compiled (Release)", @@ -212,6 +213,7 @@ "DEMO_RESTRICT_TO_ELEMENT_SIZE": "Restrict to element size", "DEMO_RESTRICT_TO_SCREEN_SIZE": "Restrict to screen size", "DEMO_RESTRICTIONS_SECTION_HEADER": "Restrictions", + "DEMO_SAFE_MARGIN_SWITCH": "Safe margin on abr switch rendition", "DEMO_SAFE_SEEK_OFFSET": "Safe Seek Offset", "DEMO_SAFE_SKIP_DISTANCE": "Safe Skip Distance", "DEMO_SEGMENT_RELATIVE_VTT_TIMING": "Enable segment-relative VTT Timing", diff --git a/demo/locales/source.json b/demo/locales/source.json index 67c08d8fea..6aa5f75384 100644 --- a/demo/locales/source.json +++ b/demo/locales/source.json @@ -159,6 +159,10 @@ "description": "The name of a configuration value.", "message": "Clock Sync URI" }, + "DEMO_CLEAR_BUFFER_SWITCH": { + "description": "Clear video buffer on abr rendition switch.", + "message": "Clear buffer" + }, "DEMO_CMCD_SECTION_HEADER": { "description": "The header for a section of configuration values.", "message": "[JARGON:CMCD]" @@ -847,6 +851,10 @@ "description": "The header for a section of configuration values.", "message": "Restrictions" }, + "DEMO_SAFE_MARGIN_SWITCH": { + "description": "Safe margin on abr switch rendition", + "message": "safe margin" + }, "DEMO_SAFE_SEEK_OFFSET": { "description": "The name of a configuration value.", "message": "Safe Seek Offset" diff --git a/externs/shaka/player.js b/externs/shaka/player.js index e19af7c06d..7bee748aa9 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -1265,7 +1265,9 @@ shaka.extern.AdsConfiguration; * advanced: shaka.extern.AdvancedAbrConfiguration, * restrictToElementSize: boolean, * restrictToScreenSize: boolean, - * ignoreDevicePixelRatio: boolean + * ignoreDevicePixelRatio: boolean, + * clearBufferSwitch: boolean, + * safeMarginSwitch: number * }} * * @property {boolean} enabled @@ -1307,6 +1309,19 @@ shaka.extern.AdsConfiguration; * If true,device pixel ratio is ignored when restricting the quality to * media element size or screen size. * Defaults false. + * @property {boolean} clearBufferSwitch + * If true, the buffer will be cleared during the switch. + * The default automatic behavior is false to have a smoother transition. + * On some device it's better to clear buffer. + * Defaults false. + * @property {number} safeMarginSwitch + * Optional amount of buffer (in seconds) to + * retain when clearing the buffer during the automatic switch. + * Useful for switching variant quickly without causing a buffering event. + * Defaults to 0 if not provided. Ignored if clearBuffer is false. + * Can cause hiccups on some browsers if chosen too small, e.g. + * The amount of two segments is a fair minimum to consider as safeMargin + * value. * @exportDoc */ shaka.extern.AbrConfiguration; diff --git a/lib/abr/simple_abr_manager.js b/lib/abr/simple_abr_manager.js index dfc02f83ce..ceeeeca91a 100644 --- a/lib/abr/simple_abr_manager.js +++ b/lib/abr/simple_abr_manager.js @@ -64,7 +64,8 @@ shaka.abr.SimpleAbrManager = class { } const chosenVariant = this.chooseVariant(); if (chosenVariant) { - this.switch_(chosenVariant); + this.switch_(chosenVariant, this.config_.clearBufferSwitch, + this.config_.safeMarginSwitch); } } }; @@ -106,7 +107,8 @@ shaka.abr.SimpleAbrManager = class { if (this.config_.restrictToElementSize) { const chosenVariant = this.chooseVariant(); if (chosenVariant) { - this.switch_(chosenVariant); + this.switch_(chosenVariant, this.config_.clearBufferSwitch, + this.config_.safeMarginSwitch); } } }); @@ -382,7 +384,8 @@ shaka.abr.SimpleAbrManager = class { 'Calling switch_(), bandwidth=' + currentBandwidthKbps + ' kbps'); // If any of these chosen streams are already chosen, Player will filter // them out before passing the choices on to StreamingEngine. - this.switch_(chosenVariant); + this.switch_(chosenVariant, this.config_.clearBufferSwitch, + this.config_.safeMarginSwitch); } } diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index 59a732aaee..9b0183011c 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -288,6 +288,8 @@ shaka.util.PlayerConfiguration = class { restrictToElementSize: false, restrictToScreenSize: false, ignoreDevicePixelRatio: false, + clearBufferSwitch: false, + safeMarginSwitch: 0, }; const cmcd = { diff --git a/test/abr/simple_abr_manager_unit.js b/test/abr/simple_abr_manager_unit.js index d232c0b54c..148a0c23d4 100644 --- a/test/abr/simple_abr_manager_unit.js +++ b/test/abr/simple_abr_manager_unit.js @@ -158,7 +158,7 @@ describe('SimpleAbrManager', () => { // and variant 5 - for bandwidth = 6e5 const expectedVariant = (bandwidth == 6e5) ? variants[5] : variants[2]; - expect(switchCallback).toHaveBeenCalledWith(expectedVariant); + expect(switchCallback).toHaveBeenCalledWith(expectedVariant, false, 0); }); } @@ -205,7 +205,7 @@ describe('SimpleAbrManager', () => { // Expect variants 4 to be chosen const expectedVariant = variants[3]; - expect(switchCallback).toHaveBeenCalledWith(expectedVariant); + expect(switchCallback).toHaveBeenCalledWith(expectedVariant, false, 0); }); it('does not call switchCallback() if not enabled', () => { @@ -283,7 +283,35 @@ describe('SimpleAbrManager', () => { // The second parameter is missing to indicate that the buffer should not be // cleared. - expect(switchCallback).toHaveBeenCalledWith(jasmine.any(Object)); + expect(switchCallback).toHaveBeenCalledWith(jasmine.any(Object), false, 0); + }); + + it('does clear the buffer on upgrade with safemargin to 4', () => { + // Simulate some segments being downloaded at a high rate, to trigger an + // upgrade. + const bandwidth = 5e5; + const bytesPerSecond = sufficientBWMultiplier * bandwidth / 8.0; + + // Set the clear buffer to true and the safe margin to 4. + config.clearBufferSwitch = true; + config.safeMarginSwitch = 4; + abrManager.configure(config); + + abrManager.setVariants(variants); + abrManager.chooseVariant(); + + abrManager.segmentDownloaded(1000, bytesPerSecond); + abrManager.segmentDownloaded(1000, bytesPerSecond); + + abrManager.enable(); + + // Make another call to segmentDownloaded(). switchCallback() will be + // called to upgrade. + abrManager.segmentDownloaded(1000, bytesPerSecond); + + // The second parameter is missing to indicate that the buffer should not be + // cleared. + expect(switchCallback).toHaveBeenCalledWith(jasmine.any(Object), true, 4); }); it('does not clear the buffer on downgrade', () => { @@ -310,7 +338,7 @@ describe('SimpleAbrManager', () => { // The second parameter is missing to indicate that the buffer should not be // cleared. - expect(switchCallback).toHaveBeenCalledWith(jasmine.any(Object)); + expect(switchCallback).toHaveBeenCalledWith(jasmine.any(Object), false, 0); }); it('will respect restrictions', () => {