From 3f413d9c93a98e3aa039752e1f285e5d5dc76ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Fri, 3 Jan 2025 12:57:09 +0100 Subject: [PATCH] fix: Follow the configured playRange for src= (#7825) When 'playRangeStart' and/or 'playRangeEnd' is specified in the config, limit the actual play range to this range. In particular, this means stop the video after playRangeEnd. This implies that 'video.ended' is no longer a good marker of the end of the video. --- lib/cast/cast_utils.js | 1 + lib/player.js | 59 ++++++++++++++++++++++++++++++++++-------- ui/controls.js | 4 +++ ui/play_button.js | 2 +- 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/lib/cast/cast_utils.js b/lib/cast/cast_utils.js index 3dbdee74d4..e8239ba59c 100644 --- a/lib/cast/cast_utils.js +++ b/lib/cast/cast_utils.js @@ -329,6 +329,7 @@ shaka.cast.CastUtils.PlayerGetterMethods = { 'getLoadMode': 10, 'getManifestType': 10, 'isFullyLoaded': 1, + 'isEnded': 1, }; diff --git a/lib/player.js b/lib/player.js index 949480910c..3f8fd4ba1b 100644 --- a/lib/player.js +++ b/lib/player.js @@ -869,7 +869,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.preloadDueAdManagerTimer_.stop(); if (!this.preloadDueAdManager_) { this.preloadDueAdManagerVideo_ = this.video_; - this.preloadDueAdManagerVideoEnded_ = this.video_.ended; + this.preloadDueAdManagerVideoEnded_ = this.isEnded(); const saveLivePosition = /** @type {boolean} */( e['saveLivePosition']) || false; this.preloadDueAdManager_ = await this.detachAndSavePreload( @@ -2767,7 +2767,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { if (this.manifest_.nextUrl) { if (this.config_.streaming.preloadNextUrlWindow > 0) { const onTimeUpdate = async () => { - const timeToEnd = this.video_.duration - this.video_.currentTime; + const timeToEnd = this.seekRange().end - this.video_.currentTime; if (!isNaN(timeToEnd)) { if (timeToEnd <= this.config_.streaming.preloadNextUrlWindow) { this.loadEventManager_.unlisten( @@ -4491,10 +4491,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget { // seekable range. This covers both plain mp4s and native HLS playbacks. if (this.video_ && this.video_.src) { const seekable = this.video_.seekable; - if (seekable.length) { + if (seekable && seekable.length) { + const playRangeStart = + this.config_ ? this.config_.playRangeStart : 0; + const start = Math.max(seekable.start(0), playRangeStart); + const playRangeEnd = + this.config_ ? this.config_.playRangeEnd : Infinity; + const end = Math.min(seekable.end(seekable.length - 1), playRangeEnd); return { - 'start': seekable.start(0), - 'end': seekable.end(seekable.length - 1), + 'start': start, + 'end': end, }; } } @@ -5920,7 +5926,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const ContentType = shaka.util.ManifestParserUtils.ContentType; - let duration = this.video_.duration; + const seekRange = this.seekRange(); + let duration = seekRange.end - seekRange.start; if (this.manifest_) { duration = this.manifest_.presentationTimeline.getDuration(); } @@ -6034,7 +6041,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { } const ContentType = shaka.util.ManifestParserUtils.ContentType; - let duration = this.video_.duration; + const seekRange = this.seekRange(); + let duration = seekRange.end - seekRange.start; if (this.manifest_) { duration = this.manifest_.presentationTimeline.getDuration(); } @@ -6801,7 +6809,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { let updateState = 'playing'; if (this.bufferObserver_.getState() == State.STARVING) { updateState = 'buffering'; - } else if (this.video_.ended) { + } else if (this.isEnded()) { updateState = 'ended'; } else if (this.video_.paused) { updateState = 'paused'; @@ -7016,7 +7024,20 @@ shaka.Player = class extends shaka.util.FakeEventTarget { return false; }; - const completionRatio = this.video_.currentTime / this.video_.duration; + const checkEnded = () => { + if (this.config_ && this.config_.playRangeEnd != Infinity) { + // Make sure the video stops when we reach the end. + // This is required when there is a custom playRangeEnd specified. + if (this.isEnded()) { + this.video_.pause(); + } + } + }; + + const seekRange = this.seekRange(); + const duration = seekRange.end - seekRange.start; + const completionRatio = + duration > 0 ? this.video_.currentTime / duration : 0; if (isNaN(completionRatio)) { return; @@ -7039,6 +7060,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget { } else if (isQuartile(100, percent)) { event = shaka.Player.makeEvent_( shaka.util.FakeEvent.EventName.Complete); + checkEnded(); + } else { + checkEnded(); } if (event) { @@ -8139,7 +8163,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { // This is a strong guarantee that we are buffered to the end, because it // means the playhead is already at that end. - if (this.video_.ended) { + if (this.isEnded()) { return true; } @@ -8179,7 +8203,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { // This is a strong guarantee that we are buffered to the end, because it // means the playhead is already at that end. - if (this.video_.ended) { + if (this.isEnded()) { return true; } @@ -8220,6 +8244,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget { } return this.video_.remote.state != 'disconnected'; } + + /** + * Indicate if the video has ended. + * + * @return {boolean} + * @export + */ + isEnded() { + if (!this.video_ || this.video_.ended) { + return true; + } + return this.video_.currentTime >= this.seekRange().end; + } }; /** diff --git a/ui/controls.js b/ui/controls.js index 61bf62a60f..d22ed78f93 100644 --- a/ui/controls.js +++ b/ui/controls.js @@ -869,6 +869,10 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget { } if (this.presentationIsPaused()) { + // If we are at the end, go back to the beginning. + if (this.player_.isEnded()) { + this.video_.currentTime = this.player_.seekRange().start; + } this.video_.play(); } else { this.video_.pause(); diff --git a/ui/play_button.js b/ui/play_button.js index b2b062c98d..ecfe2dc489 100644 --- a/ui/play_button.js +++ b/ui/play_button.js @@ -116,7 +116,7 @@ shaka.ui.PlayButton = class extends shaka.ui.Element { return false; } - return this.video.ended; + return this.player ? this.player.isEnded() : true; } /**