add setting to disable speed boost

This commit is contained in:
Pas 2025-09-03 21:14:04 -06:00
parent e79a04c204
commit b34e263369
9 changed files with 101 additions and 9 deletions

View file

@ -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 <bold>extension</bold> is required for that source. <br><br> <strong>(The default order is best for most users)</strong>",
"sourceOrderEnableLabel": "Custom source order",

View file

@ -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(

View file

@ -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<ReturnType<typeof setTimeout> | 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;

View file

@ -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,
],
);

View file

@ -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,
],
);

View file

@ -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,
},
};
}

View file

@ -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 (
<SubPageLayout>
@ -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}
/>
</div>
<div id="settings-appearance" className="mt-28">

View file

@ -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: {
</p>
</div>
</div>
{/* Hold to Boost Preference */}
<div>
<p className="text-white font-bold mb-3">
{t("settings.preferences.holdToBoost")}
</p>
<p className="max-w-[25rem] font-medium">
{t("settings.preferences.holdToBoostDescription")}
</p>
<div
onClick={() =>
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"
>
<Toggle enabled={props.enableHoldToBoost} />
<p className="flex-1 text-white font-bold">
{t("settings.preferences.holdToBoostLabel")}
</p>
</div>
</div>
</div>
{/* Column */}

View file

@ -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",