refactor(player): 重构播放控制系统,移除 Howler.js 改用原生 HTMLAudioElement

- 新建 playbackController.ts,使用 generation-based 取消替代 playbackRequestManager 状态机
- audioService 重写:单一持久 HTMLAudioElement + Web Audio API,createMediaElementSource 只调一次
- playerCore 瘦身为纯状态管理,移除 handlePlayMusic/playAudio/checkPlaybackState
- playlist next/prev 简化,区分用户手动切歌和歌曲自然播完
- MusicHook 适配 HTMLAudioElement API(.currentTime/.duration/.paused)
- preloadService 从 Howl 实例缓存改为 URL 可用性验证
- 所有 view/component 调用者迁移到 playbackController.playTrack()

修复:快速切歌竞态、seek 到未缓冲位置失败、重启后自动播放循环提示、EQ 重建崩溃
This commit is contained in:
alger
2026-03-29 13:18:05 +08:00
parent 167f081ee6
commit 0cfec3dd82
17 changed files with 1150 additions and 1919 deletions
+3 -4
View File
@@ -99,9 +99,9 @@ import { useRoute, useRouter } from 'vue-router';
import { getNewAlbums } from '@/api/album';
import { getAlbum } from '@/api/list';
import StickyTabPage from '@/components/common/StickyTabPage.vue';
import { navigateToMusicList } from '@/components/common/MusicListNavigator';
import { usePlayerCoreStore } from '@/store/modules/playerCore';
import StickyTabPage from '@/components/common/StickyTabPage.vue';
import { playTrack } from '@/services/playbackController';
import { usePlaylistStore } from '@/store/modules/playlist';
import { calculateAnimationDelay, getImgUrl } from '@/utils';
@@ -213,7 +213,6 @@ const playAlbum = async (album: any) => {
try {
const { data } = await getAlbum(album.id);
if (data.code === 200 && data.songs?.length > 0) {
const playerCore = usePlayerCoreStore();
const playlistStore = usePlaylistStore();
const albumCover = data.album?.picUrl || album.picUrl;
@@ -228,7 +227,7 @@ const playAlbum = async (album: any) => {
}));
playlistStore.setPlayList(playlist, false, false);
await playerCore.handlePlayMusic(playlist[0], true);
await playTrack(playlist[0], true);
}
} catch (error) {
console.error('Failed to play album:', error);