Files
AlgerMusicPlayer/src/renderer/store/modules/playlist.ts
T
2025-12-13 11:31:49 +08:00

640 lines
21 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useThrottleFn } from '@vueuse/core';
import { createDiscreteApi } from 'naive-ui';
import { defineStore, storeToRefs } from 'pinia';
import { computed, ref, shallowRef } from 'vue';
import i18n from '@/../i18n/renderer';
import { useSongDetail } from '@/hooks/usePlayerHooks';
import { preloadService } from '@/services/preloadService';
import type { SongResult } from '@/types/music';
import { getImgUrl } from '@/utils';
import { performShuffle, preloadCoverImage } from '@/utils/playerUtils';
import { useIntelligenceModeStore } from './intelligenceMode';
import { usePlayerCoreStore } from './playerCore';
import { useSleepTimerStore } from './sleepTimer';
const { message } = createDiscreteApi(['message']);
/**
* 播放列表管理 Store
* 负责:播放列表、索引、播放模式、预加载、上/下一首
*/
export const usePlaylistStore = defineStore(
'playlist',
() => {
// ==================== 状态 ====================
// 状态将由 pinia-plugin-persistedstate 自动从 localStorage 恢复
const playList = shallowRef<SongResult[]>([]);
const playListIndex = ref(0);
const playMode = ref(0);
const originalPlayList = shallowRef<SongResult[]>([]);
const playListDrawerVisible = ref(false);
// ==================== Computed ====================
const currentPlayList = computed(() => playList.value);
const currentPlayListIndex = computed(() => playListIndex.value);
// ==================== Actions ====================
/**
* 获取歌曲详情并预加载
*/
const fetchSongs = async (startIndex: number, endIndex: number) => {
try {
const songs = playList.value.slice(
Math.max(0, startIndex),
Math.min(endIndex, playList.value.length)
);
const { getSongDetail } = useSongDetail();
const detailedSongs = await Promise.all(
songs.map(async (song: SongResult) => {
try {
if (!song.playMusicUrl || (song.source === 'netease' && !song.backgroundColor)) {
return await getSongDetail(song);
}
return song;
} catch (error) {
console.error('获取歌曲详情失败:', error);
return song;
}
})
);
const nextSong = detailedSongs[0];
if (nextSong && !(nextSong.lyric && nextSong.lyric.lrcTimeArray.length > 0)) {
try {
const { useLyrics } = await import('@/hooks/usePlayerHooks');
const { loadLrc } = useLyrics();
nextSong.lyric = await loadLrc(nextSong.id);
} catch (error) {
console.error('加载歌词失败:', error);
}
}
detailedSongs.forEach((song, index) => {
if (song && startIndex + index < playList.value.length) {
playList.value[startIndex + index] = song;
}
});
// 预加载下一首歌曲的音频和封面
if (nextSong) {
if (nextSong.playMusicUrl) {
preloadService.load(nextSong);
}
if (nextSong.picUrl) {
preloadCoverImage(nextSong.picUrl, getImgUrl);
}
}
} catch (error) {
console.error('获取歌曲列表失败:', error);
}
};
/**
* 智能预加载下一首歌曲
*/
const preloadNextSongs = (currentIndex: number) => {
if (playList.value.length <= 1) return;
let nextIndex: number;
if (playMode.value === 0) {
// 顺序播放模式
if (currentIndex >= playList.value.length - 1) {
return;
}
nextIndex = currentIndex + 1;
} else {
// 循环播放和随机播放模式
nextIndex = (currentIndex + 1) % playList.value.length;
}
const endIndex = Math.min(nextIndex + 2, playList.value.length);
if (nextIndex < playList.value.length) {
fetchSongs(nextIndex, endIndex);
// 循环模式且接近列表末尾,预加载列表开头
if (
(playMode.value === 1 || playMode.value === 2) &&
nextIndex + 1 >= playList.value.length &&
playList.value.length > 2
) {
setTimeout(() => {
fetchSongs(0, 1);
}, 1000);
}
}
};
/**
* 应用随机播放
*/
const shufflePlayList = () => {
if (playList.value.length <= 1) return;
// 保存原始播放列表
if (originalPlayList.value.length === 0) {
originalPlayList.value = [...playList.value];
}
const currentSong = playList.value[playListIndex.value];
const shuffledList = performShuffle(playList.value, currentSong);
playList.value = shuffledList;
playListIndex.value = 0;
// pinia-plugin-persistedstate 会自动保存状态
};
/**
* 恢复原始播放列表顺序
*/
const restoreOriginalOrder = () => {
if (originalPlayList.value.length === 0) return;
const playerCore = usePlayerCoreStore();
const { playMusic } = storeToRefs(playerCore);
const currentSong = playMusic.value;
const originalIndex = originalPlayList.value.findIndex((song) => song.id === currentSong.id);
playList.value = [...originalPlayList.value];
playListIndex.value = Math.max(0, originalIndex);
originalPlayList.value = [];
// pinia-plugin-persistedstate 会自动保存状态
};
/**
* 设置播放列表
*/
const setPlayList = (
list: SongResult[],
keepIndex: boolean = false,
fromIntelligenceMode: boolean = false
) => {
// 如果不是从心动模式调用,清除心动模式状态
if (!fromIntelligenceMode) {
const intelligenceStore = useIntelligenceModeStore();
if (intelligenceStore.isIntelligenceMode) {
intelligenceStore.clearIntelligenceMode();
}
}
if (list.length === 0) {
playList.value = [];
playListIndex.value = 0;
originalPlayList.value = [];
return;
}
const playerCore = usePlayerCoreStore();
const { playMusic } = storeToRefs(playerCore);
// 根据当前播放模式处理新的播放列表
if (playMode.value === 2) {
// 随机模式
console.log('随机模式下设置新播放列表,保存原始顺序并洗牌');
originalPlayList.value = [...list];
const currentSong = playMusic.value;
const shuffledList = performShuffle(list, currentSong);
if (currentSong && currentSong.id) {
const currentSongIndex = shuffledList.findIndex((song) => song.id === currentSong.id);
playListIndex.value =
currentSongIndex !== -1 ? 0 : keepIndex ? Math.max(0, playListIndex.value) : 0;
} else {
playListIndex.value = keepIndex ? Math.max(0, playListIndex.value) : 0;
}
playList.value = shuffledList;
} else {
console.log('顺序/循环模式下设置新播放列表');
if (originalPlayList.value.length > 0) {
originalPlayList.value = [];
}
if (!keepIndex) {
const foundIndex = list.findIndex((item) => item.id === playMusic.value.id);
playListIndex.value = foundIndex !== -1 ? foundIndex : 0;
}
playList.value = list;
}
// pinia-plugin-persistedstate 会自动保存状态
};
/**
* 添加到下一首播放
*/
const addToNextPlay = (song: SongResult) => {
const list = [...playList.value];
const currentIndex = playListIndex.value;
// 如果歌曲已在播放列表中,先移除
const existingIndex = list.findIndex((item) => item.id === song.id);
if (existingIndex !== -1) {
list.splice(existingIndex, 1);
if (existingIndex <= currentIndex) {
playListIndex.value = Math.max(0, playListIndex.value - 1);
}
}
// 插入到当前播放歌曲的下一个位置
const insertIndex = playListIndex.value + 1;
list.splice(insertIndex, 0, song);
setPlayList(list, true);
};
/**
* 从播放列表移除歌曲
*/
const removeFromPlayList = (id: number | string) => {
const index = playList.value.findIndex((item) => item.id === id);
if (index === -1) return;
const playerCore = usePlayerCoreStore();
const { playMusic } = storeToRefs(playerCore);
// 如果删除的是当前播放的歌曲,先切换到下一首
if (id === playMusic.value.id) {
nextPlay();
}
const newPlayList = [...playList.value];
newPlayList.splice(index, 1);
setPlayList(newPlayList);
};
/**
* 清空播放列表
*/
const clearPlayAll = async () => {
const { audioService } = await import('@/services/audioService');
const playerCore = usePlayerCoreStore();
audioService.pause();
setTimeout(() => {
playerCore.playMusic = {} as SongResult;
playerCore.playMusicUrl = '';
playList.value = [];
playListIndex.value = 0;
originalPlayList.value = [];
// 只清除 playerCore 的 localStorage(这些由 playerCore store 管理)
localStorage.removeItem('currentPlayMusic');
localStorage.removeItem('currentPlayMusicUrl');
// playlist 状态由 pinia-plugin-persistedstate 自动管理
}, 500);
};
/**
* 切换播放模式
*/
const togglePlayMode = async () => {
const { useUserStore } = await import('./user');
const userStore = useUserStore();
const wasIntelligence = playMode.value === 3;
const newMode = (playMode.value + 1) % 4;
const wasRandom = playMode.value === 2;
const isRandom = newMode === 2;
const isIntelligence = newMode === 3;
// 如果要切换到心动模式,但用户未使用cookie登录,则跳过
if (isIntelligence && (!userStore.user || userStore.loginType !== 'cookie')) {
console.log('跳过心动模式:需要cookie登录');
playMode.value = 0;
return;
}
playMode.value = newMode;
// pinia-plugin-persistedstate 会自动保存状态
// 切换到随机模式时洗牌
if (isRandom && !wasRandom && playList.value.length > 0) {
shufflePlayList();
console.log('切换到随机模式,洗牌播放列表');
}
// 从随机模式切换出去时恢复原始顺序
if (!isRandom && wasRandom && !isIntelligence) {
restoreOriginalOrder();
console.log('切换出随机模式,恢复原始顺序');
}
// 切换到心动模式
if (isIntelligence && !wasIntelligence) {
console.log('切换到心动模式');
const intelligenceStore = useIntelligenceModeStore();
await intelligenceStore.playIntelligenceMode();
}
// 从心动模式切换出去
if (!isIntelligence && wasIntelligence) {
console.log('退出心动模式');
const intelligenceStore = useIntelligenceModeStore();
intelligenceStore.clearIntelligenceMode();
}
};
/**
* 下一首
*/
const _nextPlay = async (retryCount: number = 0, maxRetries: number = 3) => {
try {
if (playList.value.length === 0) {
return;
}
const playerCore = usePlayerCoreStore();
const sleepTimerStore = useSleepTimerStore();
// 检查是否是播放列表的最后一首且设置了播放列表结束定时
if (
playMode.value === 0 &&
playListIndex.value === playList.value.length - 1 &&
sleepTimerStore.sleepTimer.type === 'end'
) {
sleepTimerStore.stopPlayback();
return;
}
const currentIndex = playListIndex.value;
const nowPlayListIndex = (playListIndex.value + 1) % playList.value.length;
const nextSong = { ...playList.value[nowPlayListIndex] };
console.log(
`[nextPlay] 尝试播放下一首: ${nextSong.name}, 索引: ${currentIndex} -> ${nowPlayListIndex}, 重试次数: ${retryCount}/${maxRetries}`
);
// 先尝试播放歌曲,成功后再更新索引
const success = await playerCore.handlePlayMusic(nextSong, true);
if (success) {
// 播放成功,更新索引并重置重试计数
playListIndex.value = nowPlayListIndex;
console.log(`[nextPlay] 播放成功,索引已更新为: ${nowPlayListIndex}`);
sleepTimerStore.handleSongChange();
} else {
console.error(`[nextPlay] 播放下一首失败,当前索引: ${currentIndex}`);
// 如果还有重试次数,先更新索引再重试下一首
if (retryCount < maxRetries && playList.value.length > 1) {
console.log(
`[nextPlay] 跳过失败的歌曲,尝试播放下下首,重试 ${retryCount + 1}/${maxRetries}`
);
// 更新索引到失败的歌曲位置,这样下次递归调用会继续往下
playListIndex.value = nowPlayListIndex;
// 延迟后递归调用,尝试播放下一首
setTimeout(() => {
_nextPlay(retryCount + 1, maxRetries);
}, 500);
} else {
// 重试次数用尽或只有一首歌
if (retryCount >= maxRetries) {
console.error(`[nextPlay] 连续${maxRetries}首歌曲播放失败,停止尝试`);
message.error('连续多首歌曲播放失败,请检查网络或音源设置');
} else {
message.error(i18n.global.t('player.playFailed'));
}
playerCore.setIsPlay(false);
}
}
} catch (error) {
console.error('切换下一首出错:', error);
}
};
const nextPlay = useThrottleFn(_nextPlay, 500);
/**
* 上一首
*/
const _prevPlay = async () => {
try {
if (playList.value.length === 0) {
return;
}
const playerCore = usePlayerCoreStore();
const currentIndex = playListIndex.value;
const nowPlayListIndex =
(playListIndex.value - 1 + playList.value.length) % playList.value.length;
const prevSong = { ...playList.value[nowPlayListIndex] };
console.log(
`[prevPlay] 尝试播放上一首: ${prevSong.name}, 索引: ${currentIndex} -> ${nowPlayListIndex}`
);
let success = false;
let retryCount = 0;
const maxRetries = 2;
// 先尝试播放歌曲,成功后再更新索引
while (!success && retryCount < maxRetries) {
success = await playerCore.handlePlayMusic(prevSong);
if (!success) {
retryCount++;
console.error(`播放上一首失败,尝试 ${retryCount}/${maxRetries}`);
if (retryCount >= maxRetries) {
console.error('多次尝试播放失败,将从播放列表中移除此歌曲');
const newPlayList = [...playList.value];
newPlayList.splice(nowPlayListIndex, 1);
if (newPlayList.length > 0) {
const keepCurrentIndexPosition = true;
setPlayList(newPlayList, keepCurrentIndexPosition);
if (newPlayList.length === 1) {
playListIndex.value = 0;
} else {
const newPrevIndex =
(playListIndex.value - 1 + newPlayList.length) % newPlayList.length;
playListIndex.value = newPrevIndex;
}
setTimeout(() => {
prevPlay();
}, 300);
return;
} else {
console.error('播放列表为空,停止尝试');
break;
}
}
}
}
if (success) {
// 播放成功,更新索引
playListIndex.value = nowPlayListIndex;
console.log(`[prevPlay] 播放成功,索引已更新为: ${nowPlayListIndex}`);
} else {
console.error(`[prevPlay] 播放上一首失败,保持当前索引: ${currentIndex}`);
playerCore.setIsPlay(false);
message.error(i18n.global.t('player.playFailed'));
}
} catch (error) {
console.error('切换上一首出错:', error);
}
};
const prevPlay = useThrottleFn(_prevPlay, 500);
/**
* 设置播放列表抽屉显示状态
*/
const setPlayListDrawerVisible = (value: boolean) => {
playListDrawerVisible.value = value;
};
/**
* 设置播放(兼容旧API)
*/
const setPlay = async (song: SongResult) => {
try {
const playerCore = usePlayerCoreStore();
// 检查URL是否已过期
if (song.expiredAt && song.expiredAt < Date.now()) {
console.info(`歌曲URL已过期,重新获取: ${song.name}`);
song.playMusicUrl = undefined;
song.expiredAt = undefined;
}
// 如果是当前正在播放的音乐,则切换播放/暂停状态
if (
playerCore.playMusic.id === song.id &&
playerCore.playMusic.playMusicUrl === song.playMusicUrl &&
!song.isFirstPlay
) {
if (playerCore.play) {
playerCore.setPlayMusic(false);
const { audioService } = await import('@/services/audioService');
audioService.getCurrentSound()?.pause();
playerCore.userPlayIntent = false;
} else {
playerCore.setPlayMusic(true);
playerCore.userPlayIntent = true;
const { audioService } = await import('@/services/audioService');
const sound = audioService.getCurrentSound();
if (sound) {
sound.play();
// checkPlaybackState 已在 playAudio 中自动调用,无需在这里重复调用
}
}
return;
}
if (song.isFirstPlay) {
song.isFirstPlay = false;
}
// 查找歌曲在播放列表中的索引
const songIndex = playList.value.findIndex(
(item: SongResult) => item.id === song.id && item.source === song.source
);
// 更新播放索引
if (songIndex !== -1 && songIndex !== playListIndex.value) {
console.log('歌曲索引不匹配,更新为:', songIndex);
playListIndex.value = songIndex;
}
const success = await playerCore.handlePlayMusic(song);
// playerCore 的状态由其自己的 store 管理
if (success) {
playerCore.isPlay = true;
// 预加载下一首歌曲
if (songIndex !== -1) {
setTimeout(() => {
preloadNextSongs(playListIndex.value);
}, 3000);
}
}
return success;
} catch (error) {
console.error('设置播放失败:', error);
return false;
}
};
/**
* 初始化播放列表
* 注意:状态已由 pinia-plugin-persistedstate 自动恢复
* 这里只需要处理特殊逻辑(如随机模式的恢复)
*/
const initializePlaylist = async () => {
// 重启后恢复随机播放状态
if (playMode.value === 2 && playList.value.length > 0) {
if (originalPlayList.value.length === 0) {
console.log('重启后恢复随机播放模式,重新洗牌播放列表');
shufflePlayList();
} else {
console.log('重启后恢复随机播放模式,播放列表已是洗牌状态');
}
}
};
return {
// 状态
playList,
playListIndex,
playMode,
originalPlayList,
playListDrawerVisible,
// Computed
currentPlayList,
currentPlayListIndex,
// Actions
setPlayList,
addToNextPlay,
removeFromPlayList,
clearPlayAll,
togglePlayMode,
shufflePlayList,
restoreOriginalOrder,
preloadNextSongs,
nextPlay: nextPlay as unknown as typeof _nextPlay,
prevPlay: prevPlay as unknown as typeof _prevPlay,
setPlayListDrawerVisible,
setPlay,
initializePlaylist,
fetchSongs,
updateSong: (song: SongResult) => {
const index = playList.value.findIndex(
(item) => item.id === song.id && item.source === song.source
);
if (index !== -1) {
playList.value[index] = song;
// 触发响应式更新
playList.value = [...playList.value];
}
}
};
},
{
// 配置 pinia-plugin-persistedstate
persist: {
key: 'playlist-store',
storage: localStorage,
// 持久化所有状态,除了 playListDrawerVisibleUI 状态不需要持久化)
pick: ['playList', 'playListIndex', 'playMode', 'originalPlayList']
}
}
);