From 2260aa9cf6a47eebecfc023a69be7934de77362e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Tue, 19 Nov 2024 10:47:18 +0100 Subject: [PATCH] feat: Update usage of minBufferTime according to the DASH spec (#7616) Related to https://github.com/shaka-project/shaka-player/issues/7602#issuecomment-2479518970 From 23009-1: The value of the minimum buffer time does not provide any instructions to the client on how long to buffer the media. The value however describes how much buffer a client should have under ideal network conditions. As such, MBT is not describing the burstiness or jitter in the network, it is describing the burstiness or jitter in the content encoding. Together with the BW value, it is a property of the content. Using the "leaky bucket" model, it is the size of the bucket that makes BW true, given the way the content is encoded --- docs/tutorials/faq.md | 13 ----- docs/tutorials/manifest-parser.md | 1 - .../tutorials/network-and-buffering-config.md | 3 -- externs/shaka/manifest.js | 6 --- externs/shaka/player.js | 3 +- lib/dash/dash_parser.js | 17 +++--- lib/hls/hls_parser.js | 1 - lib/media/playhead.js | 8 +-- lib/media/streaming_engine.js | 8 +-- lib/mss/mss_parser.js | 1 - lib/offline/manifest_converter.js | 1 - lib/player.js | 16 ++---- test/dash/dash_parser_manifest_unit.js | 54 +------------------ test/media/playhead_unit.js | 3 +- test/media/streaming_engine_integration.js | 1 - test/media/streaming_engine_unit.js | 6 --- test/offline/storage_compatibility_unit.js | 1 - test/test/util/manifest_generator.js | 2 - test/test/util/streaming_engine_util.js | 1 - 19 files changed, 16 insertions(+), 130 deletions(-) diff --git a/docs/tutorials/faq.md b/docs/tutorials/faq.md index 9faddee816..eb9824f6cf 100644 --- a/docs/tutorials/faq.md +++ b/docs/tutorials/faq.md @@ -191,19 +191,6 @@ the ad blocker in compiled mode as well.
-**Q:** Why does some DASH content take a long time to start playback? - -**A:** Shaka Player honors the `minBufferTime` field in DASH. If this field is -set to a large value, Shaka Player will buffer that much content before -beginning playback. To override this behavior and ignore the `minBufferTime` -field, we offer the following configuration: - -```js -player.configure('manifest.dash.ignoreMinBufferTime', true); -``` - -
- **Q:** My HLS stream is failing on Chrome, with a chunk demuxer append failed error. diff --git a/docs/tutorials/manifest-parser.md b/docs/tutorials/manifest-parser.md index a596d0b477..8d8f709749 100644 --- a/docs/tutorials/manifest-parser.md +++ b/docs/tutorials/manifest-parser.md @@ -256,7 +256,6 @@ MyManifestParser.prototype.loadManifest_ = function(data) { return { presentationTimeline: timeline, - minBufferTime: 5, // seconds offlineSessionIds: [], variants: [ this.loadVariant_(true, true), diff --git a/docs/tutorials/network-and-buffering-config.md b/docs/tutorials/network-and-buffering-config.md index 14b690fd33..af2f0c7140 100644 --- a/docs/tutorials/network-and-buffering-config.md +++ b/docs/tutorials/network-and-buffering-config.md @@ -73,9 +73,6 @@ This is a minimum; if the stream's max segment size is longer than the *NOTES:* - *`rebufferingGoal` should always be less than `bufferingGoal`.* - - *A DASH manifest's `minBufferTime`, if greater, overrides `rebufferingGoal`.* - - *You can ignore `minBufferTime` by setting the - `manifest.dash.ignoreMinBufferTime` configuration to true.* All of these settings should be customized for your application. The default values are very conservative. diff --git a/externs/shaka/manifest.js b/externs/shaka/manifest.js index e725cb5677..6f41f93c32 100644 --- a/externs/shaka/manifest.js +++ b/externs/shaka/manifest.js @@ -17,7 +17,6 @@ * textStreams: !Array., * imageStreams: !Array., * offlineSessionIds: !Array., - * minBufferTime: number, * sequenceMode: boolean, * ignoreManifestTimestampsInSegmentsMode: boolean, * type: string, @@ -76,11 +75,6 @@ * @property {!Array.} offlineSessionIds * Defaults to [].
* An array of EME sessions to load for offline playback. - * @property {number} minBufferTime - * Defaults to 0.
- * The minimum number of seconds of content that must be buffered before - * playback can begin. Can be overridden by a higher value from the Player - * configuration. * @property {boolean} sequenceMode * If true, we will append the media segments using sequence mode; that is to * say, ignoring any timestamps inside the media files. diff --git a/externs/shaka/player.js b/externs/shaka/player.js index bdaf20b6f9..fd87ecca1c 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -1104,8 +1104,7 @@ shaka.extern.xml.Node; * Defaults to false. * @property {boolean} ignoreMinBufferTime * If true will cause DASH parser to ignore minBufferTime from - * manifest. It allows player config to take precedence over manifest for - * rebufferingGoal. + * manifest. *
* Defaults to false. * @property {boolean} autoCorrectDrift diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index a816901781..069affbfd1 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -643,13 +643,6 @@ shaka.dash.DashParser = class { } this.manifestPatchContext_.availabilityTimeOffset = availabilityTimeOffset; - const ignoreMinBufferTime = this.config_.dash.ignoreMinBufferTime; - let minBufferTime = 0; - if (!ignoreMinBufferTime) { - minBufferTime = - TXml.parseAttr(mpd, 'minBufferTime', TXml.parseDuration) || 0; - } - this.updatePeriod_ = /** @type {number} */ (TXml.parseAttr( mpd, 'minimumUpdatePeriod', TXml.parseDuration, -1)); @@ -702,6 +695,12 @@ shaka.dash.DashParser = class { } } } else { + const ignoreMinBufferTime = this.config_.dash.ignoreMinBufferTime; + let minBufferTime = 0; + if (!ignoreMinBufferTime) { + minBufferTime = + TXml.parseAttr(mpd, 'minBufferTime', TXml.parseDuration) || 0; + } // DASH IOP v3.0 suggests using a default delay between minBufferTime // and timeShiftBufferDepth. This is literally the range of all // feasible choices for the value. Nothing older than @@ -799,10 +798,7 @@ shaka.dash.DashParser = class { await contentSteeringPromise; - // Set minBufferTime to 0 for low-latency DASH live stream to achieve the - // best latency if (this.lowLatencyMode_) { - minBufferTime = 0; const presentationDelay = suggestedPresentationDelay != null ? suggestedPresentationDelay : this.config_.defaultPresentationDelay; presentationTimeline.setDelay(presentationDelay); @@ -818,7 +814,6 @@ shaka.dash.DashParser = class { textStreams: this.periodCombiner_.getTextStreams(), imageStreams: this.periodCombiner_.getImageStreams(), offlineSessionIds: [], - minBufferTime: minBufferTime || 0, sequenceMode: this.config_.dash.sequenceMode, ignoreManifestTimestampsInSegmentsMode: false, type: shaka.media.ManifestParser.DASH, diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 3834cc560f..14a06556d8 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -1002,7 +1002,6 @@ shaka.hls.HlsParser = class { textStreams, imageStreams, offlineSessionIds: [], - minBufferTime: 0, sequenceMode: this.config_.hls.sequenceMode, ignoreManifestTimestampsInSegmentsMode: this.config_.hls.ignoreManifestTimestampsInSegmentsMode, diff --git a/lib/media/playhead.js b/lib/media/playhead.js index 35b0a90108..0d56428480 100644 --- a/lib/media/playhead.js +++ b/lib/media/playhead.js @@ -231,9 +231,6 @@ shaka.media.MediaSourcePlayhead = class { /** @private {shaka.media.PresentationTimeline} */ this.timeline_ = manifest.presentationTimeline; - /** @private {number} */ - this.minBufferTime_ = manifest.minBufferTime || 0; - /** @private {?shaka.extern.StreamingConfiguration} */ this.config_ = config; @@ -482,10 +479,7 @@ shaka.media.MediaSourcePlayhead = class { const isBuffered = (playheadTime) => shaka.media.TimeRangesUtils.isBuffered( this.mediaElement_.buffered, playheadTime); - const rebufferingGoal = Math.max( - this.minBufferTime_, - this.config_.rebufferingGoal); - + const rebufferingGoal = this.config_.rebufferingGoal; const safeSeekOffset = this.config_.safeSeekOffset; let start = this.timeline_.getSeekRangeStart(); diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index eca072ed05..43a788b0ba 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -779,9 +779,7 @@ shaka.media.StreamingEngine = class { // If the new segment can be finished in time without risking a buffer // underflow, we should abort the old one and switch. const bufferedAhead = (bufferEnd || 0) - presentationTime; - const safetyBuffer = Math.max( - this.manifest_.minBufferTime || 0, - this.config_.rebufferingGoal); + const safetyBuffer = this.config_.rebufferingGoal; const safeBufferedAhead = bufferedAhead - safetyBuffer; if (timeToFetchNewSegment < safeBufferedAhead) { return true; @@ -1366,9 +1364,7 @@ shaka.media.StreamingEngine = class { 'bufferedAhead=' + bufferedAhead); const unscaledBufferingGoal = Math.max( - this.manifest_.minBufferTime || 0, - this.config_.rebufferingGoal, - this.config_.bufferingGoal); + this.config_.rebufferingGoal, this.config_.bufferingGoal); const scaledBufferingGoal = Math.max(1, unscaledBufferingGoal * this.bufferingScale_); diff --git a/lib/mss/mss_parser.js b/lib/mss/mss_parser.js index f8be5fc390..a2cea2585d 100644 --- a/lib/mss/mss_parser.js +++ b/lib/mss/mss_parser.js @@ -431,7 +431,6 @@ shaka.mss.MssParser = class { textStreams: context.textStreams, imageStreams: [], offlineSessionIds: [], - minBufferTime: 0, sequenceMode: this.config_.mss.sequenceMode, ignoreManifestTimestampsInSegmentsMode: false, type: shaka.media.ManifestParser.MSS, diff --git a/lib/offline/manifest_converter.js b/lib/offline/manifest_converter.js index 6d22d3310b..b07c6e9cc7 100644 --- a/lib/offline/manifest_converter.js +++ b/lib/offline/manifest_converter.js @@ -84,7 +84,6 @@ shaka.offline.ManifestConverter = class { return { presentationTimeline: timeline, - minBufferTime: 2, offlineSessionIds: manifestDB.sessionIds, variants: Array.from(variants.values()), textStreams: textStreams, diff --git a/lib/player.js b/lib/player.js index 0334686401..5b79665eec 100644 --- a/lib/player.js +++ b/lib/player.js @@ -2649,13 +2649,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.playheadObservers_ = this.createPlayheadObserversForMSE_(startTime); - // We need to start the buffer management code near the end because it - // will set the initial buffering state and that depends on other - // components being initialized. - const rebufferThreshold = Math.max( - this.manifest_.minBufferTime, - this.config_.streaming.rebufferingGoal); - this.startBufferManagement_(mediaElement, rebufferThreshold); + this.startBufferManagement_( + mediaElement, this.config_.streaming.rebufferingGoal); }; if (!this.config_.streaming.startAtSegmentBoundary) { @@ -4207,12 +4202,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.onAbrStatusChanged_(); } if (this.bufferObserver_) { - let rebufferThreshold = this.config_.streaming.rebufferingGoal; - if (this.manifest_) { - rebufferThreshold = - Math.max(rebufferThreshold, this.manifest_.minBufferTime); - } - this.updateBufferingSettings_(rebufferThreshold); + this.updateBufferingSettings_(this.config_.streaming.rebufferingGoal); } if (this.manifest_) { diff --git a/test/dash/dash_parser_manifest_unit.js b/test/dash/dash_parser_manifest_unit.js index 74bfb04732..d84df6138a 100644 --- a/test/dash/dash_parser_manifest_unit.js +++ b/test/dash/dash_parser_manifest_unit.js @@ -170,7 +170,6 @@ describe('DashParser Manifest', () => { manifest.sequenceMode = true; manifest.type = shaka.media.ManifestParser.DASH; manifest.anyTimeline(); - manifest.minBufferTime = 75; manifest.addPartialVariant((variant) => { variant.language = 'en'; variant.bandwidth = 200; @@ -256,7 +255,6 @@ describe('DashParser Manifest', () => { manifest.sequenceMode = false; manifest.type = shaka.media.ManifestParser.DASH; manifest.anyTimeline(); - manifest.minBufferTime = 75; manifest.addPartialVariant((variant) => { variant.language = 'en'; variant.bandwidth = 200; @@ -1759,56 +1757,6 @@ describe('DashParser Manifest', () => { expect(stream).toBeUndefined(); }); - it('override manifest value if ignoreMinBufferTime is true', async () => { - const manifestText = [ - '', - ' ', - ' ', - ' ', - ' v-sd.mp4', - ' ', - ' ', - ' ', - ' ', - '', - ].join('\n'); - - fakeNetEngine.setResponseText('dummy://foo', manifestText); - const config = shaka.util.PlayerConfiguration.createDefault().manifest; - config.dash.ignoreMinBufferTime = true; - parser.configure(config); - - /** @type {shaka.extern.Manifest} */ - const manifest = await parser.start('dummy://foo', playerInterface); - const minBufferTime = manifest.minBufferTime; - expect(minBufferTime).toBe(0); - }); - - it('get manifest value if ignoreMinBufferTime is false', async () => { - const manifestText = [ - '', - ' ', - ' ', - ' ', - ' v-sd.mp4', - ' ', - ' ', - ' ', - ' ', - '', - ].join('\n'); - - fakeNetEngine.setResponseText('dummy://foo', manifestText); - const config = shaka.util.PlayerConfiguration.createDefault().manifest; - config.dash.ignoreMinBufferTime = false; - parser.configure(config); - - /** @type {shaka.extern.Manifest} */ - const manifest = await parser.start('dummy://foo', playerInterface); - const minBufferTime = manifest.minBufferTime; - expect(minBufferTime).toBe(75); - }); - it('honors the ignoreMaxSegmentDuration config', async () => { const manifestText = [ '', @@ -1944,7 +1892,7 @@ describe('DashParser Manifest', () => { const manifest = await parser.start('dummy://foo', playerInterface); const presentationTimeline = manifest.presentationTimeline; const presentationDelay = presentationTimeline.getDelay(); - expect(presentationDelay).toBe(1.5*manifest.minBufferTime); + expect(presentationDelay).toBe(3); }); it('Honors the ignoreEmptyAdaptationSet config', async () => { diff --git a/test/media/playhead_unit.js b/test/media/playhead_unit.js index 9472a25c3d..28ff248ce2 100644 --- a/test/media/playhead_unit.js +++ b/test/media/playhead_unit.js @@ -131,7 +131,6 @@ describe('Playhead', () => { textStreams: [], imageStreams: [], presentationTimeline: timeline, - minBufferTime: 10, offlineSessionIds: [], sequenceMode: false, ignoreManifestTimestampsInSegmentsMode: false, @@ -405,6 +404,8 @@ describe('Playhead', () => { timeline.getSeekRangeStart.and.returnValue(5); timeline.getSeekRangeEnd.and.returnValue(60); + config.rebufferingGoal = 10; + playhead = new shaka.media.MediaSourcePlayhead( video, manifest, diff --git a/test/media/streaming_engine_integration.js b/test/media/streaming_engine_integration.js index 9f47541b27..dbaefe2dc2 100644 --- a/test/media/streaming_engine_integration.js +++ b/test/media/streaming_engine_integration.js @@ -598,7 +598,6 @@ describe('StreamingEngine', () => { return { presentationTimeline: timeline, offlineSessionIds: [], - minBufferTime: 2, textStreams: [], imageStreams: [], sequenceMode: false, diff --git a/test/media/streaming_engine_unit.js b/test/media/streaming_engine_unit.js index b129ad92e0..0d342df942 100644 --- a/test/media/streaming_engine_unit.js +++ b/test/media/streaming_engine_unit.js @@ -2641,8 +2641,6 @@ describe('StreamingEngine', () => { beforeEach(() => { setupVod(); - manifest.minBufferTime = 1; - config = shaka.util.PlayerConfiguration.createDefault().streaming; config.rebufferingGoal = 1; config.bufferingGoal = 1; @@ -2752,8 +2750,6 @@ describe('StreamingEngine', () => { it('does not fail immediately', async () => { setupVod(); - manifest.minBufferTime = 1; - // Create StreamingEngine. const config = shaka.util.PlayerConfiguration.createDefault().streaming; config.rebufferingGoal = 1; @@ -2816,8 +2812,6 @@ describe('StreamingEngine', () => { it('fails after multiple QuotaExceededError', async () => { setupVod(); - manifest.minBufferTime = 1; - // Create StreamingEngine. const config = shaka.util.PlayerConfiguration.createDefault().streaming; config.rebufferingGoal = 1; diff --git a/test/offline/storage_compatibility_unit.js b/test/offline/storage_compatibility_unit.js index 32bdb426f0..8f1f338cf8 100644 --- a/test/offline/storage_compatibility_unit.js +++ b/test/offline/storage_compatibility_unit.js @@ -279,7 +279,6 @@ filterDescribe('Storage Compatibility', offlineSupported, () => { const expected = shaka.test.ManifestGenerator.generate((manifest) => { manifest.anyTimeline(); - manifest.minBufferTime = 2; manifest.addPartialVariant((variant) => { variant.addPartialStream(ContentType.VIDEO, (stream) => { diff --git a/test/test/util/manifest_generator.js b/test/test/util/manifest_generator.js index bf335e62f7..0016af3115 100644 --- a/test/test/util/manifest_generator.js +++ b/test/test/util/manifest_generator.js @@ -93,8 +93,6 @@ shaka.test.ManifestGenerator.Manifest = class { this.presentationTimeline = timeline; /** @type {!Array.} */ this.offlineSessionIds = []; - /** @type {number} */ - this.minBufferTime = 0; /** @type {boolean} */ this.sequenceMode = false; /** @type {boolean} */ diff --git a/test/test/util/streaming_engine_util.js b/test/test/util/streaming_engine_util.js index 11a69c9fe3..2b40f9bf9b 100644 --- a/test/test/util/streaming_engine_util.js +++ b/test/test/util/streaming_engine_util.js @@ -329,7 +329,6 @@ shaka.test.StreamingEngineUtil = class { /** @type {shaka.extern.Manifest} */ const manifest = { presentationTimeline, - minBufferTime: 2, offlineSessionIds: [], variants: [], textStreams: [],