Files
KVideo/components/player/hooks/usePlayerSettings.ts
T

230 lines
8.3 KiB
TypeScript

'use client';
import { useState, useEffect, useCallback } from 'react';
import {
settingsStore,
type AppSettings,
type AdFilterMode,
} from '@/lib/store/settings-store';
import {
premiumModeSettingsStore,
type ModeSettings,
} from '@/lib/store/premium-mode-settings';
import { useRuntimeFeatures } from '@/components/RuntimeFeaturesProvider';
interface PlayerSettingsSnapshot {
autoNextEpisode: boolean;
autoSkipIntro: boolean;
skipIntroSeconds: number;
autoSkipOutro: boolean;
skipOutroSeconds: number;
showModeIndicator: boolean;
adFilter: boolean;
adFilterMode: AdFilterMode;
adKeywords: string[];
customAdFilterCode: string;
customAdFilterVersion: number;
fullscreenType: 'auto' | 'native' | 'window';
proxyMode: 'retry' | 'none' | 'always';
danmakuEnabled: boolean;
danmakuApiUrl: string;
danmakuOpacity: number;
danmakuFontSize: number;
danmakuDisplayArea: number;
}
function getPlayerSettingsSnapshot(isPremium: boolean, mediaProxyEnabled: boolean): PlayerSettingsSnapshot {
const globalSettings = settingsStore.getSettings();
const modeSettings = isPremium ? premiumModeSettingsStore.getSettings() : globalSettings;
return {
autoNextEpisode: modeSettings.autoNextEpisode,
autoSkipIntro: modeSettings.autoSkipIntro,
skipIntroSeconds: modeSettings.skipIntroSeconds,
autoSkipOutro: modeSettings.autoSkipOutro,
skipOutroSeconds: modeSettings.skipOutroSeconds,
showModeIndicator: modeSettings.showModeIndicator,
adFilter: globalSettings.adFilter,
adFilterMode: modeSettings.adFilterMode,
adKeywords: globalSettings.adKeywords,
customAdFilterCode: globalSettings.customAdFilterCode || '',
customAdFilterVersion: globalSettings.customAdFilterVersion || 0,
fullscreenType: modeSettings.fullscreenType,
proxyMode: mediaProxyEnabled ? modeSettings.proxyMode : 'none',
danmakuEnabled: modeSettings.danmakuEnabled,
danmakuApiUrl: modeSettings.danmakuApiUrl,
danmakuOpacity: modeSettings.danmakuOpacity,
danmakuFontSize: modeSettings.danmakuFontSize,
danmakuDisplayArea: modeSettings.danmakuDisplayArea,
};
}
function playerSettingsEqual(a: PlayerSettingsSnapshot, b: PlayerSettingsSnapshot): boolean {
return (
a.autoNextEpisode === b.autoNextEpisode &&
a.autoSkipIntro === b.autoSkipIntro &&
a.skipIntroSeconds === b.skipIntroSeconds &&
a.autoSkipOutro === b.autoSkipOutro &&
a.skipOutroSeconds === b.skipOutroSeconds &&
a.showModeIndicator === b.showModeIndicator &&
a.adFilter === b.adFilter &&
a.adFilterMode === b.adFilterMode &&
a.adKeywords === b.adKeywords &&
a.customAdFilterCode === b.customAdFilterCode &&
a.customAdFilterVersion === b.customAdFilterVersion &&
a.fullscreenType === b.fullscreenType &&
a.proxyMode === b.proxyMode &&
a.danmakuEnabled === b.danmakuEnabled &&
a.danmakuApiUrl === b.danmakuApiUrl &&
a.danmakuOpacity === b.danmakuOpacity &&
a.danmakuFontSize === b.danmakuFontSize &&
a.danmakuDisplayArea === b.danmakuDisplayArea
);
}
/**
* Hook to access and update player settings from the settings store
* Provides reactive updates when settings change
*/
export function usePlayerSettings(isPremium: boolean = false) {
const { mediaProxyEnabled } = useRuntimeFeatures();
const [settings, setSettings] = useState(() => getPlayerSettingsSnapshot(isPremium, mediaProxyEnabled));
// Subscribe to settings changes. Reuse the previous snapshot when
// non-player fields change (e.g. episodeReverseOrder) so HLS is not rebuilt.
useEffect(() => {
const syncSettings = () => {
const next = getPlayerSettingsSnapshot(isPremium, mediaProxyEnabled);
setSettings((prev) => (playerSettingsEqual(prev, next) ? prev : next));
};
const modeStore = isPremium ? premiumModeSettingsStore : settingsStore;
const unsubscribeModeStore = modeStore.subscribe(syncSettings);
const unsubscribeGlobalStore = isPremium ? settingsStore.subscribe(syncSettings) : null;
syncSettings();
return () => {
unsubscribeModeStore();
unsubscribeGlobalStore?.();
};
}, [isPremium, mediaProxyEnabled]);
const updateModeSettings = useCallback((partial: Partial<ModeSettings>) => {
if (isPremium) {
const currentSettings = premiumModeSettingsStore.getSettings();
premiumModeSettingsStore.saveSettings({
...currentSettings,
...partial,
});
return;
}
const currentSettings = settingsStore.getSettings();
settingsStore.saveSettings({
...currentSettings,
...partial,
});
}, [isPremium]);
const updateGlobalSettings = useCallback((partial: Partial<AppSettings>) => {
const currentSettings = settingsStore.getSettings();
settingsStore.saveSettings({
...currentSettings,
...partial,
});
}, []);
const setAutoNextEpisode = useCallback((value: boolean) => {
updateModeSettings({ autoNextEpisode: value });
}, [updateModeSettings]);
const setAutoSkipIntro = useCallback((value: boolean) => {
updateModeSettings({ autoSkipIntro: value });
}, [updateModeSettings]);
const setSkipIntroSeconds = useCallback((value: number) => {
updateModeSettings({ skipIntroSeconds: Math.max(0, value) });
}, [updateModeSettings]);
const setAutoSkipOutro = useCallback((value: boolean) => {
updateModeSettings({ autoSkipOutro: value });
}, [updateModeSettings]);
const setSkipOutroSeconds = useCallback((value: number) => {
updateModeSettings({ skipOutroSeconds: Math.max(0, value) });
}, [updateModeSettings]);
const setShowModeIndicator = useCallback((value: boolean) => {
updateModeSettings({ showModeIndicator: value });
}, [updateModeSettings]);
const setAdFilter = useCallback((value: boolean) => {
updateGlobalSettings({ adFilter: value });
}, [updateGlobalSettings]);
const setAdFilterMode = useCallback((value: AdFilterMode) => {
updateModeSettings({ adFilterMode: value });
}, [updateModeSettings]);
const setAdKeywords = useCallback((value: string[]) => {
updateGlobalSettings({ adKeywords: value });
}, [updateGlobalSettings]);
const setCustomAdFilterCode = useCallback((code: string, version?: number) => {
updateGlobalSettings({
customAdFilterCode: code,
...(version !== undefined ? { customAdFilterVersion: version } : {})
});
}, [updateGlobalSettings]);
const setFullscreenType = useCallback((value: 'auto' | 'native' | 'window') => {
updateModeSettings({ fullscreenType: value });
}, [updateModeSettings]);
const setProxyMode = useCallback((value: 'retry' | 'none' | 'always') => {
updateModeSettings({ proxyMode: mediaProxyEnabled ? value : 'none' });
}, [mediaProxyEnabled, updateModeSettings]);
const setDanmakuEnabled = useCallback((value: boolean) => {
updateModeSettings({ danmakuEnabled: value });
}, [updateModeSettings]);
const setDanmakuApiUrl = useCallback((value: string) => {
updateModeSettings({ danmakuApiUrl: value });
}, [updateModeSettings]);
const setDanmakuOpacity = useCallback((value: number) => {
updateModeSettings({ danmakuOpacity: Math.max(0.1, Math.min(1, value)) });
}, [updateModeSettings]);
const setDanmakuFontSize = useCallback((value: number) => {
updateModeSettings({ danmakuFontSize: value });
}, [updateModeSettings]);
const setDanmakuDisplayArea = useCallback((value: number) => {
updateModeSettings({ danmakuDisplayArea: value });
}, [updateModeSettings]);
return {
...settings,
setAutoNextEpisode,
setAutoSkipIntro,
setSkipIntroSeconds,
setAutoSkipOutro,
setSkipOutroSeconds,
setShowModeIndicator,
setAdFilter,
setAdFilterMode,
setAdKeywords,
setCustomAdFilterCode,
setFullscreenType,
setProxyMode,
setDanmakuEnabled,
setDanmakuApiUrl,
setDanmakuOpacity,
setDanmakuFontSize,
setDanmakuDisplayArea,
};
}