From b77a94773dc4675b21e6c9c8ab937d5347d70411 Mon Sep 17 00:00:00 2001 From: theodab Date: Tue, 27 Jun 2023 20:25:08 -0700 Subject: [PATCH] fix(media): Fix region checking in livestreams (#5361) In some cases, in a livestream situation, the region observer's poll method ends up being called before the media time is set to the live edge. This does not happen every time; it's likely based on the exact time it takes the player to start playing. When this does happen, it leads to the region observer "skipping" over every region between time=0 and the live head, as it erroneously believes that it has actually played through all of that time. This could result in confusion for applications that are relying on region data. This change modifiers the region observer so that it does not start examining regions until the time gets past 0 seconds, except in the case of VOD. Closes #5213 --- lib/media/region_observer.js | 20 +++++++++++++++++++- lib/player.js | 11 ++++++++--- test/media/region_observer_unit.js | 3 ++- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/media/region_observer.js b/lib/media/region_observer.js index e4666d42f8..f7f19f94eb 100644 --- a/lib/media/region_observer.js +++ b/lib/media/region_observer.js @@ -27,13 +27,24 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget { * destroy the timeline. * * @param {!shaka.media.RegionTimeline} timeline + * @param {boolean} startsPastZero */ - constructor(timeline) { + constructor(timeline, startsPastZero) { super(); /** @private {shaka.media.RegionTimeline} */ this.timeline_ = timeline; + /** + * Whether the asset is expected to start at a time beyond 0 seconds. + * For example, if the asset is a live stream. + * If true, we will not start polling for regions until the playhead has + * moved past 0 seconds, to avoid bad behaviors where the current time is + * briefly 0 before we have enough data to play. + * @private {boolean} + */ + this.startsPastZero_ = startsPastZero; + /** * A mapping between a region and where we previously were relative to it. * When the value here differs from what we calculate, it means we moved and @@ -121,6 +132,13 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget { /** @override */ poll(positionInSeconds, wasSeeking) { const RegionObserver = shaka.media.RegionObserver; + if (this.startsPastZero_ && positionInSeconds == 0) { + // Don't start checking regions until the timeline has begun moving. + return; + } + // Now that we have seen the playhead go past 0, it's okay if it goes + // back there (e.g. seeking back to the start). + this.startsPastZero_ = false; for (const region of this.timeline_.regions()) { const previousPosition = this.oldPosition_.get(region); diff --git a/lib/player.js b/lib/player.js index 3a7410e4e3..df4c7a758a 100644 --- a/lib/player.js +++ b/lib/player.js @@ -2191,7 +2191,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { } this.playhead_ = this.createPlayhead(has.startTime); - this.playheadObservers_ = this.createPlayheadObserversForMSE_(); + this.playheadObservers_ = + this.createPlayheadObserversForMSE_(has.startTimeOfLoad); // 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 @@ -2918,17 +2919,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget { * Create the observers for MSE playback. These observers are responsible for * notifying the app and player of specific events during MSE playback. * + * @param {number} startTimeOfLoad * @return {!shaka.media.PlayheadObserverManager} * @private */ - createPlayheadObserversForMSE_() { + createPlayheadObserversForMSE_(startTimeOfLoad) { goog.asserts.assert(this.manifest_, 'Must have manifest'); goog.asserts.assert(this.regionTimeline_, 'Must have region timeline'); goog.asserts.assert(this.video_, 'Must have video element'); + const startsPastZero = this.isLive() || startTimeOfLoad > 0; + // Create the region observer. This will allow us to notify the app when we // move in and out of timeline regions. - const regionObserver = new shaka.media.RegionObserver(this.regionTimeline_); + const regionObserver = new shaka.media.RegionObserver( + this.regionTimeline_, startsPastZero); regionObserver.addEventListener('enter', (event) => { /** @type {shaka.extern.TimelineRegionInfo} */ diff --git a/test/media/region_observer_unit.js b/test/media/region_observer_unit.js index 6d49f38a43..b41dac7a1e 100644 --- a/test/media/region_observer_unit.js +++ b/test/media/region_observer_unit.js @@ -31,7 +31,8 @@ describe('RegionObserver', () => { timeline = new shaka.media.RegionTimeline( shaka.test.Util.spyFunc(onSeekRange)); - observer = new shaka.media.RegionObserver(timeline); + observer = new shaka.media.RegionObserver( + timeline, /* startsPastZero= */ false); observer.addEventListener('enter', (event) => { shaka.test.Util.spyFunc(onEnterRegion)(event['region'], event['seeking']); });