mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
prevent duplicate audio device in selector (#1598)
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { useAudioDevices } from '/@/renderer/features/settings/components/playback/audio-settings';
|
||||||
import { ListConfigTable } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigTable } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import {
|
import {
|
||||||
usePlaybackType,
|
|
||||||
usePlayerActions,
|
usePlayerActions,
|
||||||
usePlayerProperties,
|
usePlayerProperties,
|
||||||
usePlayerSongProperties,
|
usePlayerSongProperties,
|
||||||
@@ -25,29 +25,9 @@ import { SegmentedControl } from '/@/shared/components/segmented-control/segment
|
|||||||
import { Select } from '/@/shared/components/select/select';
|
import { Select } from '/@/shared/components/select/select';
|
||||||
import { Slider } from '/@/shared/components/slider/slider';
|
import { Slider } from '/@/shared/components/slider/slider';
|
||||||
import { Switch } from '/@/shared/components/switch/switch';
|
import { Switch } from '/@/shared/components/switch/switch';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
|
||||||
import { CrossfadeStyle, PlayerStatus, PlayerStyle, PlayerType } from '/@/shared/types/types';
|
import { CrossfadeStyle, PlayerStatus, PlayerStyle, PlayerType } from '/@/shared/types/types';
|
||||||
|
|
||||||
const ipc = isElectron() ? window.api.ipc : null;
|
const ipc = isElectron() ? window.api.ipc : null;
|
||||||
const mpvPlayer = isElectron() ? window.api.mpvPlayer : null;
|
|
||||||
|
|
||||||
const getAudioDevice = async () => {
|
|
||||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
||||||
return (devices || []).filter((dev: MediaDeviceInfo) => dev.kind === 'audiooutput');
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMpvAudioDevices = async () => {
|
|
||||||
if (!mpvPlayer) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await mpvPlayer.getAudioDevices();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to get MPV audio devices:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PlayerConfig = () => {
|
export const PlayerConfig = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -252,47 +232,11 @@ const AudioPlayerTypeConfig = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AudioDeviceConfig = () => {
|
const AudioDeviceConfig = () => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const status = usePlayerStatus();
|
const status = usePlayerStatus();
|
||||||
const playbackType = usePlaybackType();
|
|
||||||
const playbackSettings = usePlaybackSettings();
|
const playbackSettings = usePlaybackSettings();
|
||||||
const { setSettings } = useSettingsStoreActions();
|
const { setSettings } = useSettingsStoreActions();
|
||||||
const [audioDevices, setAudioDevices] = useState<{ label: string; value: string }[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const audioDevices = useAudioDevices();
|
||||||
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 (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { t } from 'i18next';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -6,7 +7,7 @@ import {
|
|||||||
SettingOption,
|
SettingOption,
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
} from '/@/renderer/features/settings/components/settings-section';
|
} from '/@/renderer/features/settings/components/settings-section';
|
||||||
import { usePlayerStatus } from '/@/renderer/store';
|
import { usePlaybackType, usePlayerStatus } from '/@/renderer/store';
|
||||||
import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store';
|
import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store';
|
||||||
import { Select } from '/@/shared/components/select/select';
|
import { Select } from '/@/shared/components/select/select';
|
||||||
import { Switch } from '/@/shared/components/switch/switch';
|
import { Switch } from '/@/shared/components/switch/switch';
|
||||||
@@ -14,37 +15,85 @@ import { toast } from '/@/shared/components/toast/toast';
|
|||||||
import { PlayerStatus, PlayerType } from '/@/shared/types/types';
|
import { PlayerStatus, PlayerType } from '/@/shared/types/types';
|
||||||
|
|
||||||
const ipc = isElectron() ? window.api.ipc : null;
|
const ipc = isElectron() ? window.api.ipc : null;
|
||||||
|
const mpvPlayer = isElectron() ? window.api.mpvPlayer : null;
|
||||||
|
|
||||||
const getAudioDevice = async () => {
|
const getAudioDevices = async () => {
|
||||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
return (devices || []).filter((dev: MediaDeviceInfo) => dev.kind === 'audiooutput');
|
return (devices || []).filter((dev: MediaDeviceInfo) => dev.kind === 'audiooutput');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMpvAudioDevices = async () => {
|
||||||
|
if (!mpvPlayer) {
|
||||||
|
console.log('mpvPlayer not found');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await mpvPlayer.getAudioDevices();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get MPV audio devices:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAudioDevices = () => {
|
||||||
|
const playbackType = usePlaybackType();
|
||||||
|
const [audioDevices, setAudioDevices] = useState<{ label: string; value: string }[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchAudioDevices = async () => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playbackType === PlayerType.WEB) {
|
||||||
|
getAudioDevices()
|
||||||
|
.then((dev) => {
|
||||||
|
const uniqueDevices = dev.filter(
|
||||||
|
(d, index, self) =>
|
||||||
|
index === self.findIndex((t) => t.deviceId === d.deviceId),
|
||||||
|
);
|
||||||
|
setAudioDevices(
|
||||||
|
uniqueDevices.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();
|
||||||
|
const uniqueDevices = devices.filter(
|
||||||
|
(d, index, self) => index === self.findIndex((t) => t.value === d.value),
|
||||||
|
);
|
||||||
|
setAudioDevices(uniqueDevices);
|
||||||
|
} catch {
|
||||||
|
toast.error({
|
||||||
|
message: t('error.audioDeviceFetchError', {
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAudioDevices();
|
||||||
|
}, [playbackType]);
|
||||||
|
|
||||||
|
return audioDevices;
|
||||||
|
};
|
||||||
|
|
||||||
export const AudioSettings = memo(() => {
|
export const AudioSettings = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const settings = usePlaybackSettings();
|
const settings = usePlaybackSettings();
|
||||||
const { setSettings } = useSettingsStoreActions();
|
const { setSettings } = useSettingsStoreActions();
|
||||||
const status = usePlayerStatus();
|
const status = usePlayerStatus();
|
||||||
|
|
||||||
const [audioDevices, setAudioDevices] = useState<{ label: string; value: string }[]>([]);
|
const audioDevices = useAudioDevices();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const getAudioDevices = () => {
|
|
||||||
getAudioDevice()
|
|
||||||
.then((dev) =>
|
|
||||||
setAudioDevices(dev.map((d) => ({ label: d.label, value: d.deviceId }))),
|
|
||||||
)
|
|
||||||
.catch(() =>
|
|
||||||
toast.error({
|
|
||||||
message: t('error.audioDeviceFetchError', { postProcess: 'sentenceCase' }),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (settings.type === PlayerType.WEB) {
|
|
||||||
getAudioDevices();
|
|
||||||
}
|
|
||||||
}, [settings.type, t]);
|
|
||||||
|
|
||||||
const audioOptions: SettingOption[] = [
|
const audioOptions: SettingOption[] = [
|
||||||
{
|
{
|
||||||
@@ -83,7 +132,7 @@ export const AudioSettings = memo(() => {
|
|||||||
clearable
|
clearable
|
||||||
data={audioDevices}
|
data={audioDevices}
|
||||||
defaultValue={settings.audioDeviceId}
|
defaultValue={settings.audioDeviceId}
|
||||||
disabled={settings.type !== PlayerType.WEB}
|
disabled={!isElectron()}
|
||||||
onChange={(e) => setSettings({ playback: { audioDeviceId: e } })}
|
onChange={(e) => setSettings({ playback: { audioDeviceId: e } })}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -91,7 +140,6 @@ export const AudioSettings = memo(() => {
|
|||||||
context: 'description',
|
context: 'description',
|
||||||
postProcess: 'sentenceCase',
|
postProcess: 'sentenceCase',
|
||||||
}),
|
}),
|
||||||
isHidden: !isElectron() || settings.type !== PlayerType.WEB,
|
|
||||||
title: t('setting.audioDevice', { postProcess: 'sentenceCase' }),
|
title: t('setting.audioDevice', { postProcess: 'sentenceCase' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user