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 4e18ea71d64d9..c335a101576bc 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 */ @@ -944,13 +945,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) } } } @@ -988,7 +989,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 @@ -1960,30 +1961,56 @@ export default defineComponent({ // #region mouse scroll handlers + const mouseScrollThrottleWaitMs = 200 + /** * @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() + + // Touchpad scroll = small deltaX/deltaY + if (Math.abs(event.deltaX) <= 5 && Math.abs(event.deltaY) <= 5) { + mouseScrollPlaybackRateThrottle(event) + } else { + mouseScrollPlaybackRate(event) + } + } /** * @param {WheelEvent} event */ function mouseScrollSkip(event) { + if ((event.deltaY < 0 || event.deltaX > 0)) { + seekBySeconds(defaultSkipInterval.value * player.getPlaybackRate(), true) + } else if ((event.deltaY > 0 || event.deltaX < 0)) { + seekBySeconds(-defaultSkipInterval.value * player.getPlaybackRate(), true) + } + } + const mouseScrollSkipThrottle = throttle(mouseScrollSkip, mouseScrollThrottleWaitMs) + /** + * @param {WheelEvent} event + */ + function mouseScrollSkipHandler(event) { if (canSeek()) { event.preventDefault() - if ((event.deltaY < 0 || event.deltaX > 0)) { - seekBySeconds(defaultSkipInterval.value * player.getPlaybackRate(), true) - } else if ((event.deltaY > 0 || event.deltaX < 0)) { - seekBySeconds(-defaultSkipInterval.value * player.getPlaybackRate(), true) + // Touchpad scroll = small deltaX/deltaY + if (Math.abs(event.deltaX) <= 5 && Math.abs(event.deltaY) <= 5) { + mouseScrollSkipThrottle(event) + } else { + mouseScrollSkip(event) } } } @@ -1992,23 +2019,35 @@ export default defineComponent({ * @param {WheelEvent} event */ function mouseScrollVolume(event) { - if (!event.ctrlKey && !event.metaKey) { - event.preventDefault() - event.stopPropagation() + const video_ = video.value - const video_ = video.value + if (video_.muted && (event.deltaY < 0 || event.deltaX > 0)) { + video_.muted = false + video_.volume = 0 + } - 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) } + } + } + const mouseScrollVolumeThrottle = throttle(mouseScrollVolume, mouseScrollThrottleWaitMs) + /** + * @param {WheelEvent} event + */ + function mouseScrollVolumeHandler(event) { + if (!event.ctrlKey && !event.metaKey) { + event.preventDefault() + event.stopPropagation() - if (!video_.muted) { - if ((event.deltaY < 0 || event.deltaX > 0)) { - changeVolume(0.05) - } else if ((event.deltaY > 0 || event.deltaX < 0)) { - changeVolume(-0.05) - } + // Touchpad scroll = small deltaX/deltaY + if (Math.abs(event.deltaX) <= 5 && Math.abs(event.deltaY) <= 5) { + mouseScrollVolumeThrottle(event) + } else { + mouseScrollVolume(event) } } } diff --git a/src/renderer/helpers/utils.js b/src/renderer/helpers/utils.js index 60ffa9c454d66..53b231e7190e2 100644 --- a/src/renderer/helpers/utils.js +++ b/src/renderer/helpers/utils.js @@ -1001,3 +1001,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) + } + } +}