From a394641f2e7ca1041e1677ac028f417e8390aadf Mon Sep 17 00:00:00 2001 From: Alvaro Velad Galvan Date: Thu, 27 Apr 2023 16:32:22 +0200 Subject: [PATCH] fix: Fix buffering on the end of MSS streams --- lib/mss/mss_parser.js | 25 +++++++++++-------- test/mss/mss_player_integration.js | 40 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/lib/mss/mss_parser.js b/lib/mss/mss_parser.js index e8e4ab3e369..10dcded2695 100644 --- a/lib/mss/mss_parser.js +++ b/lib/mss/mss_parser.js @@ -54,6 +54,9 @@ shaka.mss.MssParser = class { */ this.updatePeriod_ = 0; + /** @private {?shaka.media.PresentationTimeline} */ + this.presentationTimeline_ = null; + /** * An ewma that tracks how long updates take. * This is to mitigate issues caused by slow parsing on embedded devices. @@ -286,12 +289,8 @@ shaka.mss.MssParser = class { manifestPreprocessor(mss); } - /** @type {!shaka.media.PresentationTimeline} */ - let presentationTimeline; - if (this.manifest_) { - presentationTimeline = this.manifest_.presentationTimeline; - } else { - presentationTimeline = new shaka.media.PresentationTimeline( + if (!this.presentationTimeline_) { + this.presentationTimeline_ = new shaka.media.PresentationTimeline( /* presentationStartTime= */ null, /* delay= */ 0); } @@ -305,7 +304,7 @@ shaka.mss.MssParser = class { shaka.util.Error.Code.MSS_LIVE_CONTENT_NOT_SUPPORTED); } - presentationTimeline.setStatic(!isLive); + this.presentationTimeline_.setStatic(!isLive); const timescale = XmlUtils.parseAttr(mss, 'TimeScale', XmlUtils.parseNonNegativeInt, shaka.mss.MssParser.DEFAULT_TIME_SCALE_); @@ -342,7 +341,7 @@ shaka.mss.MssParser = class { segmentAvailabilityDuration = Infinity; } - presentationTimeline.setSegmentAvailabilityDuration( + this.presentationTimeline_.setSegmentAvailabilityDuration( segmentAvailabilityDuration); // Duration in timescale units. @@ -352,7 +351,7 @@ shaka.mss.MssParser = class { 'Duration must be defined!'); if (!isLive) { - presentationTimeline.setDuration(duration / timescale); + this.presentationTimeline_.setDuration(duration / timescale); } /** @type {!shaka.mss.MssParser.Context} */ @@ -368,7 +367,7 @@ shaka.mss.MssParser = class { // These steps are not done on manifest update. if (!this.manifest_) { this.manifest_ = { - presentationTimeline: presentationTimeline, + presentationTimeline: this.presentationTimeline_, variants: context.variants, textStreams: context.textStreams, imageStreams: [], @@ -382,7 +381,7 @@ shaka.mss.MssParser = class { // This is the first point where we have a meaningful presentation start // time, and we need to tell PresentationTimeline that so that it can // maintain consistency from here on. - presentationTimeline.lockStartTime(); + this.presentationTimeline_.lockStartTime(); } else { // Just update the variants and text streams. this.manifest_.variants = context.variants; @@ -491,6 +490,10 @@ shaka.mss.MssParser = class { duration = end - start; } + const presentationDuration = this.presentationTimeline_.getDuration(); + this.presentationTimeline_.setDuration( + Math.min(duration, presentationDuration)); + /** @type {!shaka.extern.Stream} */ const stream = { id: id, diff --git a/test/mss/mss_player_integration.js b/test/mss/mss_player_integration.js index e136df95eff..63f417717dc 100644 --- a/test/mss/mss_player_integration.js +++ b/test/mss/mss_player_integration.js @@ -79,6 +79,46 @@ describe('MSS Player', () => { // longer than 10 seconds, fail the test. await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 5, 10); + if (video.ended) { + checkEndedTime(); + } else { + // Expect that in 30 seconds of playback, we go through at least 20 + // seconds of content. This allows for some buffering or network + // flake. + expect(video.currentTime).toBeGreaterThan(5); + + // Since video.ended is false, we expect the current time to be before + // the video duration. + expect(video.currentTime).toBeLessThan(video.duration); + + // Seek close to the end and play the rest of the content. + video.currentTime = video.duration - 5; + + // Wait for the video to start playback again after seeking. If it + // takes longer than 10 seconds, fail the test. + await waiter.waitForMovementOrFailOnTimeout(video, 10); + + // Play for 20 seconds, but stop early if the video ends. + await waiter.waitForEndOrTimeout(video, 20); + + checkEndedTime(); + } + await player.unload(); }); + + /** + * Check the video time for videos that we expect to have ended. + */ + function checkEndedTime() { + if (video.currentTime >= video.duration) { + // On some platforms, currentTime surpasses duration by more than 1s. + // For the purposes of this test, this is fine, so don't set any precise + // expectations on currentTime if it's larger. + } else { + // On some platforms, currentTime is less than duration, but it should be + // close. + expect(video.currentTime).toBeGreaterThan(video.duration - 1); + } + } });