mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-17 10:27:30 +08:00
refactor: 更新 eslint 和 prettier 配置 格式化代码
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { ref } from 'vue';
|
||||
|
||||
// 定义表配置的泛型接口
|
||||
|
||||
+165
-129
@@ -1,32 +1,59 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { createDiscreteApi } from 'naive-ui';
|
||||
import { computed, nextTick, onUnmounted, ref, watch } from 'vue';
|
||||
import { computed, type ComputedRef,nextTick, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import { getBilibiliAudioUrl } from '@/api/bilibili';
|
||||
import useIndexedDB from '@/hooks/IndexDBHook';
|
||||
import { audioService } from '@/services/audioService';
|
||||
import pinia, { usePlayerStore } from '@/store';
|
||||
import type { usePlayerStore } from '@/store';
|
||||
import { getSongUrl } from '@/store/modules/player';
|
||||
import type { Artist, ILyricText, SongResult } from '@/type/music';
|
||||
import { isElectron } from '@/utils';
|
||||
import { getTextColors } from '@/utils/linearColor';
|
||||
import { getSongUrl } from '@/store/modules/player';
|
||||
|
||||
const windowData = window as any;
|
||||
|
||||
const playerStore = usePlayerStore(pinia);
|
||||
// 全局 playerStore 引用,通过 initMusicHook 函数注入
|
||||
let playerStore: ReturnType<typeof usePlayerStore> | null = null;
|
||||
|
||||
// 初始化函数,接受 store 实例
|
||||
export const initMusicHook = (store: ReturnType<typeof usePlayerStore>) => {
|
||||
playerStore = store;
|
||||
|
||||
// 创建 computed 属性
|
||||
playMusic = computed(() => getPlayerStore().playMusic as SongResult);
|
||||
artistList = computed(
|
||||
() => (getPlayerStore().playMusic.ar || getPlayerStore().playMusic?.song?.artists) as Artist[]
|
||||
);
|
||||
|
||||
// 在 store 注入后初始化需要 store 的功能
|
||||
setupKeyboardListeners();
|
||||
initProgressAnimation();
|
||||
setupMusicWatchers();
|
||||
setupCorrectionTimeWatcher();
|
||||
setupPlayStateWatcher();
|
||||
};
|
||||
|
||||
// 获取 playerStore 的辅助函数
|
||||
const getPlayerStore = () => {
|
||||
if (!playerStore) {
|
||||
throw new Error('MusicHook not initialized. Call initMusicHook first.');
|
||||
}
|
||||
return playerStore;
|
||||
};
|
||||
export const lrcArray = ref<ILyricText[]>([]); // 歌词数组
|
||||
export const lrcTimeArray = ref<number[]>([]); // 歌词时间数组
|
||||
export const nowTime = ref(0); // 当前播放时间
|
||||
export const allTime = ref(0); // 总播放时间
|
||||
export const nowIndex = ref(0); // 当前播放歌词
|
||||
export const currentLrcProgress = ref(0); // 来存储当前歌词的进度
|
||||
export const playMusic = computed(() => playerStore.playMusic as SongResult); // 当前播放歌曲
|
||||
export const sound = ref<Howl | null>(audioService.getCurrentSound());
|
||||
export const isLyricWindowOpen = ref(false); // 新增状态
|
||||
export const textColors = ref<any>(getTextColors());
|
||||
export const artistList = computed(
|
||||
() => (playerStore.playMusic.ar || playerStore.playMusic?.song?.artists) as Artist[]
|
||||
);
|
||||
|
||||
// 这些 computed 属性需要在初始化后创建
|
||||
export let playMusic: ComputedRef<SongResult>;
|
||||
export let artistList: ComputedRef<Artist[]>;
|
||||
|
||||
export const musicDB = await useIndexedDB('musicDB', [
|
||||
{ name: 'music', keyPath: 'id' },
|
||||
@@ -34,25 +61,29 @@ export const musicDB = await useIndexedDB('musicDB', [
|
||||
{ name: 'api_cache', keyPath: 'id' }
|
||||
]);
|
||||
|
||||
document.onkeyup = (e) => {
|
||||
// 检查事件目标是否是输入框元素
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
|
||||
return;
|
||||
}
|
||||
// 键盘事件处理器,在初始化后设置
|
||||
const setupKeyboardListeners = () => {
|
||||
document.onkeyup = (e) => {
|
||||
// 检查事件目标是否是输入框元素
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.code) {
|
||||
case 'Space':
|
||||
if (playerStore.play) {
|
||||
playerStore.setPlayMusic(false);
|
||||
audioService.getCurrentSound()?.pause();
|
||||
} else {
|
||||
playerStore.setPlayMusic(true);
|
||||
audioService.getCurrentSound()?.play();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
const store = getPlayerStore();
|
||||
switch (e.code) {
|
||||
case 'Space':
|
||||
if (store.play) {
|
||||
store.setPlayMusic(false);
|
||||
audioService.getCurrentSound()?.pause();
|
||||
} else {
|
||||
store.setPlayMusic(true);
|
||||
audioService.getCurrentSound()?.play();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const { message } = createDiscreteApi(['message']);
|
||||
@@ -72,7 +103,7 @@ const stopProgressAnimation = () => {
|
||||
|
||||
// 全局更新函数
|
||||
const updateProgress = () => {
|
||||
if (!playerStore.play) {
|
||||
if (!getPlayerStore().play) {
|
||||
stopProgressAnimation();
|
||||
return;
|
||||
}
|
||||
@@ -120,11 +151,11 @@ const updateProgress = () => {
|
||||
Math.floor(currentTime) !== Math.floor(lastSavedTime.value)
|
||||
) {
|
||||
lastSavedTime.value = currentTime;
|
||||
if (playerStore.playMusic && playerStore.playMusic.id) {
|
||||
if (getPlayerStore().playMusic && getPlayerStore().playMusic.id) {
|
||||
localStorage.setItem(
|
||||
'playProgress',
|
||||
JSON.stringify({
|
||||
songId: playerStore.playMusic.id,
|
||||
songId: getPlayerStore().playMusic.id,
|
||||
progress: currentTime
|
||||
})
|
||||
);
|
||||
@@ -175,7 +206,7 @@ const initProgressAnimation = () => {
|
||||
let debounceTimer: any = null;
|
||||
|
||||
watch(
|
||||
() => playerStore.play,
|
||||
() => getPlayerStore().play,
|
||||
(newIsPlaying) => {
|
||||
console.log('播放状态变化:', newIsPlaying);
|
||||
|
||||
@@ -217,7 +248,7 @@ const initProgressAnimation = () => {
|
||||
// 监听当前歌词索引变化
|
||||
watch(nowIndex, () => {
|
||||
currentLrcProgress.value = 0;
|
||||
if (playerStore.play) {
|
||||
if (getPlayerStore().play) {
|
||||
startProgressAnimation();
|
||||
}
|
||||
});
|
||||
@@ -225,45 +256,45 @@ const initProgressAnimation = () => {
|
||||
// 监听音频对象变化
|
||||
watch(sound, (newSound) => {
|
||||
console.log('sound 对象变化:', !!newSound);
|
||||
if (newSound && playerStore.play) {
|
||||
if (newSound && getPlayerStore().play) {
|
||||
startProgressAnimation();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化进度动画
|
||||
initProgressAnimation();
|
||||
// 设置音乐相关的监听器
|
||||
const setupMusicWatchers = () => {
|
||||
const store = getPlayerStore();
|
||||
|
||||
// 移除对 playerStore.playMusicUrl 的监听,因为播放逻辑已经在 player.ts 中处理
|
||||
// 保留 watch 对 playerStore.playMusic 的监听以更新歌词数据
|
||||
// 监听 playerStore.playMusic 的变化以更新歌词数据
|
||||
watch(
|
||||
() => store.playMusic,
|
||||
() => {
|
||||
nextTick(async () => {
|
||||
console.log('歌曲切换,更新歌词数据');
|
||||
// 更新歌词数据
|
||||
lrcArray.value = playMusic.value.lyric?.lrcArray || [];
|
||||
lrcTimeArray.value = playMusic.value.lyric?.lrcTimeArray || [];
|
||||
|
||||
watch(
|
||||
() => playerStore.playMusic,
|
||||
() => {
|
||||
nextTick(async () => {
|
||||
console.log('歌曲切换,更新歌词数据');
|
||||
// 更新歌词数据
|
||||
lrcArray.value = playMusic.value.lyric?.lrcArray || [];
|
||||
lrcTimeArray.value = playMusic.value.lyric?.lrcTimeArray || [];
|
||||
|
||||
// 当歌词数据更新时,如果歌词窗口打开,则发送数据
|
||||
if (isElectron && isLyricWindowOpen.value) {
|
||||
console.log('歌词窗口已打开,同步最新歌词数据');
|
||||
// 不管歌词数组是否为空,都发送最新数据
|
||||
sendLyricToWin();
|
||||
|
||||
// 再次延迟发送,确保歌词窗口已完全加载
|
||||
setTimeout(() => {
|
||||
// 当歌词数据更新时,如果歌词窗口打开,则发送数据
|
||||
if (isElectron && isLyricWindowOpen.value) {
|
||||
console.log('歌词窗口已打开,同步最新歌词数据');
|
||||
// 不管歌词数组是否为空,都发送最新数据
|
||||
sendLyricToWin();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
// 再次延迟发送,确保歌词窗口已完全加载
|
||||
setTimeout(() => {
|
||||
sendLyricToWin();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const setupAudioListeners = () => {
|
||||
let interval: any = null;
|
||||
@@ -331,9 +362,9 @@ const setupAudioListeners = () => {
|
||||
|
||||
// 监听播放
|
||||
audioService.on('play', () => {
|
||||
playerStore.setPlayMusic(true);
|
||||
getPlayerStore().setPlayMusic(true);
|
||||
if (isElectron) {
|
||||
window.api.sendSong(cloneDeep(playerStore.playMusic));
|
||||
window.api.sendSong(cloneDeep(getPlayerStore().playMusic));
|
||||
}
|
||||
clearInterval();
|
||||
interval = window.setInterval(() => {
|
||||
@@ -383,7 +414,7 @@ const setupAudioListeners = () => {
|
||||
// 监听暂停
|
||||
audioService.on('pause', () => {
|
||||
console.log('音频暂停事件触发');
|
||||
playerStore.setPlayMusic(false);
|
||||
getPlayerStore().setPlayMusic(false);
|
||||
clearInterval();
|
||||
if (isElectron && isLyricWindowOpen.value) {
|
||||
sendLyricToWin();
|
||||
@@ -400,17 +431,17 @@ const setupAudioListeners = () => {
|
||||
}
|
||||
|
||||
// 重新播放当前歌曲
|
||||
if (playerStore.playMusicUrl && playMusic.value) {
|
||||
const newSound = await audioService.play(playerStore.playMusicUrl, playMusic.value);
|
||||
if (getPlayerStore().playMusicUrl && playMusic.value) {
|
||||
const newSound = await audioService.play(getPlayerStore().playMusicUrl, playMusic.value);
|
||||
sound.value = newSound as Howl;
|
||||
setupAudioListeners();
|
||||
} else {
|
||||
console.error('No music URL or playMusic data available');
|
||||
playerStore.nextPlay();
|
||||
getPlayerStore().nextPlay();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error replaying song:', error);
|
||||
playerStore.nextPlay();
|
||||
getPlayerStore().nextPlay();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -419,36 +450,36 @@ const setupAudioListeners = () => {
|
||||
console.log('音频播放结束事件触发');
|
||||
clearInterval();
|
||||
|
||||
if (playerStore.playMode === 1) {
|
||||
if (getPlayerStore().playMode === 1) {
|
||||
// 单曲循环模式
|
||||
if (sound.value) {
|
||||
replayMusic();
|
||||
}
|
||||
} else if (playerStore.playMode === 2) {
|
||||
} else if (getPlayerStore().playMode === 2) {
|
||||
// 随机播放模式
|
||||
|
||||
if (playerStore.playList.length <= 1) {
|
||||
if (getPlayerStore().playList.length <= 1) {
|
||||
replayMusic();
|
||||
} else {
|
||||
let randomIndex;
|
||||
do {
|
||||
randomIndex = Math.floor(Math.random() * playerStore.playList.length);
|
||||
} while (randomIndex === playerStore.playListIndex && playerStore.playList.length > 1);
|
||||
playerStore.playListIndex = randomIndex;
|
||||
playerStore.setPlay(playerStore.playList[randomIndex]);
|
||||
randomIndex = Math.floor(Math.random() * getPlayerStore().playList.length);
|
||||
} while (randomIndex === getPlayerStore().playListIndex && getPlayerStore().playList.length > 1);
|
||||
getPlayerStore().playListIndex = randomIndex;
|
||||
getPlayerStore().setPlay(getPlayerStore().playList[randomIndex]);
|
||||
}
|
||||
} else {
|
||||
// 列表循环模式
|
||||
playerStore.nextPlay();
|
||||
getPlayerStore().nextPlay();
|
||||
}
|
||||
});
|
||||
|
||||
audioService.on('previoustrack', () => {
|
||||
playerStore.prevPlay();
|
||||
getPlayerStore().prevPlay();
|
||||
});
|
||||
|
||||
audioService.on('nexttrack', () => {
|
||||
playerStore.nextPlay();
|
||||
getPlayerStore().nextPlay();
|
||||
});
|
||||
|
||||
return clearInterval;
|
||||
@@ -464,11 +495,11 @@ export const pause = () => {
|
||||
try {
|
||||
// 保存当前播放进度
|
||||
const currentTime = currentSound.seek() as number;
|
||||
if (playerStore.playMusic && playerStore.playMusic.id) {
|
||||
if (getPlayerStore().playMusic && getPlayerStore().playMusic.id) {
|
||||
localStorage.setItem(
|
||||
'playProgress',
|
||||
JSON.stringify({
|
||||
songId: playerStore.playMusic.id,
|
||||
songId: getPlayerStore().playMusic.id,
|
||||
progress: currentTime
|
||||
})
|
||||
);
|
||||
@@ -503,15 +534,18 @@ loadCorrectionMap();
|
||||
// 歌词矫正时间,当前歌曲
|
||||
export const correctionTime = ref(0);
|
||||
|
||||
// 切歌时自动读取矫正时间
|
||||
watch(
|
||||
() => playMusic.value?.id,
|
||||
(id) => {
|
||||
if (!id) return;
|
||||
correctionTime.value = correctionTimeMap.value[id] ?? 0;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
// 设置歌词矫正时间的监听器
|
||||
const setupCorrectionTimeWatcher = () => {
|
||||
// 切歌时自动读取矫正时间
|
||||
watch(
|
||||
() => playMusic.value?.id,
|
||||
(id) => {
|
||||
if (!id) return;
|
||||
correctionTime.value = correctionTimeMap.value[id] ?? 0;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 调整歌词矫正时间(每首歌独立)
|
||||
@@ -557,7 +591,7 @@ const currentLrcTiming = computed(() => {
|
||||
export const getLrcStyle = (index: number) => {
|
||||
const currentTime = nowTime.value + correctionTime.value;
|
||||
const start = lrcTimeArray.value[index];
|
||||
const end = lrcTimeArray.value[index + 1] ?? (start + 1);
|
||||
const end = lrcTimeArray.value[index + 1] ?? start + 1;
|
||||
|
||||
if (currentTime >= start && currentTime < end) {
|
||||
// 当前句,显示进度
|
||||
@@ -638,7 +672,7 @@ export const sendLyricToWin = () => {
|
||||
nowTime: nowTime.value,
|
||||
startCurrentTime: lrcTimeArray.value[nowIndex] || 0,
|
||||
nextTime: lrcTimeArray.value[nowIndex + 1] || 0,
|
||||
isPlay: playerStore.play,
|
||||
isPlay: getPlayerStore().play,
|
||||
lrcArray: lrcArray.value,
|
||||
lrcTimeArray: lrcTimeArray.value,
|
||||
allTime: allTime.value,
|
||||
@@ -657,7 +691,7 @@ export const sendLyricToWin = () => {
|
||||
nowTime: nowTime.value,
|
||||
startCurrentTime: 0,
|
||||
nextTime: 0,
|
||||
isPlay: playerStore.play,
|
||||
isPlay: getPlayerStore().play,
|
||||
lrcArray: [{ text: '当前歌曲暂无歌词', trText: '' }],
|
||||
lrcTimeArray: [0],
|
||||
allTime: allTime.value,
|
||||
@@ -682,14 +716,14 @@ const startLyricSync = () => {
|
||||
|
||||
// 每秒同步一次歌词数据
|
||||
lyricSyncInterval = setInterval(() => {
|
||||
if (isElectron && isLyricWindowOpen.value && playerStore.play && playMusic.value?.id) {
|
||||
if (isElectron && isLyricWindowOpen.value && getPlayerStore().play && playMusic.value?.id) {
|
||||
// 发送当前播放进度的更新
|
||||
try {
|
||||
const updateData = {
|
||||
type: 'update',
|
||||
nowIndex: getLrcIndex(nowTime.value),
|
||||
nowTime: nowTime.value,
|
||||
isPlay: playerStore.play
|
||||
isPlay: getPlayerStore().play
|
||||
};
|
||||
window.api.sendLyric(JSON.stringify(updateData));
|
||||
} catch (error) {
|
||||
@@ -735,7 +769,7 @@ export const openLyric = () => {
|
||||
nowTime: nowTime.value,
|
||||
startCurrentTime: 0,
|
||||
nextTime: 0,
|
||||
isPlay: playerStore.play,
|
||||
isPlay: getPlayerStore().play,
|
||||
lrcArray: [{ text: '加载歌词中...', trText: '' }],
|
||||
lrcTimeArray: [0],
|
||||
allTime: allTime.value,
|
||||
@@ -771,25 +805,28 @@ export const closeLyric = () => {
|
||||
stopLyricSync();
|
||||
};
|
||||
|
||||
// 在组件挂载时设置对播放状态的监听
|
||||
watch(
|
||||
() => playerStore.play,
|
||||
(isPlaying) => {
|
||||
// 如果歌词窗口打开,根据播放状态控制同步
|
||||
if (isElectron && isLyricWindowOpen.value) {
|
||||
if (isPlaying) {
|
||||
startLyricSync();
|
||||
} else {
|
||||
// 如果暂停播放,发送一次暂停状态的更新
|
||||
const pauseData = {
|
||||
type: 'update',
|
||||
isPlay: false
|
||||
};
|
||||
window.api.sendLyric(JSON.stringify(pauseData));
|
||||
// 设置播放状态监听器
|
||||
const setupPlayStateWatcher = () => {
|
||||
// 在组件挂载时设置对播放状态的监听
|
||||
watch(
|
||||
() => getPlayerStore().play,
|
||||
(isPlaying) => {
|
||||
// 如果歌词窗口打开,根据播放状态控制同步
|
||||
if (isElectron && isLyricWindowOpen.value) {
|
||||
if (isPlaying) {
|
||||
startLyricSync();
|
||||
} else {
|
||||
// 如果暂停播放,发送一次暂停状态的更新
|
||||
const pauseData = {
|
||||
type: 'update',
|
||||
isPlay: false
|
||||
};
|
||||
window.api.sendLyric(JSON.stringify(pauseData));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
// 在组件卸载时清理资源
|
||||
onUnmounted(() => {
|
||||
@@ -801,20 +838,20 @@ if (isElectron) {
|
||||
windowData.electron.ipcRenderer.on('lyric-control-back', (_, command: string) => {
|
||||
switch (command) {
|
||||
case 'playpause':
|
||||
if (playerStore.play) {
|
||||
playerStore.setPlayMusic(false);
|
||||
if (getPlayerStore().play) {
|
||||
getPlayerStore().setPlayMusic(false);
|
||||
audioService.getCurrentSound()?.pause();
|
||||
} else {
|
||||
playerStore.setPlayMusic(true);
|
||||
getPlayerStore().setPlayMusic(true);
|
||||
|
||||
audioService.getCurrentSound()?.play();
|
||||
}
|
||||
break;
|
||||
case 'prev':
|
||||
playerStore.prevPlay();
|
||||
getPlayerStore().prevPlay();
|
||||
break;
|
||||
case 'next':
|
||||
playerStore.nextPlay();
|
||||
getPlayerStore().nextPlay();
|
||||
break;
|
||||
case 'close':
|
||||
isLyricWindowOpen.value = false; // 确保状态更新
|
||||
@@ -830,7 +867,7 @@ if (isElectron) {
|
||||
export const initAudioListeners = async () => {
|
||||
try {
|
||||
// 确保有正在播放的音乐
|
||||
if (!playerStore.playMusic || !playerStore.playMusic.id) {
|
||||
if (!getPlayerStore().playMusic || !getPlayerStore().playMusic.id) {
|
||||
console.log('没有正在播放的音乐,跳过音频监听器初始化');
|
||||
return;
|
||||
}
|
||||
@@ -905,7 +942,7 @@ audioService.on('url_expired', async (expiredTrack) => {
|
||||
|
||||
// 更新存储
|
||||
(expiredTrack as any).playMusicUrl = newUrl;
|
||||
playerStore.playMusicUrl = newUrl;
|
||||
getPlayerStore().playMusicUrl = newUrl;
|
||||
|
||||
// 重新播放并设置进度
|
||||
const newSound = await audioService.play(newUrl, expiredTrack);
|
||||
@@ -919,9 +956,9 @@ audioService.on('url_expired', async (expiredTrack) => {
|
||||
}
|
||||
|
||||
// 如果之前是播放状态,继续播放
|
||||
if (playerStore.play) {
|
||||
if (getPlayerStore().play) {
|
||||
newSound.play();
|
||||
playerStore.setIsPlay(true);
|
||||
getPlayerStore().setIsPlay(true);
|
||||
}
|
||||
|
||||
message.success('已自动恢复播放');
|
||||
@@ -933,7 +970,6 @@ audioService.on('url_expired', async (expiredTrack) => {
|
||||
// 处理网易云音乐,重新获取URL
|
||||
console.log('重新获取网易云音乐URL');
|
||||
try {
|
||||
|
||||
const newUrl = await getSongUrl(expiredTrack.id, expiredTrack as any);
|
||||
|
||||
if (newUrl) {
|
||||
@@ -941,7 +977,7 @@ audioService.on('url_expired', async (expiredTrack) => {
|
||||
|
||||
// 更新存储
|
||||
(expiredTrack as any).playMusicUrl = newUrl;
|
||||
playerStore.playMusicUrl = newUrl;
|
||||
getPlayerStore().playMusicUrl = newUrl;
|
||||
|
||||
// 重新播放并设置进度
|
||||
const newSound = await audioService.play(newUrl, expiredTrack);
|
||||
@@ -955,9 +991,9 @@ audioService.on('url_expired', async (expiredTrack) => {
|
||||
}
|
||||
|
||||
// 如果之前是播放状态,继续播放
|
||||
if (playerStore.play) {
|
||||
if (getPlayerStore().play) {
|
||||
newSound.play();
|
||||
playerStore.setIsPlay(true);
|
||||
getPlayerStore().setIsPlay(true);
|
||||
}
|
||||
|
||||
message.success('已自动恢复播放');
|
||||
@@ -982,16 +1018,16 @@ window.addEventListener('audio-ready', ((event: CustomEvent) => {
|
||||
if (newSound) {
|
||||
// 更新本地 sound 引用
|
||||
sound.value = newSound as Howl;
|
||||
|
||||
|
||||
// 设置音频监听器
|
||||
setupAudioListeners();
|
||||
|
||||
|
||||
// 获取当前播放位置并更新显示
|
||||
const currentPosition = newSound.seek() as number;
|
||||
if (typeof currentPosition === 'number' && !Number.isNaN(currentPosition)) {
|
||||
nowTime.value = currentPosition;
|
||||
}
|
||||
|
||||
|
||||
console.log('音频就绪,已设置监听器并更新进度显示');
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
import { getSongUrl } from '@/store/modules/player';
|
||||
import type { SongResult } from '@/type/music';
|
||||
@@ -13,23 +13,23 @@ const ipcRenderer = isElectron ? window.electron.ipcRenderer : null;
|
||||
const createDownloadManager = () => {
|
||||
// 正在下载的文件集合
|
||||
const activeDownloads = new Set<string>();
|
||||
|
||||
|
||||
// 已经发送了通知的文件集合(避免重复通知)
|
||||
const notifiedDownloads = new Set<string>();
|
||||
|
||||
|
||||
// 事件监听器是否已初始化
|
||||
let isInitialized = false;
|
||||
|
||||
|
||||
// 监听器引用(用于清理)
|
||||
let completeListener: ((event: any, data: any) => void) | null = null;
|
||||
let errorListener: ((event: any, data: any) => void) | null = null;
|
||||
|
||||
|
||||
return {
|
||||
// 添加下载
|
||||
addDownload: (filename: string) => {
|
||||
activeDownloads.add(filename);
|
||||
},
|
||||
|
||||
|
||||
// 移除下载
|
||||
removeDownload: (filename: string) => {
|
||||
activeDownloads.delete(filename);
|
||||
@@ -38,98 +38,100 @@ const createDownloadManager = () => {
|
||||
notifiedDownloads.delete(filename);
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
|
||||
// 标记文件已通知
|
||||
markNotified: (filename: string) => {
|
||||
notifiedDownloads.add(filename);
|
||||
},
|
||||
|
||||
|
||||
// 检查文件是否已通知
|
||||
isNotified: (filename: string) => {
|
||||
return notifiedDownloads.has(filename);
|
||||
},
|
||||
|
||||
|
||||
// 清理所有下载
|
||||
clearDownloads: () => {
|
||||
activeDownloads.clear();
|
||||
notifiedDownloads.clear();
|
||||
},
|
||||
|
||||
|
||||
// 初始化事件监听器
|
||||
initEventListeners: (message: any, t: any) => {
|
||||
if (isInitialized) return;
|
||||
|
||||
|
||||
// 移除可能存在的旧监听器
|
||||
if (completeListener) {
|
||||
ipcRenderer?.removeListener('music-download-complete', completeListener);
|
||||
}
|
||||
|
||||
|
||||
if (errorListener) {
|
||||
ipcRenderer?.removeListener('music-download-error', errorListener);
|
||||
}
|
||||
|
||||
|
||||
// 创建新的监听器
|
||||
completeListener = (_event, data) => {
|
||||
if (!data.filename || !activeDownloads.has(data.filename)) return;
|
||||
|
||||
|
||||
// 如果该文件已经通知过,则跳过
|
||||
if (notifiedDownloads.has(data.filename)) return;
|
||||
|
||||
|
||||
// 标记为已通知
|
||||
notifiedDownloads.add(data.filename);
|
||||
|
||||
|
||||
// 从活动下载移除
|
||||
activeDownloads.delete(data.filename);
|
||||
};
|
||||
|
||||
|
||||
errorListener = (_event, data) => {
|
||||
if (!data.filename || !activeDownloads.has(data.filename)) return;
|
||||
|
||||
|
||||
// 如果该文件已经通知过,则跳过
|
||||
if (notifiedDownloads.has(data.filename)) return;
|
||||
|
||||
|
||||
// 标记为已通知
|
||||
notifiedDownloads.add(data.filename);
|
||||
|
||||
|
||||
// 显示失败通知
|
||||
message.error(t('songItem.message.downloadFailed', {
|
||||
filename: data.filename,
|
||||
error: data.error || '未知错误'
|
||||
}));
|
||||
|
||||
message.error(
|
||||
t('songItem.message.downloadFailed', {
|
||||
filename: data.filename,
|
||||
error: data.error || '未知错误'
|
||||
})
|
||||
);
|
||||
|
||||
// 从活动下载移除
|
||||
activeDownloads.delete(data.filename);
|
||||
};
|
||||
|
||||
|
||||
// 添加监听器
|
||||
ipcRenderer?.on('music-download-complete', completeListener);
|
||||
ipcRenderer?.on('music-download-error', errorListener);
|
||||
|
||||
|
||||
isInitialized = true;
|
||||
},
|
||||
|
||||
|
||||
// 清理事件监听器
|
||||
cleanupEventListeners: () => {
|
||||
if (!isInitialized) return;
|
||||
|
||||
|
||||
if (completeListener) {
|
||||
ipcRenderer?.removeListener('music-download-complete', completeListener);
|
||||
completeListener = null;
|
||||
}
|
||||
|
||||
|
||||
if (errorListener) {
|
||||
ipcRenderer?.removeListener('music-download-error', errorListener);
|
||||
errorListener = null;
|
||||
}
|
||||
|
||||
|
||||
isInitialized = false;
|
||||
},
|
||||
|
||||
|
||||
// 获取活跃下载数量
|
||||
getActiveDownloadCount: () => {
|
||||
return activeDownloads.size;
|
||||
},
|
||||
|
||||
|
||||
// 检查是否有特定文件正在下载
|
||||
hasDownload: (filename: string) => {
|
||||
return activeDownloads.has(filename);
|
||||
@@ -170,7 +172,7 @@ export const useDownload = () => {
|
||||
// 构建文件名
|
||||
const artistNames = (song.ar || song.song?.artists)?.map((a) => a.name).join(',');
|
||||
const filename = `${song.name} - ${artistNames}`;
|
||||
|
||||
|
||||
// 检查是否已在下载
|
||||
if (downloadManager.hasDownload(filename)) {
|
||||
isDownloading.value = false;
|
||||
@@ -182,7 +184,7 @@ export const useDownload = () => {
|
||||
|
||||
const songData = cloneDeep(song);
|
||||
songData.ar = songData.ar || songData.song?.artists;
|
||||
|
||||
|
||||
// 发送下载请求
|
||||
ipcRenderer?.send('download-music', {
|
||||
url: typeof musicUrl === 'string' ? musicUrl : musicUrl.url,
|
||||
@@ -195,7 +197,7 @@ export const useDownload = () => {
|
||||
});
|
||||
|
||||
message.success(t('songItem.message.downloadQueued'));
|
||||
|
||||
|
||||
// 简化的监听逻辑,基本通知由全局监听器处理
|
||||
setTimeout(() => {
|
||||
isDownloading.value = false;
|
||||
@@ -230,7 +232,7 @@ export const useDownload = () => {
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
const totalCount = songs.length;
|
||||
|
||||
|
||||
// 下载进度追踪
|
||||
const trackProgress = () => {
|
||||
if (successCount + failCount === totalCount) {
|
||||
@@ -260,36 +262,36 @@ export const useDownload = () => {
|
||||
trackProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const songData = cloneDeep(song);
|
||||
const filename = `${song.name} - ${(song.ar || song.song?.artists)?.map((a) => a.name).join(',')}`;
|
||||
|
||||
|
||||
// 检查是否已在下载
|
||||
if (downloadManager.hasDownload(filename)) {
|
||||
failCount++;
|
||||
trackProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 添加到活动下载集合
|
||||
downloadManager.addDownload(filename);
|
||||
|
||||
|
||||
const songInfo = {
|
||||
...songData,
|
||||
ar: songData.ar || songData.song?.artists,
|
||||
downloadTime: Date.now()
|
||||
};
|
||||
|
||||
|
||||
ipcRenderer?.send('download-music', {
|
||||
url,
|
||||
filename,
|
||||
songInfo,
|
||||
type
|
||||
});
|
||||
|
||||
|
||||
successCount++;
|
||||
});
|
||||
|
||||
|
||||
// 所有下载开始后,检查进度
|
||||
trackProgress();
|
||||
} catch (error) {
|
||||
@@ -305,4 +307,4 @@ export const useDownload = () => {
|
||||
downloadMusic,
|
||||
batchDownloadMusic
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { usePlayerStore } from '@/store';
|
||||
import type { SongResult } from '@/type/music';
|
||||
import { computed, ref } from 'vue';
|
||||
import { getImgUrl } from '@/utils';
|
||||
import { getImageBackground } from '@/utils/linearColor';
|
||||
import { useMessage, useDialog } from 'naive-ui';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useDownload } from './useDownload';
|
||||
import { useArtist } from './useArtist';
|
||||
|
||||
export function useSongItem(props: {
|
||||
item: SongResult;
|
||||
canRemove?: boolean;
|
||||
}) {
|
||||
import { useArtist } from './useArtist';
|
||||
import { useDownload } from './useDownload';
|
||||
|
||||
export function useSongItem(props: { item: SongResult; canRemove?: boolean }) {
|
||||
const { t } = useI18n();
|
||||
const playerStore = usePlayerStore();
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const { downloadMusic } = useDownload();
|
||||
const { navigateToArtist } = useArtist();
|
||||
|
||||
|
||||
// 状态变量
|
||||
const showDropdown = ref(false);
|
||||
const dropdownX = ref(0);
|
||||
const dropdownY = ref(0);
|
||||
const isHovering = ref(false);
|
||||
|
||||
|
||||
// 计算属性
|
||||
const play = computed(() => playerStore.isPlay);
|
||||
const playMusic = computed(() => playerStore.playMusic);
|
||||
@@ -32,18 +31,20 @@ export function useSongItem(props: {
|
||||
() => playMusic.value.id === props.item.id && playMusic.value.playLoading
|
||||
);
|
||||
const isPlaying = computed(() => playMusic.value.id === props.item.id);
|
||||
|
||||
|
||||
// 收藏与不喜欢状态
|
||||
const isFavorite = computed(() => {
|
||||
const numericId = typeof props.item.id === 'string' ? parseInt(props.item.id, 10) : props.item.id;
|
||||
const numericId =
|
||||
typeof props.item.id === 'string' ? parseInt(props.item.id, 10) : props.item.id;
|
||||
return playerStore.favoriteList.includes(numericId);
|
||||
});
|
||||
|
||||
|
||||
const isDislike = computed(() => {
|
||||
const numericId = typeof props.item.id === 'string' ? parseInt(props.item.id, 10) : props.item.id;
|
||||
const numericId =
|
||||
typeof props.item.id === 'string' ? parseInt(props.item.id, 10) : props.item.id;
|
||||
return playerStore.dislikeList.includes(numericId);
|
||||
});
|
||||
|
||||
|
||||
// 获取艺术家列表
|
||||
const artists = computed(() => {
|
||||
return (props.item.ar || props.item.song?.artists)?.slice(0, 4) || [];
|
||||
@@ -52,12 +53,12 @@ export function useSongItem(props: {
|
||||
// 处理图片加载
|
||||
const handleImageLoad = async (imageElement: HTMLImageElement) => {
|
||||
if (!imageElement) return;
|
||||
|
||||
|
||||
const { backgroundColor, primaryColor } = await getImageBackground(imageElement);
|
||||
props.item.backgroundColor = backgroundColor;
|
||||
props.item.primaryColor = primaryColor;
|
||||
};
|
||||
|
||||
|
||||
// 播放音乐
|
||||
const playMusicEvent = async (item: SongResult) => {
|
||||
try {
|
||||
@@ -71,11 +72,12 @@ export function useSongItem(props: {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 切换收藏状态
|
||||
const toggleFavorite = async (e: Event) => {
|
||||
e && e.stopPropagation();
|
||||
const numericId = typeof props.item.id === 'string' ? parseInt(props.item.id, 10) : props.item.id;
|
||||
const numericId =
|
||||
typeof props.item.id === 'string' ? parseInt(props.item.id, 10) : props.item.id;
|
||||
|
||||
if (isFavorite.value) {
|
||||
playerStore.removeFromFavorite(numericId);
|
||||
@@ -83,7 +85,7 @@ export function useSongItem(props: {
|
||||
playerStore.addToFavorite(numericId);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 切换不喜欢状态
|
||||
const toggleDislike = async (e: Event) => {
|
||||
e && e.stopPropagation();
|
||||
@@ -91,7 +93,7 @@ export function useSongItem(props: {
|
||||
playerStore.removeFromDislikeList(props.item.id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
dialog.warning({
|
||||
title: t('songItem.dialog.dislike.title'),
|
||||
content: t('songItem.dialog.dislike.content'),
|
||||
@@ -102,20 +104,20 @@ export function useSongItem(props: {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 添加到下一首播放
|
||||
const handlePlayNext = () => {
|
||||
playerStore.addToNextPlay(props.item);
|
||||
message.success(t('songItem.message.addedToNextPlay'));
|
||||
};
|
||||
|
||||
|
||||
// 获取歌曲时长
|
||||
const getDuration = (item: SongResult): number => {
|
||||
if (item.duration) return item.duration;
|
||||
if (typeof item.dt === 'number') return item.dt;
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
||||
// 格式化时长
|
||||
const formatDuration = (ms: number): string => {
|
||||
if (!ms) return '--:--';
|
||||
@@ -124,7 +126,7 @@ export function useSongItem(props: {
|
||||
const seconds = totalSeconds % 60;
|
||||
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
|
||||
// 处理右键菜单
|
||||
const handleContextMenu = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -132,7 +134,7 @@ export function useSongItem(props: {
|
||||
dropdownX.value = e.clientX;
|
||||
dropdownY.value = e.clientY;
|
||||
};
|
||||
|
||||
|
||||
// 处理菜单点击
|
||||
const handleMenuClick = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -140,17 +142,17 @@ export function useSongItem(props: {
|
||||
dropdownX.value = e.clientX;
|
||||
dropdownY.value = e.clientY;
|
||||
};
|
||||
|
||||
|
||||
// 处理艺术家点击
|
||||
const handleArtistClick = (id: number) => {
|
||||
navigateToArtist(id);
|
||||
};
|
||||
|
||||
|
||||
// 鼠标悬停处理
|
||||
const handleMouseEnter = () => {
|
||||
isHovering.value = true;
|
||||
};
|
||||
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
isHovering.value = false;
|
||||
};
|
||||
@@ -165,7 +167,7 @@ export function useSongItem(props: {
|
||||
isDislike,
|
||||
artists,
|
||||
showDropdown,
|
||||
dropdownX,
|
||||
dropdownX,
|
||||
dropdownY,
|
||||
isHovering,
|
||||
playerStore,
|
||||
@@ -185,4 +187,4 @@ export function useSongItem(props: {
|
||||
handleMouseLeave,
|
||||
downloadMusic
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ export function useZoom() {
|
||||
const MIN_ZOOM = 0.5;
|
||||
const MAX_ZOOM = 1.5;
|
||||
const ZOOM_STEP = 0.05; // 5%的步长
|
||||
|
||||
|
||||
// 当前缩放因子
|
||||
const zoomFactor = ref(1);
|
||||
|
||||
|
||||
// 初始化获取当前缩放比例
|
||||
const initZoomFactor = async () => {
|
||||
try {
|
||||
@@ -22,35 +22,35 @@ export function useZoom() {
|
||||
console.error('获取缩放比例失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 增加缩放比例,保证100%为节点
|
||||
const increaseZoom = () => {
|
||||
let newZoom;
|
||||
|
||||
|
||||
// 如果当前缩放低于100%并且增加后会超过100%,则直接设为100%
|
||||
if (zoomFactor.value < 1.0 && zoomFactor.value + ZOOM_STEP > 1.0) {
|
||||
newZoom = 1.0; // 精确设置为100%
|
||||
} else {
|
||||
newZoom = Math.min(MAX_ZOOM, Math.round((zoomFactor.value + ZOOM_STEP) * 20) / 20);
|
||||
}
|
||||
|
||||
|
||||
setZoomFactor(newZoom);
|
||||
};
|
||||
|
||||
|
||||
// 减少缩放比例,保证100%为节点
|
||||
const decreaseZoom = () => {
|
||||
let newZoom;
|
||||
|
||||
|
||||
// 如果当前缩放大于100%并且减少后会低于100%,则直接设为100%
|
||||
if (zoomFactor.value > 1.0 && zoomFactor.value - ZOOM_STEP < 1.0) {
|
||||
newZoom = 1.0; // 精确设置为100%
|
||||
} else {
|
||||
newZoom = Math.max(MIN_ZOOM, Math.round((zoomFactor.value - ZOOM_STEP) * 20) / 20);
|
||||
}
|
||||
|
||||
|
||||
setZoomFactor(newZoom);
|
||||
};
|
||||
|
||||
|
||||
// 重置缩放比例到系统建议值
|
||||
const resetZoom = async () => {
|
||||
try {
|
||||
@@ -59,18 +59,18 @@ export function useZoom() {
|
||||
console.error('重置缩放比例失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 设置为100%标准缩放
|
||||
const setZoom100 = () => {
|
||||
setZoomFactor(1.0);
|
||||
};
|
||||
|
||||
|
||||
// 设置缩放比例
|
||||
const setZoomFactor = (zoom: number) => {
|
||||
window.ipcRenderer.send('set-content-zoom', zoom);
|
||||
zoomFactor.value = zoom;
|
||||
};
|
||||
|
||||
|
||||
// 检查是否为100%缩放
|
||||
const isZoom100 = () => {
|
||||
return Math.abs(zoomFactor.value - 1.0) < 0.001;
|
||||
@@ -89,4 +89,4 @@ export function useZoom() {
|
||||
MAX_ZOOM,
|
||||
ZOOM_STEP
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user