mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-24 16:27:23 +08:00
✨ feat: 优化音源解析功能,添加音源配置
This commit is contained in:
@@ -56,6 +56,14 @@ export default {
|
|||||||
dolby: 'Dolby Atmos',
|
dolby: 'Dolby Atmos',
|
||||||
jymaster: 'Master'
|
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',
|
autoPlay: 'Auto Play',
|
||||||
autoPlayDesc: 'Auto resume playback when reopening the app'
|
autoPlayDesc: 'Auto resume playback when reopening the app'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
"playback": {
|
||||||
|
"musicSources": "音源设置",
|
||||||
|
"musicSourcesDesc": "选择音乐解析使用的音源平台",
|
||||||
|
"musicSourcesWarning": "至少需要选择一个音源平台"
|
||||||
|
}
|
||||||
@@ -56,6 +56,14 @@ export default {
|
|||||||
dolby: '杜比全景声',
|
dolby: '杜比全景声',
|
||||||
jymaster: '超清母带'
|
jymaster: '超清母带'
|
||||||
},
|
},
|
||||||
|
musicSources: '音源设置',
|
||||||
|
musicSourcesDesc: '选择音乐解析使用的音源平台',
|
||||||
|
musicSourcesWarning: '至少需要选择一个音源平台',
|
||||||
|
musicUnblockEnable: '启用音乐解析',
|
||||||
|
musicUnblockEnableDesc: '开启后将尝试解析无法播放的音乐',
|
||||||
|
configureMusicSources: '配置音源',
|
||||||
|
selectedMusicSources: '已选音源:',
|
||||||
|
noMusicSources: '未选择音源',
|
||||||
autoPlay: '自动播放',
|
autoPlay: '自动播放',
|
||||||
autoPlayDesc: '重新打开应用时是否自动继续播放'
|
autoPlayDesc: '重新打开应用时是否自动继续播放'
|
||||||
},
|
},
|
||||||
|
|||||||
+10
-4
@@ -5,16 +5,22 @@ import server from 'netease-cloud-music-api-alger/server';
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { unblockMusic } from './unblockMusic';
|
import { unblockMusic, type Platform } from './unblockMusic';
|
||||||
|
|
||||||
const store = new Store();
|
const store = new Store();
|
||||||
if (!fs.existsSync(path.resolve(os.tmpdir(), 'anonymous_token'))) {
|
if (!fs.existsSync(path.resolve(os.tmpdir(), 'anonymous_token'))) {
|
||||||
fs.writeFileSync(path.resolve(os.tmpdir(), 'anonymous_token'), '', 'utf-8');
|
fs.writeFileSync(path.resolve(os.tmpdir(), 'anonymous_token'), '', 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理解锁音乐请求
|
// 设置音乐解析的处理程序
|
||||||
ipcMain.handle('unblock-music', async (_, id, data) => {
|
ipcMain.handle('unblock-music', async (_event, id, songData, enabledSources) => {
|
||||||
return unblockMusic(id, data);
|
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<void> {
|
async function startMusicApi(): Promise<void> {
|
||||||
|
|||||||
+3
-1
@@ -21,5 +21,7 @@
|
|||||||
"downloadPath": "",
|
"downloadPath": "",
|
||||||
"language": "zh-CN",
|
"language": "zh-CN",
|
||||||
"alwaysShowDownloadButton": false,
|
"alwaysShowDownloadButton": false,
|
||||||
"unlimitedDownload": false
|
"unlimitedDownload": false,
|
||||||
|
"enableMusicUnblock": true,
|
||||||
|
"enabledMusicSources": ["migu", "kugou", "pyncmd","bilibili", "youtube"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ interface SongData {
|
|||||||
name: string;
|
name: string;
|
||||||
artists: Array<{ name: string }>;
|
artists: Array<{ name: string }>;
|
||||||
album?: { name: string };
|
album?: { name: string };
|
||||||
|
ar?: Array<{ name: string }>;
|
||||||
|
al?: { name: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResponseData {
|
interface ResponseData {
|
||||||
@@ -27,24 +29,29 @@ interface UnblockResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 所有可用平台
|
||||||
|
export const ALL_PLATFORMS: Platform[] = ['migu', 'kugou', 'pyncmd', 'kuwo', 'bilibili', 'youtube'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 音乐解析函数
|
* 音乐解析函数
|
||||||
* @param id 歌曲ID
|
* @param id 歌曲ID
|
||||||
* @param songData 歌曲信息
|
* @param songData 歌曲信息
|
||||||
* @param retryCount 重试次数
|
* @param retryCount 重试次数
|
||||||
|
* @param enabledPlatforms 启用的平台列表,默认为所有平台
|
||||||
* @returns Promise<UnblockResult>
|
* @returns Promise<UnblockResult>
|
||||||
*/
|
*/
|
||||||
const unblockMusic = async (
|
const unblockMusic = async (
|
||||||
id: number | string,
|
id: number | string,
|
||||||
songData: SongData,
|
songData: SongData,
|
||||||
retryCount = 3
|
retryCount = 1,
|
||||||
|
enabledPlatforms?: Platform[]
|
||||||
): Promise<UnblockResult> => {
|
): Promise<UnblockResult> => {
|
||||||
// 所有可用平台
|
const platforms = enabledPlatforms || ALL_PLATFORMS;
|
||||||
const platforms: Platform[] = ['migu', 'kugou', 'pyncmd', 'joox', 'kuwo', 'bilibili', 'youtube'];
|
songData.album = songData.album || songData.al;
|
||||||
|
songData.artists = songData.artists || songData.ar;
|
||||||
const retry = async (attempt: number): Promise<UnblockResult> => {
|
const retry = async (attempt: number): Promise<UnblockResult> => {
|
||||||
try {
|
try {
|
||||||
const data = await match(parseInt(String(id), 10), platforms, songData);
|
const data = await match(parseInt(String(id), 10), platforms,songData);
|
||||||
const result: UnblockResult = {
|
const result: UnblockResult = {
|
||||||
data: {
|
data: {
|
||||||
data,
|
data,
|
||||||
@@ -58,7 +65,7 @@ const unblockMusic = async (
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (attempt < retryCount) {
|
if (attempt < retryCount) {
|
||||||
// 延迟重试,每次重试增加延迟时间
|
// 延迟重试,每次重试增加延迟时间
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
|
await new Promise((resolve) => setTimeout(resolve, 100 * attempt));
|
||||||
return retry(attempt + 1);
|
return retry(attempt + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -14,7 +14,7 @@ interface API {
|
|||||||
openLyric: () => void;
|
openLyric: () => void;
|
||||||
sendLyric: (data: any) => void;
|
sendLyric: (data: any) => void;
|
||||||
sendSong: (data: any) => void;
|
sendSong: (data: any) => void;
|
||||||
unblockMusic: (id: number, data: any) => Promise<any>;
|
unblockMusic: (id: number, data: any, enabledSources?: string[]) => Promise<any>;
|
||||||
onLyricWindowClosed: (callback: () => void) => void;
|
onLyricWindowClosed: (callback: () => void) => void;
|
||||||
startDownload: (url: string) => void;
|
startDownload: (url: string) => void;
|
||||||
onDownloadProgress: (callback: (progress: number, status: string) => void) => void;
|
onDownloadProgress: (callback: (progress: number, status: string) => void) => void;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const api = {
|
|||||||
openLyric: () => ipcRenderer.send('open-lyric'),
|
openLyric: () => ipcRenderer.send('open-lyric'),
|
||||||
sendLyric: (data) => ipcRenderer.send('send-lyric', data),
|
sendLyric: (data) => ipcRenderer.send('send-lyric', data),
|
||||||
sendSong: (data) => ipcRenderer.send('update-current-song', 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) => {
|
onLyricWindowClosed: (callback: () => void) => {
|
||||||
ipcRenderer.on('lyric-window-closed', () => callback());
|
ipcRenderer.on('lyric-window-closed', () => callback());
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { ILyric } from '@/type/lyric';
|
|||||||
import { isElectron } from '@/utils';
|
import { isElectron } from '@/utils';
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import requestMusic from '@/utils/request_music';
|
import requestMusic from '@/utils/request_music';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
|
||||||
const { addData, getData, deleteData } = musicDB;
|
const { addData, getData, deleteData } = musicDB;
|
||||||
|
|
||||||
@@ -79,8 +80,15 @@ export const getMusicLrc = async (id: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getParsingMusicUrl = (id: number, data: any) => {
|
export const getParsingMusicUrl = (id: number, data: any) => {
|
||||||
|
|
||||||
if (isElectron) {
|
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<any>('/music', { params: { id } });
|
return requestMusic.get<any>('/music', { params: { id } });
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+2
@@ -28,6 +28,8 @@ declare module 'vue' {
|
|||||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||||
NForm: typeof import('naive-ui')['NForm']
|
NForm: typeof import('naive-ui')['NForm']
|
||||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
|
NGrid: typeof import('naive-ui')['NGrid']
|
||||||
|
NGridItem: typeof import('naive-ui')['NGridItem']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
NImage: typeof import('naive-ui')['NImage']
|
NImage: typeof import('naive-ui')['NImage']
|
||||||
NInput: typeof import('naive-ui')['NInput']
|
NInput: typeof import('naive-ui')['NInput']
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export const getSongUrl = async (
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('error', error);
|
console.error('error', error);
|
||||||
|
url = data.data[0].url || '';
|
||||||
}
|
}
|
||||||
if (isDownloaded) {
|
if (isDownloaded) {
|
||||||
return songDetail;
|
return songDetail;
|
||||||
@@ -344,7 +345,7 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
(item: SongResult) => item.id === music.id && item.source === music.source
|
(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) => {
|
const setPlay = async (song: SongResult) => {
|
||||||
@@ -453,7 +454,7 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await handlePlayMusic(prevSong);
|
await handlePlayMusic(prevSong);
|
||||||
await fetchSongs(playList.value, playListIndex.value - 5, nowPlayListIndex);
|
await fetchSongs(playList.value, playListIndex.value - 3, nowPlayListIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const togglePlayMode = () => {
|
const togglePlayMode = () => {
|
||||||
|
|||||||
@@ -150,6 +150,39 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="set-item">
|
||||||
|
<div>
|
||||||
|
<div class="set-item-title">{{ t('settings.playback.musicSources') }}</div>
|
||||||
|
<div class="set-item-content">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<n-switch v-model:value="setData.enableMusicUnblock">
|
||||||
|
<template #checked>{{ t('common.on') }}</template>
|
||||||
|
<template #unchecked>{{ t('common.off') }}</template>
|
||||||
|
</n-switch>
|
||||||
|
<span>{{ t('settings.playback.musicUnblockEnableDesc') }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="setData.enableMusicUnblock" class="mt-2">
|
||||||
|
<div class="text-sm">
|
||||||
|
<span class="text-gray-500">{{ t('settings.playback.selectedMusicSources') }}</span>
|
||||||
|
<span v-if="musicSources.length > 0" class="text-gray-400">
|
||||||
|
{{ musicSources.map((source) => getSourceLabel(source)).join(', ') }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-red-500 text-xs">
|
||||||
|
{{ t('settings.playback.noMusicSources') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
:disabled="!setData.enableMusicUnblock"
|
||||||
|
@click="showMusicSourcesModal = true"
|
||||||
|
>
|
||||||
|
{{ t('settings.playback.configureMusicSources') }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="set-item">
|
<div class="set-item">
|
||||||
<div>
|
<div>
|
||||||
<div class="set-item-title">{{ t('settings.playback.autoPlay') }}</div>
|
<div class="set-item-title">{{ t('settings.playback.autoPlay') }}</div>
|
||||||
@@ -470,6 +503,33 @@
|
|||||||
</n-checkbox-group>
|
</n-checkbox-group>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 音源设置弹窗 -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showMusicSourcesModal"
|
||||||
|
preset="dialog"
|
||||||
|
:title="t('settings.playback.musicSources')"
|
||||||
|
:positive-text="t('common.confirm')"
|
||||||
|
:negative-text="t('common.cancel')"
|
||||||
|
@positive-click="showMusicSourcesModal = false"
|
||||||
|
@negative-click="showMusicSourcesModal = false"
|
||||||
|
>
|
||||||
|
<n-space vertical>
|
||||||
|
<p>{{ t('settings.playback.musicSourcesDesc') }}</p>
|
||||||
|
<n-checkbox-group v-model:value="musicSources">
|
||||||
|
<n-grid :cols="2" :x-gap="12" :y-gap="8">
|
||||||
|
<n-grid-item v-for="source in musicSourceOptions" :key="source.value">
|
||||||
|
<n-checkbox :value="source.value">
|
||||||
|
{{ source.label }}
|
||||||
|
</n-checkbox>
|
||||||
|
</n-grid-item>
|
||||||
|
</n-grid>
|
||||||
|
</n-checkbox-group>
|
||||||
|
<div v-if="musicSources.length === 0" class="text-red-500 text-sm">
|
||||||
|
{{ t('settings.playback.musicSourcesWarning') }}
|
||||||
|
</div>
|
||||||
|
</n-space>
|
||||||
|
</n-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -494,6 +554,11 @@ import { checkUpdate, UpdateResult } from '@/utils/update';
|
|||||||
|
|
||||||
import config from '../../../../package.json';
|
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 settingsStore = useSettingsStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
@@ -977,6 +1042,41 @@ onMounted(() => {
|
|||||||
handleScroll({ target: { scrollTop: 0 } });
|
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;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user