diff --git a/src/renderer/hooks/MusicHook.ts b/src/renderer/hooks/MusicHook.ts index c0b4ea7..b062198 100644 --- a/src/renderer/hooks/MusicHook.ts +++ b/src/renderer/hooks/MusicHook.ts @@ -78,12 +78,8 @@ const setupKeyboardListeners = () => { const store = getPlayerStore(); switch (e.code) { case 'Space': - if (store.play) { - store.setPlayMusic(false); - audioService.getCurrentSound()?.pause(); - } else { - store.setPlayMusic(true); - audioService.getCurrentSound()?.play(); + if (store.playMusic?.id) { + void store.setPlay({ ...store.playMusic }); } break; default: @@ -97,6 +93,7 @@ const { message } = createDiscreteApi(['message']); let progressAnimationInitialized = false; let globalAnimationFrameId: number | null = null; const lastSavedTime = ref(0); +let audioListenersInitialized = false; // 全局停止函数 const stopProgressAnimation = () => { @@ -396,6 +393,12 @@ const setupMusicWatchers = () => { }; const setupAudioListeners = () => { + // 监听器只注册一次,避免重复绑定和误清理全局恢复监听器 + if (audioListenersInitialized) { + return () => {}; + } + audioListenersInitialized = true; + let interval: any = null; // 播放状态恢复定时器:当 interval 因异常被清除时,自动恢复 let recoveryTimer: any = null; @@ -486,9 +489,6 @@ const setupAudioListeners = () => { }, 500); }; - // 清理所有事件监听器 - audioService.clearAllListeners(); - // 启动恢复监控 startRecoveryMonitor(); @@ -1010,13 +1010,8 @@ if (isElectron) { windowData.electron.ipcRenderer.on('lyric-control-back', (_, command: string) => { switch (command) { case 'playpause': - if (getPlayerStore().play) { - getPlayerStore().setPlayMusic(false); - audioService.getCurrentSound()?.pause(); - } else { - getPlayerStore().setPlayMusic(true); - - audioService.getCurrentSound()?.play(); + if (getPlayerStore().playMusic?.id) { + void getPlayerStore().setPlay({ ...getPlayerStore().playMusic }); } break; case 'prev': diff --git a/src/renderer/services/audioService.ts b/src/renderer/services/audioService.ts index a35266e..b63dc42 100644 --- a/src/renderer/services/audioService.ts +++ b/src/renderer/services/audioService.ts @@ -642,6 +642,7 @@ class AudioService { newSound.on('loaderror', (_, error) => { console.error('Audio load error:', error); + this.emit('loaderror', { track, error }); if (retryCount < maxRetries && !existingSound) { // 预加载的音频通常已经 loaded,不应重试 retryCount++; @@ -657,6 +658,7 @@ class AudioService { newSound.on('playerror', (_, error) => { console.error('Audio play error:', error); + this.emit('playerror', { track, error }); if (retryCount < maxRetries) { retryCount++; console.log(`Retrying playback (${retryCount}/${maxRetries})...`); diff --git a/src/renderer/store/index.ts b/src/renderer/store/index.ts index 7358311..a41cc8c 100644 --- a/src/renderer/store/index.ts +++ b/src/renderer/store/index.ts @@ -15,6 +15,7 @@ pinia.use(({ store }) => { }); // 导出所有 store +export * from './modules/favorite'; export * from './modules/intelligenceMode'; export * from './modules/localMusic'; export * from './modules/lyric'; @@ -22,6 +23,7 @@ export * from './modules/menu'; export * from './modules/music'; export * from './modules/player'; export * from './modules/playerCore'; +export * from './modules/playHistory'; export * from './modules/playlist'; export * from './modules/recommend'; export * from './modules/search'; diff --git a/src/renderer/store/modules/playlist.ts b/src/renderer/store/modules/playlist.ts index cea300c..2f7e706 100644 --- a/src/renderer/store/modules/playlist.ts +++ b/src/renderer/store/modules/playlist.ts @@ -405,6 +405,12 @@ export const usePlaylistStore = defineStore( const nowPlayListIndex = (playListIndex.value + 1) % playList.value.length; const nextSong = { ...playList.value[nowPlayListIndex] }; + // 同一首歌重试时强制刷新在线 URL,避免卡在失效链接上 + if (singleTrackRetryCount > 0 && !nextSong.playMusicUrl?.startsWith('local://')) { + nextSong.playMusicUrl = undefined; + nextSong.expiredAt = undefined; + } + console.log( `[nextPlay] 尝试播放: ${nextSong.name}, 索引: ${currentIndex} -> ${nowPlayListIndex}, 单曲重试: ${singleTrackRetryCount}/${SINGLE_TRACK_MAX_RETRIES}, 连续失败: ${consecutiveFailCount.value}/${MAX_CONSECUTIVE_FAILS}` ); @@ -591,6 +597,20 @@ export const usePlaylistStore = defineStore( sound.play(); // 在恢复播放时也进行状态检测,防止URL已过期导致无声 playerCore.checkPlaybackState(playerCore.playMusic); + } else { + console.warn('[PlaylistStore.setPlay] 无可用音频实例,尝试重建播放链路'); + const recoverSong = { + ...playerCore.playMusic, + isFirstPlay: true, + playMusicUrl: playerCore.playMusic.playMusicUrl?.startsWith('local://') + ? playerCore.playMusic.playMusicUrl + : undefined + }; + const recovered = await playerCore.handlePlayMusic(recoverSong, true); + if (!recovered) { + playerCore.setIsPlay(false); + message.error(i18n.global.t('player.playFailed')); + } } } return; diff --git a/src/renderer/utils/appShortcuts.ts b/src/renderer/utils/appShortcuts.ts index 79ab7b0..90c719e 100644 --- a/src/renderer/utils/appShortcuts.ts +++ b/src/renderer/utils/appShortcuts.ts @@ -1,7 +1,6 @@ import { onMounted, onUnmounted } from 'vue'; import i18n from '@/../i18n/renderer'; -import { audioService } from '@/services/audioService'; import { usePlayerStore, useSettingsStore } from '@/store'; import { isElectron } from '.'; @@ -80,11 +79,13 @@ export async function handleShortcutAction(action: string) { switch (action) { case 'togglePlay': if (playerStore.play) { - await audioService.pause(); + await playerStore.handlePause(); showToast(t('player.playBar.pause'), 'ri-pause-circle-line'); } else { - await audioService.getCurrentSound()?.play(); - showToast(t('player.playBar.play'), 'ri-play-circle-line'); + if (playerStore.playMusic?.id) { + await playerStore.setPlay({ ...playerStore.playMusic }); + showToast(t('player.playBar.play'), 'ri-play-circle-line'); + } } break; case 'prevPlay':