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.');