From 08f9b5e5dfe912f7e0187bb4a8991ea0d0399662 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 29 Jan 2025 10:14:49 +0800 Subject: [PATCH] ! Fix scroll to seek/change volume/playback rate changing too fast with touchpad --- .../ft-shaka-video-player.js | 74 ++++++++++++------- src/renderer/helpers/utils.js | 25 +++++++ 2 files changed, 74 insertions(+), 25 deletions(-) diff --git a/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js b/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js index 2fa7df3495c8f..6eb9f001dc96c 100644 --- a/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js +++ b/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js @@ -22,7 +22,8 @@ import { import { addKeyboardShortcutToActionTitle, showToast, - writeFileWithPicker + writeFileWithPicker, + throttle, } from '../../helpers/utils' /** @typedef {import('../../helpers/sponsorblock').SponsorBlockCategory} SponsorBlockCategory */ @@ -904,13 +905,13 @@ export default defineComponent({ if (event.ctrlKey || event.metaKey) { if (videoPlaybackRateMouseScroll.value) { - mouseScrollPlaybackRate(event) + mouseScrollPlaybackRateHandler(event) } } else { if (videoVolumeMouseScroll.value) { - mouseScrollVolume(event) + mouseScrollVolumeHandler(event) } else if (videoSkipMouseScroll.value) { - mouseScrollSkip(event) + mouseScrollSkipHandler(event) } } } @@ -947,7 +948,7 @@ export default defineComponent({ } // make scrolling over volume slider change the volume - container.value.querySelector('.shaka-volume-bar').addEventListener('wheel', mouseScrollVolume) + container.value.querySelector('.shaka-volume-bar').addEventListener('wheel', mouseScrollVolumeHandler) // title overlay when the video is fullscreened // placing this inside the controls container so that we can fade it in and out at the same time as the controls @@ -1925,56 +1926,79 @@ export default defineComponent({ // #region mouse scroll handlers + const mouseScrollThrottleWaitMs = 100 + /** * @param {WheelEvent} event */ function mouseScrollPlaybackRate(event) { - event.preventDefault() - if ((event.deltaY < 0 || event.deltaX > 0)) { changePlayBackRate(0.05) } else if ((event.deltaY > 0 || event.deltaX < 0)) { changePlayBackRate(-0.05) } } + const mouseScrollPlaybackRateThrottle = throttle(mouseScrollPlaybackRate, mouseScrollThrottleWaitMs) + /** + * @param {WheelEvent} event + */ + function mouseScrollPlaybackRateHandler(event) { + event.preventDefault() + + mouseScrollPlaybackRateThrottle(event) + } /** * @param {WheelEvent} event */ function mouseScrollSkip(event) { + if ((event.deltaY < 0 || event.deltaX > 0)) { + seekBySeconds(defaultSkipInterval.value * video.value.playbackRate, true) + } else if ((event.deltaY > 0 || event.deltaX < 0)) { + seekBySeconds(-defaultSkipInterval.value * video.value.playbackRate, true) + } + } + const mouseScrollSkipThrottle = throttle(mouseScrollSkip, mouseScrollThrottleWaitMs) + /** + * @param {WheelEvent} event + */ + function mouseScrollSkipHandler(event) { if (canSeek()) { event.preventDefault() + mouseScrollSkipThrottle(event) + } + } + + /** + * @param {WheelEvent} event + */ + function mouseScrollVolume(event) { + const video_ = video.value + + if (video_.muted && (event.deltaY < 0 || event.deltaX > 0)) { + video_.muted = false + video_.volume = 0 + } + + if (!video_.muted) { if ((event.deltaY < 0 || event.deltaX > 0)) { - seekBySeconds(defaultSkipInterval.value * video.value.playbackRate, true) + changeVolume(0.05) } else if ((event.deltaY > 0 || event.deltaX < 0)) { - seekBySeconds(-defaultSkipInterval.value * video.value.playbackRate, true) + changeVolume(-0.05) } } } - + const mouseScrollVolumeThrottle = throttle(mouseScrollVolume, mouseScrollThrottleWaitMs) /** * @param {WheelEvent} event */ - function mouseScrollVolume(event) { + function mouseScrollVolumeHandler(event) { if (!event.ctrlKey && !event.metaKey) { event.preventDefault() event.stopPropagation() - const video_ = video.value - - if (video_.muted && (event.deltaY < 0 || event.deltaX > 0)) { - video_.muted = false - video_.volume = 0 - } - - if (!video_.muted) { - if ((event.deltaY < 0 || event.deltaX > 0)) { - changeVolume(0.05) - } else if ((event.deltaY > 0 || event.deltaX < 0)) { - changeVolume(-0.05) - } - } + mouseScrollVolumeThrottle(event) } } diff --git a/src/renderer/helpers/utils.js b/src/renderer/helpers/utils.js index 1537bbc1fb1b0..44c51f0f94443 100644 --- a/src/renderer/helpers/utils.js +++ b/src/renderer/helpers/utils.js @@ -1052,3 +1052,28 @@ export function debounce(func, wait) { }, wait) } } + +/** + * @template {Function} T + * @param {T} func + * @param {number} wait + * @returns {T} + */ +export function throttle(func, wait) { + let isWaiting + + // Using a fully fledged function here instead of an arrow function + // so that we can get `this` and pass it onto the original function. + // Vue components using the options API use `this` alot. + return function (...args) { + const context = this + if (!isWaiting) { + func.apply(context, args) + + isWaiting = true + setTimeout(() => { + isWaiting = false + }, wait) + } + } +}