feat: 重构播放 store

This commit is contained in:
alger
2025-11-08 14:26:04 +08:00
parent 1005718c07
commit 34ba2250bf
10 changed files with 2317 additions and 1946 deletions
+4
View File
@@ -1,10 +1,14 @@
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import { markRaw } from 'vue';
import router from '@/router';
// 创建 pinia 实例
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
// 添加路由到 Pinia
pinia.use(({ store }) => {
store.router = markRaw(router);
+159
View File
@@ -0,0 +1,159 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { getLikedList, likeSong } from '@/api/music';
import { hasPermission } from '@/utils/auth';
import { getLocalStorageItem, isBilibiliIdMatch, setLocalStorageItem } from '@/utils/playerUtils';
/**
* 收藏管理 Store
* 负责:收藏列表、不喜欢列表的管理
*/
export const useFavoriteStore = defineStore('favorite', () => {
// ==================== 状态 ====================
const favoriteList = ref<Array<number | string>>(getLocalStorageItem('favoriteList', []));
const dislikeList = ref<Array<number | string>>(getLocalStorageItem('dislikeList', []));
// ==================== Actions ====================
/**
* 添加到收藏列表
*/
const addToFavorite = async (id: number | string) => {
// 检查是否已存在
const isAlreadyInList = favoriteList.value.some((existingId) =>
typeof id === 'string' && id.includes('--')
? isBilibiliIdMatch(existingId, id)
: existingId === id
);
if (!isAlreadyInList) {
favoriteList.value.push(id);
setLocalStorageItem('favoriteList', favoriteList.value);
// 只有在有真实登录权限时才调用API
if (typeof id === 'number') {
const { useUserStore } = await import('./user');
const userStore = useUserStore();
if (userStore.user && hasPermission(true)) {
try {
await likeSong(id, true);
} catch (error) {
console.error('收藏歌曲API调用失败:', error);
}
}
}
}
};
/**
* 从收藏列表移除
*/
const removeFromFavorite = async (id: number | string) => {
// 对于B站视频,需要根据bvid和cid来匹配
if (typeof id === 'string' && id.includes('--')) {
favoriteList.value = favoriteList.value.filter(
(existingId) => !isBilibiliIdMatch(existingId, id)
);
} else {
favoriteList.value = favoriteList.value.filter((existingId) => existingId !== id);
// 只有在有真实登录权限时才调用API
if (typeof id === 'number') {
const { useUserStore } = await import('./user');
const userStore = useUserStore();
if (userStore.user && hasPermission(true)) {
try {
await likeSong(id, false);
} catch (error) {
console.error('取消收藏歌曲API调用失败:', error);
}
}
}
}
setLocalStorageItem('favoriteList', favoriteList.value);
};
/**
* 添加到不喜欢列表
*/
const addToDislikeList = (id: number | string) => {
if (!dislikeList.value.includes(id)) {
dislikeList.value.push(id);
setLocalStorageItem('dislikeList', dislikeList.value);
}
};
/**
* 从不喜欢列表移除
*/
const removeFromDislikeList = (id: number | string) => {
dislikeList.value = dislikeList.value.filter((existingId) => existingId !== id);
setLocalStorageItem('dislikeList', dislikeList.value);
};
/**
* 初始化收藏列表(从服务器同步)
*/
const initializeFavoriteList = async () => {
const { useUserStore } = await import('./user');
const userStore = useUserStore();
const localFavoriteList = localStorage.getItem('favoriteList');
const localList: number[] = localFavoriteList ? JSON.parse(localFavoriteList) : [];
if (userStore.user && userStore.user.userId) {
try {
const res = await getLikedList(userStore.user.userId);
if (res.data?.ids) {
const serverList = res.data.ids.reverse();
const mergedList = Array.from(new Set([...localList, ...serverList]));
favoriteList.value = mergedList;
} else {
favoriteList.value = localList;
}
} catch (error) {
console.error('获取服务器收藏列表失败,使用本地数据:', error);
favoriteList.value = localList;
}
} else {
favoriteList.value = localList;
}
setLocalStorageItem('favoriteList', favoriteList.value);
};
/**
* 检查歌曲是否已收藏
*/
const isFavorite = (id: number | string): boolean => {
return favoriteList.value.some((existingId) =>
typeof id === 'string' && id.includes('--')
? isBilibiliIdMatch(existingId, id)
: existingId === id
);
};
/**
* 检查歌曲是否在不喜欢列表中
*/
const isDisliked = (id: number | string): boolean => {
return dislikeList.value.includes(id);
};
return {
// 状态
favoriteList,
dislikeList,
// Actions
addToFavorite,
removeFromFavorite,
addToDislikeList,
removeFromDislikeList,
initializeFavoriteList,
isFavorite,
isDisliked
};
});
@@ -0,0 +1,134 @@
import { createDiscreteApi } from 'naive-ui';
import { defineStore } from 'pinia';
import { ref } from 'vue';
import i18n from '@/../i18n/renderer';
import { getLikedList } from '@/api/music';
import type { Platform } from '@/types/music';
import { getLocalStorageItem, setLocalStorageItem } from '@/utils/playerUtils';
const { message } = createDiscreteApi(['message']);
/**
* 心动模式管理 Store
* 负责:心动模式的播放和状态管理
*/
export const useIntelligenceModeStore = defineStore('intelligenceMode', () => {
// ==================== 状态 ====================
const isIntelligenceMode = ref(getLocalStorageItem('isIntelligenceMode', false));
const intelligenceModeInfo = ref<{
playlistId: number;
seedSongId: number;
} | null>(getLocalStorageItem('intelligenceModeInfo', null));
// ==================== Actions ====================
/**
* 播放心动模式
*/
const playIntelligenceMode = async () => {
const { useUserStore } = await import('./user');
const { usePlayerCoreStore } = await import('./playerCore');
const { usePlaylistStore } = await import('./playlist');
const userStore = useUserStore();
const playerCore = usePlayerCoreStore();
const playlistStore = usePlaylistStore();
const { t } = i18n.global;
// 检查是否使用cookie登录
if (!userStore.user || userStore.loginType !== 'cookie') {
message.warning(t('player.playBar.intelligenceMode.needCookieLogin'));
return;
}
try {
// 获取用户歌单列表
if (userStore.playList.length === 0) {
await userStore.initializePlaylist();
}
// 找到"我喜欢的音乐"歌单
const favoritePlaylist = userStore.playList.find(
(pl: any) => pl.userId === userStore.user?.userId && pl.specialType === 5
);
if (!favoritePlaylist) {
message.warning(t('player.playBar.intelligenceMode.noFavoritePlaylist'));
return;
}
// 获取喜欢的歌曲列表
const likedListRes = await getLikedList(userStore.user.userId);
const likedIds = likedListRes.data?.ids || [];
if (likedIds.length === 0) {
message.warning(t('player.playBar.intelligenceMode.noLikedSongs'));
return;
}
// 随机选择一首歌曲
const randomSongId = likedIds[Math.floor(Math.random() * likedIds.length)];
// 调用心动模式API
const { getIntelligenceList } = await import('@/api/music');
const res = await getIntelligenceList({
id: randomSongId,
pid: favoritePlaylist.id
});
if (res.data?.data && res.data.data.length > 0) {
const intelligenceSongs = res.data.data.map((item: any) => ({
id: item.id,
name: item.songInfo.name,
picUrl: item.songInfo.al?.picUrl,
source: 'netease' as Platform,
song: item.songInfo,
...item.songInfo,
playLoading: false
}));
// 设置心动模式状态
isIntelligenceMode.value = true;
intelligenceModeInfo.value = {
playlistId: favoritePlaylist.id,
seedSongId: randomSongId
};
playlistStore.playMode = 3; // 设置播放模式为心动模式
setLocalStorageItem('isIntelligenceMode', true);
setLocalStorageItem('intelligenceModeInfo', intelligenceModeInfo.value);
setLocalStorageItem('playMode', playlistStore.playMode);
// 替换播放列表并开始播放
playlistStore.setPlayList(intelligenceSongs, false, true);
await playerCore.handlePlayMusic(intelligenceSongs[0], true);
} else {
message.error(t('player.playBar.intelligenceMode.failed'));
}
} catch (error) {
console.error('心动模式播放失败:', error);
message.error(t('player.playBar.intelligenceMode.error'));
}
};
/**
* 清除心动模式状态
*/
const clearIntelligenceMode = () => {
isIntelligenceMode.value = false;
intelligenceModeInfo.value = null;
setLocalStorageItem('isIntelligenceMode', false);
localStorage.removeItem('intelligenceModeInfo');
};
return {
// 状态
isIntelligenceMode,
intelligenceModeInfo,
// Actions
playIntelligenceMode,
clearIntelligenceMode
};
});
File diff suppressed because it is too large Load Diff
+488
View File
@@ -0,0 +1,488 @@
import { cloneDeep } from 'lodash';
import { createDiscreteApi } from 'naive-ui';
import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
import i18n from '@/../i18n/renderer';
import { getBilibiliAudioUrl } from '@/api/bilibili';
import { getParsingMusicUrl } from '@/api/music';
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
import { useLyrics, useSongDetail } from '@/hooks/usePlayerHooks';
import { audioService } from '@/services/audioService';
import type { Platform, SongResult } from '@/types/music';
import { getImgUrl } from '@/utils';
import { getImageLinearBackground } from '@/utils/linearColor';
const musicHistory = useMusicHistory();
const { message } = createDiscreteApi(['message']);
/**
* 核心播放控制 Store
* 负责:播放/暂停、当前歌曲、音频URL、音量、播放速度、全屏状态
*/
export const usePlayerCoreStore = defineStore(
'playerCore',
() => {
// ==================== 状态 ====================
const play = ref(false);
const isPlay = ref(false);
const playMusic = ref<SongResult>({} as SongResult);
const playMusicUrl = ref('');
const musicFull = ref(false);
const playbackRate = ref(1.0);
const volume = ref(1);
const userPlayIntent = ref(true);
let checkPlayTime: NodeJS.Timeout | null = null;
// ==================== Computed ====================
const currentSong = computed(() => playMusic.value);
const isPlaying = computed(() => isPlay.value);
// ==================== Actions ====================
/**
* 设置播放状态
*/
const setIsPlay = (value: boolean) => {
isPlay.value = value;
play.value = value;
window.electron?.ipcRenderer.send('update-play-state', value);
};
/**
* 设置全屏状态
*/
const setMusicFull = (value: boolean) => {
musicFull.value = value;
};
/**
* 设置播放速度
*/
const setPlaybackRate = (rate: number) => {
playbackRate.value = rate;
audioService.setPlaybackRate(rate);
};
/**
* 设置音量
*/
const setVolume = (newVolume: number) => {
const normalizedVolume = Math.max(0, Math.min(1, newVolume));
volume.value = normalizedVolume;
audioService.setVolume(normalizedVolume);
};
/**
* 获取音量
*/
const getVolume = () => volume.value;
/**
* 增加音量
*/
const increaseVolume = (step: number = 0.1) => {
const newVolume = Math.min(1, volume.value + step);
setVolume(newVolume);
return newVolume;
};
/**
* 减少音量
*/
const decreaseVolume = (step: number = 0.1) => {
const newVolume = Math.max(0, volume.value - step);
setVolume(newVolume);
return newVolume;
};
/**
* 播放状态检测
*/
const checkPlaybackState = (song: SongResult, timeout: number = 4000) => {
if (checkPlayTime) {
clearTimeout(checkPlayTime);
}
const sound = audioService.getCurrentSound();
if (!sound) return;
const onPlayHandler = () => {
console.log('播放事件触发,歌曲成功开始播放');
audioService.off('play', onPlayHandler);
audioService.off('playerror', onPlayErrorHandler);
};
const onPlayErrorHandler = async () => {
console.log('播放错误事件触发,尝试重新获取URL');
audioService.off('play', onPlayHandler);
audioService.off('playerror', onPlayErrorHandler);
if (userPlayIntent.value && play.value) {
playMusic.value.playMusicUrl = undefined;
const refreshedSong = { ...song, isFirstPlay: true };
await handlePlayMusic(refreshedSong, true);
}
};
audioService.on('play', onPlayHandler);
audioService.on('playerror', onPlayErrorHandler);
checkPlayTime = setTimeout(() => {
if (!audioService.isActuallyPlaying() && userPlayIntent.value && play.value) {
console.log(`${timeout}ms后歌曲未真正播放且用户仍希望播放,尝试重新获取URL`);
audioService.off('play', onPlayHandler);
audioService.off('playerror', onPlayErrorHandler);
playMusic.value.playMusicUrl = undefined;
(async () => {
const refreshedSong = { ...song, isFirstPlay: true };
await handlePlayMusic(refreshedSong, true);
})();
}
}, timeout);
};
/**
* 核心播放处理函数
*/
const handlePlayMusic = async (music: SongResult, isPlay: boolean = true) => {
const currentSound = audioService.getCurrentSound();
if (currentSound) {
console.log('主动停止并卸载当前音频实例');
currentSound.stop();
currentSound.unload();
}
const originalMusic = { ...music };
const { loadLrc } = useLyrics();
const { getSongDetail } = useSongDetail();
// 并行加载歌词和背景色
const [lyrics, { backgroundColor, primaryColor }] = await Promise.all([
(async () => {
if (music.lyric && music.lyric.lrcTimeArray.length > 0) {
return music.lyric;
}
return await loadLrc(music.id);
})(),
(async () => {
if (music.backgroundColor && music.primaryColor) {
return { backgroundColor: music.backgroundColor, primaryColor: music.primaryColor };
}
return await getImageLinearBackground(getImgUrl(music?.picUrl, '30y30'));
})()
]);
// 设置歌词和背景色
music.lyric = lyrics;
music.backgroundColor = backgroundColor;
music.primaryColor = primaryColor;
music.playLoading = true;
// 更新 playMusic
playMusic.value = music;
play.value = isPlay;
// 更新标题
let title = music.name;
if (music.source === 'netease' && music?.song?.artists) {
title += ` - ${music.song.artists.reduce(
(prev: string, curr: any) => `${prev}${curr.name}/`,
''
)}`;
} else if (music.source === 'bilibili' && music?.song?.ar?.[0]) {
title += ` - ${music.song.ar[0].name}`;
}
document.title = 'AlgerMusic - ' + title;
try {
// 添加到历史记录
musicHistory.addMusic(music);
// 获取歌曲详情
const updatedPlayMusic = await getSongDetail(originalMusic);
updatedPlayMusic.lyric = lyrics;
playMusic.value = updatedPlayMusic;
playMusicUrl.value = updatedPlayMusic.playMusicUrl as string;
music.playMusicUrl = updatedPlayMusic.playMusicUrl as string;
let playInProgress = false;
try {
if (playInProgress) {
console.warn('播放操作正在进行中,避免重复调用');
return true;
}
playInProgress = true;
const result = await playAudio();
playInProgress = false;
return !!result;
} catch (error) {
console.error('自动播放音频失败:', error);
playInProgress = false;
return false;
}
} catch (error) {
console.error('处理播放音乐失败:', error);
message.error(i18n.global.t('player.playFailed'));
if (playMusic.value) {
playMusic.value.playLoading = false;
}
return false;
}
};
/**
* 播放音频
*/
const playAudio = async () => {
if (!playMusicUrl.value || !playMusic.value) return null;
try {
const shouldPlay = play.value;
console.log('播放音频,当前播放状态:', shouldPlay ? '播放' : '暂停');
// 检查保存的进度
let initialPosition = 0;
const savedProgress = JSON.parse(localStorage.getItem('playProgress') || '{}');
if (savedProgress.songId === playMusic.value.id) {
initialPosition = savedProgress.progress;
}
// B站视频URL检查
if (
playMusic.value.source === 'bilibili' &&
(!playMusicUrl.value || playMusicUrl.value === 'undefined')
) {
console.log('B站视频URL无效,尝试重新获取');
if (playMusic.value.bilibiliData) {
try {
const proxyUrl = await getBilibiliAudioUrl(
playMusic.value.bilibiliData.bvid,
playMusic.value.bilibiliData.cid
);
(playMusic.value as any).playMusicUrl = proxyUrl;
playMusicUrl.value = proxyUrl;
} catch (error) {
console.error('获取B站音频URL失败:', error);
message.error(i18n.global.t('player.playFailed'));
return null;
}
}
}
// 播放新音频
const newSound = await audioService.play(
playMusicUrl.value,
playMusic.value,
shouldPlay,
initialPosition || 0
);
// 添加播放状态检测
if (shouldPlay) {
checkPlaybackState(playMusic.value);
}
// 发布音频就绪事件
window.dispatchEvent(
new CustomEvent('audio-ready', { detail: { sound: newSound, shouldPlay } })
);
return newSound;
} catch (error) {
console.error('播放音频失败:', error);
setPlayMusic(false);
const errorMsg = error instanceof Error ? error.message : String(error);
// 操作锁错误处理
if (errorMsg.includes('操作锁激活')) {
console.log('由于操作锁正在使用,将在1000ms后重试');
try {
audioService.forceResetOperationLock();
console.log('已强制重置操作锁');
} catch (e) {
console.error('重置操作锁失败:', e);
}
setTimeout(() => {
if (userPlayIntent.value && play.value) {
playAudio().catch((e) => {
console.error('重试播放失败:', e);
});
}
}, 1000);
}
message.error(i18n.global.t('player.playFailed'));
return null;
}
};
/**
* 暂停播放
*/
const handlePause = async () => {
try {
const currentSound = audioService.getCurrentSound();
if (currentSound) {
currentSound.pause();
}
setPlayMusic(false);
userPlayIntent.value = false;
} catch (error) {
console.error('暂停播放失败:', error);
}
};
/**
* 设置播放/暂停
*/
const setPlayMusic = async (value: boolean | SongResult) => {
if (typeof value === 'boolean') {
setIsPlay(value);
userPlayIntent.value = value;
} else {
await handlePlayMusic(value);
play.value = true;
isPlay.value = true;
userPlayIntent.value = true;
}
};
/**
* 使用指定音源重新解析当前歌曲
*/
const reparseCurrentSong = async (sourcePlatform: Platform) => {
try {
const currentSong = playMusic.value;
if (!currentSong || !currentSong.id) {
console.warn('没有有效的播放对象');
return false;
}
if (currentSong.source === 'bilibili') {
console.warn('B站视频不支持重新解析');
return false;
}
const songId = String(currentSong.id);
localStorage.setItem(`song_source_${songId}`, JSON.stringify([sourcePlatform]));
const currentSound = audioService.getCurrentSound();
if (currentSound) {
currentSound.pause();
}
const numericId =
typeof currentSong.id === 'string' ? parseInt(currentSong.id, 10) : currentSong.id;
console.log(`使用音源 ${sourcePlatform} 重新解析歌曲 ${numericId}`);
const songData = cloneDeep(currentSong);
const res = await getParsingMusicUrl(numericId, songData);
if (res && res.data && res.data.data && res.data.data.url) {
const newUrl = res.data.data.url;
console.log(`解析成功,获取新URL: ${newUrl.substring(0, 50)}...`);
const updatedMusic = {
...currentSong,
playMusicUrl: newUrl,
expiredAt: Date.now() + 1800000
};
await handlePlayMusic(updatedMusic, true);
return true;
} else {
console.warn(`使用音源 ${sourcePlatform} 解析失败`);
return false;
}
} catch (error) {
console.error('重新解析失败:', error);
return false;
}
};
/**
* 初始化播放状态
*/
const initializePlayState = async () => {
const { useSettingsStore } = await import('./settings');
const settingStore = useSettingsStore();
if (playMusic.value && Object.keys(playMusic.value).length > 0) {
try {
console.log('恢复上次播放的音乐:', playMusic.value.name);
const isPlaying = settingStore.setData.autoPlay;
if (playMusic.value.source === 'bilibili' && playMusic.value.bilibiliData) {
console.log('恢复B站视频播放', playMusic.value.bilibiliData);
playMusic.value.playMusicUrl = undefined;
}
await handlePlayMusic(
{ ...playMusic.value, isFirstPlay: true, playMusicUrl: undefined },
isPlaying
);
} catch (error) {
console.error('重新获取音乐链接失败:', error);
play.value = false;
isPlay.value = false;
playMusic.value = {} as SongResult;
playMusicUrl.value = '';
}
}
setTimeout(() => {
audioService.setPlaybackRate(playbackRate.value);
}, 2000);
};
return {
// 状态
play,
isPlay,
playMusic,
playMusicUrl,
musicFull,
playbackRate,
volume,
userPlayIntent,
// Computed
currentSong,
isPlaying,
// Actions
setIsPlay,
setMusicFull,
setPlayMusic,
setPlaybackRate,
setVolume,
getVolume,
increaseVolume,
decreaseVolume,
handlePlayMusic,
playAudio,
handlePause,
checkPlaybackState,
reparseCurrentSong,
initializePlayState
};
},
{
persist: {
key: 'player-core-store',
storage: localStorage,
pick: ['playMusic', 'playMusicUrl', 'playbackRate', 'volume', 'isPlay']
}
}
);
+594
View File
@@ -0,0 +1,594 @@
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 { preloadNextSong, useSongDetail } from '@/hooks/usePlayerHooks';
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) {
preloadNextSong(nextSong.playMusicUrl);
}
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 () => {
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] };
playListIndex.value = nowPlayListIndex;
const success = await playerCore.handlePlayMusic(nextSong, true);
if (success) {
sleepTimerStore.handleSongChange();
} else {
console.error('播放下一首失败');
playListIndex.value = currentIndex;
playerCore.setIsPlay(false);
message.error(i18n.global.t('player.playFailed'));
}
} 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] };
playListIndex.value = 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) {
console.error('所有尝试都失败,无法播放上一首歌曲');
playListIndex.value = 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();
playerCore.checkPlaybackState(playerCore.playMusic);
}
}
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
};
},
{
// 配置 pinia-plugin-persistedstate
persist: {
key: 'playlist-store',
storage: localStorage,
// 持久化所有状态,除了 playListDrawerVisibleUI 状态不需要持久化)
pick: ['playList', 'playListIndex', 'playMode', 'originalPlayList']
}
}
);
+264
View File
@@ -0,0 +1,264 @@
import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
import i18n from '@/../i18n/renderer';
import { getLocalStorageItem, setLocalStorageItem } from '@/utils/playerUtils';
// 定时关闭类型
export enum SleepTimerType {
NONE = 'none',
TIME = 'time',
SONGS = 'songs',
PLAYLIST_END = 'end'
}
// 定时关闭信息
export interface SleepTimerInfo {
type: SleepTimerType;
value: number;
endTime?: number;
startSongIndex?: number;
remainingSongs?: number;
}
/**
* 定时关闭管理 Store
* 负责:定时关闭功能
*/
export const useSleepTimerStore = defineStore('sleepTimer', () => {
// ==================== 状态 ====================
const sleepTimer = ref<SleepTimerInfo>(
getLocalStorageItem('sleepTimer', {
type: SleepTimerType.NONE,
value: 0
})
);
const showSleepTimer = ref(false);
const timerInterval = ref<number | null>(null);
// ==================== Computed ====================
const currentSleepTimer = computed(() => sleepTimer.value);
const hasSleepTimerActive = computed(() => sleepTimer.value.type !== SleepTimerType.NONE);
const sleepTimerRemainingTime = computed(() => {
if (sleepTimer.value.type === SleepTimerType.TIME && sleepTimer.value.endTime) {
const remaining = Math.max(0, sleepTimer.value.endTime - Date.now());
return Math.ceil(remaining / 60000);
}
return 0;
});
const sleepTimerRemainingSongs = computed(() => {
if (sleepTimer.value.type === SleepTimerType.SONGS) {
return sleepTimer.value.remainingSongs || 0;
}
return 0;
});
// ==================== Actions ====================
/**
* 按时间设置定时关闭
*/
const setSleepTimerByTime = (minutes: number) => {
clearSleepTimer();
if (minutes <= 0) {
return false;
}
const endTime = Date.now() + minutes * 60 * 1000;
sleepTimer.value = {
type: SleepTimerType.TIME,
value: minutes,
endTime
};
setLocalStorageItem('sleepTimer', sleepTimer.value);
timerInterval.value = window.setInterval(() => {
checkSleepTimer();
}, 1000) as unknown as number;
console.log(`设置定时关闭: ${minutes}分钟后`);
return true;
};
/**
* 按歌曲数设置定时关闭
*/
const setSleepTimerBySongs = (songs: number) => {
clearSleepTimer();
if (songs <= 0) {
return false;
}
const { usePlaylistStore } = require('./playlist');
const playlistStore = usePlaylistStore();
sleepTimer.value = {
type: SleepTimerType.SONGS,
value: songs,
startSongIndex: playlistStore.playListIndex,
remainingSongs: songs
};
setLocalStorageItem('sleepTimer', sleepTimer.value);
console.log(`设置定时关闭: 再播放${songs}首歌后`);
return true;
};
/**
* 播放列表结束时关闭
*/
const setSleepTimerAtPlaylistEnd = () => {
clearSleepTimer();
sleepTimer.value = {
type: SleepTimerType.PLAYLIST_END,
value: 0
};
setLocalStorageItem('sleepTimer', sleepTimer.value);
console.log('设置定时关闭: 播放列表结束时');
return true;
};
/**
* 取消定时关闭
*/
const clearSleepTimer = () => {
if (timerInterval.value) {
window.clearInterval(timerInterval.value);
timerInterval.value = null;
}
sleepTimer.value = {
type: SleepTimerType.NONE,
value: 0
};
setLocalStorageItem('sleepTimer', sleepTimer.value);
console.log('取消定时关闭');
return true;
};
/**
* 检查定时关闭是否应该触发
*/
const checkSleepTimer = () => {
if (sleepTimer.value.type === SleepTimerType.NONE) {
return;
}
if (sleepTimer.value.type === SleepTimerType.TIME && sleepTimer.value.endTime) {
if (Date.now() >= sleepTimer.value.endTime) {
stopPlayback();
}
}
};
/**
* 停止播放并清除定时器
*/
const stopPlayback = async () => {
console.log('定时器触发:停止播放');
const { usePlayerCoreStore } = await import('./playerCore');
const playerCore = usePlayerCoreStore();
const { audioService } = await import('@/services/audioService');
if (playerCore.isPlaying) {
playerCore.setIsPlay(false);
audioService.pause();
}
// 发送通知
if (window.electron?.ipcRenderer) {
window.electron.ipcRenderer.send('show-notification', {
title: i18n.global.t('player.sleepTimer.timerEnded'),
body: i18n.global.t('player.sleepTimer.playbackStopped')
});
}
clearSleepTimer();
};
/**
* 监听歌曲变化,处理按歌曲数定时和播放列表结束定时
*/
const handleSongChange = async () => {
console.log('歌曲已切换,检查定时器状态:', sleepTimer.value);
// 处理按歌曲数定时
if (
sleepTimer.value.type === SleepTimerType.SONGS &&
sleepTimer.value.remainingSongs !== undefined
) {
sleepTimer.value.remainingSongs--;
console.log(`剩余歌曲数: ${sleepTimer.value.remainingSongs}`);
setLocalStorageItem('sleepTimer', sleepTimer.value);
if (sleepTimer.value.remainingSongs <= 0) {
console.log('已播放完设定的歌曲数,停止播放');
stopPlayback();
setTimeout(() => {
stopPlayback();
}, 1000);
}
}
// 处理播放列表结束定时
if (sleepTimer.value.type === SleepTimerType.PLAYLIST_END) {
const { usePlaylistStore } = await import('./playlist');
const playlistStore = usePlaylistStore();
const isLastSong = playlistStore.playListIndex === playlistStore.playList.length - 1;
if (isLastSong && playlistStore.playMode !== 1) {
console.log('已到达播放列表末尾,将在当前歌曲结束后停止播放');
sleepTimer.value = {
type: SleepTimerType.SONGS,
value: 1,
remainingSongs: 1
};
setLocalStorageItem('sleepTimer', sleepTimer.value);
}
}
};
/**
* 设置定时器弹窗显示状态
*/
const setShowSleepTimer = (value: boolean) => {
showSleepTimer.value = value;
};
return {
// 状态
sleepTimer,
showSleepTimer,
// Computed
currentSleepTimer,
hasSleepTimerActive,
sleepTimerRemainingTime,
sleepTimerRemainingSongs,
// Actions
setSleepTimerByTime,
setSleepTimerBySongs,
setSleepTimerAtPlaylistEnd,
clearSleepTimer,
checkSleepTimer,
stopPlayback,
handleSongChange,
setShowSleepTimer
};
});