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 @@
-
+
@@ -382,6 +382,7 @@ import {
useLyricProgress
} from '@/hooks/MusicHook';
import { useArtist } from '@/hooks/useArtist';
+import { usePlayMode } from '@/hooks/usePlayMode';
import { usePlayerStore } from '@/store/modules/player';
import { DEFAULT_LYRIC_CONFIG, LyricConfig } from '@/types/lyric';
import { getImgUrl, secondToMinute } from '@/utils';
@@ -394,19 +395,9 @@ const playerStore = usePlayerStore();
// 播放控制相关
const play = computed(() => playerStore.isPlay);
const playIcon = computed(() => (play.value ? 'ri-pause-fill' : 'ri-play-fill'));
-const playMode = computed(() => playerStore.playMode);
-const playModeIcon = computed(() => {
- switch (playMode.value) {
- case 0:
- return 'ri-repeat-line';
- case 1:
- return 'ri-repeat-one-line';
- case 2:
- return 'ri-shuffle-line';
- default:
- return 'ri-repeat-line';
- }
-});
+
+// 播放模式
+const { playMode, playModeIcon, playModeText, togglePlayMode: togglePlayModeBase } = usePlayMode();
// 打开播放列表
const showPlaylist = () => {
playerStore.setPlayListDrawerVisible(true);
@@ -963,12 +954,8 @@ const prevSong = () => {
};
const togglePlayMode = () => {
- playerStore.togglePlayMode();
- showBottomToast(
- [t('player.playMode.sequence'), t('player.playMode.loop'), t('player.playMode.random')][
- playMode.value
- ]
- );
+ togglePlayModeBase();
+ showBottomToast(playModeText.value);
};
const closeMusicFull = () => {
@@ -1525,6 +1512,10 @@ const getWordStyle = (lineIndex: number, _wordIndex: number, word: any) => {
i {
@apply text-2xl;
color: var(--text-color-primary);
+
+ &.intelligence-active {
+ @apply text-green-500;
+ }
}
&:hover {
diff --git a/src/renderer/components/player/PlayBar.vue b/src/renderer/components/player/PlayBar.vue
index c787e1f..c28b376 100644
--- a/src/renderer/components/player/PlayBar.vue
+++ b/src/renderer/components/player/PlayBar.vue
@@ -105,15 +105,23 @@
-
+
{{ 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
};
});