diff --git a/src/i18n/lang/en-US/settings.ts b/src/i18n/lang/en-US/settings.ts index 2b64b18..67f83d2 100644 --- a/src/i18n/lang/en-US/settings.ts +++ b/src/i18n/lang/en-US/settings.ts @@ -56,6 +56,14 @@ export default { dolby: 'Dolby Atmos', jymaster: 'Master' }, + musicSources: 'Music Sources', + musicSourcesDesc: 'Select music sources for song resolution', + musicSourcesWarning: 'At least one music source must be selected', + musicUnblockEnable: 'Enable Music Unblocking', + musicUnblockEnableDesc: 'When enabled, attempts to resolve unplayable songs', + configureMusicSources: 'Configure Sources', + selectedMusicSources: 'Selected sources:', + noMusicSources: 'No sources selected', autoPlay: 'Auto Play', autoPlayDesc: 'Auto resume playback when reopening the app' }, diff --git a/src/i18n/lang/zh-CN/settings.json b/src/i18n/lang/zh-CN/settings.json new file mode 100644 index 0000000..090be0a --- /dev/null +++ b/src/i18n/lang/zh-CN/settings.json @@ -0,0 +1,5 @@ +"playback": { + "musicSources": "音源设置", + "musicSourcesDesc": "选择音乐解析使用的音源平台", + "musicSourcesWarning": "至少需要选择一个音源平台" +} \ No newline at end of file diff --git a/src/i18n/lang/zh-CN/settings.ts b/src/i18n/lang/zh-CN/settings.ts index 49b4534..9fa6581 100644 --- a/src/i18n/lang/zh-CN/settings.ts +++ b/src/i18n/lang/zh-CN/settings.ts @@ -56,6 +56,14 @@ export default { dolby: '杜比全景声', jymaster: '超清母带' }, + musicSources: '音源设置', + musicSourcesDesc: '选择音乐解析使用的音源平台', + musicSourcesWarning: '至少需要选择一个音源平台', + musicUnblockEnable: '启用音乐解析', + musicUnblockEnableDesc: '开启后将尝试解析无法播放的音乐', + configureMusicSources: '配置音源', + selectedMusicSources: '已选音源:', + noMusicSources: '未选择音源', autoPlay: '自动播放', autoPlayDesc: '重新打开应用时是否自动继续播放' }, diff --git a/src/main/server.ts b/src/main/server.ts index d730de0..cfd4d14 100644 --- a/src/main/server.ts +++ b/src/main/server.ts @@ -5,16 +5,22 @@ import server from 'netease-cloud-music-api-alger/server'; import os from 'os'; import path from 'path'; -import { unblockMusic } from './unblockMusic'; +import { unblockMusic, type Platform } from './unblockMusic'; const store = new Store(); if (!fs.existsSync(path.resolve(os.tmpdir(), 'anonymous_token'))) { fs.writeFileSync(path.resolve(os.tmpdir(), 'anonymous_token'), '', 'utf-8'); } -// 处理解锁音乐请求 -ipcMain.handle('unblock-music', async (_, id, data) => { - return unblockMusic(id, data); +// 设置音乐解析的处理程序 +ipcMain.handle('unblock-music', async (_event, id, songData, enabledSources) => { + try { + const result = await unblockMusic(id, songData, 1, enabledSources as Platform[]); + return result; + } catch (error) { + console.error('音乐解析失败:', error); + return { error: (error as Error).message || '未知错误' }; + } }); async function startMusicApi(): Promise { diff --git a/src/main/set.json b/src/main/set.json index cdfd8d2..22fd2b9 100644 --- a/src/main/set.json +++ b/src/main/set.json @@ -21,5 +21,7 @@ "downloadPath": "", "language": "zh-CN", "alwaysShowDownloadButton": false, - "unlimitedDownload": false + "unlimitedDownload": false, + "enableMusicUnblock": true, + "enabledMusicSources": ["migu", "kugou", "pyncmd","bilibili", "youtube"] } diff --git a/src/main/unblockMusic.ts b/src/main/unblockMusic.ts index d392bcb..938be8c 100644 --- a/src/main/unblockMusic.ts +++ b/src/main/unblockMusic.ts @@ -6,6 +6,8 @@ interface SongData { name: string; artists: Array<{ name: string }>; album?: { name: string }; + ar?: Array<{ name: string }>; + al?: { name: string }; } interface ResponseData { @@ -27,24 +29,29 @@ interface UnblockResult { }; } +// 所有可用平台 +export const ALL_PLATFORMS: Platform[] = ['migu', 'kugou', 'pyncmd', 'kuwo', 'bilibili', 'youtube']; + /** * 音乐解析函数 * @param id 歌曲ID * @param songData 歌曲信息 * @param retryCount 重试次数 + * @param enabledPlatforms 启用的平台列表,默认为所有平台 * @returns Promise */ const unblockMusic = async ( id: number | string, songData: SongData, - retryCount = 3 + retryCount = 1, + enabledPlatforms?: Platform[] ): Promise => { - // 所有可用平台 - const platforms: Platform[] = ['migu', 'kugou', 'pyncmd', 'joox', 'kuwo', 'bilibili', 'youtube']; - + const platforms = enabledPlatforms || ALL_PLATFORMS; + songData.album = songData.album || songData.al; + songData.artists = songData.artists || songData.ar; const retry = async (attempt: number): Promise => { try { - const data = await match(parseInt(String(id), 10), platforms, songData); + const data = await match(parseInt(String(id), 10), platforms,songData); const result: UnblockResult = { data: { data, @@ -58,7 +65,7 @@ const unblockMusic = async ( } catch (err) { if (attempt < retryCount) { // 延迟重试,每次重试增加延迟时间 - await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)); + await new Promise((resolve) => setTimeout(resolve, 100 * attempt)); return retry(attempt + 1); } diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index c52f48c..4af0cd2 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -14,7 +14,7 @@ interface API { openLyric: () => void; sendLyric: (data: any) => void; sendSong: (data: any) => void; - unblockMusic: (id: number, data: any) => Promise; + unblockMusic: (id: number, data: any, enabledSources?: string[]) => Promise; onLyricWindowClosed: (callback: () => void) => void; startDownload: (url: string) => void; onDownloadProgress: (callback: (progress: number, status: string) => void) => void; diff --git a/src/preload/index.ts b/src/preload/index.ts index 17a9379..c75c40b 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -16,7 +16,7 @@ const api = { openLyric: () => ipcRenderer.send('open-lyric'), sendLyric: (data) => ipcRenderer.send('send-lyric', data), sendSong: (data) => ipcRenderer.send('update-current-song', data), - unblockMusic: (id) => ipcRenderer.invoke('unblock-music', id), + unblockMusic: (id, data, enabledSources) => ipcRenderer.invoke('unblock-music', id, data, enabledSources), // 歌词窗口关闭事件 onLyricWindowClosed: (callback: () => void) => { ipcRenderer.on('lyric-window-closed', () => callback()); diff --git a/src/renderer/api/music.ts b/src/renderer/api/music.ts index ace8793..6ab822f 100644 --- a/src/renderer/api/music.ts +++ b/src/renderer/api/music.ts @@ -4,6 +4,7 @@ import type { ILyric } from '@/type/lyric'; import { isElectron } from '@/utils'; import request from '@/utils/request'; import requestMusic from '@/utils/request_music'; +import { cloneDeep } from 'lodash'; const { addData, getData, deleteData } = musicDB; @@ -79,8 +80,15 @@ export const getMusicLrc = async (id: number) => { }; export const getParsingMusicUrl = (id: number, data: any) => { + if (isElectron) { - return window.api.unblockMusic(id, data); + const settingStore = useSettingsStore(); + + // 如果禁用了音乐解析功能,则直接返回空结果 + if (!settingStore.setData.enableMusicUnblock) { + return Promise.resolve({ data: { code: 404, message: '音乐解析功能已禁用' } }); + } + return window.api.unblockMusic(id, cloneDeep(data), cloneDeep(settingStore.setData.enabledMusicSources)); } return requestMusic.get('/music', { params: { id } }); }; diff --git a/src/renderer/components.d.ts b/src/renderer/components.d.ts index 118001c..0d700f4 100644 --- a/src/renderer/components.d.ts +++ b/src/renderer/components.d.ts @@ -28,6 +28,8 @@ declare module 'vue' { NEmpty: typeof import('naive-ui')['NEmpty'] NForm: typeof import('naive-ui')['NForm'] NFormItem: typeof import('naive-ui')['NFormItem'] + NGrid: typeof import('naive-ui')['NGrid'] + NGridItem: typeof import('naive-ui')['NGridItem'] NIcon: typeof import('naive-ui')['NIcon'] NImage: typeof import('naive-ui')['NImage'] NInput: typeof import('naive-ui')['NInput'] diff --git a/src/renderer/store/modules/player.ts b/src/renderer/store/modules/player.ts index 9e64cfc..6362a48 100644 --- a/src/renderer/store/modules/player.ts +++ b/src/renderer/store/modules/player.ts @@ -67,6 +67,7 @@ export const getSongUrl = async ( } } catch (error) { console.error('error', error); + url = data.data[0].url || ''; } if (isDownloaded) { return songDetail; @@ -344,7 +345,7 @@ export const usePlayerStore = defineStore('player', () => { (item: SongResult) => item.id === music.id && item.source === music.source ); - fetchSongs(playList.value, playListIndex.value + 1, playListIndex.value + 6); + fetchSongs(playList.value, playListIndex.value + 1, playListIndex.value + 3); }; const setPlay = async (song: SongResult) => { @@ -453,7 +454,7 @@ export const usePlayerStore = defineStore('player', () => { } await handlePlayMusic(prevSong); - await fetchSongs(playList.value, playListIndex.value - 5, nowPlayListIndex); + await fetchSongs(playList.value, playListIndex.value - 3, nowPlayListIndex); }; const togglePlayMode = () => { diff --git a/src/renderer/views/set/index.vue b/src/renderer/views/set/index.vue index 4f47257..5b819dc 100644 --- a/src/renderer/views/set/index.vue +++ b/src/renderer/views/set/index.vue @@ -150,6 +150,39 @@ /> +
+
+
{{ t('settings.playback.musicSources') }}
+
+
+ + + + + {{ t('settings.playback.musicUnblockEnableDesc') }} +
+
+
+ {{ t('settings.playback.selectedMusicSources') }} + + {{ musicSources.map((source) => getSourceLabel(source)).join(', ') }} + + + {{ t('settings.playback.noMusicSources') }} + +
+
+
+
+ + {{ t('settings.playback.configureMusicSources') }} + +
+
{{ t('settings.playback.autoPlay') }}
@@ -470,6 +503,33 @@ + + + + +

