fix playbacksettingsview settigns not working correctly

didnt save to backend or apply thumbails. Also hid them when performance mode is enabled
This commit is contained in:
Pas 2025-10-24 17:27:58 -06:00
parent 71a3d91b2a
commit 55b68c520e
5 changed files with 91 additions and 28 deletions

View file

@ -1,13 +1,19 @@
import classNames from "classnames";
export function Toggle(props: { onClick?: () => void; enabled?: boolean }) {
export function Toggle(props: {
onClick?: () => void;
enabled?: boolean;
disabled?: boolean;
}) {
return (
<button
type="button"
onClick={props.onClick}
onClick={props.disabled ? undefined : props.onClick}
disabled={props.disabled}
className={classNames(
"w-11 h-6 p-1 rounded-full grid transition-colors duration-100 group/toggle tabbable",
props.enabled ? "bg-buttons-toggle" : "bg-buttons-toggleDisabled",
props.disabled ? "opacity-50 cursor-not-allowed" : null,
)}
>
<div className="relative w-full h-full">

View file

@ -2,10 +2,13 @@ import classNames from "classnames";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { updateSettings } from "@/backend/accounts/settings";
import { Toggle } from "@/components/buttons/Toggle";
import { Icon, Icons } from "@/components/Icon";
import { Menu } from "@/components/player/internals/ContextMenu";
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { useAuthStore } from "@/stores/auth";
import { usePlayerStore } from "@/stores/player/store";
import { usePreferencesStore } from "@/stores/preferences";
import { useWatchPartyStore } from "@/stores/watchParty";
@ -188,10 +191,43 @@ export function PlaybackSettingsView({ id }: { id: string }) {
);
const isInWatchParty = useWatchPartyStore((s) => s.enabled);
const account = useAuthStore((s) => s.account);
const backendUrl = useBackendUrl();
const allowAutoplay = useMemo(() => isAutoplayAllowed(), []);
const canShowAutoplay =
!isInWatchParty && allowAutoplay && !enableLowPerformanceMode;
// Save settings to backend
const saveThumbnailSetting = useCallback(
async (value: boolean) => {
if (!account || !backendUrl) return;
try {
await updateSettings(backendUrl, account, {
enableThumbnails: value,
});
} catch (error) {
console.error("Failed to save thumbnail setting:", error);
}
},
[account, backendUrl],
);
const saveAutoplaySetting = useCallback(
async (value: boolean) => {
if (!account || !backendUrl) return;
try {
await updateSettings(backendUrl, account, {
enableAutoplay: value,
});
} catch (error) {
console.error("Failed to save autoplay setting:", error);
}
},
[account, backendUrl],
);
const setPlaybackRate = useCallback(
(v: number) => {
if (isInWatchParty) return; // Don't allow changes in watch party
@ -200,6 +236,20 @@ export function PlaybackSettingsView({ id }: { id: string }) {
[display, isInWatchParty],
);
// Handle thumbnail toggle with backend save
const handleThumbnailToggle = useCallback(() => {
const newValue = !enableThumbnails;
setEnableThumbnails(newValue);
saveThumbnailSetting(newValue);
}, [enableThumbnails, setEnableThumbnails, saveThumbnailSetting]);
// Handle autoplay toggle with backend save
const handleAutoplayToggle = useCallback(() => {
const newValue = !enableAutoplay;
setEnableAutoplay(newValue);
saveAutoplaySetting(newValue);
}, [enableAutoplay, setEnableAutoplay, saveAutoplaySetting]);
// Force 1x speed in watch party
useEffect(() => {
if (isInWatchParty && display && playbackRate !== 1) {
@ -239,23 +289,25 @@ export function PlaybackSettingsView({ id }: { id: string }) {
rightSide={
<Toggle
enabled={enableAutoplay}
onClick={() => setEnableAutoplay(!enableAutoplay)}
onClick={handleAutoplayToggle}
/>
}
>
{t("settings.preferences.autoplayLabel")}
</Menu.Link>
)}
<Menu.Link
rightSide={
<Toggle
enabled={enableThumbnails}
onClick={() => setEnableThumbnails(!enableThumbnails)}
/>
}
>
{t("settings.preferences.thumbnailLabel")}
</Menu.Link>
{!enableLowPerformanceMode && (
<Menu.Link
rightSide={
<Toggle
enabled={enableThumbnails}
onClick={handleThumbnailToggle}
/>
}
>
{t("settings.preferences.thumbnailLabel")}
</Menu.Link>
)}
</div>
</Menu.Section>
</>

View file

@ -1,7 +1,6 @@
import Hls from "hls.js";
import { useCallback, useEffect, useRef } from "react";
import { playerStatus } from "@/stores/player/slices/source";
import { ThumbnailImage } from "@/stores/player/slices/thumbnails";
import { usePlayerStore } from "@/stores/player/store";
import { LoadableSource, selectQuality } from "@/stores/player/utils/qualities";
@ -125,10 +124,11 @@ class ThumnbnailWorker {
export function ThumbnailScraper() {
const addImage = usePlayerStore((s) => s.thumbnails.addImage);
const status = usePlayerStore((s) => s.status);
const resetImages = usePlayerStore((s) => s.thumbnails.resetImages);
const meta = usePlayerStore((s) => s.meta);
const source = usePlayerStore((s) => s.source);
const hasPlayedOnce = usePlayerStore((s) => s.mediaPlaying.hasPlayedOnce);
const duration = usePlayerStore((s) => s.progress.duration);
const enableThumbnails = usePreferencesStore((s) => s.enableThumbnails);
const workerRef = useRef<ThumnbnailWorker | null>(null);
@ -144,7 +144,8 @@ export function ThumbnailScraper() {
});
// dont interrupt existing working
if (workerRef.current) return;
if (status !== playerStatus.PLAYING) return;
// Allow thumbnail generation when video is loaded and has duration
if (!hasPlayedOnce || duration <= 0) return;
if (!inputStream) return;
resetImages();
const ins = new ThumnbnailWorker({
@ -152,17 +153,17 @@ export function ThumbnailScraper() {
});
workerRef.current = ins;
ins.start(inputStream.stream);
}, [source, addImage, resetImages, status]);
}, [source, addImage, resetImages, hasPlayedOnce, duration]);
const startRef = useRef(start);
useEffect(() => {
startRef.current = start;
}, [start, status]);
}, [start]);
// start worker with the stream
useEffect(() => {
if (enableThumbnails) startRef.current();
}, [sourceSeralized, enableThumbnails]);
}, [sourceSeralized, enableThumbnails, hasPlayedOnce, duration]);
// destroy worker on unmount
useEffect(() => {
@ -186,7 +187,13 @@ export function ThumbnailScraper() {
workerRef.current = null;
}
if (enableThumbnails) startRef.current();
}, [serializedMeta, sourceSeralized, status, enableThumbnails]);
}, [
serializedMeta,
sourceSeralized,
enableThumbnails,
hasPlayedOnce,
duration,
]);
return null;
}

View file

@ -70,14 +70,7 @@ export function PreferencesPart(props: {
const navigate = useNavigate();
const handleLowPerformanceModeToggle = () => {
const newMode = !props.enableLowPerformanceMode;
props.setEnableLowPerformanceMode(newMode);
// When enabling low performance mode, disable bandwidth-heavy features
if (newMode) {
props.setEnableThumbnails(false);
props.setEnableAutoplay(false);
}
props.setEnableLowPerformanceMode(!props.enableLowPerformanceMode);
};
const handleSourceToggle = (sourceId: string) => {

View file

@ -174,6 +174,11 @@ export const usePreferencesStore = create(
setEnableLowPerformanceMode(v) {
set((s) => {
s.enableLowPerformanceMode = v;
// When enabling performance mode, disable bandwidth-heavy features
if (v) {
s.enableThumbnails = false;
s.enableAutoplay = false;
}
});
},
setEnableNativeSubtitles(v) {