mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
refactor some usePlayerSong consumers to only fetch needed properties
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
|||||||
useButtonSize,
|
useButtonSize,
|
||||||
usePlayerRepeat,
|
usePlayerRepeat,
|
||||||
usePlayerShuffle,
|
usePlayerShuffle,
|
||||||
usePlayerSong,
|
usePlayerSongProperties,
|
||||||
usePlayerStatus,
|
usePlayerStatus,
|
||||||
useSkipButtons,
|
useSkipButtons,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
@@ -197,13 +197,14 @@ const SkipBackwardButton = ({ disabled }: { disabled?: boolean }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CenterPlayButton = ({ disabled }: { disabled?: boolean }) => {
|
const CenterPlayButton = ({ disabled }: { disabled?: boolean }) => {
|
||||||
const currentSong = usePlayerSong();
|
const { id: currentSongId } = usePlayerSongProperties(['id']) ?? {};
|
||||||
|
|
||||||
const status = usePlayerStatus();
|
const status = usePlayerStatus();
|
||||||
const { mediaTogglePlayPause } = usePlayer();
|
const { mediaTogglePlayPause } = usePlayer();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainPlayButton
|
<MainPlayButton
|
||||||
disabled={disabled || currentSong?.id === undefined}
|
disabled={disabled || currentSongId === undefined}
|
||||||
isPaused={status === PlayerStatus.PAUSED}
|
isPaused={status === PlayerStatus.PAUSED}
|
||||||
onClick={mediaTogglePlayPause}
|
onClick={mediaTogglePlayPause}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { ListConfigTable } from '/@/renderer/features/shared/components/list-con
|
|||||||
import {
|
import {
|
||||||
usePlaybackType,
|
usePlaybackType,
|
||||||
usePlayerActions,
|
usePlayerActions,
|
||||||
usePlayerData,
|
|
||||||
usePlayerProperties,
|
usePlayerProperties,
|
||||||
|
usePlayerSongProperties,
|
||||||
usePlayerSpeed,
|
usePlayerSpeed,
|
||||||
usePlayerStatus,
|
usePlayerStatus,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
@@ -51,13 +51,6 @@ const getMpvAudioDevices = async () => {
|
|||||||
|
|
||||||
export const PlayerConfig = () => {
|
export const PlayerConfig = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { currentSong } = usePlayerData();
|
|
||||||
const speed = usePlayerSpeed();
|
|
||||||
const status = usePlayerStatus();
|
|
||||||
const playbackType = usePlaybackType();
|
|
||||||
const { crossfadeDuration, crossfadeStyle, transitionType } = usePlayerProperties();
|
|
||||||
const { setCrossfadeDuration, setCrossfadeStyle, setSpeed, setTransitionType } =
|
|
||||||
usePlayerActions();
|
|
||||||
const preservePitch = useSettingsStore((state) => state.playback.preservePitch);
|
const preservePitch = useSettingsStore((state) => state.playback.preservePitch);
|
||||||
const showLyricsInSidebar = useShowLyricsInSidebar();
|
const showLyricsInSidebar = useShowLyricsInSidebar();
|
||||||
const showVisualizerInSidebar = useShowVisualizerInSidebar();
|
const showVisualizerInSidebar = useShowVisualizerInSidebar();
|
||||||
@@ -75,101 +68,15 @@ export const PlayerConfig = () => {
|
|||||||
[playbackSettings, setSettings],
|
[playbackSettings, setSettings],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [audioDevices, setAudioDevices] = useState<{ label: string; value: string }[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchAudioDevices = async () => {
|
|
||||||
if (!isElectron()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playbackType === PlayerType.WEB) {
|
|
||||||
getAudioDevice()
|
|
||||||
.then((dev) =>
|
|
||||||
setAudioDevices(dev.map((d) => ({ label: d.label, value: d.deviceId }))),
|
|
||||||
)
|
|
||||||
.catch(() =>
|
|
||||||
toast.error({
|
|
||||||
message: t('error.audioDeviceFetchError', {
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else if (playbackType === PlayerType.LOCAL && mpvPlayer) {
|
|
||||||
try {
|
|
||||||
const devices = await getMpvAudioDevices();
|
|
||||||
setAudioDevices(devices);
|
|
||||||
} catch {
|
|
||||||
toast.error({
|
|
||||||
message: t('error.audioDeviceFetchError', {
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchAudioDevices();
|
|
||||||
}, [playbackType, t]);
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
const formatPlaybackSpeedSliderLabel = (value: number) => {
|
|
||||||
const bpm = Number(currentSong?.bpm);
|
|
||||||
if (bpm > 0) {
|
|
||||||
return `${value} x / ${(bpm * value).toFixed(1)} BPM`;
|
|
||||||
}
|
|
||||||
return `${value} x`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const allOptions = [
|
const allOptions = [
|
||||||
{
|
{
|
||||||
component: (
|
component: <AudioPlayerTypeConfig />,
|
||||||
<Select
|
|
||||||
comboboxProps={{ withinPortal: false }}
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
disabled: !isElectron(),
|
|
||||||
label: 'MPV',
|
|
||||||
value: PlayerType.LOCAL,
|
|
||||||
},
|
|
||||||
{ label: 'Web', value: PlayerType.WEB },
|
|
||||||
]}
|
|
||||||
defaultValue={playbackSettings.type}
|
|
||||||
disabled={status === PlayerStatus.PLAYING}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSettings({
|
|
||||||
playback: { ...playbackSettings, type: e as PlayerType },
|
|
||||||
});
|
|
||||||
ipc?.send('settings-set', {
|
|
||||||
property: 'playbackType',
|
|
||||||
value: e,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
width="100%"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
id: 'audioPlayerType',
|
id: 'audioPlayerType',
|
||||||
label: t('setting.audioPlayer', { postProcess: 'titleCase' }),
|
label: t('setting.audioPlayer', { postProcess: 'titleCase' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: (
|
component: <AudioDeviceConfig />,
|
||||||
<Select
|
|
||||||
clearable
|
|
||||||
comboboxProps={{ withinPortal: false }}
|
|
||||||
data={audioDevices}
|
|
||||||
defaultValue={playbackSettings.audioDeviceId}
|
|
||||||
disabled={status === PlayerStatus.PLAYING}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSettings({
|
|
||||||
playback: {
|
|
||||||
...playbackSettings,
|
|
||||||
audioDeviceId: e,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
width="100%"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
id: 'audioDevice',
|
id: 'audioDevice',
|
||||||
label: t('setting.audioDevice', { postProcess: 'titleCase' }),
|
label: t('setting.audioDevice', { postProcess: 'titleCase' }),
|
||||||
},
|
},
|
||||||
@@ -180,93 +87,21 @@ export const PlayerConfig = () => {
|
|||||||
label: '',
|
label: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: (
|
component: <TransitionTypeConfig />,
|
||||||
<SegmentedControl
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
label: t('setting.playbackStyle', {
|
|
||||||
context: 'optionNormal',
|
|
||||||
postProcess: 'titleCase',
|
|
||||||
}),
|
|
||||||
value: PlayerStyle.GAPLESS,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('setting.playbackStyle', {
|
|
||||||
context: 'optionCrossFade',
|
|
||||||
postProcess: 'titleCase',
|
|
||||||
}),
|
|
||||||
value: PlayerStyle.CROSSFADE,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
disabled={
|
|
||||||
playbackSettings.type !== PlayerType.WEB ||
|
|
||||||
status === PlayerStatus.PLAYING
|
|
||||||
}
|
|
||||||
onChange={(value) => setTransitionType(value as PlayerStyle)}
|
|
||||||
size="sm"
|
|
||||||
value={transitionType}
|
|
||||||
w="100%"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
id: 'transitionType',
|
id: 'transitionType',
|
||||||
label: t('setting.playbackStyle', {
|
label: t('setting.playbackStyle', {
|
||||||
postProcess: 'titleCase',
|
postProcess: 'titleCase',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: (
|
component: <CrossfadeStyleConfig />,
|
||||||
<Select
|
|
||||||
comboboxProps={{ withinPortal: false }}
|
|
||||||
data={[
|
|
||||||
{ label: 'Linear', value: CrossfadeStyle.LINEAR },
|
|
||||||
{ label: 'Equal Power', value: CrossfadeStyle.EQUAL_POWER },
|
|
||||||
{ label: 'S-Curve', value: CrossfadeStyle.S_CURVE },
|
|
||||||
{ label: 'Exponential', value: CrossfadeStyle.EXPONENTIAL },
|
|
||||||
]}
|
|
||||||
defaultValue={crossfadeStyle}
|
|
||||||
disabled={
|
|
||||||
playbackSettings.type !== PlayerType.WEB ||
|
|
||||||
transitionType !== PlayerStyle.CROSSFADE ||
|
|
||||||
status === PlayerStatus.PLAYING
|
|
||||||
}
|
|
||||||
onChange={(e) => {
|
|
||||||
if (e) {
|
|
||||||
setCrossfadeStyle(e as CrossfadeStyle);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
width="100%"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
id: 'crossfadeStyle',
|
id: 'crossfadeStyle',
|
||||||
label: t('setting.crossfadeStyle', {
|
label: t('setting.crossfadeStyle', {
|
||||||
postProcess: 'titleCase',
|
postProcess: 'titleCase',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: (
|
component: <CrossfadeDurationConfig />,
|
||||||
<Slider
|
|
||||||
defaultValue={crossfadeDuration}
|
|
||||||
disabled={
|
|
||||||
playbackSettings.type !== PlayerType.WEB ||
|
|
||||||
transitionType !== PlayerStyle.CROSSFADE ||
|
|
||||||
status === PlayerStatus.PLAYING
|
|
||||||
}
|
|
||||||
marks={[
|
|
||||||
{ label: '3', value: 3 },
|
|
||||||
{ label: '6', value: 6 },
|
|
||||||
{ label: '9', value: 9 },
|
|
||||||
{ label: '12', value: 12 },
|
|
||||||
{ label: '15', value: 15 },
|
|
||||||
]}
|
|
||||||
max={15}
|
|
||||||
min={3}
|
|
||||||
onChangeEnd={setCrossfadeDuration}
|
|
||||||
styles={{
|
|
||||||
root: {},
|
|
||||||
}}
|
|
||||||
w="100%"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
id: 'crossfadeDuration',
|
id: 'crossfadeDuration',
|
||||||
label: t('setting.crossfadeDuration', {
|
label: t('setting.crossfadeDuration', {
|
||||||
postProcess: 'titleCase',
|
postProcess: 'titleCase',
|
||||||
@@ -279,31 +114,7 @@ export const PlayerConfig = () => {
|
|||||||
label: '',
|
label: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: (
|
component: <PlaybackSpeedSlider />,
|
||||||
<Slider
|
|
||||||
defaultValue={speed}
|
|
||||||
label={formatPlaybackSpeedSliderLabel}
|
|
||||||
marks={[
|
|
||||||
{ label: '0.5', value: 0.5 },
|
|
||||||
{ label: '0.75', value: 0.75 },
|
|
||||||
{ label: '1', value: 1 },
|
|
||||||
{ label: '1.25', value: 1.25 },
|
|
||||||
{ label: '1.5', value: 1.5 },
|
|
||||||
{ label: '1.75', value: 1.75 },
|
|
||||||
{ label: '2', value: 2 },
|
|
||||||
]}
|
|
||||||
max={2}
|
|
||||||
min={0.5}
|
|
||||||
onChangeEnd={setSpeed}
|
|
||||||
onDoubleClick={() => setSpeed(1)}
|
|
||||||
step={0.01}
|
|
||||||
styles={{
|
|
||||||
markLabel: {},
|
|
||||||
root: {},
|
|
||||||
}}
|
|
||||||
w="100%"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
id: 'playbackSpeed',
|
id: 'playbackSpeed',
|
||||||
label: t('player.playbackSpeed', { postProcess: 'titleCase' }),
|
label: t('player.playbackSpeed', { postProcess: 'titleCase' }),
|
||||||
},
|
},
|
||||||
@@ -376,20 +187,8 @@ export const PlayerConfig = () => {
|
|||||||
return allOptions;
|
return allOptions;
|
||||||
}, [
|
}, [
|
||||||
t,
|
t,
|
||||||
playbackSettings,
|
|
||||||
status,
|
|
||||||
audioDevices,
|
|
||||||
transitionType,
|
|
||||||
crossfadeStyle,
|
|
||||||
crossfadeDuration,
|
|
||||||
setCrossfadeDuration,
|
|
||||||
speed,
|
|
||||||
setSpeed,
|
|
||||||
preservePitch,
|
preservePitch,
|
||||||
currentSong?.bpm,
|
|
||||||
setSettings,
|
setSettings,
|
||||||
setTransitionType,
|
|
||||||
setCrossfadeStyle,
|
|
||||||
setPreservePitch,
|
setPreservePitch,
|
||||||
showLyricsInSidebar,
|
showLyricsInSidebar,
|
||||||
showVisualizerInSidebar,
|
showVisualizerInSidebar,
|
||||||
@@ -419,3 +218,238 @@ export const PlayerConfig = () => {
|
|||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AudioPlayerTypeConfig = () => {
|
||||||
|
const status = usePlayerStatus();
|
||||||
|
const playbackSettings = usePlaybackSettings();
|
||||||
|
const { setSettings } = useSettingsStoreActions();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
comboboxProps={{ withinPortal: false }}
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
disabled: !isElectron(),
|
||||||
|
label: 'MPV',
|
||||||
|
value: PlayerType.LOCAL,
|
||||||
|
},
|
||||||
|
{ label: 'Web', value: PlayerType.WEB },
|
||||||
|
]}
|
||||||
|
defaultValue={playbackSettings.type}
|
||||||
|
disabled={status === PlayerStatus.PLAYING}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
playback: { ...playbackSettings, type: e as PlayerType },
|
||||||
|
});
|
||||||
|
ipc?.send('settings-set', {
|
||||||
|
property: 'playbackType',
|
||||||
|
value: e,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AudioDeviceConfig = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const status = usePlayerStatus();
|
||||||
|
const playbackType = usePlaybackType();
|
||||||
|
const playbackSettings = usePlaybackSettings();
|
||||||
|
const { setSettings } = useSettingsStoreActions();
|
||||||
|
const [audioDevices, setAudioDevices] = useState<{ label: string; value: string }[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchAudioDevices = async () => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playbackType === PlayerType.WEB) {
|
||||||
|
getAudioDevice()
|
||||||
|
.then((dev) =>
|
||||||
|
setAudioDevices(dev.map((d) => ({ label: d.label, value: d.deviceId }))),
|
||||||
|
)
|
||||||
|
.catch(() =>
|
||||||
|
toast.error({
|
||||||
|
message: t('error.audioDeviceFetchError', {
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (playbackType === PlayerType.LOCAL && mpvPlayer) {
|
||||||
|
try {
|
||||||
|
const devices = await getMpvAudioDevices();
|
||||||
|
setAudioDevices(devices);
|
||||||
|
} catch {
|
||||||
|
toast.error({
|
||||||
|
message: t('error.audioDeviceFetchError', {
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAudioDevices();
|
||||||
|
}, [playbackType, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
clearable
|
||||||
|
comboboxProps={{ withinPortal: false }}
|
||||||
|
data={audioDevices}
|
||||||
|
defaultValue={playbackSettings.audioDeviceId}
|
||||||
|
disabled={status === PlayerStatus.PLAYING}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
playback: {
|
||||||
|
...playbackSettings,
|
||||||
|
audioDeviceId: e,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TransitionTypeConfig = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const status = usePlayerStatus();
|
||||||
|
const playbackSettings = usePlaybackSettings();
|
||||||
|
const { transitionType } = usePlayerProperties();
|
||||||
|
const { setTransitionType } = usePlayerActions();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SegmentedControl
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
label: t('setting.playbackStyle', {
|
||||||
|
context: 'optionNormal',
|
||||||
|
postProcess: 'titleCase',
|
||||||
|
}),
|
||||||
|
value: PlayerStyle.GAPLESS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('setting.playbackStyle', {
|
||||||
|
context: 'optionCrossFade',
|
||||||
|
postProcess: 'titleCase',
|
||||||
|
}),
|
||||||
|
value: PlayerStyle.CROSSFADE,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
disabled={playbackSettings.type !== PlayerType.WEB || status === PlayerStatus.PLAYING}
|
||||||
|
onChange={(value) => setTransitionType(value as PlayerStyle)}
|
||||||
|
size="sm"
|
||||||
|
value={transitionType}
|
||||||
|
w="100%"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CrossfadeStyleConfig = () => {
|
||||||
|
const status = usePlayerStatus();
|
||||||
|
const playbackSettings = usePlaybackSettings();
|
||||||
|
const { crossfadeStyle, transitionType } = usePlayerProperties();
|
||||||
|
const { setCrossfadeStyle } = usePlayerActions();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
comboboxProps={{ withinPortal: false }}
|
||||||
|
data={[
|
||||||
|
{ label: 'Linear', value: CrossfadeStyle.LINEAR },
|
||||||
|
{ label: 'Equal Power', value: CrossfadeStyle.EQUAL_POWER },
|
||||||
|
{ label: 'S-Curve', value: CrossfadeStyle.S_CURVE },
|
||||||
|
{ label: 'Exponential', value: CrossfadeStyle.EXPONENTIAL },
|
||||||
|
]}
|
||||||
|
defaultValue={crossfadeStyle}
|
||||||
|
disabled={
|
||||||
|
playbackSettings.type !== PlayerType.WEB ||
|
||||||
|
transitionType !== PlayerStyle.CROSSFADE ||
|
||||||
|
status === PlayerStatus.PLAYING
|
||||||
|
}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e) {
|
||||||
|
setCrossfadeStyle(e as CrossfadeStyle);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CrossfadeDurationConfig = () => {
|
||||||
|
const status = usePlayerStatus();
|
||||||
|
const playbackSettings = usePlaybackSettings();
|
||||||
|
const { crossfadeDuration, transitionType } = usePlayerProperties();
|
||||||
|
const { setCrossfadeDuration } = usePlayerActions();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Slider
|
||||||
|
defaultValue={crossfadeDuration}
|
||||||
|
disabled={
|
||||||
|
playbackSettings.type !== PlayerType.WEB ||
|
||||||
|
transitionType !== PlayerStyle.CROSSFADE ||
|
||||||
|
status === PlayerStatus.PLAYING
|
||||||
|
}
|
||||||
|
marks={[
|
||||||
|
{ label: '3', value: 3 },
|
||||||
|
{ label: '6', value: 6 },
|
||||||
|
{ label: '9', value: 9 },
|
||||||
|
{ label: '12', value: 12 },
|
||||||
|
{ label: '15', value: 15 },
|
||||||
|
]}
|
||||||
|
max={15}
|
||||||
|
min={3}
|
||||||
|
onChangeEnd={setCrossfadeDuration}
|
||||||
|
styles={{
|
||||||
|
root: {},
|
||||||
|
}}
|
||||||
|
w="100%"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PlaybackSpeedSlider = () => {
|
||||||
|
const speed = usePlayerSpeed();
|
||||||
|
const { setSpeed } = usePlayerActions();
|
||||||
|
const { bpm } = usePlayerSongProperties(['bpm']) ?? {};
|
||||||
|
|
||||||
|
const formatPlaybackSpeedSliderLabel = useMemo(
|
||||||
|
() => (value: number) => {
|
||||||
|
const bpmValue = Number(bpm);
|
||||||
|
if (bpmValue > 0) {
|
||||||
|
return `${value} x / ${(bpmValue * value).toFixed(1)} BPM`;
|
||||||
|
}
|
||||||
|
return `${value} x`;
|
||||||
|
},
|
||||||
|
[bpm],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Slider
|
||||||
|
defaultValue={speed}
|
||||||
|
label={formatPlaybackSpeedSliderLabel}
|
||||||
|
marks={[
|
||||||
|
{ label: '0.5', value: 0.5 },
|
||||||
|
{ label: '0.75', value: 0.75 },
|
||||||
|
{ label: '1', value: 1 },
|
||||||
|
{ label: '1.25', value: 1.25 },
|
||||||
|
{ label: '1.5', value: 1.5 },
|
||||||
|
{ label: '1.75', value: 1.75 },
|
||||||
|
{ label: '2', value: 2 },
|
||||||
|
]}
|
||||||
|
max={2}
|
||||||
|
min={0.5}
|
||||||
|
onChangeEnd={setSpeed}
|
||||||
|
onDoubleClick={() => setSpeed(1)}
|
||||||
|
step={0.01}
|
||||||
|
styles={{
|
||||||
|
markLabel: {},
|
||||||
|
root: {},
|
||||||
|
}}
|
||||||
|
w="100%"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1984,6 +1984,26 @@ export const usePlayerSong = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const usePlayerSongProperties = <T extends keyof QueueSong>(
|
||||||
|
properties: T[],
|
||||||
|
): Partial<Pick<QueueSong, T>> => {
|
||||||
|
return usePlayerStoreBase(
|
||||||
|
useShallow((state) => {
|
||||||
|
const song = state.getCurrentSong();
|
||||||
|
if (!song) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {} as Pick<QueueSong, T>;
|
||||||
|
|
||||||
|
for (const prop of properties) {
|
||||||
|
result[prop] = song[prop];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const usePlayerNum = () => {
|
export const usePlayerNum = () => {
|
||||||
return usePlayerStoreBase((state) => state.player.playerNum);
|
return usePlayerStoreBase((state) => state.player.playerNum);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user