mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-24 16:27:23 +08:00
✨ feat: 优化歌词页面样式 添加歌词进度显示 优化歌曲及列表加载方式 大幅提升歌曲歌词播放速度
This commit is contained in:
@@ -42,6 +42,9 @@
|
|||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"no-continue": "off",
|
"no-continue": "off",
|
||||||
"no-restricted-syntax": "off",
|
"no-restricted-syntax": "off",
|
||||||
|
"no-return-assign": "off",
|
||||||
|
"no-unused-expressions": "off",
|
||||||
|
"no-return-await": "off",
|
||||||
"no-plusplus": "off",
|
"no-plusplus": "off",
|
||||||
"no-param-reassign": "off",
|
"no-param-reassign": "off",
|
||||||
"no-shadow": "off",
|
"no-shadow": "off",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function createWindow() {
|
|||||||
win.setMinimumSize(1200, 780);
|
win.setMinimumSize(1200, 780);
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
win.webContents.openDevTools({ mode: 'detach' });
|
win.webContents.openDevTools({ mode: 'detach' });
|
||||||
win.loadURL('http://localhost:7788/');
|
win.loadURL('http://localhost:4488/');
|
||||||
} else {
|
} else {
|
||||||
win.loadURL(`file://${__dirname}/dist/index.html`);
|
win.loadURL(`file://${__dirname}/dist/index.html`);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "alger-music",
|
"name": "alger-music",
|
||||||
"version": "1.7.0",
|
"version": "2.0.0",
|
||||||
"description": "这是一个用于音乐播放的应用程序。",
|
"description": "这是一个用于音乐播放的应用程序。",
|
||||||
"author": "Alger <algerkc@qq.com>",
|
"author": "Alger <algerkc@qq.com>",
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
body{
|
body{
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-popover:has(.music-play){
|
||||||
|
border-radius: 1.5rem !important;
|
||||||
}
|
}
|
||||||
+1
-1
@@ -43,7 +43,7 @@ export const getRecommendMusic = (params: IRecommendMusicParams) => {
|
|||||||
|
|
||||||
// 获取每日推荐
|
// 获取每日推荐
|
||||||
export const getDayRecommend = () => {
|
export const getDayRecommend = () => {
|
||||||
return request.get<IData<IDayRecommend>>('/recommend/songs');
|
return request.get<IData<IData<IDayRecommend>>>('/recommend/songs');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取最新专辑推荐
|
// 获取最新专辑推荐
|
||||||
|
|||||||
@@ -74,32 +74,32 @@ const store = useStore();
|
|||||||
const hotSingerData = ref<IHotSinger>();
|
const hotSingerData = ref<IHotSinger>();
|
||||||
const dayRecommendData = ref<IDayRecommend>();
|
const dayRecommendData = ref<IDayRecommend>();
|
||||||
const showMusic = ref(false);
|
const showMusic = ref(false);
|
||||||
// // 加载推荐歌手
|
|
||||||
// const loadSingerList = async () => {
|
|
||||||
// const { data } = await getHotSinger({ offset: 0, limit: 5 });
|
|
||||||
// hotSingerData.value = data;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const loadDayRecommend = async () => {
|
|
||||||
// const { data } = await getDayRecommend();
|
|
||||||
// dayRecommendData.value = data.data;
|
|
||||||
// };
|
|
||||||
// 页面初始化
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadData();
|
await loadData();
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
const [{ data: singerData }, { data: dayRecommend }] = await Promise.all([
|
// 第一个请求:获取热门歌手
|
||||||
getHotSinger({ offset: 0, limit: 5 }),
|
const { data: singerData } = await getHotSinger({ offset: 0, limit: 5 });
|
||||||
getDayRecommend(),
|
|
||||||
]);
|
// 第二个请求:获取每日推荐
|
||||||
if (dayRecommend.data) {
|
try {
|
||||||
singerData.artists = singerData.artists.slice(0, 4);
|
const {
|
||||||
|
data: { data: dayRecommend },
|
||||||
|
} = await getDayRecommend();
|
||||||
|
console.log('dayRecommend', dayRecommend);
|
||||||
|
// 处理数据
|
||||||
|
if (dayRecommend) {
|
||||||
|
singerData.artists = singerData.artists.slice(0, 4);
|
||||||
|
}
|
||||||
|
dayRecommendData.value = dayRecommend;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('error', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
hotSingerData.value = singerData;
|
hotSingerData.value = singerData;
|
||||||
dayRecommendData.value = dayRecommend.data;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('error', error);
|
console.error('error', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,9 +78,11 @@ const imageLoad = async () => {
|
|||||||
if (!songImageRef.value) {
|
if (!songImageRef.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const background = await getImageBackground((songImageRef.value as any).imageRef as unknown as HTMLImageElement);
|
const { backgroundColor } = await getImageBackground(
|
||||||
|
(songImageRef.value as any).imageRef as unknown as HTMLImageElement,
|
||||||
|
);
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
// eslint-disable-next-line vue/no-mutating-props
|
||||||
props.item.backgroundColor = background;
|
props.item.backgroundColor = backgroundColor;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 播放音乐 设置音乐详情 打开音乐底栏
|
// 播放音乐 设置音乐详情 打开音乐底栏
|
||||||
|
|||||||
+135
-116
@@ -1,156 +1,177 @@
|
|||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { getMusicLrc } from '@/api/music';
|
import { getMusicLrc } from '@/api/music';
|
||||||
|
import store from '@/store';
|
||||||
import { ILyric } from '@/type/lyric';
|
import { ILyric } from '@/type/lyric';
|
||||||
|
import type { ILyricText, SongResult } from '@/type/music';
|
||||||
|
|
||||||
const windowData = window as any;
|
const windowData = window as any;
|
||||||
|
|
||||||
export const isElectron = computed(() => {
|
export const isElectron = computed(() => !!windowData.electronAPI);
|
||||||
return !!windowData.electronAPI;
|
|
||||||
});
|
|
||||||
|
|
||||||
interface ILrcData {
|
export const lrcArray = ref<ILyricText[]>([]); // 歌词数组
|
||||||
text: string;
|
export const lrcTimeArray = ref<number[]>([]); // 歌词时间数组
|
||||||
trText: string;
|
export const nowTime = ref(0); // 当前播放时间
|
||||||
}
|
export const allTime = ref(0); // 总播放时间
|
||||||
|
export const nowIndex = ref(0); // 当前播放歌词
|
||||||
|
export const correctionTime = ref(0.4); // 歌词矫正时间Correction time
|
||||||
|
export const currentLrcProgress = ref(0); // 来存储当前歌词的进度
|
||||||
|
export const audio = ref<HTMLAudioElement>(); // 音频对象
|
||||||
|
export const playMusic = computed(() => store.state.playMusic as SongResult); // 当前播放歌曲
|
||||||
|
|
||||||
export const lrcData = ref<ILyric>();
|
watch(
|
||||||
export const newLrcIndex = ref<number>(0);
|
() => store.state.playMusic,
|
||||||
export const lrcArray = ref<Array<ILrcData>>([]);
|
() => {
|
||||||
export const lrcTimeArray = ref<Array<Number>>([]);
|
nextTick(() => {
|
||||||
|
lrcArray.value = playMusic.value.lyric?.lrcArray || [];
|
||||||
export const parseTime = (timeString: string) => {
|
lrcTimeArray.value = playMusic.value.lyric?.lrcTimeArray || [];
|
||||||
const [minutes, seconds] = timeString.split(':');
|
});
|
||||||
return Number(minutes) * 60 + Number(seconds);
|
},
|
||||||
};
|
{
|
||||||
|
deep: true,
|
||||||
const TIME_REGEX = /(\d{2}:\d{2}(\.\d*)?)/g;
|
},
|
||||||
const LRC_REGEX = /(\[(\d{2}):(\d{2})(\.(\d*))?\])/g;
|
);
|
||||||
|
const isPlaying = computed(() => store.state.play as boolean);
|
||||||
function parseLyricLine(lyricLine: string) {
|
|
||||||
const timeText = lyricLine.match(TIME_REGEX)?.[0] || '';
|
|
||||||
const time = parseTime(timeText);
|
|
||||||
const text = lyricLine.replace(LRC_REGEX, '').trim();
|
|
||||||
return { time, text };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ILyricText {
|
|
||||||
text: string;
|
|
||||||
trText: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseLyrics(lyricsString: string) {
|
|
||||||
const lines = lyricsString.split('\n');
|
|
||||||
const lyrics: Array<ILyricText> = [];
|
|
||||||
const times: number[] = [];
|
|
||||||
lines.forEach((line) => {
|
|
||||||
const { time, text } = parseLyricLine(line);
|
|
||||||
times.push(time);
|
|
||||||
lyrics.push({ text, trText: '' });
|
|
||||||
});
|
|
||||||
return { lyrics, times };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const loadLrc = async (playMusicId: number): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const { data } = await getMusicLrc(playMusicId);
|
|
||||||
const { lyrics, times } = parseLyrics(data.lrc.lyric);
|
|
||||||
let tlyric: {
|
|
||||||
[key: string]: string;
|
|
||||||
} = {};
|
|
||||||
if (data.tlyric.lyric) {
|
|
||||||
const { lyrics: tLyrics, times: tTimes } = parseLyrics(data.tlyric.lyric);
|
|
||||||
tlyric = tLyrics.reduce((acc: any, cur, index) => {
|
|
||||||
acc[tTimes[index]] = cur.text;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
if (Object.keys(tlyric).length) {
|
|
||||||
lyrics.forEach((item, index) => {
|
|
||||||
item.trText = item.text ? tlyric[times[index].toString()] : '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
lrcTimeArray.value = times;
|
|
||||||
lrcArray.value = lyrics;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('err', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 歌词矫正时间Correction time
|
|
||||||
const correctionTime = ref(0.4);
|
|
||||||
|
|
||||||
// 增加矫正时间
|
// 增加矫正时间
|
||||||
export const addCorrectionTime = (time: number) => {
|
export const addCorrectionTime = (time: number) => (correctionTime.value += time);
|
||||||
correctionTime.value += time;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 减少矫正时间
|
// 减少矫正时间
|
||||||
export const reduceCorrectionTime = (time: number) => {
|
export const reduceCorrectionTime = (time: number) => (correctionTime.value -= time);
|
||||||
correctionTime.value -= time;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isCurrentLrc = (index: number, time: number) => {
|
// 获取当前播放歌词
|
||||||
const currentTime = Number(lrcTimeArray.value[index]);
|
export const isCurrentLrc = (index: number, time: number): boolean => {
|
||||||
const nextTime = Number(lrcTimeArray.value[index + 1]);
|
const currentTime = lrcTimeArray.value[index];
|
||||||
|
const nextTime = lrcTimeArray.value[index + 1];
|
||||||
const nowTime = time + correctionTime.value;
|
const nowTime = time + correctionTime.value;
|
||||||
const isTrue = nowTime > currentTime && nowTime < nextTime;
|
const isTrue = nowTime > currentTime && nowTime < nextTime;
|
||||||
if (isTrue) {
|
|
||||||
newLrcIndex.value = index;
|
|
||||||
}
|
|
||||||
return isTrue;
|
return isTrue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const nowTime = ref(0);
|
// 获取当前播放歌词INDEX
|
||||||
export const allTime = ref(0);
|
export const getLrcIndex = (time: number): number => {
|
||||||
export const nowIndex = ref(0);
|
|
||||||
|
|
||||||
export const getLrcIndex = (time: number) => {
|
|
||||||
for (let i = 0; i < lrcTimeArray.value.length; i++) {
|
for (let i = 0; i < lrcTimeArray.value.length; i++) {
|
||||||
if (isCurrentLrc(i, time)) {
|
if (isCurrentLrc(i, time)) {
|
||||||
nowIndex.value = i || nowIndex.value;
|
nowIndex.value = i;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nowIndex.value;
|
return nowIndex.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置当前播放时间
|
// 获取当前播放歌词进度
|
||||||
export const setAudioTime = (index: number, audio: HTMLAudioElement) => {
|
const currentLrcTiming = computed(() => {
|
||||||
audio.currentTime = lrcTimeArray.value[index] as number;
|
const start = lrcTimeArray.value[nowIndex.value] || 0;
|
||||||
audio.play();
|
const end = lrcTimeArray.value[nowIndex.value + 1] || start + 1;
|
||||||
|
return { start, end };
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取歌词样式
|
||||||
|
export const getLrcStyle = (index: number) => {
|
||||||
|
if (index === nowIndex.value) {
|
||||||
|
return {
|
||||||
|
backgroundImage: `linear-gradient(to right, #ffffff ${currentLrcProgress.value}%, #ffffff8a ${currentLrcProgress.value}%)`,
|
||||||
|
backgroundClip: 'text',
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
color: 'transparent',
|
||||||
|
transition: 'background-image 0.1s linear',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算这个歌词的播放时间
|
watch(nowTime, (newTime) => {
|
||||||
const getLrcTime = (index: number) => {
|
const newIndex = getLrcIndex(newTime);
|
||||||
return Number(lrcTimeArray.value[index]);
|
if (newIndex !== nowIndex.value) {
|
||||||
|
nowIndex.value = newIndex;
|
||||||
|
currentLrcProgress.value = 0; // 重置进度
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 播放进度
|
||||||
|
export const useLyricProgress = () => {
|
||||||
|
let animationFrameId: number | null = null;
|
||||||
|
|
||||||
|
const updateProgress = () => {
|
||||||
|
if (!isPlaying.value) return;
|
||||||
|
audio.value = audio.value || (document.querySelector('#MusicAudio') as HTMLAudioElement);
|
||||||
|
if (!audio.value) return;
|
||||||
|
const { start, end } = currentLrcTiming.value;
|
||||||
|
const duration = end - start;
|
||||||
|
const elapsed = audio.value.currentTime - start;
|
||||||
|
currentLrcProgress.value = Math.min(Math.max((elapsed / duration) * 100, 0), 100);
|
||||||
|
|
||||||
|
animationFrameId = requestAnimationFrame(updateProgress);
|
||||||
|
};
|
||||||
|
|
||||||
|
const startProgressAnimation = () => {
|
||||||
|
if (!animationFrameId && isPlaying.value) {
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopProgressAnimation = () => {
|
||||||
|
if (animationFrameId) {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
animationFrameId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(isPlaying, (newIsPlaying) => {
|
||||||
|
if (newIsPlaying) {
|
||||||
|
startProgressAnimation();
|
||||||
|
} else {
|
||||||
|
stopProgressAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (isPlaying.value) {
|
||||||
|
startProgressAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopProgressAnimation();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentLrcProgress,
|
||||||
|
getLrcStyle,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置当前播放时间
|
||||||
|
export const setAudioTime = (index: number, audio: HTMLAudioElement) => {
|
||||||
|
audio.currentTime = lrcTimeArray.value[index];
|
||||||
|
audio.play();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取当前播放的歌词
|
// 获取当前播放的歌词
|
||||||
export const getCurrentLrc = () => {
|
export const getCurrentLrc = () => {
|
||||||
const index = getLrcIndex(nowTime.value);
|
const index = getLrcIndex(nowTime.value);
|
||||||
const currentLrc = lrcArray.value[index];
|
return {
|
||||||
const nextLrc = lrcArray.value[index + 1];
|
currentLrc: lrcArray.value[index],
|
||||||
return { currentLrc, nextLrc };
|
nextLrc: lrcArray.value[index + 1],
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取一句歌词播放时间是 几秒到几秒
|
// 获取一句歌词播放时间是 几秒到几秒
|
||||||
export const getLrcTimeRange = (index: number) => {
|
export const getLrcTimeRange = (index: number) => ({
|
||||||
const currentTime = Number(lrcTimeArray.value[index]);
|
currentTime: lrcTimeArray.value[index],
|
||||||
const nextTime = Number(lrcTimeArray.value[index + 1]);
|
nextTime: lrcTimeArray.value[index + 1],
|
||||||
return { currentTime, nextTime };
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const sendLyricToWin = (isPlay: boolean = true) => {
|
export const sendLyricToWin = (isPlay: boolean = true) => {
|
||||||
|
if (!isElectron.value) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!isElectron.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 设置lyricWinData 获取 当前播放的两句歌词 和歌词时间
|
|
||||||
let lyricWinData = null;
|
|
||||||
if (lrcArray.value.length > 0) {
|
if (lrcArray.value.length > 0) {
|
||||||
const nowIndex = getLrcIndex(nowTime.value);
|
const nowIndex = getLrcIndex(nowTime.value);
|
||||||
const { currentLrc, nextLrc } = getCurrentLrc();
|
const { currentLrc, nextLrc } = getCurrentLrc();
|
||||||
const { currentTime, nextTime } = getLrcTimeRange(nowIndex);
|
const { currentTime, nextTime } = getLrcTimeRange(nowIndex);
|
||||||
lyricWinData = {
|
// 设置lyricWinData 获取 当前播放的两句歌词 和歌词时间
|
||||||
|
const lyricWinData = {
|
||||||
currentLrc,
|
currentLrc,
|
||||||
nextLrc,
|
nextLrc,
|
||||||
currentTime,
|
currentTime,
|
||||||
@@ -160,20 +181,18 @@ export const sendLyricToWin = (isPlay: boolean = true) => {
|
|||||||
lrcArray: lrcArray.value,
|
lrcArray: lrcArray.value,
|
||||||
nowTime: nowTime.value,
|
nowTime: nowTime.value,
|
||||||
allTime: allTime.value,
|
allTime: allTime.value,
|
||||||
startCurrentTime: getLrcTime(nowIndex),
|
startCurrentTime: lrcTimeArray.value[nowIndex],
|
||||||
isPlay,
|
isPlay,
|
||||||
};
|
};
|
||||||
windowData.electronAPI.sendLyric(JSON.stringify(lyricWinData));
|
windowData.electronAPI.sendLyric(JSON.stringify(lyricWinData));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('error', error);
|
console.error('Error sending lyric to window:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const openLyric = () => {
|
export const openLyric = () => {
|
||||||
if (!isElectron.value) {
|
if (!isElectron.value) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
windowData.electronAPI.openLyric();
|
windowData.electronAPI.openLyric();
|
||||||
sendLyricToWin();
|
sendLyricToWin();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
import { getMusicLrc, getMusicUrl, getParsingMusicUrl } from '@/api/music';
|
||||||
|
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
|
||||||
|
import type { ILyric, ILyricText, SongResult } from '@/type/music';
|
||||||
|
import { getImgUrl, getMusicProxyUrl } from '@/utils';
|
||||||
|
import { getImageLinearBackground } from '@/utils/linearColor';
|
||||||
|
|
||||||
|
const musicHistory = useMusicHistory();
|
||||||
|
|
||||||
|
// 获取歌曲url
|
||||||
|
const getSongUrl = async (id: number) => {
|
||||||
|
const { data } = await getMusicUrl(id);
|
||||||
|
let url = '';
|
||||||
|
try {
|
||||||
|
if (data.data[0].freeTrialInfo || !data.data[0].url) {
|
||||||
|
const res = await getParsingMusicUrl(id);
|
||||||
|
url = res.data.data.url;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('error', error);
|
||||||
|
}
|
||||||
|
url = url || data.data[0].url;
|
||||||
|
return getMusicProxyUrl(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSongDetail = async (playMusic: SongResult) => {
|
||||||
|
if (playMusic.playMusicUrl) {
|
||||||
|
return playMusic;
|
||||||
|
}
|
||||||
|
playMusic.playLoading = true;
|
||||||
|
const playMusicUrl = await getSongUrl(playMusic.id);
|
||||||
|
const { backgroundColor, primaryColor } =
|
||||||
|
playMusic.backgroundColor && playMusic.primaryColor
|
||||||
|
? playMusic
|
||||||
|
: await getImageLinearBackground(getImgUrl(playMusic?.picUrl, '30y30'));
|
||||||
|
|
||||||
|
playMusic.playLoading = false;
|
||||||
|
return { ...playMusic, playMusicUrl, backgroundColor, primaryColor };
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载 当前歌曲 歌曲列表数据 下一首mp3预加载 歌词数据
|
||||||
|
export const useMusicListHook = () => {
|
||||||
|
const handlePlayMusic = async (state: any, playMusic: SongResult) => {
|
||||||
|
const updatedPlayMusic = await getSongDetail(playMusic);
|
||||||
|
state.playMusic = updatedPlayMusic;
|
||||||
|
state.playMusicUrl = updatedPlayMusic.playMusicUrl;
|
||||||
|
state.play = true;
|
||||||
|
loadLrcAsync(state, updatedPlayMusic.id);
|
||||||
|
musicHistory.addMusic(state.playMusic);
|
||||||
|
const playListIndex = state.playList.findIndex((item: SongResult) => item.id === playMusic.id);
|
||||||
|
state.playListIndex = playListIndex;
|
||||||
|
// 请求后续五首歌曲的详情
|
||||||
|
fetchSongs(state, playListIndex + 1, playListIndex + 6);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 用于预加载下一首歌曲的 MP3 数据
|
||||||
|
const preloadNextSong = (nextSongUrl: string) => {
|
||||||
|
const audio = new Audio(nextSongUrl);
|
||||||
|
audio.preload = 'auto'; // 设置预加载
|
||||||
|
audio.load(); // 手动加载
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchSongs = async (state: any, startIndex: number, endIndex: number) => {
|
||||||
|
const songs = state.playList.slice(Math.max(0, startIndex), Math.min(endIndex, state.playList.length));
|
||||||
|
|
||||||
|
const detailedSongs = await Promise.all(
|
||||||
|
songs.map(async (song: SongResult) => {
|
||||||
|
// 如果歌曲详情已经存在,就不重复请求
|
||||||
|
if (!song.playMusicUrl) {
|
||||||
|
return await getSongDetail(song);
|
||||||
|
}
|
||||||
|
return song;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// 加载下一首的歌词
|
||||||
|
const nextSong = detailedSongs[0];
|
||||||
|
if (!(nextSong.lyric && nextSong.lyric.lrcTimeArray.length > 0)) {
|
||||||
|
nextSong.lyric = await loadLrc(nextSong.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新播放列表中的歌曲详情
|
||||||
|
detailedSongs.forEach((song, index) => {
|
||||||
|
state.playList[startIndex + index] = song;
|
||||||
|
});
|
||||||
|
preloadNextSong(nextSong.playMusicUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextPlay = async (state: any) => {
|
||||||
|
if (state.playList.length === 0) {
|
||||||
|
state.play = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const playListIndex = (state.playListIndex + 1) % state.playList.length;
|
||||||
|
await handlePlayMusic(state, state.playList[playListIndex]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevPlay = async (state: any) => {
|
||||||
|
if (state.playList.length === 0) {
|
||||||
|
state.play = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const playListIndex = (state.playListIndex - 1 + state.playList.length) % state.playList.length;
|
||||||
|
await handlePlayMusic(state, state.playList[playListIndex]);
|
||||||
|
await fetchSongs(state, playListIndex - 5, playListIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseTime = (timeString: string): number => {
|
||||||
|
const [minutes, seconds] = timeString.split(':');
|
||||||
|
return Number(minutes) * 60 + Number(seconds);
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseLyricLine = (lyricLine: string): { time: number; text: string } => {
|
||||||
|
const TIME_REGEX = /(\d{2}:\d{2}(\.\d*)?)/g;
|
||||||
|
const LRC_REGEX = /(\[(\d{2}):(\d{2})(\.(\d*))?\])/g;
|
||||||
|
const timeText = lyricLine.match(TIME_REGEX)?.[0] || '';
|
||||||
|
const time = parseTime(timeText);
|
||||||
|
const text = lyricLine.replace(LRC_REGEX, '').trim();
|
||||||
|
return { time, text };
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseLyrics = (lyricsString: string): { lyrics: ILyricText[]; times: number[] } => {
|
||||||
|
const lines = lyricsString.split('\n');
|
||||||
|
const lyrics: ILyricText[] = [];
|
||||||
|
const times: number[] = [];
|
||||||
|
lines.forEach((line) => {
|
||||||
|
const { time, text } = parseLyricLine(line);
|
||||||
|
times.push(time);
|
||||||
|
lyrics.push({ text, trText: '' });
|
||||||
|
});
|
||||||
|
return { lyrics, times };
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadLrc = async (playMusicId: number): Promise<ILyric> => {
|
||||||
|
try {
|
||||||
|
const { data } = await getMusicLrc(playMusicId);
|
||||||
|
const { lyrics, times } = parseLyrics(data.lrc.lyric);
|
||||||
|
const tlyric: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (data.tlyric.lyric) {
|
||||||
|
const { lyrics: tLyrics, times: tTimes } = parseLyrics(data.tlyric.lyric);
|
||||||
|
tLyrics.forEach((lyric, index) => {
|
||||||
|
tlyric[tTimes[index].toString()] = lyric.text;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
lyrics.forEach((item, index) => {
|
||||||
|
item.trText = item.text ? tlyric[times[index].toString()] || '' : '';
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
lrcTimeArray: times,
|
||||||
|
lrcArray: lyrics,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading lyrics:', err);
|
||||||
|
return {
|
||||||
|
lrcTimeArray: [],
|
||||||
|
lrcArray: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 异步加载歌词的方法
|
||||||
|
const loadLrcAsync = async (state: any, playMusicId: number) => {
|
||||||
|
if (state.playMusic.lyric && state.playMusic.lyric.lrcTimeArray.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lyrics = await loadLrc(playMusicId);
|
||||||
|
state.playMusic.lyric = lyrics;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
handlePlayMusic,
|
||||||
|
nextPlay,
|
||||||
|
prevPlay,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,69 +1,72 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-drawer :show="musicFull" height="100vh" placement="bottom" :style="{ backgroundColor: 'transparent' }">
|
<n-drawer :show="musicFull" height="100vh" placement="bottom" :style="{ background: background }">
|
||||||
<div id="drawer-target">
|
<div id="drawer-target">
|
||||||
<div class="drawer-back" :style="{ background: background }"></div>
|
<div class="drawer-back"></div>
|
||||||
<div class="music-img">
|
<div class="music-img">
|
||||||
<n-image ref="PicImgRef" :src="getImgUrl(playMusic?.picUrl, '300y300')" class="img" lazy preview-disabled />
|
<n-image ref="PicImgRef" :src="getImgUrl(playMusic?.picUrl, '300y300')" class="img" lazy preview-disabled />
|
||||||
|
<div>
|
||||||
|
<div class="music-content-name">{{ playMusic.name }}</div>
|
||||||
|
<div class="music-content-singer">
|
||||||
|
<span v-for="(item, index) in playMusic.song.artists" :key="index">
|
||||||
|
{{ item.name }}{{ index < playMusic.song.artists.length - 1 ? ' / ' : '' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="music-content">
|
<div class="music-content">
|
||||||
<div class="music-content-name">{{ playMusic.name }}</div>
|
|
||||||
<div class="music-content-singer">
|
|
||||||
<span v-for="(item, index) in playMusic.song.artists" :key="index">
|
|
||||||
{{ item.name }}{{ index < playMusic.song.artists.length - 1 ? ' / ' : '' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<n-layout
|
<n-layout
|
||||||
ref="lrcSider"
|
ref="lrcSider"
|
||||||
class="music-lrc"
|
class="music-lrc"
|
||||||
style="height: 55vh"
|
style="height: 60vh"
|
||||||
:native-scrollbar="false"
|
:native-scrollbar="false"
|
||||||
@mouseover="mouseOverLayout"
|
@mouseover="mouseOverLayout"
|
||||||
@mouseleave="mouseLeaveLayout"
|
@mouseleave="mouseLeaveLayout"
|
||||||
>
|
>
|
||||||
<template v-for="(item, index) in lrcArray" :key="index">
|
<div ref="lrcContainer">
|
||||||
<div
|
<div
|
||||||
|
v-for="(item, index) in lrcArray"
|
||||||
|
:id="`music-lrc-text-${index}`"
|
||||||
|
:key="index"
|
||||||
class="music-lrc-text"
|
class="music-lrc-text"
|
||||||
:class="{ 'now-text': isCurrentLrc(index, nowTime) }"
|
:class="{ 'now-text': index === nowIndex }"
|
||||||
@click="setAudioTime(index, audio)"
|
@click="setAudioTime(index, audio)"
|
||||||
>
|
>
|
||||||
<div>{{ item.text }}</div>
|
<span :style="getLrcStyle(index)">{{ item.text }}</span>
|
||||||
<div class="music-lrc-text-tr">{{ item.trText }}</div>
|
<div class="music-lrc-text-tr">{{ item.trText }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</n-layout>
|
</n-layout>
|
||||||
<!-- 时间矫正 -->
|
<!-- 时间矫正 -->
|
||||||
<div class="music-content-time">
|
<!-- <div class="music-content-time">
|
||||||
<n-button @click="reduceCorrectionTime(0.2)">-</n-button>
|
<n-button @click="reduceCorrectionTime(0.2)">-</n-button>
|
||||||
<n-button @click="addCorrectionTime(0.2)">+</n-button>
|
<n-button @click="addCorrectionTime(0.2)">+</n-button>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-drawer>
|
</n-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useStore } from 'vuex';
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addCorrectionTime,
|
addCorrectionTime,
|
||||||
isCurrentLrc,
|
|
||||||
lrcArray,
|
lrcArray,
|
||||||
newLrcIndex,
|
nowIndex,
|
||||||
nowTime,
|
playMusic,
|
||||||
reduceCorrectionTime,
|
reduceCorrectionTime,
|
||||||
setAudioTime,
|
setAudioTime,
|
||||||
|
useLyricProgress,
|
||||||
} from '@/hooks/MusicHook';
|
} from '@/hooks/MusicHook';
|
||||||
import type { SongResult } from '@/type/music';
|
|
||||||
import { getImgUrl } from '@/utils';
|
import { getImgUrl } from '@/utils';
|
||||||
|
|
||||||
const store = useStore();
|
const { getLrcStyle } = useLyricProgress();
|
||||||
|
|
||||||
// 播放的音乐信息
|
|
||||||
const playMusic = computed(() => store.state.playMusic as SongResult);
|
|
||||||
// const isPlaying = computed(() => store.state.play as boolean);
|
// const isPlaying = computed(() => store.state.play as boolean);
|
||||||
// 获取歌词滚动dom
|
// 获取歌词滚动dom
|
||||||
const lrcSider = ref<any>(null);
|
const lrcSider = ref<any>(null);
|
||||||
const isMouse = ref(false);
|
const isMouse = ref(false);
|
||||||
|
const lrcContainer = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
musicFull: {
|
musicFull: {
|
||||||
@@ -81,21 +84,44 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 歌词滚动方法
|
// 歌词滚动方法
|
||||||
const lrcScroll = () => {
|
const lrcScroll = (behavior = 'smooth') => {
|
||||||
if (props.musicFull && !isMouse.value) {
|
const nowEl = document.querySelector(`#music-lrc-text-${nowIndex.value}`);
|
||||||
const top = newLrcIndex.value * 60 - 225;
|
if (props.musicFull && !isMouse.value && nowEl && lrcContainer.value) {
|
||||||
lrcSider.value.scrollTo({ top, behavior: 'smooth' });
|
const containerRect = lrcContainer.value.getBoundingClientRect();
|
||||||
|
const nowElRect = nowEl.getBoundingClientRect();
|
||||||
|
const relativeTop = nowElRect.top - containerRect.top;
|
||||||
|
const scrollTop = relativeTop - lrcSider.value.$el.getBoundingClientRect().height / 2;
|
||||||
|
lrcSider.value.scrollTo({ top: scrollTop, behavior });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const debouncedLrcScroll = useDebounceFn(lrcScroll, 200);
|
||||||
|
|
||||||
const mouseOverLayout = () => {
|
const mouseOverLayout = () => {
|
||||||
isMouse.value = true;
|
isMouse.value = true;
|
||||||
};
|
};
|
||||||
const mouseLeaveLayout = () => {
|
const mouseLeaveLayout = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isMouse.value = false;
|
isMouse.value = false;
|
||||||
}, 3000);
|
lrcScroll();
|
||||||
|
}, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(nowIndex, () => {
|
||||||
|
debouncedLrcScroll();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.musicFull,
|
||||||
|
() => {
|
||||||
|
if (props.musicFull) {
|
||||||
|
nextTick(() => {
|
||||||
|
lrcScroll('instant');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
lrcScroll,
|
lrcScroll,
|
||||||
});
|
});
|
||||||
@@ -112,15 +138,11 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
.drawer-back {
|
.drawer-back {
|
||||||
@apply absolute bg-cover bg-center;
|
@apply absolute bg-cover bg-center;
|
||||||
// filter: brightness(80%);
|
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
width: 200%;
|
width: 200%;
|
||||||
height: 200%;
|
height: 200%;
|
||||||
top: -50%;
|
top: -50%;
|
||||||
left: -50%;
|
left: -50%;
|
||||||
// animation: round 20s linear infinite;
|
|
||||||
// will-change: transform;
|
|
||||||
// transform: translateZ(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-back.paused {
|
.drawer-back.paused {
|
||||||
@@ -128,30 +150,28 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
#drawer-target {
|
#drawer-target {
|
||||||
@apply top-0 left-0 absolute w-full h-full overflow-hidden rounded px-24 pt-24 pb-48 flex items-center;
|
@apply top-0 left-0 absolute overflow-hidden rounded px-24 flex items-center justify-center w-full h-full pb-8;
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
background-color: rgba(0, 0, 0, 0.747);
|
|
||||||
animation-duration: 300ms;
|
animation-duration: 300ms;
|
||||||
|
|
||||||
.music-img {
|
.music-img {
|
||||||
@apply flex-1 flex justify-center mr-24;
|
@apply flex-1 flex justify-center mr-16 flex-col;
|
||||||
|
max-width: 360px;
|
||||||
|
max-height: 360px;
|
||||||
.img {
|
.img {
|
||||||
width: 350px;
|
@apply rounded-xl w-full h-full shadow-2xl;
|
||||||
height: 350px;
|
|
||||||
@apply rounded-xl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.music-content {
|
.music-content {
|
||||||
@apply flex flex-col justify-center items-center;
|
@apply flex flex-col justify-center items-center relative;
|
||||||
|
|
||||||
&-name {
|
&-name {
|
||||||
@apply font-bold text-3xl py-2;
|
@apply font-bold text-xl pb-1 pt-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-singer {
|
&-singer {
|
||||||
@apply text-base py-2;
|
@apply text-base;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,25 +179,25 @@ defineExpose({
|
|||||||
display: none;
|
display: none;
|
||||||
@apply flex justify-center items-center;
|
@apply flex justify-center items-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.music-lrc {
|
.music-lrc {
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
width: 500px;
|
width: 500px;
|
||||||
height: 550px;
|
height: 550px;
|
||||||
.now-text {
|
|
||||||
@apply text-green-500;
|
|
||||||
}
|
|
||||||
&-text {
|
&-text {
|
||||||
@apply text-white text-lg flex flex-col justify-center items-center cursor-pointer font-bold;
|
@apply text-2xl cursor-pointer font-bold px-2 py-4;
|
||||||
height: 60px;
|
color: #ffffff8a;
|
||||||
transition: all 0.2s ease-out;
|
// transition: all 0.5s ease;
|
||||||
|
span {
|
||||||
|
padding-right: 100px;
|
||||||
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply font-bold text-green-500;
|
@apply font-bold opacity-100 rounded-xl;
|
||||||
|
background-color: #ffffff26;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-tr {
|
&-tr {
|
||||||
@apply text-sm font-normal;
|
@apply font-normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
raw
|
raw
|
||||||
:show-arrow="false"
|
:show-arrow="false"
|
||||||
:delay="200"
|
:delay="200"
|
||||||
|
arrow-wrapper-style=" border-radius:1.5rem"
|
||||||
@update-show="scrollToPlayList"
|
@update-show="scrollToPlayList"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@@ -111,7 +112,7 @@ import { useTemplateRef } from 'vue';
|
|||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
|
|
||||||
import SongItem from '@/components/common/SongItem.vue';
|
import SongItem from '@/components/common/SongItem.vue';
|
||||||
import { allTime, isElectron, loadLrc, nowTime, openLyric, sendLyricToWin } from '@/hooks/MusicHook';
|
import { allTime, getCurrentLrc, isElectron, nowTime, openLyric, sendLyricToWin } from '@/hooks/MusicHook';
|
||||||
import type { SongResult } from '@/type/music';
|
import type { SongResult } from '@/type/music';
|
||||||
import { getImgUrl, secondToMinute, setAnimationClass } from '@/utils';
|
import { getImgUrl, secondToMinute, setAnimationClass } from '@/utils';
|
||||||
|
|
||||||
@@ -134,7 +135,6 @@ const background = ref('#000');
|
|||||||
watch(
|
watch(
|
||||||
() => store.state.playMusic,
|
() => store.state.playMusic,
|
||||||
async () => {
|
async () => {
|
||||||
loadLrc(playMusic.value.id);
|
|
||||||
background.value = playMusic.value.backgroundColor as string;
|
background.value = playMusic.value.backgroundColor as string;
|
||||||
},
|
},
|
||||||
{ immediate: true, deep: true },
|
{ immediate: true, deep: true },
|
||||||
@@ -209,15 +209,16 @@ function handleGetAudioTime(this: HTMLAudioElement) {
|
|||||||
// 监听音频播放的实时时间事件
|
// 监听音频播放的实时时间事件
|
||||||
const audio = this as HTMLAudioElement;
|
const audio = this as HTMLAudioElement;
|
||||||
// 获取当前播放时间
|
// 获取当前播放时间
|
||||||
nowTime.value = Math.floor(audio.currentTime);
|
nowTime.value = audio.currentTime;
|
||||||
|
getCurrentLrc();
|
||||||
// 获取总时间
|
// 获取总时间
|
||||||
allTime.value = audio.duration;
|
allTime.value = audio.duration;
|
||||||
// 获取音量
|
// 获取音量
|
||||||
audioVolume.value = audio.volume;
|
audioVolume.value = audio.volume;
|
||||||
sendLyricToWin(store.state.isPlay);
|
sendLyricToWin(store.state.isPlay);
|
||||||
if (musicFullVisible.value) {
|
// if (musicFullVisible.value) {
|
||||||
MusicFullRef.value?.lrcScroll();
|
// MusicFullRef.value?.lrcScroll();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 播放暂停按钮事件
|
// 播放暂停按钮事件
|
||||||
@@ -273,7 +274,8 @@ const scrollToPlayList = (val: boolean) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.play-bar-opcity {
|
.play-bar-opcity {
|
||||||
background-color: rgba(0, 0, 0, 0.218);
|
@apply bg-transparent;
|
||||||
|
box-shadow: 0 0 20px 5px #0000001d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.play-bar-img {
|
.play-bar-img {
|
||||||
@@ -374,8 +376,4 @@ const scrollToPlayList = (val: boolean) => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-popover) {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+6
-56
@@ -1,11 +1,8 @@
|
|||||||
import { createStore } from 'vuex';
|
import { createStore } from 'vuex';
|
||||||
|
|
||||||
import { getMusicUrl, getParsingMusicUrl } from '@/api/music';
|
import { useMusicListHook } from '@/hooks/MusicListHook';
|
||||||
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
|
|
||||||
import homeRouter from '@/router/home';
|
import homeRouter from '@/router/home';
|
||||||
import type { SongResult } from '@/type/music';
|
import type { SongResult } from '@/type/music';
|
||||||
import { getImgUrl, getMusicProxyUrl } from '@/utils';
|
|
||||||
import { getImageLinearBackground } from '@/utils/linearColor';
|
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
menus: any[];
|
menus: any[];
|
||||||
@@ -38,17 +35,16 @@ const state: State = {
|
|||||||
searchValue: '',
|
searchValue: '',
|
||||||
searchType: 1,
|
searchType: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const windowData = window as any;
|
const windowData = window as any;
|
||||||
|
|
||||||
const musicHistory = useMusicHistory();
|
const { handlePlayMusic, nextPlay, prevPlay } = useMusicListHook();
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
setMenus(state: State, menus: any[]) {
|
setMenus(state: State, menus: any[]) {
|
||||||
state.menus = menus;
|
state.menus = menus;
|
||||||
},
|
},
|
||||||
async setPlay(state: State, playMusic: SongResult) {
|
async setPlay(state: State, playMusic: SongResult) {
|
||||||
await getSongDetail(state, playMusic);
|
await handlePlayMusic(state, playMusic);
|
||||||
},
|
},
|
||||||
setIsPlay(state: State, isPlay: boolean) {
|
setIsPlay(state: State, isPlay: boolean) {
|
||||||
state.isPlay = isPlay;
|
state.isPlay = isPlay;
|
||||||
@@ -61,63 +57,17 @@ const mutations = {
|
|||||||
state.playList = playList;
|
state.playList = playList;
|
||||||
},
|
},
|
||||||
async nextPlay(state: State) {
|
async nextPlay(state: State) {
|
||||||
if (state.playList.length === 0) {
|
await nextPlay(state);
|
||||||
state.play = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const playListIndex = (state.playListIndex + 1) % state.playList.length;
|
|
||||||
await getSongDetail(state, state.playList[playListIndex]);
|
|
||||||
},
|
},
|
||||||
async prevPlay(state: State) {
|
async prevPlay(state: State) {
|
||||||
if (state.playList.length === 0) {
|
await prevPlay(state);
|
||||||
state.play = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const playListIndex = (state.playListIndex - 1 + state.playList.length) % state.playList.length;
|
|
||||||
await getSongDetail(state, state.playList[playListIndex]);
|
|
||||||
},
|
},
|
||||||
async setSetData(state: State, setData: any) {
|
async setSetData(state: State, setData: any) {
|
||||||
state.setData = setData;
|
state.setData = setData;
|
||||||
windowData.electron.ipcRenderer.setStoreValue('set', JSON.parse(JSON.stringify(setData)));
|
window.electron && window.electron.ipcRenderer.setStoreValue('set', JSON.parse(JSON.stringify(setData)));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSongUrl = async (id: number) => {
|
|
||||||
const { data } = await getMusicUrl(id);
|
|
||||||
let url = '';
|
|
||||||
try {
|
|
||||||
if (data.data[0].freeTrialInfo || !data.data[0].url) {
|
|
||||||
const res = await getParsingMusicUrl(id);
|
|
||||||
url = res.data.data.url;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('error', error);
|
|
||||||
}
|
|
||||||
url = url || data.data[0].url;
|
|
||||||
return getMusicProxyUrl(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updatePlayMusic = async (state: State) => {
|
|
||||||
state.playMusic = state.playList[state.playListIndex];
|
|
||||||
state.playMusicUrl = await getSongUrl(state.playMusic.id);
|
|
||||||
state.play = true;
|
|
||||||
musicHistory.addMusic(state.playMusic);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSongDetail = async (state: State, playMusic: SongResult) => {
|
|
||||||
state.playMusic.playLoading = true;
|
|
||||||
state.playMusicUrl = await getSongUrl(playMusic.id);
|
|
||||||
const backgroundColor = playMusic.backgroundColor
|
|
||||||
? playMusic.backgroundColor
|
|
||||||
: await getImageLinearBackground(getImgUrl(playMusic?.picUrl, '30y30'));
|
|
||||||
state.playMusic = { ...playMusic, backgroundColor };
|
|
||||||
// state.playMusic = { ...playMusic };
|
|
||||||
state.play = true;
|
|
||||||
musicHistory.addMusic(playMusic);
|
|
||||||
state.playListIndex = state.playList.findIndex((item) => item.id === playMusic.id);
|
|
||||||
state.playMusic.playLoading = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const store = createStore({
|
const store = createStore({
|
||||||
state,
|
state,
|
||||||
mutations,
|
mutations,
|
||||||
|
|||||||
@@ -3,6 +3,14 @@ export interface IRecommendMusic {
|
|||||||
category: number;
|
category: number;
|
||||||
result: SongResult[];
|
result: SongResult[];
|
||||||
}
|
}
|
||||||
|
export interface ILyricText {
|
||||||
|
text: string;
|
||||||
|
trText: string;
|
||||||
|
}
|
||||||
|
export interface ILyric {
|
||||||
|
lrcTimeArray: number[];
|
||||||
|
lrcArray: ILyricText[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface SongResult {
|
export interface SongResult {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -19,6 +27,9 @@ export interface SongResult {
|
|||||||
ar?: Artist[];
|
ar?: Artist[];
|
||||||
al?: Album;
|
al?: Album;
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
|
primaryColor?: string;
|
||||||
|
playMusicUrl?: string;
|
||||||
|
lyric?: ILyric;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Song {
|
export interface Song {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export const getIsMc = () => {
|
|||||||
if (windowData.electron.ipcRenderer.getStoreValue('set').isProxy) {
|
if (windowData.electron.ipcRenderer.getStoreValue('set').isProxy) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if(window.location.origin.includes('localhost')){}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
const ProxyUrl = import.meta.env.VITE_API_PROXY || 'http://110.42.251.190:9856';
|
const ProxyUrl = import.meta.env.VITE_API_PROXY || 'http://110.42.251.190:9856';
|
||||||
|
|||||||
@@ -1,20 +1,37 @@
|
|||||||
export const getImageLinearBackground = async (imageSrc: string): Promise<string> => {
|
interface IColor {
|
||||||
|
backgroundColor: string;
|
||||||
|
primaryColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getImageLinearBackground = async (imageSrc: string): Promise<IColor> => {
|
||||||
try {
|
try {
|
||||||
const primaryColor = await getImagePrimaryColor(imageSrc);
|
const primaryColor = await getImagePrimaryColor(imageSrc);
|
||||||
return generateGradientBackground(primaryColor);
|
return {
|
||||||
|
backgroundColor: generateGradientBackground(primaryColor),
|
||||||
|
primaryColor,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('error', error);
|
console.error('error', error);
|
||||||
return '';
|
return {
|
||||||
|
backgroundColor: '',
|
||||||
|
primaryColor: '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getImageBackground = async (img: HTMLImageElement): Promise<string> => {
|
export const getImageBackground = async (img: HTMLImageElement): Promise<IColor> => {
|
||||||
try {
|
try {
|
||||||
const primaryColor = await getImageColor(img);
|
const primaryColor = await getImageColor(img);
|
||||||
return generateGradientBackground(primaryColor);
|
return {
|
||||||
|
backgroundColor: generateGradientBackground(primaryColor),
|
||||||
|
primaryColor,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('error', error);
|
console.error('error', error);
|
||||||
return '';
|
return {
|
||||||
|
backgroundColor: '',
|
||||||
|
primaryColor: '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,8 +100,8 @@ const generateGradientBackground = (color: string): string => {
|
|||||||
const [h, s, l] = rgbToHsl(r, g, b);
|
const [h, s, l] = rgbToHsl(r, g, b);
|
||||||
|
|
||||||
// 增加亮度和暗度的差异
|
// 增加亮度和暗度的差异
|
||||||
const lightL = Math.min(l + 0.5, 0.95);
|
const lightL = Math.min(l + 0.2, 0.95);
|
||||||
const darkL = Math.max(l - 0.5, 0.05);
|
const darkL = Math.max(l - 0.3, 0.05);
|
||||||
const midL = (lightL + darkL) / 2;
|
const midL = (lightL + darkL) / 2;
|
||||||
|
|
||||||
// 调整饱和度以增强效果
|
// 调整饱和度以增强效果
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
// 指定端口
|
// 指定端口
|
||||||
port: 7788,
|
port: 4488,
|
||||||
proxy: {
|
proxy: {
|
||||||
// with options
|
// with options
|
||||||
'/api': {
|
'/api': {
|
||||||
|
|||||||
Reference in New Issue
Block a user