diff --git a/src/i18n/lang/en-US/player.ts b/src/i18n/lang/en-US/player.ts index ad27418..41cc503 100644 --- a/src/i18n/lang/en-US/player.ts +++ b/src/i18n/lang/en-US/player.ts @@ -66,7 +66,17 @@ export default { favorite: 'Favorite {name}', unFavorite: 'Unfavorite {name}', playbackSpeed: 'Playback Speed', - advancedControls: 'Advanced Controls' + advancedControls: 'Advanced Controls', + intelligenceMode: { + title: 'Intelligence Mode', + needCookieLogin: 'Please login with Cookie method to use Intelligence Mode', + noFavoritePlaylist: 'Favorite playlist not found', + noLikedSongs: 'You have no liked songs yet', + loading: 'Loading Intelligence Mode', + success: 'Loaded {count} songs', + failed: 'Failed to get Intelligence Mode list', + error: 'Intelligence Mode error' + } }, eq: { title: 'Equalizer', diff --git a/src/i18n/lang/ja-JP/player.ts b/src/i18n/lang/ja-JP/player.ts index 28e4dc7..2901fcc 100644 --- a/src/i18n/lang/ja-JP/player.ts +++ b/src/i18n/lang/ja-JP/player.ts @@ -67,7 +67,17 @@ export default { unFavorite: '{name}をお気に入りから削除しました', miniPlayBar: 'ミニ再生バー', playbackSpeed: '再生速度', - advancedControls: 'その他の設定' + advancedControls: 'その他の設定', + intelligenceMode: { + title: 'インテリジェンスモード', + needCookieLogin: 'Cookie方式でログインしてからインテリジェンスモードを使用してください', + noFavoritePlaylist: '「お気に入りの音楽」プレイリストが見つかりません', + noLikedSongs: 'まだ「いいね」した楽曲がありません', + loading: 'インテリジェンスモードを読み込み中', + success: '{count} 曲を読み込みました', + failed: 'インテリジェンスモードのリスト取得に失敗しました', + error: 'インテリジェンスモードの再生でエラーが発生しました' + } }, eq: { title: 'イコライザー', diff --git a/src/i18n/lang/ko-KR/player.ts b/src/i18n/lang/ko-KR/player.ts index 68662ef..1d679f9 100644 --- a/src/i18n/lang/ko-KR/player.ts +++ b/src/i18n/lang/ko-KR/player.ts @@ -67,7 +67,17 @@ export default { unFavorite: '{name} 즐겨찾기 해제됨', miniPlayBar: '미니 재생바', playbackSpeed: '재생 속도', - advancedControls: '고급 설정' + advancedControls: '고급 설정', + intelligenceMode: { + title: '인텔리전스 모드', + needCookieLogin: '쿠키 방식으로 로그인한 후 인텔리전스 모드를 사용할 수 있습니다', + noFavoritePlaylist: '내가 좋아하는 음악 재생목록을 찾을 수 없습니다', + noLikedSongs: '아직 좋아한 노래가 없습니다', + loading: '인텔리전스 모드를 불러오는 중', + success: '총 {count}곡을 불러왔습니다', + failed: '인텔리전스 모드 목록을 가져오는 데 실패했습니다', + error: '인텔리전스 모드 재생 오류' + } }, eq: { title: '이퀄라이저', diff --git a/src/i18n/lang/zh-CN/player.ts b/src/i18n/lang/zh-CN/player.ts index e99388b..642b93b 100644 --- a/src/i18n/lang/zh-CN/player.ts +++ b/src/i18n/lang/zh-CN/player.ts @@ -67,7 +67,17 @@ export default { unFavorite: '已取消收藏{name}', miniPlayBar: '迷你播放栏', playbackSpeed: '播放速度', - advancedControls: '更多设置s' + advancedControls: '更多设置', + intelligenceMode: { + title: '心动模式', + needCookieLogin: '请使用 Cookie 方式登录后使用心动模式', + noFavoritePlaylist: '未找到我喜欢的音乐歌单', + noLikedSongs: '您还没有喜欢的歌曲', + loading: '正在加载心动模式', + success: '已加载 {count} 首歌曲', + failed: '获取心动模式列表失败', + error: '心动模式播放出错' + } }, eq: { title: '均衡器', diff --git a/src/i18n/lang/zh-Hant/player.ts b/src/i18n/lang/zh-Hant/player.ts index b96a2c5..eb559a3 100644 --- a/src/i18n/lang/zh-Hant/player.ts +++ b/src/i18n/lang/zh-Hant/player.ts @@ -67,7 +67,17 @@ export default { unFavorite: '已取消收藏{name}', miniPlayBar: '迷你播放列', playbackSpeed: '播放速度', - advancedControls: '更多設定s' + advancedControls: '更多設定', + intelligenceMode: { + title: '心動模式', + needCookieLogin: '請使用 Cookie 方式登入後使用心動模式', + noFavoritePlaylist: '未找到我喜歡的音樂歌單', + noLikedSongs: '您還沒有喜歡的歌曲', + loading: '正在載入心動模式', + success: '已載入 {count} 首歌曲', + failed: '取得心動模式清單失敗', + error: '心動模式播放出錯' + } }, eq: { title: '等化器', diff --git a/src/renderer/api/music.ts b/src/renderer/api/music.ts index dd41fa2..5fc2e9d 100644 --- a/src/renderer/api/music.ts +++ b/src/renderer/api/music.ts @@ -210,3 +210,15 @@ export function getHistoryRecommendSongs(date: string) { params: { date } }); } + +/** + * 心动模式/智能播放 + * @param params id: 歌曲id, pid: 歌单id, sid: 要开始播放的歌曲id(可选) + */ +export function getIntelligenceList(params: { id: number; pid: number; sid?: number }) { + return request({ + url: '/playmode/intelligence/list', + method: 'get', + params + }); +} diff --git a/src/renderer/components/lyric/MusicFullMobile.vue b/src/renderer/components/lyric/MusicFullMobile.vue index 36521de..e4d47f7 100644 --- a/src/renderer/components/lyric/MusicFullMobile.vue +++ b/src/renderer/components/lyric/MusicFullMobile.vue @@ -343,7 +343,7 @@
- +
{{ playModeText }} @@ -174,6 +182,7 @@ import { textColors } from '@/hooks/MusicHook'; import { useArtist } from '@/hooks/useArtist'; +import { usePlayMode } from '@/hooks/usePlayMode'; import { audioService } from '@/services/audioService'; import { isBilibiliIdMatch, usePlayerStore } from '@/store/modules/player'; import { useSettingsStore } from '@/store/modules/settings'; @@ -282,38 +291,10 @@ const handleVolumeWheel = (e: WheelEvent) => { }; // 播放模式 -const playMode = computed(() => playerStore.playMode); -const playModeIcon = computed(() => { - switch (playMode.value) { - case 0: - return 'ri-repeat-2-line'; - case 1: - return 'ri-repeat-one-line'; - case 2: - return 'ri-shuffle-line'; - default: - return 'ri-repeat-2-line'; - } -}); -const playModeText = computed(() => { - switch (playMode.value) { - case 0: - return t('player.playBar.playMode.sequence'); - case 1: - return t('player.playBar.playMode.loop'); - case 2: - return t('player.playBar.playMode.random'); - default: - return t('player.playBar.playMode.sequence'); - } -}); +const { playMode, playModeIcon, playModeText, togglePlayMode } = usePlayMode(); // 播放速度控制 const { playbackRate } = storeToRefs(playerStore); -// 切换播放模式 -const togglePlayMode = () => { - playerStore.togglePlayMode(); -}; function handleNext() { playerStore.nextPlay(); @@ -645,6 +626,10 @@ const openPlayListDrawer = () => { @apply text-red-500 hover:text-red-600 !important; } +.intelligence-active { + @apply text-green-500 hover:text-green-600 !important; +} + .disabled-icon { @apply opacity-50 cursor-not-allowed !important; &:hover { diff --git a/src/renderer/components/player/SimplePlayBar.vue b/src/renderer/components/player/SimplePlayBar.vue index 94ec888..9b15b00 100644 --- a/src/renderer/components/player/SimplePlayBar.vue +++ b/src/renderer/components/player/SimplePlayBar.vue @@ -20,7 +20,10 @@
@@ -73,6 +76,7 @@ import { computed, onMounted, ref, watch } from 'vue'; import { allTime, nowTime, playMusic } from '@/hooks/MusicHook'; +import { usePlayMode } from '@/hooks/usePlayMode'; import { audioService } from '@/services/audioService'; import { usePlayerStore } from '@/store/modules/player'; import { secondToMinute } from '@/utils'; @@ -93,24 +97,7 @@ const playBarRef = ref(null); const play = computed(() => playerStore.isPlay); // 播放模式 -const playMode = computed(() => playerStore.playMode); -const playModeIcon = computed(() => { - switch (playMode.value) { - case 0: - return 'ri-repeat-2-line'; - case 1: - return 'ri-repeat-one-line'; - case 2: - return 'ri-shuffle-line'; - default: - return 'ri-repeat-2-line'; - } -}); - -// 切换播放模式 -const togglePlayMode = () => { - playerStore.togglePlayMode(); -}; +const { playMode, playModeIcon, togglePlayMode } = usePlayMode(); // 音量控制 const audioVolume = ref( @@ -527,4 +514,8 @@ onMounted(() => { color: var(--fill-color); text-shadow: 0 0 8px var(--fill-color-transparent); } + +.intelligence-active { + @apply text-green-500; +} diff --git a/src/renderer/hooks/MusicHook.ts b/src/renderer/hooks/MusicHook.ts index 8306d1f..b740c31 100644 --- a/src/renderer/hooks/MusicHook.ts +++ b/src/renderer/hooks/MusicHook.ts @@ -758,7 +758,6 @@ export const getLrcTimeRange = (index: number) => ({ watch( () => lrcArray.value, (newLrcArray) => { - console.log('lrcArray.value', lrcArray.value); if (newLrcArray.length > 0 && isElectron && isLyricWindowOpen.value) { sendLyricToWin(); } diff --git a/src/renderer/hooks/usePlayMode.ts b/src/renderer/hooks/usePlayMode.ts new file mode 100644 index 0000000..a1b0d1f --- /dev/null +++ b/src/renderer/hooks/usePlayMode.ts @@ -0,0 +1,60 @@ +import { computed } from 'vue'; +import { useI18n } from 'vue-i18n'; + +import { usePlayerStore } from '@/store/modules/player'; + +/** + * 播放模式相关的 Hook + * 提供播放模式的图标、文本和切换功能 + */ +export function usePlayMode() { + const { t } = useI18n(); + const playerStore = usePlayerStore(); + + // 当前播放模式 + const playMode = computed(() => playerStore.playMode); + + // 播放模式图标 + const playModeIcon = computed(() => { + switch (playMode.value) { + case 0: + return 'ri-repeat-2-line'; + case 1: + return 'ri-repeat-one-line'; + case 2: + return 'ri-shuffle-line'; + case 3: + return 'ri-heart-pulse-line'; + default: + return 'ri-repeat-2-line'; + } + }); + + // 播放模式文本 + const playModeText = computed(() => { + switch (playMode.value) { + case 0: + return t('player.playBar.playMode.sequence'); + case 1: + return t('player.playBar.playMode.loop'); + case 2: + return t('player.playBar.playMode.random'); + case 3: + return t('player.playBar.intelligenceMode.title'); + default: + return t('player.playBar.playMode.sequence'); + } + }); + + // 切换播放模式 + const togglePlayMode = () => { + playerStore.togglePlayMode(); + }; + + return { + playMode, + playModeIcon, + playModeText, + togglePlayMode + }; +} diff --git a/src/renderer/services/audioService.ts b/src/renderer/services/audioService.ts index 92add84..1c2d874 100644 --- a/src/renderer/services/audioService.ts +++ b/src/renderer/services/audioService.ts @@ -853,10 +853,6 @@ class AudioService { const isLoading = this.isLoading(); const contextRunning = Howler.ctx && Howler.ctx.state === 'running'; - console.log( - `实际播放状态检查: playing=${isPlaying}, loading=${isLoading}, contextRunning=${contextRunning}` - ); - // 只有在三个条件都满足时才认为是真正在播放 return isPlaying && !isLoading && contextRunning; } catch (error) { diff --git a/src/renderer/store/modules/player.ts b/src/renderer/store/modules/player.ts index efa2c8c..d736709 100644 --- a/src/renderer/store/modules/player.ts +++ b/src/renderer/store/modules/player.ts @@ -330,8 +330,6 @@ export const loadLrc = async (id: string | number): Promise => { }); } - console.log('lyrics', lyrics); - return { lrcTimeArray: times, lrcArray: lyrics, @@ -537,6 +535,13 @@ export const usePlayerStore = defineStore('player', () => { // 原始播放列表 - 保存切换到随机模式前的顺序 const originalPlayList = ref(getLocalStorageItem('originalPlayList', [])); + // 心动模式状态 + const isIntelligenceMode = ref(getLocalStorageItem('isIntelligenceMode', false)); + const intelligenceModeInfo = ref<{ + playlistId: number; + seedSongId: number; + } | null>(getLocalStorageItem('intelligenceModeInfo', null)); + // 通用洗牌函数 - Fisher-Yates 算法 const performShuffle = (list: SongResult[], currentSong?: SongResult): SongResult[] => { if (list.length <= 1) return [...list]; @@ -960,7 +965,19 @@ export const usePlayerStore = defineStore('player', () => { musicFull.value = value; }; - const setPlayList = (list: SongResult[], keepIndex: boolean = false) => { + const setPlayList = ( + list: SongResult[], + keepIndex: boolean = false, + fromIntelligenceMode: boolean = false + ) => { + // 如果不是从心动模式调用,则清除心动模式状态 + if (!fromIntelligenceMode && isIntelligenceMode.value) { + isIntelligenceMode.value = false; + intelligenceModeInfo.value = null; + localStorage.removeItem('isIntelligenceMode'); + localStorage.removeItem('intelligenceModeInfo'); + } + if (list.length === 0) { playList.value = []; playListIndex.value = 0; @@ -1341,10 +1358,21 @@ export const usePlayerStore = defineStore('player', () => { // 节流 const prevPlay = useThrottleFn(_prevPlay, 500); - const togglePlayMode = () => { - const newMode = (playMode.value + 1) % 3; + const togglePlayMode = async () => { + const userStore = useUserStore(); + const wasIntelligence = playMode.value === 3; + const newMode = (playMode.value + 1) % 4; // 扩展到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; // 跳到顺序模式 + localStorage.setItem('playMode', JSON.stringify(playMode.value)); + return; + } playMode.value = newMode; localStorage.setItem('playMode', JSON.stringify(playMode.value)); @@ -1356,10 +1384,25 @@ export const usePlayerStore = defineStore('player', () => { } // 当从随机模式切换出去时,恢复原始顺序 - if (!isRandom && wasRandom) { + if (!isRandom && wasRandom && !isIntelligence) { restoreOriginalOrder(); console.log('切换出随机模式,恢复原始顺序'); } + + // 当切换到心动模式时,触发心动模式播放 + if (isIntelligence && !wasIntelligence) { + console.log('切换到心动模式'); + await playIntelligenceMode(); + } + + // 当从心动模式切换出去时,清除心动模式状态 + if (!isIntelligence && wasIntelligence) { + console.log('退出心动模式'); + isIntelligenceMode.value = false; + intelligenceModeInfo.value = null; + localStorage.setItem('isIntelligenceMode', JSON.stringify(false)); + localStorage.removeItem('intelligenceModeInfo'); + } }; const addToFavorite = async (id: number | string) => { @@ -1444,9 +1487,12 @@ export const usePlayerStore = defineStore('player', () => { const settingStore = useSettingsStore(); const savedPlayList = getLocalStorageItem('playList', []); const savedPlayMusic = getLocalStorageItem('currentPlayMusic', null); + // 恢复心动模式状态 + const savedIntelligenceMode = getLocalStorageItem('isIntelligenceMode', false); if (savedPlayList.length > 0) { - setPlayList(savedPlayList); + // 如果是心动模式,保持状态 + setPlayList(savedPlayList, false, savedIntelligenceMode); // 重启后恢复随机播放状态 if (playMode.value === 2) { @@ -1747,6 +1793,86 @@ export const usePlayerStore = defineStore('player', () => { return newVolume; }; + // 心动模式播放 + const playIntelligenceMode = async () => { + const userStore = useUserStore(); + 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 + }; + playMode.value = 3; // 设置播放模式为心动模式 + localStorage.setItem('isIntelligenceMode', JSON.stringify(true)); + localStorage.setItem('intelligenceModeInfo', JSON.stringify(intelligenceModeInfo.value)); + localStorage.setItem('playMode', JSON.stringify(playMode.value)); + + // 替换播放列表并开始播放 + await setPlayList(intelligenceSongs, false, true); + await handlePlayMusic(intelligenceSongs[0], true); + } else { + message.error(t('player.playBar.intelligenceMode.failed')); + } + } catch (error) { + console.error('心动模式播放失败:', error); + message.error(t('player.playBar.intelligenceMode.error')); + } + }; + return { play, isPlay, @@ -1812,6 +1938,11 @@ export const usePlayerStore = defineStore('player', () => { originalPlayList, shufflePlayList, restoreOriginalOrder, - preloadNextSongs + preloadNextSongs, + + // 心动模式 + playIntelligenceMode, + isIntelligenceMode, + intelligenceModeInfo }; });