From ca510206026eee74e1b90b909ea565d254e0a72a Mon Sep 17 00:00:00 2001 From: alger Date: Thu, 22 May 2025 20:58:47 +0800 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20=E5=B0=86=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E6=8F=90=E5=8F=96=E5=88=B0useDownload=20hook?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/components/common/SongItem.vue | 74 +-------- src/renderer/hooks/useDownload.ts | 173 ++++++++++++++++++++ src/renderer/store/modules/player.ts | 126 +++++++------- src/renderer/views/favorite/index.vue | 131 +++------------ 4 files changed, 269 insertions(+), 235 deletions(-) create mode 100644 src/renderer/hooks/useDownload.ts diff --git a/src/renderer/components/common/SongItem.vue b/src/renderer/components/common/SongItem.vue index cffaf5a..0b0ec33 100644 --- a/src/renderer/components/common/SongItem.vue +++ b/src/renderer/components/common/SongItem.vue @@ -144,6 +144,7 @@ import { usePlayerStore } from '@/store'; import type { SongResult } from '@/type/music'; import { getImgUrl, isElectron } from '@/utils'; import { getImageBackground } from '@/utils/linearColor'; +import { useDownload } from '@/hooks/useDownload'; const { t } = useI18n(); @@ -191,8 +192,6 @@ const dropdownX = ref(0); const dropdownY = ref(0); const isHovering = ref(false); -const isDownloading = ref(false); - const openPlaylistDrawer = inject<(songId: number | string) => void>('openPlaylistDrawer'); const { navigateToArtist } = useArtist(); @@ -344,7 +343,7 @@ const handleMenuClick = (e: MouseEvent) => { const handleSelect = (key: string | number) => { showDropdown.value = false; if (key === 'download') { - downloadMusic(); + downloadMusic(props.item); } else if (key === 'playNext') { handlePlayNext(); } else if (key === 'addToPlaylist') { @@ -359,74 +358,7 @@ const handleSelect = (key: string | number) => { }; // 下载音乐 -const downloadMusic = async () => { - if (isDownloading.value) { - message.warning(t('songItem.message.downloading')); - return; - } - - try { - isDownloading.value = true; - - const data = (await getSongUrl(props.item.id as number, cloneDeep(props.item), true)) as any; - if (!data || !data.url) { - throw new Error(t('songItem.message.getUrlFailed')); - } - - // 构建文件名 - const artistNames = (props.item.ar || props.item.song?.artists)?.map((a) => a.name).join(','); - const filename = `${props.item.name} - ${artistNames}`; - console.log('props.item', props.item); - - const songData = cloneDeep(props.item); - songData.ar = songData.ar || songData.song?.artists; - // 发送下载请求 - window.electron.ipcRenderer.send('download-music', { - url: data.url, - type: data.type, - filename, - songInfo: { - ...songData, - downloadTime: Date.now() - } - }); - - message.success(t('songItem.message.downloadQueued')); - - // 监听下载完成事件 - const handleDownloadComplete = (_, result) => { - if (result.filename === filename) { - isDownloading.value = false; - removeListeners(); - } - }; - - // 监听下载错误事件 - const handleDownloadError = (_, result) => { - if (result.filename === filename) { - isDownloading.value = false; - removeListeners(); - } - }; - - // 移除监听器函数 - const removeListeners = () => { - window.electron.ipcRenderer.removeListener('music-download-complete', handleDownloadComplete); - window.electron.ipcRenderer.removeListener('music-download-error', handleDownloadError); - }; - - // 添加事件监听器 - window.electron.ipcRenderer.once('music-download-complete', handleDownloadComplete); - window.electron.ipcRenderer.once('music-download-error', handleDownloadError); - - // 30秒后自动清理监听器(以防下载过程中出现未知错误) - setTimeout(removeListeners, 30000); - } catch (error: any) { - console.error('Download error:', error); - isDownloading.value = false; - message.error(error.message || t('songItem.message.downloadFailed')); - } -}; +const { isDownloading, downloadMusic } = useDownload(); const emits = defineEmits(['play', 'select', 'remove-song']); const songImageRef = useTemplateRef('songImg'); diff --git a/src/renderer/hooks/useDownload.ts b/src/renderer/hooks/useDownload.ts new file mode 100644 index 0000000..decb217 --- /dev/null +++ b/src/renderer/hooks/useDownload.ts @@ -0,0 +1,173 @@ +import { cloneDeep } from 'lodash'; +import { ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { useMessage } from 'naive-ui'; + +import { getSongUrl } from '@/store/modules/player'; +import type { SongResult } from '@/type/music'; + +export const useDownload = () => { + const { t } = useI18n(); + const message = useMessage(); + const isDownloading = ref(false); + + /** + * 下载单首音乐 + * @param song 歌曲信息 + * @returns Promise + */ + const downloadMusic = async (song: SongResult) => { + if (isDownloading.value) { + message.warning(t('songItem.message.downloading')); + return; + } + + try { + isDownloading.value = true; + + const musicUrl = (await getSongUrl(song.id as number, cloneDeep(song), true)) as any; + if (!musicUrl) { + throw new Error(t('songItem.message.getUrlFailed')); + } + + // 构建文件名 + const artistNames = (song.ar || song.song?.artists)?.map((a) => a.name).join(','); + const filename = `${song.name} - ${artistNames}`; + + const songData = cloneDeep(song); + songData.ar = songData.ar || songData.song?.artists; + // 发送下载请求 + window.electron.ipcRenderer.send('download-music', { + url: musicUrl, + filename, + songInfo: { + ...songData, + downloadTime: Date.now() + } + }); + + message.success(t('songItem.message.downloadQueued')); + + // 监听下载完成事件 + const handleDownloadComplete = (_, result) => { + if (result.filename === filename) { + isDownloading.value = false; + removeListeners(); + } + }; + + // 监听下载错误事件 + const handleDownloadError = (_, result) => { + if (result.filename === filename) { + isDownloading.value = false; + removeListeners(); + } + }; + + // 移除监听器函数 + const removeListeners = () => { + window.electron.ipcRenderer.removeListener('music-download-complete', handleDownloadComplete); + window.electron.ipcRenderer.removeListener('music-download-error', handleDownloadError); + }; + + // 添加事件监听器 + window.electron.ipcRenderer.once('music-download-complete', handleDownloadComplete); + window.electron.ipcRenderer.once('music-download-error', handleDownloadError); + + // 30秒后自动清理监听器(以防下载过程中出现未知错误) + setTimeout(removeListeners, 30000); + } catch (error: any) { + console.error('Download error:', error); + isDownloading.value = false; + message.error(error.message || t('songItem.message.downloadFailed')); + } + }; + + /** + * 批量下载音乐 + * @param songs 歌曲列表 + * @returns Promise + */ + const batchDownloadMusic = async (songs: SongResult[]) => { + if (isDownloading.value) { + message.warning(t('favorite.downloading')); + return; + } + + if (songs.length === 0) { + message.warning(t('favorite.selectSongsFirst')); + return; + } + + try { + isDownloading.value = true; + message.success(t('favorite.downloading')); + + // 移除旧的监听器 + window.electron.ipcRenderer.removeAllListeners('music-download-complete'); + + let successCount = 0; + let failCount = 0; + + // 添加新的监听器 + window.electron.ipcRenderer.on('music-download-complete', (_, result) => { + if (result.success) { + successCount++; + } else { + failCount++; + } + + // 当所有下载完成时 + if (successCount + failCount === songs.length) { + isDownloading.value = false; + message.success(t('favorite.downloadSuccess')); + window.electron.ipcRenderer.removeAllListeners('music-download-complete'); + } + }); + + // 并行获取所有歌曲的下载链接 + const downloadUrls = await Promise.all( + songs.map(async (song) => { + try { + const data = (await getSongUrl(song.id, song, true)) as any; + return { song, ...data }; + } catch (error) { + console.error(`获取歌曲 ${song.name} 下载链接失败:`, error); + return { song, url: null }; + } + }) + ); + + // 开始下载有效的链接 + downloadUrls.forEach(({ song, url, type }) => { + if (!url) { + failCount++; + return; + } + const songData = cloneDeep(song); + const songInfo = { + ...songData, + ar: songData.ar || songData.song?.artists, + downloadTime: Date.now() + }; + window.electron.ipcRenderer.send('download-music', { + url, + filename: `${song.name} - ${(song.ar || song.song?.artists)?.map((a) => a.name).join(',')}`, + songInfo, + type + }); + }); + } catch (error) { + console.error('下载失败:', error); + isDownloading.value = false; + message.destroyAll(); + message.error(t('favorite.downloadFailed')); + } + }; + + return { + isDownloading, + downloadMusic, + batchDownloadMusic + }; +}; \ No newline at end of file diff --git a/src/renderer/store/modules/player.ts b/src/renderer/store/modules/player.ts index 741999f..a60259d 100644 --- a/src/renderer/store/modules/player.ts +++ b/src/renderer/store/modules/player.ts @@ -81,70 +81,76 @@ export const getSongUrl = async ( songData: SongResult, isDownloaded: boolean = false ) => { - if (songData.playMusicUrl) { - return songData.playMusicUrl; - } - - if (songData.source === 'bilibili' && songData.bilibiliData) { - console.log('加载B站音频URL'); - if (!songData.playMusicUrl && songData.bilibiliData.bvid && songData.bilibiliData.cid) { - try { - songData.playMusicUrl = await getBilibiliAudioUrl( - songData.bilibiliData.bvid, - songData.bilibiliData.cid - ); - return songData.playMusicUrl; - } catch (error) { - console.error('重启后获取B站音频URL失败:', error); - return ''; - } - } - return songData.playMusicUrl || ''; - } - - const numericId = typeof id === 'string' ? parseInt(id, 10) : id; - - // 检查是否有自定义音源设置 - const songId = String(id); - const savedSource = localStorage.getItem(`song_source_${songId}`); - - // 如果有自定义音源设置,直接使用getParsingMusicUrl获取URL - if (savedSource && songData.source !== 'bilibili') { - try { - console.log(`使用自定义音源解析歌曲 ID: ${songId}`); - const res = await getParsingMusicUrl(numericId, cloneDeep(songData)); - if (res && res.data && res.data.data && res.data.data.url) { - return res.data.data.url; - } - // 如果自定义音源解析失败,继续使用正常的获取流程 - console.warn('自定义音源解析失败,使用默认音源'); - } catch (error) { - console.error('error',error) - console.error('自定义音源解析出错:', error); - } - } - - // 正常获取URL流程 - const { data } = await getMusicUrl(numericId, isDownloaded); - let url = ''; - let songDetail = null; try { - if (data.data[0].freeTrialInfo || !data.data[0].url) { - const res = await getParsingMusicUrl(numericId, cloneDeep(songData)); - url = res.data.data.url; - songDetail = res.data.data; - } else { - songDetail = data.data[0] as any; + if (songData.playMusicUrl) { + return songData.playMusicUrl; } + + if (songData.source === 'bilibili' && songData.bilibiliData) { + console.log('加载B站音频URL'); + if (!songData.playMusicUrl && songData.bilibiliData.bvid && songData.bilibiliData.cid) { + try { + songData.playMusicUrl = await getBilibiliAudioUrl( + songData.bilibiliData.bvid, + songData.bilibiliData.cid + ); + return songData.playMusicUrl; + } catch (error) { + console.error('重启后获取B站音频URL失败:', error); + return ''; + } + } + return songData.playMusicUrl || ''; + } + + const numericId = typeof id === 'string' ? parseInt(id, 10) : id; + + // 检查是否有自定义音源设置 + const songId = String(id); + const savedSource = localStorage.getItem(`song_source_${songId}`); + + // 如果有自定义音源设置,直接使用getParsingMusicUrl获取URL + if (savedSource && songData.source !== 'bilibili') { + try { + console.log(`使用自定义音源解析歌曲 ID: ${songId}`); + const res = await getParsingMusicUrl(numericId, cloneDeep(songData)); + console.log('res',res) + if (res && res.data && res.data.data && res.data.data.url) { + return res.data.data.url; + } + // 如果自定义音源解析失败,继续使用正常的获取流程 + console.warn('自定义音源解析失败,使用默认音源'); + } catch (error) { + console.error('error',error) + console.error('自定义音源解析出错:', error); + } + } + + // 正常获取URL流程 + const { data } = await getMusicUrl(numericId, isDownloaded); + let url = ''; + let songDetail = null; + try { + if (data.data[0].freeTrialInfo || !data.data[0].url) { + const res = await getParsingMusicUrl(numericId, cloneDeep(songData)); + url = res.data.data.url; + songDetail = res.data.data; + } else { + songDetail = data.data[0] as any; + } + } catch (error) { + console.error('error', error); + url = data.data[0].url || ''; + } + if (isDownloaded) { + return songDetail; + } + url = url || data.data[0].url; + return url; } catch (error) { - console.error('error', error); - url = data.data[0].url || ''; + console.error('error',error) + return null; } - if (isDownloaded) { - return songDetail; - } - url = url || data.data[0].url; - return url; }; const parseTime = (timeString: string): number => { diff --git a/src/renderer/views/favorite/index.vue b/src/renderer/views/favorite/index.vue index 3f1f68d..afdbf99 100644 --- a/src/renderer/views/favorite/index.vue +++ b/src/renderer/views/favorite/index.vue @@ -1,4 +1,4 @@ -