crossfade player enhancements, reorganize settings

This commit is contained in:
jeffvli
2025-11-19 15:43:20 -08:00
parent 725e44f048
commit 0dff13c43f
8 changed files with 546 additions and 398 deletions
@@ -7,13 +7,17 @@ import {
SettingsSection,
} from '/@/renderer/features/settings/components/settings-section';
import {
BarAlign,
GenreTarget,
PlayerbarSliderType,
SideQueueType,
useGeneralSettings,
usePlayerbarSlider,
useSettingsStoreActions,
} from '/@/renderer/store/settings.store';
import { Group } from '/@/shared/components/group/group';
import { NumberInput } from '/@/shared/components/number-input/number-input';
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
import { Select } from '/@/shared/components/select/select';
import { Slider } from '/@/shared/components/slider/slider';
import { Switch } from '/@/shared/components/switch/switch';
@@ -43,6 +47,7 @@ const SIDE_QUEUE_OPTIONS = [
export const ControlSettings = () => {
const { t } = useTranslation();
const settings = useGeneralSettings();
const playerbarSlider = usePlayerbarSlider();
const { setSettings } = useSettingsStoreActions();
const controlOptions: SettingOption[] = [
@@ -621,6 +626,202 @@ export const ControlSettings = () => {
isHidden: false,
title: t('setting.playerbarOpenDrawer', { postProcess: 'sentenceCase' }),
},
{
control: (
<SegmentedControl
data={[
{
label: t('setting.playerbarSliderType', {
context: 'optionSlider',
postProcess: 'titleCase',
}),
value: PlayerbarSliderType.SLIDER,
},
{
label: t('setting.playerbarSliderType', {
context: 'optionWaveform',
postProcess: 'titleCase',
}),
value: PlayerbarSliderType.WAVEFORM,
},
]}
onChange={(value) => {
setSettings({
general: {
...settings,
playerbarSlider: {
...playerbarSlider,
type: value as PlayerbarSliderType,
},
},
});
}}
size="sm"
value={playerbarSlider?.type || PlayerbarSliderType.WAVEFORM}
w="100%"
/>
),
description: t('setting.playerbarSlider', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: false,
title: t('setting.playerbarSlider', { postProcess: 'sentenceCase' }),
},
...(playerbarSlider?.type === PlayerbarSliderType.WAVEFORM
? [
{
control: (
<SegmentedControl
data={[
{
label: t('setting.playerbarWaveformAlign', {
context: 'optionTop',
postProcess: 'titleCase',
}),
value: BarAlign.TOP,
},
{
label: t('setting.playerbarWaveformAlign', {
context: 'optionCenter',
postProcess: 'titleCase',
}),
value: BarAlign.CENTER,
},
{
label: t('setting.playerbarWaveformAlign', {
context: 'optionBottom',
postProcess: 'titleCase',
}),
value: BarAlign.BOTTOM,
},
]}
onChange={(value) => {
setSettings({
general: {
...settings,
playerbarSlider: {
...playerbarSlider,
barAlign: (value as BarAlign) || BarAlign.CENTER,
},
},
});
}}
size="sm"
value={playerbarSlider?.barAlign || BarAlign.CENTER}
w="100%"
/>
),
description: t('setting.playerbarWaveformAlign', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: false,
title: t('setting.playerbarWaveformAlign', {
postProcess: 'sentenceCase',
}),
},
{
control: (
<Slider
defaultValue={playerbarSlider?.barWidth ?? 2}
max={10}
min={0}
onChangeEnd={(value) => {
setSettings({
general: {
...settings,
playerbarSlider: {
...playerbarSlider,
barWidth: value,
},
},
});
}}
step={1}
styles={{
root: {},
}}
w="120px"
/>
),
description: t('setting.playerbarWaveformBarWidth', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: false,
title: t('setting.playerbarWaveformBarWidth', {
postProcess: 'sentenceCase',
}),
},
{
control: (
<Slider
defaultValue={playerbarSlider?.barGap || 0}
max={10}
min={0}
onChangeEnd={(value) => {
setSettings({
general: {
...settings,
playerbarSlider: {
...playerbarSlider,
barGap: value,
},
},
});
}}
step={1}
styles={{
root: {},
}}
w="120px"
/>
),
description: t('setting.playerbarWaveformGap', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: false,
title: t('setting.playerbarWaveformGap', {
postProcess: 'sentenceCase',
}),
},
{
control: (
<Slider
defaultValue={playerbarSlider?.barRadius ?? 4}
max={20}
min={0}
onChangeEnd={(value) => {
setSettings({
general: {
...settings,
playerbarSlider: {
...playerbarSlider,
barRadius: value,
},
},
});
}}
step={1}
styles={{
root: {},
}}
w="120px"
/>
),
description: t('setting.playerbarWaveformRadius', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: false,
title: t('setting.playerbarWaveformRadius', {
postProcess: 'sentenceCase',
}),
},
]
: []),
];
return <SettingsSection options={controlOptions} />;
@@ -6,13 +6,12 @@ import {
SettingOption,
SettingsSection,
} from '/@/renderer/features/settings/components/settings-section';
import { usePlayerActions, usePlayerProperties, usePlayerStatus } from '/@/renderer/store';
import { usePlayerStatus } from '/@/renderer/store';
import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store';
import { Select } from '/@/shared/components/select/select';
import { Slider } from '/@/shared/components/slider/slider';
import { Switch } from '/@/shared/components/switch/switch';
import { toast } from '/@/shared/components/toast/toast';
import { CrossfadeStyle, PlayerStatus, PlayerStyle, PlayerType } from '/@/shared/types/types';
import { PlayerStatus, PlayerType } from '/@/shared/types/types';
const ipc = isElectron() ? window.api.ipc : null;
@@ -27,9 +26,6 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) =>
const { setSettings } = useSettingsStoreActions();
const status = usePlayerStatus();
const { crossfadeDuration, transitionType } = usePlayerProperties();
const { setCrossfadeDuration, setTransitionType } = usePlayerActions();
const [audioDevices, setAudioDevices] = useState<{ label: string; value: string }[]>([]);
useEffect(() => {
@@ -98,41 +94,6 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) =>
isHidden: !isElectron() || settings.type !== PlayerType.WEB,
title: t('setting.audioDevice', { postProcess: 'sentenceCase' }),
},
{
control: (
<Select
data={[
{
label: t('setting.playbackStyle', {
context: 'optionNormal',
postProcess: 'titleCase',
}),
value: PlayerStyle.GAPLESS,
},
{
label: t('setting.playbackStyle', {
context: 'optionCrossFade',
postProcess: 'titleCase',
}),
value: PlayerStyle.CROSSFADE,
},
]}
defaultValue={transitionType}
disabled={settings.type !== PlayerType.WEB || status === PlayerStatus.PLAYING}
onChange={(e) => setTransitionType(e as PlayerStyle)}
/>
),
description: t('setting.playbackStyle', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: settings.type !== PlayerType.WEB,
note: status === PlayerStatus.PLAYING ? 'Player must be paused' : undefined,
title: t('setting.playbackStyle', {
context: 'description',
postProcess: 'sentenceCase',
}),
},
{
control: (
<Switch
@@ -174,71 +135,6 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) =>
postProcess: 'sentenceCase',
}),
},
{
control: (
<Slider
defaultValue={crossfadeDuration}
disabled={
settings.type !== PlayerType.WEB ||
settings.style !== PlayerStyle.CROSSFADE ||
status === PlayerStatus.PLAYING
}
max={15}
min={3}
onChangeEnd={(e) => setCrossfadeDuration(e)}
w={100}
/>
),
description: t('setting.crossfadeDuration', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: settings.type !== PlayerType.WEB,
note: status === PlayerStatus.PLAYING ? 'Player must be paused' : undefined,
title: t('setting.crossfadeDuration', {
postProcess: 'sentenceCase',
}),
},
{
control: (
<Select
data={[
{ label: 'Linear', value: CrossfadeStyle.LINEAR },
{ label: 'Constant Power', value: CrossfadeStyle.CONSTANT_POWER },
{
label: 'Constant Power (Slow cut)',
value: CrossfadeStyle.CONSTANT_POWER_SLOW_CUT,
},
{
label: 'Constant Power (Slow fade)',
value: CrossfadeStyle.CONSTANT_POWER_SLOW_FADE,
},
{ label: 'Dipped', value: CrossfadeStyle.DIPPED },
{ label: 'Equal Power', value: CrossfadeStyle.EQUALPOWER },
]}
defaultValue={settings.crossfadeStyle}
disabled={
settings.type !== PlayerType.WEB ||
settings.style !== PlayerStyle.CROSSFADE ||
status === PlayerStatus.PLAYING
}
onChange={(e) => {
if (!e) return;
setSettings({
playback: { ...settings, crossfadeStyle: e as CrossfadeStyle },
});
}}
width={200}
/>
),
description: t('setting.crossfadeStyle', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: settings.type !== PlayerType.WEB,
note: status === PlayerStatus.PLAYING ? 'Player must be paused' : undefined,
title: t('setting.crossfadeStyle', { postProcess: 'sentenceCase' }),
},
];
return <SettingsSection divider={!hasFancyAudio} options={audioOptions} />;