From b34e263369095706567b1dfa91867f7323156799 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Wed, 3 Sep 2025 21:14:04 -0600 Subject: [PATCH] add setting to disable speed boost --- src/assets/locales/en.json | 3 ++ src/backend/accounts/settings.ts | 2 ++ .../player/internals/KeyboardEvents.tsx | 28 +++++++++++++++---- .../player/internals/VideoClickTarget.tsx | 8 +++++- src/hooks/auth/useAuthData.ts | 8 ++++++ src/hooks/useSettingsState.ts | 16 ++++++++++- src/pages/Settings.tsx | 14 +++++++++- src/pages/parts/settings/PreferencesPart.tsx | 23 +++++++++++++++ src/stores/preferences/index.tsx | 8 ++++++ 9 files changed, 101 insertions(+), 9 deletions(-) diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index 6c943d7a..00abbc7a 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -1027,6 +1027,9 @@ "lowPerformanceMode": "Low performance/bandwidth mode", "lowPerformanceModeDescription": "Optimizes the application for slower connections and devices by disabling bandwidth-heavy features. This mode reduces data usage and improves performance while keeping the core search and watch functionality intact. ", "lowPerformanceModeLabel": "Low performance mode", + "holdToBoost": "Hold to boost speed", + "holdToBoostDescription": "Hold spacebar or touch and hold the screen to temporarily increase playback speed to 2x. Release to return to previous speed.", + "holdToBoostLabel": "Enable hold to boost", "sourceOrder": "Reordering sources", "sourceOrderDescription": "Drag and drop to reorder sources. This will determine the order in which sources are checked for the media you are trying to watch. If a source is greyed out, it means the extension is required for that source.

