From 2e96161bd01eff04916bc44f9cdaaca6d02c4d8b Mon Sep 17 00:00:00 2001 From: alger Date: Sat, 17 May 2025 13:27:50 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20=E4=BF=AE=E6=94=B9=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=88=97=E8=A1=A8=E5=B1=95=E7=A4=BA=E5=BD=A2=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E6=92=AD=E6=94=BE=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=B8=85=E7=A9=BA=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/i18n/lang/en-US/player.ts | 8 + src/i18n/lang/zh-CN/player.ts | 8 + src/renderer/components/common/SearchItem.vue | 5 +- src/renderer/components/common/SongItem.vue | 19 +- .../components/player/MiniPlayBar.vue | 20 +- .../components/player/MobilePlayBar.vue | 21 +- src/renderer/components/player/PlayBar.vue | 109 +------ .../components/player/PlayListDrawer.vue | 287 ++++++++++++++++++ src/renderer/hooks/MusicHook.ts | 3 +- src/renderer/layout/AppLayout.vue | 6 +- src/renderer/services/audioService.ts | 1 - src/renderer/store/modules/player.ts | 57 +++- .../views/bilibili/BilibiliPlayer.vue | 2 +- src/renderer/views/favorite/index.vue | 2 +- 14 files changed, 382 insertions(+), 166 deletions(-) create mode 100644 src/renderer/components/player/PlayListDrawer.vue diff --git a/src/i18n/lang/en-US/player.ts b/src/i18n/lang/en-US/player.ts index 2afd8bc..8ec5932 100644 --- a/src/i18n/lang/en-US/player.ts +++ b/src/i18n/lang/en-US/player.ts @@ -105,5 +105,13 @@ export default { playbackStopped: 'Music playback stopped', minutesRemaining: '{minutes} min remaining', songsRemaining: '{count} songs remaining' + }, + playList: { + clearAll: 'Clear Playlist', + alreadyEmpty: 'Playlist is already empty', + cleared: 'Playlist cleared', + empty: 'Playlist is empty', + clearConfirmTitle: 'Clear Playlist', + clearConfirmContent: 'This will clear all songs in the playlist and stop the current playback. Continue?' } }; diff --git a/src/i18n/lang/zh-CN/player.ts b/src/i18n/lang/zh-CN/player.ts index de4adea..bc5296b 100644 --- a/src/i18n/lang/zh-CN/player.ts +++ b/src/i18n/lang/zh-CN/player.ts @@ -106,5 +106,13 @@ export default { playbackStopped: '音乐播放已停止', minutesRemaining: '剩余{minutes}分钟', songsRemaining: '剩余{count}首歌' + }, + playList: { + clearAll: '清空播放列表', + alreadyEmpty: '播放列表已经为空', + cleared: '已清空播放列表', + empty: '播放列表为空', + clearConfirmTitle: '清空播放列表', + clearConfirmContent: '这将清空所有播放列表中的歌曲并停止当前播放。是否继续?' } }; diff --git a/src/renderer/components/common/SearchItem.vue b/src/renderer/components/common/SearchItem.vue index 516ab6b..a8c65d0 100644 --- a/src/renderer/components/common/SearchItem.vue +++ b/src/renderer/components/common/SearchItem.vue @@ -33,7 +33,6 @@ diff --git a/src/renderer/components/common/SongItem.vue b/src/renderer/components/common/SongItem.vue index fa96efc..2c8c658 100644 --- a/src/renderer/components/common/SongItem.vue +++ b/src/renderer/components/common/SongItem.vue @@ -122,7 +122,7 @@ :x="dropdownX" :y="dropdownY" :options="dropdownOptions" - :z-index="99999" + :z-index="99999999" placement="bottom-start" @clickoutside="showDropdown = false" @select="handleSelect" @@ -137,9 +137,8 @@ import { NEllipsis, NImage, useMessage } from 'naive-ui'; import { computed, h, inject, ref, useTemplateRef } from 'vue'; import { useI18n } from 'vue-i18n'; -import { getSongUrl } from '@/hooks/MusicListHook'; +import { getSongUrl } from '@/store/modules/player'; import { useArtist } from '@/hooks/useArtist'; -import { audioService } from '@/services/audioService'; import { usePlayerStore } from '@/store'; import type { SongResult } from '@/type/music'; import { getImgUrl, isElectron } from '@/utils'; @@ -444,18 +443,6 @@ const imageLoad = async () => { // 播放音乐 设置音乐详情 打开音乐底栏 const playMusicEvent = async (item: SongResult) => { - // 如果是当前正在播放的音乐,则切换播放/暂停状态 - if (playMusic.value.id === item.id) { - if (play.value) { - playerStore.setPlayMusic(false); - audioService.getCurrentSound()?.pause(); - } else { - playerStore.setPlayMusic(true); - audioService.getCurrentSound()?.play(); - } - return; - } - try { // 使用store的setPlay方法,该方法已经包含了B站视频URL处理逻辑 const result = await playerStore.setPlay(item); @@ -551,7 +538,7 @@ const handleMouseLeave = () => { @apply rounded-3xl p-3 flex items-center transition bg-transparent dark:text-white text-gray-900; &:hover { - @apply bg-gray-100 dark:bg-gray-800; + @apply bg-light-100 dark:bg-dark-100; .song-item-operating-compact { .song-item-operating-like, diff --git a/src/renderer/components/player/MiniPlayBar.vue b/src/renderer/components/player/MiniPlayBar.vue index ed4eedc..0fe989d 100644 --- a/src/renderer/components/player/MiniPlayBar.vue +++ b/src/renderer/components/player/MiniPlayBar.vue @@ -312,25 +312,7 @@ const handleNext = () => playerStore.nextPlay(); const playMusicEvent = async () => { try { - if (!playerStore.playMusic?.id || !playerStore.playMusicUrl) { - console.warn('No valid music or URL available'); - playerStore.setPlay(playerStore.playMusic); - return; - } - - if (play.value) { - if (audioService.getCurrentSound()) { - audioService.pause(); - playerStore.setPlayMusic(false); - } - } else { - if (audioService.getCurrentSound()) { - audioService.play(); - } else { - await audioService.play(playerStore.playMusicUrl, playerStore.playMusic); - } - playerStore.setPlayMusic(true); - } + playerStore.setPlay(playerStore.playMusic); } catch (error) { console.error('播放出错:', error); playerStore.nextPlay(); diff --git a/src/renderer/components/player/MobilePlayBar.vue b/src/renderer/components/player/MobilePlayBar.vue index d46dafc..7f7edc5 100644 --- a/src/renderer/components/player/MobilePlayBar.vue +++ b/src/renderer/components/player/MobilePlayBar.vue @@ -158,7 +158,6 @@ import SongItem from '@/components/common/SongItem.vue'; import SleepTimerPopover from '@/components/player/SleepTimerPopover.vue'; import { allTime, artistList, nowTime, playMusic, sound, textColors } from '@/hooks/MusicHook'; import MusicFull from '@/layout/components/MusicFull.vue'; -import { audioService } from '@/services/audioService'; import { usePlayerStore } from '@/store/modules/player'; import { useSettingsStore } from '@/store/modules/settings'; import type { SongResult } from '@/type/music'; @@ -235,25 +234,7 @@ const toggleFavorite = () => { // 播放暂停按钮事件 const playMusicEvent = async () => { try { - if (!playMusic.value?.id || !playerStore.playMusicUrl) { - console.warn('No valid music or URL available'); - playerStore.setPlay(playMusic.value); - return; - } - - if (play.value) { - if (audioService.getCurrentSound()) { - audioService.pause(); - playerStore.setPlayMusic(false); - } - } else { - if (audioService.getCurrentSound()) { - audioService.play(); - } else { - await audioService.play(playerStore.playMusicUrl, playMusic.value); - } - playerStore.setPlayMusic(true); - } + playerStore.setPlay(playMusic.value); } catch (error) { console.error('播放出错:', error); playerStore.nextPlay(); diff --git a/src/renderer/components/player/PlayBar.vue b/src/renderer/components/player/PlayBar.vue index 9cc36c9..cc2b5f2 100644 --- a/src/renderer/components/player/PlayBar.vue +++ b/src/renderer/components/player/PlayBar.vue @@ -155,42 +155,12 @@ - + -
-
- - - -
-
+ {{ t('player.playBar.playList') }} + @@ -200,10 +170,9 @@ diff --git a/src/renderer/components/player/PlayListDrawer.vue b/src/renderer/components/player/PlayListDrawer.vue new file mode 100644 index 0000000..06a593c --- /dev/null +++ b/src/renderer/components/player/PlayListDrawer.vue @@ -0,0 +1,287 @@ + + + + + \ No newline at end of file diff --git a/src/renderer/hooks/MusicHook.ts b/src/renderer/hooks/MusicHook.ts index 45e0f4e..9b8f1e9 100644 --- a/src/renderer/hooks/MusicHook.ts +++ b/src/renderer/hooks/MusicHook.ts @@ -9,6 +9,7 @@ import pinia, { usePlayerStore } from '@/store'; import type { Artist, ILyricText, SongResult } from '@/type/music'; import { isElectron } from '@/utils'; import { getTextColors } from '@/utils/linearColor'; +import { getSongUrl } from '@/store/modules/player'; const windowData = window as any; @@ -905,7 +906,7 @@ audioService.on('url_expired', async (expiredTrack) => { // 处理网易云音乐,重新获取URL console.log('重新获取网易云音乐URL'); try { - const { getSongUrl } = await import('@/store/modules/player'); + const newUrl = await getSongUrl(expiredTrack.id, expiredTrack as any); if (newUrl) { diff --git a/src/renderer/layout/AppLayout.vue b/src/renderer/layout/AppLayout.vue index 5ad2d59..074de88 100644 --- a/src/renderer/layout/AppLayout.vue +++ b/src/renderer/layout/AppLayout.vue @@ -20,7 +20,7 @@ - + @@ -46,6 +46,8 @@ settingsStore.setData?.hasDownloadingTasks) " /> + + @@ -88,7 +90,7 @@ const PlayBar = defineAsyncComponent(() => import('@/components/player/PlayBar.v const MobilePlayBar = defineAsyncComponent(() => import('@/components/player/MobilePlayBar.vue')); const SearchBar = defineAsyncComponent(() => import('./components/SearchBar.vue')); const TitleBar = defineAsyncComponent(() => import('./components/TitleBar.vue')); - +const PlayListDrawer = defineAsyncComponent(() => import('@/components/player/PlayListDrawer.vue')); const PlaylistDrawer = defineAsyncComponent(() => import('@/components/common/PlaylistDrawer.vue')); const playerStore = usePlayerStore(); diff --git a/src/renderer/services/audioService.ts b/src/renderer/services/audioService.ts index 97b854d..585f015 100644 --- a/src/renderer/services/audioService.ts +++ b/src/renderer/services/audioService.ts @@ -720,7 +720,6 @@ class AudioService { } pause() { - // 直接强制重置操作锁 this.forceResetOperationLock(); if (this.currentSound) { diff --git a/src/renderer/store/modules/player.ts b/src/renderer/store/modules/player.ts index deed24b..596aa8c 100644 --- a/src/renderer/store/modules/player.ts +++ b/src/renderer/store/modules/player.ts @@ -389,11 +389,29 @@ export const usePlayerStore = defineStore('player', () => { const favoriteList = ref>(getLocalStorageItem('favoriteList', [])); const savedPlayProgress = ref(); + // 添加播放列表抽屉状态 + const playListDrawerVisible = ref(false); + // 定时关闭相关状态 const sleepTimer = ref(getLocalStorageItem('sleepTimer', { type: SleepTimerType.NONE, value: 0 })); + + // 清空播放列表 + const clearPlayAll = async () => { + audioService.pause() + setTimeout(() => { + playMusic.value = {} as SongResult; + playMusicUrl.value = ''; + playList.value = []; + playListIndex.value = 0; + localStorage.removeItem('currentPlayMusic'); + localStorage.removeItem('currentPlayMusicUrl'); + localStorage.removeItem('playList'); + localStorage.removeItem('playListIndex'); + }, 500); + }; const timerInterval = ref(null); @@ -529,6 +547,17 @@ export const usePlayerStore = defineStore('player', () => { const setPlay = async (song: SongResult) => { try { + // 如果是当前正在播放的音乐,则切换播放/暂停状态 + if (playMusic.value.id === song.id) { + if (play.value) { + setPlayMusic(false); + audioService.getCurrentSound()?.pause(); + } else { + setPlayMusic(true); + audioService.getCurrentSound()?.play(); + } + return; + } // 直接调用 handlePlayMusic,它会处理索引更新和播放逻辑 const success = await handlePlayMusic(song); @@ -986,7 +1015,7 @@ export const usePlayerStore = defineStore('player', () => { if (!isAlreadyInList) { favoriteList.value.push(id); localStorage.setItem('favoriteList', JSON.stringify(favoriteList.value)); - typeof id === 'number' && likeSong(id, true); + typeof id === 'number' && useUserStore().user && likeSong(id, true); } }; @@ -996,7 +1025,7 @@ export const usePlayerStore = defineStore('player', () => { favoriteList.value = favoriteList.value.filter(existingId => !isBilibiliIdMatch(existingId, id)); } else { favoriteList.value = favoriteList.value.filter(existingId => existingId !== id); - likeSong(Number(id), false); + useUserStore().user && likeSong(Number(id), false); } localStorage.setItem('favoriteList', JSON.stringify(favoriteList.value)); }; @@ -1252,6 +1281,24 @@ export const usePlayerStore = defineStore('player', () => { } }; + // 设置播放列表抽屉显示状态 + const setPlayListDrawerVisible = (value: boolean) => { + playListDrawerVisible.value = value; + }; + + // 播放 + const handlePause = async () => { + try { + const currentSound = audioService.getCurrentSound(); + if (currentSound) { + currentSound.pause(); + } + setPlayMusic(false); + } catch (error) { + console.error('暂停播放失败:', error); + } + } + return { play, isPlay, @@ -1263,6 +1310,7 @@ export const usePlayerStore = defineStore('player', () => { musicFull, savedPlayProgress, favoriteList, + playListDrawerVisible, // 定时关闭相关 sleepTimer, @@ -1280,6 +1328,7 @@ export const usePlayerStore = defineStore('player', () => { currentPlayList, currentPlayListIndex, + clearPlayAll, setPlay, setIsPlay, nextPlay, @@ -1295,6 +1344,8 @@ export const usePlayerStore = defineStore('player', () => { removeFromFavorite, removeFromPlayList, playAudio, - reparseCurrentSong + reparseCurrentSong, + setPlayListDrawerVisible, + handlePause }; }); diff --git a/src/renderer/views/bilibili/BilibiliPlayer.vue b/src/renderer/views/bilibili/BilibiliPlayer.vue index 6e2b7aa..ef7ec07 100644 --- a/src/renderer/views/bilibili/BilibiliPlayer.vue +++ b/src/renderer/views/bilibili/BilibiliPlayer.vue @@ -425,7 +425,7 @@ const playCurrentAudio = async () => { // 播放当前选中的分P console.log('播放当前选中的分P:', currentAudio.name, '音频URL:', currentAudio.playMusicUrl); - playerStore.setPlayMusic(currentAudio); + playerStore.setPlay(currentAudio); // 播放后通知用户已开始播放 message.success('已开始播放'); diff --git a/src/renderer/views/favorite/index.vue b/src/renderer/views/favorite/index.vue index f10c133..3f1f68d 100644 --- a/src/renderer/views/favorite/index.vue +++ b/src/renderer/views/favorite/index.vue @@ -112,7 +112,7 @@ import { getMusicDetail } from '@/api/music'; import { getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili'; import SongItem from '@/components/common/SongItem.vue'; - import { getSongUrl } from '@/hooks/MusicListHook'; + import { getSongUrl } from '@/store/modules/player'; import { usePlayerStore } from '@/store'; import type { SongResult } from '@/type/music'; import { isElectron, setAnimationClass, setAnimationDelay } from '@/utils';