Files
feishin/src/renderer/features/settings/components/playback-tab.tsx
T
2022-11-08 00:20:39 -08:00

285 lines
8.1 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Divider, Group, SelectItem, Stack } from '@mantine/core';
import isElectron from 'is-electron';
import {
NumberInput,
SegmentedControl,
Select,
Slider,
Switch,
toast,
Tooltip,
} from '@/renderer/components';
import { mpvPlayer } from '@/renderer/features/player/utils/mpv-player';
import { SettingsOptions } from '@/renderer/features/settings/components/settings-option';
import { usePlayerStore } from '@/renderer/store';
import { useSettingsStore } from '@/renderer/store/settings.store';
import {
Play,
PlaybackStyle,
PlaybackType,
PlayerStatus,
} from '@/renderer/types';
const getAudioDevice = async () => {
const devices = await navigator.mediaDevices.enumerateDevices();
return (devices || []).filter(
(dev: MediaDeviceInfo) => dev.kind === 'audiooutput'
);
};
const ipc = isElectron() ? window.electron.ipcRenderer : null;
const set = (property: string, value: any) => {
ipc?.SETTINGS_SET({ property, value });
};
export const PlaybackTab = () => {
const settings = useSettingsStore((state) => state.player);
const update = useSettingsStore((state) => state.setSettings);
const status = usePlayerStore((state) => state.current.status);
const [audioDevices, setAudioDevices] = useState<SelectItem[]>([]);
useEffect(() => {
const getAudioDevices = () => {
getAudioDevice()
.then((dev) =>
setAudioDevices(
dev.map((d) => ({ label: d.label, value: d.deviceId }))
)
)
.catch(() => toast.error({ message: 'Error fetching audio devices' }));
};
getAudioDevices();
}, []);
const playerOptions = [
{
control: (
<SegmentedControl
data={[
{
disabled: !isElectron(),
label: 'MPV',
value: PlaybackType.LOCAL,
},
{ label: 'Web', value: PlaybackType.WEB },
]}
defaultValue={settings.type}
disabled={status === PlayerStatus.PLAYING}
onChange={(e) => {
update({ player: { ...settings, type: e as PlaybackType } });
if (isElectron() && e === PlaybackType.LOCAL) {
const queueData = usePlayerStore.getState().getPlayerData();
mpvPlayer.setQueue(queueData);
}
}}
/>
),
description: 'The audio player to use for playback (desktop only)',
isHidden: !isElectron(),
note:
status === PlayerStatus.PLAYING ? 'Player must be paused' : undefined,
title: 'Audio player',
},
{
control: (
<Select
clearable
data={audioDevices}
defaultValue={settings.audioDeviceId}
disabled={settings.type !== PlaybackType.WEB}
onChange={(e) =>
update({ player: { ...settings, audioDeviceId: e } })
}
/>
),
description: 'The audio device to use for playback (web player only)',
isHidden: !isElectron(),
title: 'Audio device',
},
{
control: (
<SegmentedControl
data={[
{ label: 'Normal', value: PlaybackStyle.GAPLESS },
{ label: 'Crossfade', value: PlaybackStyle.CROSSFADE },
]}
defaultValue={settings.style}
disabled={
settings.type !== PlaybackType.WEB ||
status === PlayerStatus.PLAYING
}
onChange={(e) =>
update({ player: { ...settings, style: e as PlaybackStyle } })
}
/>
),
description: 'Adjust the playback style (web player only)',
isHidden: false,
note:
status === PlayerStatus.PLAYING ? 'Player must be paused' : undefined,
title: 'Playback style',
},
{
control: (
<Slider
defaultValue={settings.crossfadeDuration}
disabled={
settings.type !== PlaybackType.WEB ||
settings.style !== PlaybackStyle.CROSSFADE ||
status === PlayerStatus.PLAYING
}
max={15}
min={0}
w={100}
onChangeEnd={(e) =>
update({ player: { ...settings, crossfadeDuration: e } })
}
/>
),
description: 'Adjust the crossfade duration (web player only)',
isHidden: false,
note:
status === PlayerStatus.PLAYING ? 'Player must be paused' : undefined,
title: 'Crossfade Duration',
},
{
control: (
<Switch
defaultChecked={settings.globalMediaHotkeys}
disabled={!isElectron()}
onChange={(e) => {
update({
player: {
...settings,
globalMediaHotkeys: e.currentTarget.checked,
},
});
set('global_media_hotkeys', e.currentTarget.checked);
if (e.currentTarget.checked) {
ipc?.PLAYER_MEDIA_KEYS_ENABLE();
} else {
ipc?.PLAYER_MEDIA_KEYS_DISABLE();
}
}}
/>
),
description:
'Enable or disable the usage of your system media hotkeys to control the audio player (desktop only)',
isHidden: false,
title: 'Global media hotkeys',
},
];
const otherOptions = [
{
control: (
<SegmentedControl
data={[
{ label: 'Now', value: Play.NOW },
{ label: 'Next', value: Play.NEXT },
{ label: 'Last', value: Play.LAST },
]}
defaultValue={settings.playButtonBehavior}
onChange={(e) =>
update({
player: {
...settings,
playButtonBehavior: e as Play,
},
})
}
/>
),
description:
'The default behavior of the play button when adding songs to the queue',
isHidden: false,
title: 'Play button behavior',
},
{
control: (
<Switch
defaultChecked={settings.skipButtons?.enabled}
onChange={(e) =>
update({
player: {
...settings,
skipButtons: {
...settings.skipButtons,
enabled: e.currentTarget.checked,
},
},
})
}
/>
),
description: 'Show or hide the skip buttons on the playerbar',
isHidden: false,
title: 'Show skip buttons',
},
{
control: (
<Group>
<Tooltip label="Backward">
<NumberInput
defaultValue={settings.skipButtons.skipBackwardSeconds}
width={75}
onBlur={(e) =>
update({
player: {
...settings,
skipButtons: {
...settings.skipButtons,
skipBackwardSeconds: Number(e.currentTarget.value),
},
},
})
}
/>
</Tooltip>
<Tooltip label="Forward">
<NumberInput
defaultValue={settings.skipButtons.skipForwardSeconds}
width={75}
onBlur={(e) =>
update({
player: {
...settings,
skipButtons: {
...settings.skipButtons,
skipForwardSeconds: Number(e.currentTarget.value),
},
},
})
}
/>
</Tooltip>
</Group>
),
description:
'The number (in seconds) to skip forward or backward when using the skip buttons',
isHidden: false,
title: 'Skip duration',
},
];
return (
<Stack my={10} spacing="xl">
{playerOptions
.filter((o) => !o.isHidden)
.map((option) => (
<SettingsOptions key={`playback-${option.title}`} {...option} />
))}
<Divider />
{otherOptions
.filter((o) => !o.isHidden)
.map((option) => (
<SettingsOptions key={`playerbar-${option.title}`} {...option} />
))}
</Stack>
);
};