From 2c6a2ee87dfd567add7044c2c3eef7600e3037d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Wed, 27 May 2026 14:19:32 +0200 Subject: [PATCH] fix: Emit ratechange event when playbackRate stays at 0 (#10130) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This can happen when using playbackRate values ​​that are outside the range supported by the platform, and we have to use our internal alternative. See the implementation at https://github.com/shaka-project/shaka-player/blob/main/lib/media/play_rate_controller.js --- lib/player.js | 18 +++++++++++++++++ test/player_integration.js | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/lib/player.js b/lib/player.js index 08b54b2a2..528bd0a32 100644 --- a/lib/player.js +++ b/lib/player.js @@ -5275,6 +5275,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget { return; } + const prevRealRate = this.video_.playbackRate; + const prevRate = this.playRateController_.getRealRate(); + this.playRateController_.set(rate); if (this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) { @@ -5282,6 +5285,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.useTrickPlayTrackIfAvailable(useTrickPlayTrack && rate != 1); } this.setupTrickPlayEventListeners_(rate); + + // When trick play falls back to manual seeking (for example, rewind or + // unsupported playback rates), PlayRateController keeps the media element + // playbackRate at 0 and advances the playhead with a timer instead. + // + // Because the underlying media element playbackRate remains unchanged, + // the browser will not fire a native 'ratechange' event in this case. + // + // Invoke onRateChange_() manually so the player can run the normal + // playback-rate update flow and dispatch the corresponding RateChange + // event for the effective trick play rate change. + if (prevRealRate === 0 && this.video_.playbackRate === 0 && + rate != prevRate) { + this.onRateChange_(); + } } /** diff --git a/test/player_integration.js b/test/player_integration.js index d1ad8a16d..284d3ce36 100644 --- a/test/player_integration.js +++ b/test/player_integration.js @@ -351,6 +351,46 @@ describe('Player', () => { expect(video.playbackRate).toBe(1); }); + it('dispatch ratechange event', async () => { + await player.load('test:sintel_compiled'); + await video.play(); + await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10); + + video.pause(); + + let numberOfRatechangeEvents = 0; + eventManager.listen(player, 'ratechange', () => { + numberOfRatechangeEvents++; + }); + + player.trickPlay(2); + expect(player.getPlaybackRate()).toBe(2); + + await shaka.test.Util.shortDelay(); + + player.trickPlay(32); + expect(player.getPlaybackRate()).toBe(32); + + await shaka.test.Util.shortDelay(); + + player.trickPlay(64); + expect(player.getPlaybackRate()).toBe(64); + + await shaka.test.Util.shortDelay(); + + player.trickPlay(-1); + expect(player.getPlaybackRate()).toBe(-1); + + await shaka.test.Util.shortDelay(); + + player.cancelTrickPlay(); + expect(player.getPlaybackRate()).toBe(1); + + await shaka.test.Util.shortDelay(); + + expect(numberOfRatechangeEvents).toBe(5); + }); + it('in sequence mode', async () => { if (!deviceDetected.supportsSequenceMode()) { pending('Sequence mode is not supported by the platform.');