feat: 优化歌词页面样式 添加歌词进度显示 优化歌曲及列表加载方式 大幅提升歌曲歌词播放速度

This commit is contained in:
alger
2024-10-18 18:37:53 +08:00
parent 7abc087d70
commit 06bffe7618
16 changed files with 466 additions and 266 deletions
+135 -116
View File
@@ -1,156 +1,177 @@
import { computed, ref } from 'vue';
import { getMusicLrc } from '@/api/music';
import store from '@/store';
import { ILyric } from '@/type/lyric';
import type { ILyricText, SongResult } from '@/type/music';
const windowData = window as any;
export const isElectron = computed(() => {
return !!windowData.electronAPI;
});
export const isElectron = computed(() => !!windowData.electronAPI);
interface ILrcData {
text: string;
trText: string;
}
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 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>();
export const newLrcIndex = ref<number>(0);
export const lrcArray = ref<Array<ILrcData>>([]);
export const lrcTimeArray = ref<Array<Number>>([]);
export const parseTime = (timeString: string) => {
const [minutes, seconds] = timeString.split(':');
return Number(minutes) * 60 + Number(seconds);
};
const TIME_REGEX = /(\d{2}:\d{2}(\.\d*)?)/g;
const LRC_REGEX = /(\[(\d{2}):(\d{2})(\.(\d*))?\])/g;
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);
watch(
() => store.state.playMusic,
() => {
nextTick(() => {
lrcArray.value = playMusic.value.lyric?.lrcArray || [];
lrcTimeArray.value = playMusic.value.lyric?.lrcTimeArray || [];
});
},
{
deep: true,
},
);
const isPlaying = computed(() => store.state.play as boolean);
// 增加矫正时间
export const addCorrectionTime = (time: number) => {
correctionTime.value += time;
};
export const addCorrectionTime = (time: number) => (correctionTime.value += time);
// 减少矫正时间
export const reduceCorrectionTime = (time: number) => {
correctionTime.value -= time;
};
export const reduceCorrectionTime = (time: number) => (correctionTime.value -= time);
export const isCurrentLrc = (index: number, time: number) => {
const currentTime = Number(lrcTimeArray.value[index]);
const nextTime = Number(lrcTimeArray.value[index + 1]);
// 获取当前播放歌词
export const isCurrentLrc = (index: number, time: number): boolean => {
const currentTime = lrcTimeArray.value[index];
const nextTime = lrcTimeArray.value[index + 1];
const nowTime = time + correctionTime.value;
const isTrue = nowTime > currentTime && nowTime < nextTime;
if (isTrue) {
newLrcIndex.value = index;
}
return isTrue;
};
export const nowTime = ref(0);
export const allTime = ref(0);
export const nowIndex = ref(0);
export const getLrcIndex = (time: number) => {
// 获取当前播放歌词INDEX
export const getLrcIndex = (time: number): number => {
for (let i = 0; i < lrcTimeArray.value.length; i++) {
if (isCurrentLrc(i, time)) {
nowIndex.value = i || nowIndex.value;
nowIndex.value = i;
return i;
}
}
return nowIndex.value;
};
// 设置当前播放时间
export const setAudioTime = (index: number, audio: HTMLAudioElement) => {
audio.currentTime = lrcTimeArray.value[index] as number;
audio.play();
// 获取当前播放歌词进度
const currentLrcTiming = computed(() => {
const start = lrcTimeArray.value[nowIndex.value] || 0;
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 {};
};
// 计算这个歌词的播放时间
const getLrcTime = (index: number) => {
return Number(lrcTimeArray.value[index]);
watch(nowTime, (newTime) => {
const newIndex = getLrcIndex(newTime);
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 = () => {
const index = getLrcIndex(nowTime.value);
const currentLrc = lrcArray.value[index];
const nextLrc = lrcArray.value[index + 1];
return { currentLrc, nextLrc };
return {
currentLrc: lrcArray.value[index],
nextLrc: lrcArray.value[index + 1],
};
};
// 获取一句歌词播放时间是 几秒到几秒
export const getLrcTimeRange = (index: number) => {
const currentTime = Number(lrcTimeArray.value[index]);
const nextTime = Number(lrcTimeArray.value[index + 1]);
return { currentTime, nextTime };
};
export const getLrcTimeRange = (index: number) => ({
currentTime: lrcTimeArray.value[index],
nextTime: lrcTimeArray.value[index + 1],
});
export const sendLyricToWin = (isPlay: boolean = true) => {
if (!isElectron.value) return;
try {
if (!isElectron.value) {
return;
}
// 设置lyricWinData 获取 当前播放的两句歌词 和歌词时间
let lyricWinData = null;
if (lrcArray.value.length > 0) {
const nowIndex = getLrcIndex(nowTime.value);
const { currentLrc, nextLrc } = getCurrentLrc();
const { currentTime, nextTime } = getLrcTimeRange(nowIndex);
lyricWinData = {
// 设置lyricWinData 获取 当前播放的两句歌词 和歌词时间
const lyricWinData = {
currentLrc,
nextLrc,
currentTime,
@@ -160,20 +181,18 @@ export const sendLyricToWin = (isPlay: boolean = true) => {
lrcArray: lrcArray.value,
nowTime: nowTime.value,
allTime: allTime.value,
startCurrentTime: getLrcTime(nowIndex),
startCurrentTime: lrcTimeArray.value[nowIndex],
isPlay,
};
windowData.electronAPI.sendLyric(JSON.stringify(lyricWinData));
}
} catch (error) {
console.error('error', error);
console.error('Error sending lyric to window:', error);
}
};
export const openLyric = () => {
if (!isElectron.value) {
return;
}
if (!isElectron.value) return;
windowData.electronAPI.openLyric();
sendLyricToWin();
};