(The default order is best for most users)", "sourceOrderEnableLabel": "Custom source order", diff --git a/src/backend/accounts/settings.ts b/src/backend/accounts/settings.ts index b9092023..7851b32f 100644 --- a/src/backend/accounts/settings.ts +++ b/src/backend/accounts/settings.ts @@ -26,6 +26,7 @@ export interface SettingsInput { proxyTmdb?: boolean; enableLowPerformanceMode?: boolean; enableNativeSubtitles?: boolean; + enableHoldToBoost?: boolean; } export interface SettingsResponse { @@ -50,6 +51,7 @@ export interface SettingsResponse { proxyTmdb?: boolean; enableLowPerformanceMode?: boolean; enableNativeSubtitles?: boolean; + enableHoldToBoost?: boolean; } export function updateSettings( diff --git a/src/components/player/internals/KeyboardEvents.tsx b/src/components/player/internals/KeyboardEvents.tsx index b68a05dc..47ce41a8 100644 --- a/src/components/player/internals/KeyboardEvents.tsx +++ b/src/components/player/internals/KeyboardEvents.tsx @@ -5,6 +5,7 @@ import { useVolume } from "@/components/player/hooks/useVolume"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { useOverlayStack } from "@/stores/interface/overlayStack"; import { usePlayerStore } from "@/stores/player/store"; +import { usePreferencesStore } from "@/stores/preferences"; import { useSubtitleStore } from "@/stores/subtitles"; import { useEmpheralVolumeStore } from "@/stores/volume"; import { useWatchPartyStore } from "@/stores/watchParty"; @@ -26,6 +27,7 @@ export function KeyboardEvents() { const setShowDelayIndicator = useSubtitleStore( (s) => s.setShowDelayIndicator, ); + const enableHoldToBoost = usePreferencesStore((s) => s.enableHoldToBoost); const [isRolling, setIsRolling] = useState(false); const volumeDebounce = useRef | undefined>(); @@ -69,6 +71,7 @@ export function KeyboardEvents() { speedIndicatorTimeoutRef, boostTimeoutRef, isPendingBoostRef, + enableHoldToBoost, }); useEffect(() => { @@ -97,6 +100,7 @@ export function KeyboardEvents() { speedIndicatorTimeoutRef, boostTimeoutRef, isPendingBoostRef, + enableHoldToBoost, }; }, [ setShowVolume, @@ -118,6 +122,7 @@ export function KeyboardEvents() { isInWatchParty, setSpeedBoosted, setShowSpeedIndicator, + enableHoldToBoost, ]); useEffect(() => { @@ -159,8 +164,12 @@ export function KeyboardEvents() { if (next) dataRef.current.display?.setPlaybackRate(next); } - // Handle spacebar press for play/pause and hold for 2x speed - disabled in watch party - if (k === " " && !dataRef.current.isInWatchParty) { + // Handle spacebar press for play/pause and hold for 2x speed - disabled in watch party or when hold to boost is disabled + if ( + k === " " && + !dataRef.current.isInWatchParty && + dataRef.current.enableHoldToBoost + ) { // Skip if a button is targeted if ( evt.target && @@ -216,8 +225,11 @@ export function KeyboardEvents() { }, 300); // 300ms delay before boost takes effect } - // Handle spacebar press for play/pause only in watch party mode - if (k === " " && dataRef.current.isInWatchParty) { + // Handle spacebar press for simple play/pause when hold to boost is disabled or in watch party mode + if ( + k === " " && + (!dataRef.current.enableHoldToBoost || dataRef.current.isInWatchParty) + ) { // Skip if a button is targeted if ( evt.target && @@ -302,8 +314,12 @@ export function KeyboardEvents() { const keyupEventHandler = (evt: KeyboardEvent) => { const k = evt.key; - // Handle spacebar release - only handle speed boost logic when not in watch party - if (k === " " && !dataRef.current.isInWatchParty) { + // Handle spacebar release - only handle speed boost logic when not in watch party and hold to boost is enabled + if ( + k === " " && + !dataRef.current.isInWatchParty && + dataRef.current.enableHoldToBoost + ) { // If we haven't applied the boost yet but were about to, cancel it if (dataRef.current.isPendingBoostRef.current) { dataRef.current.isPendingBoostRef.current = false; diff --git a/src/components/player/internals/VideoClickTarget.tsx b/src/components/player/internals/VideoClickTarget.tsx index f288309e..5d43956f 100644 --- a/src/components/player/internals/VideoClickTarget.tsx +++ b/src/components/player/internals/VideoClickTarget.tsx @@ -6,6 +6,7 @@ import { useShouldShowVideoElement } from "@/components/player/internals/VideoCo import { useOverlayStack } from "@/stores/interface/overlayStack"; import { PlayerHoverState } from "@/stores/player/slices/interface"; import { usePlayerStore } from "@/stores/player/store"; +import { usePreferencesStore } from "@/stores/preferences"; import { useWatchPartyStore } from "@/stores/watchParty"; export function VideoClickTarget(props: { showingControls: boolean }) { @@ -21,6 +22,7 @@ export function VideoClickTarget(props: { showingControls: boolean }) { const hovering = usePlayerStore((s) => s.interface.hovering); const setCurrentOverlay = useOverlayStack((s) => s.setCurrentOverlay); const isInWatchParty = useWatchPartyStore((s) => s.enabled); + const enableHoldToBoost = usePreferencesStore((s) => s.enableHoldToBoost); const [_, cancel, reset] = useTimeoutFn(() => { updateInterfaceHovering(PlayerHoverState.NOT_HOVERING); @@ -87,7 +89,8 @@ export function VideoClickTarget(props: { showingControls: boolean }) { if ( ((e.pointerType === "mouse" && e.button === 0) || e.pointerType === "touch") && - !isInWatchParty + !isInWatchParty && + enableHoldToBoost ) { if (isPaused) return; // Don't boost if video is paused @@ -128,6 +131,7 @@ export function VideoClickTarget(props: { showingControls: boolean }) { setShowSpeedIndicator, setCurrentOverlay, isInWatchParty, + enableHoldToBoost, ], ); @@ -143,6 +147,7 @@ export function VideoClickTarget(props: { showingControls: boolean }) { if ( isHoldingRef.current && + enableHoldToBoost && ((e.pointerType === "mouse" && e.button === 0) || e.pointerType === "touch") ) { @@ -175,6 +180,7 @@ export function VideoClickTarget(props: { showingControls: boolean }) { setShowSpeedIndicator, setCurrentOverlay, isPendingBoost, + enableHoldToBoost, ], ); diff --git a/src/hooks/auth/useAuthData.ts b/src/hooks/auth/useAuthData.ts index b75a6d8a..2bdfc8e5 100644 --- a/src/hooks/auth/useAuthData.ts +++ b/src/hooks/auth/useAuthData.ts @@ -62,6 +62,9 @@ export function useAuthData() { const setEnableNativeSubtitles = usePreferencesStore( (s) => s.setEnableNativeSubtitles, ); + const setEnableHoldToBoost = usePreferencesStore( + (s) => s.setEnableHoldToBoost, + ); const login = useCallback( async ( @@ -185,6 +188,10 @@ export function useAuthData() { if (settings.enableNativeSubtitles !== undefined) { setEnableNativeSubtitles(settings.enableNativeSubtitles); } + + if (settings.enableHoldToBoost !== undefined) { + setEnableHoldToBoost(settings.enableHoldToBoost); + } }, [ replaceBookmarks, @@ -207,6 +214,7 @@ export function useAuthData() { setFebboxKey, setEnableLowPerformanceMode, setEnableNativeSubtitles, + setEnableHoldToBoost, ], ); diff --git a/src/hooks/useSettingsState.ts b/src/hooks/useSettingsState.ts index 1ae0bed6..6fcc8270 100644 --- a/src/hooks/useSettingsState.ts +++ b/src/hooks/useSettingsState.ts @@ -65,6 +65,7 @@ export function useSettingsState( enableCarouselView: boolean, forceCompactEpisodeView: boolean, enableLowPerformanceMode: boolean, + enableHoldToBoost: boolean, ) { const [proxyUrlsState, setProxyUrls, resetProxyUrls, proxyUrlsChanged] = useDerived(proxyUrls); @@ -174,6 +175,12 @@ export function useSettingsState( resetEnableLowPerformanceMode, enableLowPerformanceModeChanged, ] = useDerived(enableLowPerformanceMode); + const [ + enableHoldToBoostState, + setEnableHoldToBoostState, + resetEnableHoldToBoost, + enableHoldToBoostChanged, + ] = useDerived(enableHoldToBoost); function reset() { resetTheme(); @@ -199,6 +206,7 @@ export function useSettingsState( resetEnableCarouselView(); resetForceCompactEpisodeView(); resetEnableLowPerformanceMode(); + resetEnableHoldToBoost(); } const changed = @@ -223,7 +231,8 @@ export function useSettingsState( proxyTmdbChanged || enableCarouselViewChanged || forceCompactEpisodeViewChanged || - enableLowPerformanceModeChanged; + enableLowPerformanceModeChanged || + enableHoldToBoostChanged; return { reset, @@ -338,5 +347,10 @@ export function useSettingsState( set: setEnableLowPerformanceModeState, changed: enableLowPerformanceModeChanged, }, + enableHoldToBoost: { + state: enableHoldToBoostState, + set: setEnableHoldToBoostState, + changed: enableHoldToBoostChanged, + }, }; } diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 9daf2b06..8a9d0f86 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -192,6 +192,11 @@ export function SettingsPage() { (s) => s.setEnableLowPerformanceMode, ); + const enableHoldToBoost = usePreferencesStore((s) => s.enableHoldToBoost); + const setEnableHoldToBoost = usePreferencesStore( + (s) => s.setEnableHoldToBoost, + ); + const account = useAuthStore((s) => s.account); const updateProfile = useAuthStore((s) => s.setAccountProfile); const updateDeviceName = useAuthStore((s) => s.updateDeviceName); @@ -243,6 +248,7 @@ export function SettingsPage() { enableCarouselView, forceCompactEpisodeView, enableLowPerformanceMode, + enableHoldToBoost, ); const availableSources = useMemo(() => { @@ -300,7 +306,8 @@ export function SettingsPage() { state.proxyTmdb.changed || state.enableCarouselView.changed || state.forceCompactEpisodeView.changed || - state.enableLowPerformanceMode.changed + state.enableLowPerformanceMode.changed || + state.enableHoldToBoost.changed ) { await updateSettings(backendUrl, account, { applicationLanguage: state.appLanguage.state, @@ -321,6 +328,7 @@ export function SettingsPage() { enableCarouselView: state.enableCarouselView.state, forceCompactEpisodeView: state.forceCompactEpisodeView.state, enableLowPerformanceMode: state.enableLowPerformanceMode.state, + enableHoldToBoost: state.enableHoldToBoost.state, }); } if (state.deviceName.changed) { @@ -360,6 +368,7 @@ export function SettingsPage() { setEnableCarouselView(state.enableCarouselView.state); setForceCompactEpisodeView(state.forceCompactEpisodeView.state); setEnableLowPerformanceMode(state.enableLowPerformanceMode.state); + setEnableHoldToBoost(state.enableHoldToBoost.state); if (state.profile.state) { updateProfile(state.profile.state); @@ -403,6 +412,7 @@ export function SettingsPage() { setEnableCarouselView, setForceCompactEpisodeView, setEnableLowPerformanceMode, + setEnableHoldToBoost, ]); return ( @@ -453,6 +463,8 @@ export function SettingsPage() { setenableSourceOrder={state.enableSourceOrder.set} enableLowPerformanceMode={state.enableLowPerformanceMode.state} setEnableLowPerformanceMode={state.enableLowPerformanceMode.set} + enableHoldToBoost={state.enableHoldToBoost.state} + setEnableHoldToBoost={state.enableHoldToBoost.set} />
diff --git a/src/pages/parts/settings/PreferencesPart.tsx b/src/pages/parts/settings/PreferencesPart.tsx index 17c74497..7edc2896 100644 --- a/src/pages/parts/settings/PreferencesPart.tsx +++ b/src/pages/parts/settings/PreferencesPart.tsx @@ -29,6 +29,8 @@ export function PreferencesPart(props: { setenableSourceOrder: (v: boolean) => void; enableLowPerformanceMode: boolean; setEnableLowPerformanceMode: (v: boolean) => void; + enableHoldToBoost: boolean; + setEnableHoldToBoost: (v: boolean) => void; }) { const { t } = useTranslation(); const sorted = sortLangCodes(appLanguageOptions.map((item) => item.code)); @@ -191,6 +193,27 @@ export function PreferencesPart(props: {

+ + {/* Hold to Boost Preference */} +
+

+ {t("settings.preferences.holdToBoost")} +

+

+ {t("settings.preferences.holdToBoostDescription")} +

+
+ props.setEnableHoldToBoost(!props.enableHoldToBoost) + } + className="bg-dropdown-background hover:bg-dropdown-hoverBackground select-none my-4 cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg" + > + +

+ {t("settings.preferences.holdToBoostLabel")} +

+
+
{/* Column */} diff --git a/src/stores/preferences/index.tsx b/src/stores/preferences/index.tsx index e367be70..0b08dac0 100644 --- a/src/stores/preferences/index.tsx +++ b/src/stores/preferences/index.tsx @@ -21,6 +21,7 @@ export interface PreferencesStore { realDebridKey: string | null; enableLowPerformanceMode: boolean; enableNativeSubtitles: boolean; + enableHoldToBoost: boolean; setEnableThumbnails(v: boolean): void; setEnableAutoplay(v: boolean): void; @@ -40,6 +41,7 @@ export interface PreferencesStore { setRealDebridKey(v: string | null): void; setEnableLowPerformanceMode(v: boolean): void; setEnableNativeSubtitles(v: boolean): void; + setEnableHoldToBoost(v: boolean): void; } export const usePreferencesStore = create( @@ -63,6 +65,7 @@ export const usePreferencesStore = create( realDebridKey: null, enableLowPerformanceMode: false, enableNativeSubtitles: false, + enableHoldToBoost: true, setEnableThumbnails(v) { set((s) => { s.enableThumbnails = v; @@ -153,6 +156,11 @@ export const usePreferencesStore = create( s.enableNativeSubtitles = v; }); }, + setEnableHoldToBoost(v) { + set((s) => { + s.enableHoldToBoost = v; + }); + }, })), { name: "__MW::preferences",