feat: 优化音乐播放逻辑

This commit is contained in:
alger
2026-03-04 19:53:50 +08:00
parent bb2dbc3f00
commit 36917a979d
5 changed files with 40 additions and 20 deletions
+11 -16
View File
@@ -78,12 +78,8 @@ const setupKeyboardListeners = () => {
const store = getPlayerStore(); const store = getPlayerStore();
switch (e.code) { switch (e.code) {
case 'Space': case 'Space':
if (store.play) { if (store.playMusic?.id) {
store.setPlayMusic(false); void store.setPlay({ ...store.playMusic });
audioService.getCurrentSound()?.pause();
} else {
store.setPlayMusic(true);
audioService.getCurrentSound()?.play();
} }
break; break;
default: default:
@@ -97,6 +93,7 @@ const { message } = createDiscreteApi(['message']);
let progressAnimationInitialized = false; let progressAnimationInitialized = false;
let globalAnimationFrameId: number | null = null; let globalAnimationFrameId: number | null = null;
const lastSavedTime = ref(0); const lastSavedTime = ref(0);
let audioListenersInitialized = false;
// 全局停止函数 // 全局停止函数
const stopProgressAnimation = () => { const stopProgressAnimation = () => {
@@ -396,6 +393,12 @@ const setupMusicWatchers = () => {
}; };
const setupAudioListeners = () => { const setupAudioListeners = () => {
// 监听器只注册一次,避免重复绑定和误清理全局恢复监听器
if (audioListenersInitialized) {
return () => {};
}
audioListenersInitialized = true;
let interval: any = null; let interval: any = null;
// 播放状态恢复定时器:当 interval 因异常被清除时,自动恢复 // 播放状态恢复定时器:当 interval 因异常被清除时,自动恢复
let recoveryTimer: any = null; let recoveryTimer: any = null;
@@ -486,9 +489,6 @@ const setupAudioListeners = () => {
}, 500); }, 500);
}; };
// 清理所有事件监听器
audioService.clearAllListeners();
// 启动恢复监控 // 启动恢复监控
startRecoveryMonitor(); startRecoveryMonitor();
@@ -1010,13 +1010,8 @@ if (isElectron) {
windowData.electron.ipcRenderer.on('lyric-control-back', (_, command: string) => { windowData.electron.ipcRenderer.on('lyric-control-back', (_, command: string) => {
switch (command) { switch (command) {
case 'playpause': case 'playpause':
if (getPlayerStore().play) { if (getPlayerStore().playMusic?.id) {
getPlayerStore().setPlayMusic(false); void getPlayerStore().setPlay({ ...getPlayerStore().playMusic });
audioService.getCurrentSound()?.pause();
} else {
getPlayerStore().setPlayMusic(true);
audioService.getCurrentSound()?.play();
} }
break; break;
case 'prev': case 'prev':
+2
View File
@@ -642,6 +642,7 @@ class AudioService {
newSound.on('loaderror', (_, error) => { newSound.on('loaderror', (_, error) => {
console.error('Audio load error:', error); console.error('Audio load error:', error);
this.emit('loaderror', { track, error });
if (retryCount < maxRetries && !existingSound) { if (retryCount < maxRetries && !existingSound) {
// 预加载的音频通常已经 loaded,不应重试 // 预加载的音频通常已经 loaded,不应重试
retryCount++; retryCount++;
@@ -657,6 +658,7 @@ class AudioService {
newSound.on('playerror', (_, error) => { newSound.on('playerror', (_, error) => {
console.error('Audio play error:', error); console.error('Audio play error:', error);
this.emit('playerror', { track, error });
if (retryCount < maxRetries) { if (retryCount < maxRetries) {
retryCount++; retryCount++;
console.log(`Retrying playback (${retryCount}/${maxRetries})...`); console.log(`Retrying playback (${retryCount}/${maxRetries})...`);
+2
View File
@@ -15,6 +15,7 @@ pinia.use(({ store }) => {
}); });
// 导出所有 store // 导出所有 store
export * from './modules/favorite';
export * from './modules/intelligenceMode'; export * from './modules/intelligenceMode';
export * from './modules/localMusic'; export * from './modules/localMusic';
export * from './modules/lyric'; export * from './modules/lyric';
@@ -22,6 +23,7 @@ export * from './modules/menu';
export * from './modules/music'; export * from './modules/music';
export * from './modules/player'; export * from './modules/player';
export * from './modules/playerCore'; export * from './modules/playerCore';
export * from './modules/playHistory';
export * from './modules/playlist'; export * from './modules/playlist';
export * from './modules/recommend'; export * from './modules/recommend';
export * from './modules/search'; export * from './modules/search';
+20
View File
@@ -405,6 +405,12 @@ export const usePlaylistStore = defineStore(
const nowPlayListIndex = (playListIndex.value + 1) % playList.value.length; const nowPlayListIndex = (playListIndex.value + 1) % playList.value.length;
const nextSong = { ...playList.value[nowPlayListIndex] }; const nextSong = { ...playList.value[nowPlayListIndex] };
// 同一首歌重试时强制刷新在线 URL,避免卡在失效链接上
if (singleTrackRetryCount > 0 && !nextSong.playMusicUrl?.startsWith('local://')) {
nextSong.playMusicUrl = undefined;
nextSong.expiredAt = undefined;
}
console.log( console.log(
`[nextPlay] 尝试播放: ${nextSong.name}, 索引: ${currentIndex} -> ${nowPlayListIndex}, 单曲重试: ${singleTrackRetryCount}/${SINGLE_TRACK_MAX_RETRIES}, 连续失败: ${consecutiveFailCount.value}/${MAX_CONSECUTIVE_FAILS}` `[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(); sound.play();
// 在恢复播放时也进行状态检测,防止URL已过期导致无声 // 在恢复播放时也进行状态检测,防止URL已过期导致无声
playerCore.checkPlaybackState(playerCore.playMusic); 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; return;
+5 -4
View File
@@ -1,7 +1,6 @@
import { onMounted, onUnmounted } from 'vue'; import { onMounted, onUnmounted } from 'vue';
import i18n from '@/../i18n/renderer'; import i18n from '@/../i18n/renderer';
import { audioService } from '@/services/audioService';
import { usePlayerStore, useSettingsStore } from '@/store'; import { usePlayerStore, useSettingsStore } from '@/store';
import { isElectron } from '.'; import { isElectron } from '.';
@@ -80,11 +79,13 @@ export async function handleShortcutAction(action: string) {
switch (action) { switch (action) {
case 'togglePlay': case 'togglePlay':
if (playerStore.play) { if (playerStore.play) {
await audioService.pause(); await playerStore.handlePause();
showToast(t('player.playBar.pause'), 'ri-pause-circle-line'); showToast(t('player.playBar.pause'), 'ri-pause-circle-line');
} else { } else {
await audioService.getCurrentSound()?.play(); if (playerStore.playMusic?.id) {
showToast(t('player.playBar.play'), 'ri-play-circle-line'); await playerStore.setPlay({ ...playerStore.playMusic });
showToast(t('player.playBar.play'), 'ri-play-circle-line');
}
} }
break; break;
case 'prevPlay': case 'prevPlay':