{{ t('settings.playback.musicSourcesDesc') }}

+ + + + + {{ source.label }} + + + + +
+ {{ t('settings.playback.musicSourcesWarning') }} +
+
+
@@ -494,6 +554,11 @@ import { checkUpdate, UpdateResult } from '@/utils/update'; import config from '../../../../package.json'; +// 手动定义Platform类型,避免从主进程导入的问题 +type Platform = 'qq' | 'migu' | 'kugou' | 'pyncmd'| 'kuwo' | 'bilibili' | 'youtube'; +// 所有平台 +const ALL_PLATFORMS: Platform[] = ['migu', 'kugou', 'pyncmd', 'kuwo', 'bilibili', 'youtube']; + const settingsStore = useSettingsStore(); const userStore = useUserStore(); @@ -977,6 +1042,41 @@ onMounted(() => { handleScroll({ target: { scrollTop: 0 } }); }); }); + +// 音源设置相关 +const musicSourceOptions = ref([ + { label: 'MiGu音乐', value: 'migu' }, + { label: '酷狗音乐', value: 'kugou' }, + { label: 'pyncmd', value: 'pyncmd' }, + { label: '酷我音乐', value: 'kuwo' }, + { label: 'Bilibili音乐', value: 'bilibili' }, + { label: 'YouTube', value: 'youtube' } +]); + +// 已选择的音源列表 +const musicSources = computed({ + get: () => { + if (!setData.value.enabledMusicSources) { + return ALL_PLATFORMS; + } + return setData.value.enabledMusicSources as Platform[]; + }, + set: (newValue: Platform[]) => { + // 确保至少选择一个音源 + const valuesToSet = newValue.length > 0 ? newValue : ALL_PLATFORMS; + setData.value = { + ...setData.value, + enabledMusicSources: valuesToSet + }; + } +}); + +const showMusicSourcesModal = ref(false); + +const getSourceLabel = (source: Platform) => { + const sourceLabel = musicSourceOptions.value.find(s => s.value === source)?.label; + return sourceLabel || source; +};