mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-17 14:10:14 +02:00
Handle web player
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user