进行一些改进

1.修复 pinia 无法重置变量的问题。
2.修复 weather.vue 中的某些错误。
3.补全遗漏的语音提示。
4.逐字样式修改,添加逐字上色增强效果,但默认不开启,因为这坨山还有 N 个 BUG 要修,仅仅能跑,一点不稳定。
5.修复(?)歌词解析时因 API 返回玄学内容造成的异常。
6.逐字解析逻辑修改。
...
More fixes needed
This commit is contained in:
NanoRocky 2024-11-22 21:52:05 +08:00
parent dd81d31748
commit 7d02b4da23
13 changed files with 819 additions and 649 deletions

View File

@ -18,15 +18,15 @@
"aplayer": "^1.10.1",
"axios": "^1.7.7",
"dayjs": "^1.11.13",
"element-plus": "^2.8.7",
"element-plus": "^2.8.8",
"fetch-jsonp": "^1.3.0",
"lodash-es": "^4.17.21",
"pinia": "^2.2.5",
"pinia-plugin-persistedstate": "^4.1.2",
"pinia-plugin-persistedstate-2": "^2.0.24",
"swiper": "^11.1.14",
"pinia": "^2.2.6",
"pinia-plugin-persistedstate": "^4.1.3",
"pinia-plugin-persistedstate-2": "^2.0.27",
"swiper": "^11.1.15",
"three": "^0.170.0",
"vue": "^3.5.12"
"vue": "^3.5.13"
},
"devDependencies": {
"@icon-park/vue-next": "^1.4.2",
@ -36,15 +36,15 @@
"@vicons/material": "^0.12.0",
"@vicons/tabler": "^0.12.0",
"@vicons/utils": "^0.1.4",
"@vitejs/plugin-vue": "^5.1.4",
"eslint": "^9.14.0",
"eslint-plugin-vue": "^9.30.0",
"@vitejs/plugin-vue": "^5.2.0",
"eslint": "^9.15.0",
"eslint-plugin-vue": "^9.31.0",
"prettier": "^3.3.3",
"sass": "^1.80.6",
"sass": "^1.81.0",
"terser": "^5.36.0",
"unplugin-auto-import": "^0.18.3",
"unplugin-auto-import": "^0.18.5",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.10",
"vite": "^5.4.11",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-pwa": "^0.20.5"
}

1008
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -47,7 +47,7 @@ const changeBg = (type) => {
bgUrl.value = "https://api.vvhan.com/api/wallpaper/views";
} else if (type == 3) {
bgUrl.value = "https://api.vvhan.com/api/wallpaper/acg";
}
};
};
//

View File

@ -41,7 +41,11 @@
:key="store.playerLrc.length != 0 ? `lrc-line-${store.playerLrc[0][2]}` : `lrc-line-null`">
<music-one theme="filled" size="18" fill="#efefef" />
<span class="yrc-box">
<span class="yrc-1 lrc-text text-hidden">
<span class="yrc-2 lrc-text text-hidden" id="yrc-2-wrap">
<span v-for="i in store.playerLrc" :key="`lrc-over-char-${i[2]}-${i[3]}`" v-html="i[4]">
</span>
</span>
<span class="yrc-1 lrc-text text-hidden" id="yrc-1-wrap">
<span v-for="i in store.playerLrc" :key="`lrc-char-${i[2]}-${i[3]}`"
:style="`opacity: ${i[1] ? '1' : '0.6'}`"
:class="`yrc-char ${i[0] ? 'fade-in' : 'fade-in-start'} ${i[0] > 1.5 ? 'long-tone' : 'fade-in-start'}`"
@ -87,14 +91,14 @@ const siteUrl = computed(() => {
//
if (!url.startsWith("http://") && !url.startsWith("https://")) {
return "//" + url;
}
};
return url;
});
</script>
<style lang="scss" scoped>
// 1
.yrc-char {
//
display: inline-block;
opacity: 0.3;
transform: translateY(1px);
@ -174,8 +178,32 @@ const siteUrl = computed(() => {
}
}
// 2
#yrc-2-wrap>span {
display: inline-block;
white-space: nowrap;
overflow: hidden;
width: 0;
}
#yrc-2-wrap {
display: inline-block;
position: absolute;
width: auto;
opacity: 0.5;
text-shadow: 0 0 4px rgba(255, 255, 255, 0.8);
font-family: MiSans-Regular;
overflow: hidden;
white-space: nowrap;
transition:
opacity 0.3s linear,
color 0.5s linear,
transform 0.3s linear,
width 0.3s linear;
}
//
.lrc-char {
//
display: inline;
opacity: 1;
background-clip: text;
@ -187,6 +215,8 @@ const siteUrl = computed(() => {
color 0.5s linear;
}
// End
#footer {
width: 100%;
position: absolute;
@ -196,7 +226,7 @@ const siteUrl = computed(() => {
line-height: 46px;
text-align: center;
z-index: 0;
font-size: 14px;
font-size: 16px;
//
word-break: keep-all;
white-space: nowrap;
@ -211,6 +241,8 @@ const siteUrl = computed(() => {
flex-direction: row;
align-items: center;
justify-content: center;
z-index: 1;
justify-content: flex-start;
.lrc-all {
width: 98%;
@ -218,6 +250,7 @@ const siteUrl = computed(() => {
flex-direction: row;
justify-content: center;
align-items: center;
white-space: nowrap;
.lrc-text {
margin: 0 8px;
@ -250,9 +283,6 @@ const siteUrl = computed(() => {
.yrc-2 {
position: absolute;
z-index: 1000;
overflow: hidden;
float: left;
text-align: left;
}
}
}

View File

@ -57,7 +57,7 @@ const siteLinksList = computed(() => {
for (let i = 0; i < siteLinks.length; i += 6) {
const subArr = siteLinks.slice(i, i + 6);
result.push(subArr);
}
};
return result;
});
@ -78,7 +78,7 @@ const jumpLink = (data) => {
if (typeof $openList === "function") $openList();
} else {
window.open(data.link, "_blank");
}
};
};
</script>

View File

@ -47,7 +47,7 @@ const siteUrl = computed(() => {
if (url.startsWith("http://") || url.startsWith("https://")) {
const urlFormat = url.replace(/^(https?:\/\/)/, "");
return urlFormat.split(".");
}
};
return url.split(".");
});
@ -76,7 +76,7 @@ const changeBox = () => {
const vstyle = import.meta.env.VITE_TTS_Style;
SpeechLocal("分辨率不足.mp3");
};
}
};
};
//
@ -95,7 +95,7 @@ watch(
} else {
descriptionText.hello = import.meta.env.VITE_DESC_HELLO;
descriptionText.text = import.meta.env.VITE_DESC_TEXT;
}
};
},
);
</script>

View File

@ -13,10 +13,8 @@ import APlayer from "@worstone/vue-aplayer";
import { Speech, stopSpeech, SpeechLocal } from "@/utils/speech";
import { decodeYrc } from "../utils/decodeYrc";
let lastTimestamp = Date.now();
let webglRenderer;
const store = mainStore();
let lastTimestamp = Date.now();
// DOM
const player = ref(null);
@ -175,150 +173,198 @@ const onPause = () => {
store.setPlayerState(player.value.audioRef.paused);
};
let nowLineStart = -1
//
function showYrc() {
if (player.value == null) {
return requestAnimationFrame(showYrc);
}
const aplayer = player.value.aplayer;
const lyrics = aplayer.lyrics[playIndex.value];
if (store.playerYrcShow != true) {
store.yrcEnable = false;
store.yrcTemp = [];
store.yrcLoading = false;
}
else {
if (store.yrcIndex != playIndex.value) {
const yrcUrl = aplayer.audio[aplayer.index]["lrc"] + "&yrc=true";
store.yrcIndex = playIndex.value;
store.yrcLoading = true;
fetch(yrcUrl)
.then((i) => {
if (i.status < 200 || i.status >= 400) {
throw i.text();
};
return i.text();
})
.then((i) => {
store.yrcIndex = playIndex.value;
if (i.startsWith("[ch:0]")) {
store.yrcEnable = true;
store.yrcTemp = decodeYrc(i);
store.yrcLoading = false;
return;
} else if (!store.playerYrcATDB) {
// YRC ...bushi... qrc yrc
try {
// try 西[20720,-4200]西 try
if (player.value == null) {
return requestAnimationFrame(showYrc);
}
const aplayer = player.value.aplayer;
const lyrics = aplayer.lyrics[playIndex.value];
if (store.playerYrcShow != true) {
store.yrcEnable = false;
store.yrcTemp = [];
store.yrcLoading = false;
}
else {
if (store.yrcIndex != playIndex.value) {
const yrcUrl = aplayer.audio[aplayer.index]["lrc"] + "&yrc=true";
store.yrcIndex = playIndex.value;
store.yrcLoading = true;
fetch(yrcUrl)
.then((i) => {
if (i.status < 200 || i.status >= 400) {
throw i.text();
};
return i.text();
})
.then((i) => {
store.yrcIndex = playIndex.value;
if (i.startsWith("[ch:0]")) {
store.yrcEnable = true;
store.yrcTemp = decodeYrc(i);
store.yrcLoading = false;
return;
} else if (!store.playerYrcATDB) {
store.yrcEnable = false;
store.yrcTemp = [];
store.yrcLoading = false;
return;
};
// AMLL TTML Database
const songUrlInf = new URLSearchParams(new URL(yrcUrl).search)
const songId = songUrlInf.get('id')
const songServer = songUrlInf.get("server");
if (!songId) {
return;
};
const songUrlInfUrl = {
'netease': `https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/ncm-lyrics/${songId}.yrc`,
'tencent': `https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/qq-lyrics/${songId}.qrc`
};
if (!['netease', 'tencent'].includes(songServer)) {
return;
};
const amllUrl = songUrlInfUrl[songServer]
return fetch(amllUrl)
.then((response) => {
if (!response.ok) {
throw response.text()
};
return response.text();
}).then((amllyrcfile) => {
store.yrcEnable = true;
store.yrcTemp = decodeYrc(amllyrcfile);
store.yrcLoading = false;
});
}).catch(() => {
store.yrcEnable = false;
store.yrcTemp = [];
store.yrcLoading = false;
return;
};
// AMLL TTML Database
const songIdMatch = yrcUrl.match(/netease.*?id=(.*?)&/);
const songId = songIdMatch ? songIdMatch[1] : null;
if (!songId) {
return;
};
const amllUrl = `https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/ncm-lyrics/${songId}.yrc`;
return fetch(amllUrl)
.then((response) => {
if (!response.ok) {
throw response.text()
};
return response.text();
}).then((amllyrcfile) => {
store.yrcEnable = true;
store.yrcTemp = decodeYrc(amllyrcfile);
store.yrcLoading = false;
});
}).catch(() => {
store.yrcEnable = false;
store.yrcTemp = [];
store.yrcLoading = false;
});
};
};
if (!store.yrcEnable || store.yrcTemp.length == 0 || store.yrcLoading) {
//
let lyricIndex = player.value.aplayer.lyricIndex;
if (lyrics === undefined || lyrics[lyricIndex] === undefined) {
return requestAnimationFrame(showYrc);
}
let lrc = lyrics[lyricIndex][1];
if (lrc === "Loading") {
lrc = "歌词加载中";
} else if (lrc === "Not available") {
if (store.playerYrcATDB) {
//
const lrcUrlw = aplayer.audio[aplayer.index]["lrc"];
const songIdMatchlrc = lrcUrlw.match(/netease.*?id=(.*?)&/);
const songIdlrc = songIdMatchlrc ? songIdMatchlrc[1] : null;
if (songIdlrc) {
const amllUrllrc = `https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/ncm-lyrics/${songIdlrc}.lrc`;
fetch(amllUrllrc)
.then((response) => {
if (response.status === 404 || !response.ok) {
lrc = "歌词加载失败";
return;
} else {
return response.text();
}
})
.catch(() => {
lrc = "歌词加载失败";
});
}
} else {
lrc = "歌词加载失败";
});
};
}
const output = [[true, 1, lyricIndex, 0, lrc]];
if (store.playerLrc.toString() != output.toString()) {
store.setPlayerLrc(output);
}
return requestAnimationFrame(showYrc);
}
//
if (store.playerYrcShowPro) {
if (!webglRenderer) {
const canvas = document.getElementById("lyricsCanvas");
webglRenderer = new WebGLLyricsRenderer(canvas);
}
}
const now = player.value.audioStatus.playedTime * 1000;
const yrcFiltered = store.yrcTemp.filter((i) => i[0] < now);
const yrc1 = document.querySelector(".yrc-1")
if (yrc1 == null) {
return requestAnimationFrame(showYrc);
}
const yrcLyric =
yrcFiltered.length > 0
? yrcFiltered.splice(-1)[0][2].map((it) => {
const [[start, duration], word, line, row] = it;
const isCurrent = now >= start && now <= start + duration;
const isSungLyrics = start + duration < now;
if (!isCurrent) {
};
if (!store.yrcEnable || store.yrcTemp.length == 0 || store.yrcLoading) {
//
let lyricIndex = player.value.aplayer.lyricIndex;
if (lyrics === undefined || lyrics[lyricIndex] === undefined) {
return requestAnimationFrame(showYrc);
}
let lrc = lyrics[lyricIndex][1];
if (lrc === "Loading") {
lrc = "歌词加载中...";
} else if (lrc === "Not available") {
if (store.playerYrcATDB) {
//
const songUrlInfw = new URLSearchParams(new URL(yrcUrl).search)
const songIdlrc = songUrlInfw.get('id')
const songServerlrc = songUrlInfw.get("server");
if (songIdlrc) {
const songUrlInfwurl = {
'netease': `https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/ncm-lyrics/${songIdlrc}.lrc`,
'tencent': `https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/qq-lyrics/${songIdlrc}.lrc`
};
if (!['netease', 'tencent'].includes(songServerlrc)) {
return;
};
const amllUrllrc = songUrlInfwurl[songServerlrc].replace('${songIdlrc}', songIdlrc);
fetch(amllUrllrc)
.then((response) => {
if (response.status === 404 || !response.ok) {
lrc = "歌词加载失败";
return;
} else {
return response.text();
}
})
.catch(() => {
lrc = "歌词加载失败";
});
}
} else {
lrc = "歌词加载失败";
};
}
const output = [[true, 1, lyricIndex, 0, lrc]];
if (store.playerLrc.toString() != output.toString()) {
store.setPlayerLrc(output);
};
return requestAnimationFrame(showYrc);
};
//
const now = player.value.audioStatus.playedTime * 1000;
const yrcFiltered = store.yrcTemp.filter((i) => i[0] < now);
let animationTmp = [];
const yrcLyric =
yrcFiltered.length > 0
? yrcFiltered.slice(-1)[0][2].map((it) => {
const [[start, duration], word, line, row] = it;
const isCurrent = now >= start && now <= start + duration;
const isSungLyrics = start + duration < now;
return [isCurrent, isSungLyrics, line, row, word, "auto"];
}
const thisDom = yrc1.querySelector(`#lrc-char-${line}-${row}`)
if (thisDom == null) {
return [isCurrent, isSungLyrics, line, row, word, "auto"];
}
const x = thisDom.offsetWidth * (now - start) / duration
if (x == null || x == NaN) {
return [isCurrent, isSungLyrics, line, row, word, "auto"];
}
return [isCurrent, isSungLyrics, line, row, word, `${x}px`]
})
: [[true, 1, 0, 0, `${store.playerTitle} - ${store.playerArtist}`]];
if (store.playerLrc.toString() != yrcLyric.toString()) {
store.setPlayerLrc(yrcLyric);
}
if (store.playerYrcShowPro) {
webglRenderer.render(yrcLyric);
}
requestAnimationFrame(showYrc);
}
})
: [[true, 1, 0, 0, `${store.playerTitle} - ${store.playerArtist}`]];
if (store.playerLrc.toString() != yrcLyric.toString()) {
store.setPlayerLrc(yrcLyric);
};
if (store.playerYrcShowPro) {
// N
if (yrcFiltered.length === 0) {
return requestAnimationFrame(showYrc);
};
const lineStart = yrcFiltered.slice(-1)[0][0];
if (nowLineStart == lineStart) {
return requestAnimationFrame(showYrc);
};
const yrc2 = document.getElementsByClassName("yrc-box")[0];
if (yrc2 == undefined) {
return requestAnimationFrame(showYrc);
};
const outputDom = yrc2.querySelectorAll("#yrc-2-wrap span");
const inputDom = yrc2.querySelectorAll("#yrc-1-wrap span");
if (inputDom.length == 0 || outputDom.length == 0) {
return requestAnimationFrame(showYrc);
};
const nowLineWord = yrcFiltered.slice(-1)[0][2];
for (let i = 0; i < nowLineWord.length; i++) {
const [[start, duration], _a, _b, _c] = nowLineWord[i];
const intputItem = inputDom[i];
if (!intputItem || intputItem.hasAttribute('data-start')) {
return requestAnimationFrame(showYrc);
};
const computedStyle = window.getComputedStyle(intputItem);
const width = parseFloat(computedStyle.width);
const outputItem = outputDom[i];
const animateOptions = {
delay: Math.max(0, start - now),
duration: duration,
fill: "forwards",
easing: "linear",
};
outputItem.style.transform = "translateY(-1px)"
const outputAnimate = outputItem.animate(
[
{ width: 0, },
{ width: `${width}px` },
],
animateOptions,
);
animationTmp.push(outputAnimate);
outputAnimate.onfinish = () => {
outputItem.style.transform = "translateY(1px)";
animationTmp = animationTmp.filter(a => a !== outputAnimate);
};
};
nowLineStart = yrcFiltered.slice(-1)[0][0];
};
requestAnimationFrame(showYrc);
} catch (error) {
requestAnimationFrame(showYrc);
};
};
requestAnimationFrame(showYrc);
//

View File

@ -59,7 +59,7 @@
<el-switch v-model="playerYrcShow" inline-prompt :active-icon="CheckSmall" :inactive-icon="CloseSmall" />
</div>
<div v-if="playerLrcShow && playerYrcShow" class="item">
<span class="text">逐字效果增强开关更高的性能要求</span>
<span class="text">逐字效果增强开关</span>
<el-switch v-model="playerYrcShowPro" inline-prompt :active-icon="CheckSmall" :inactive-icon="CloseSmall" />
</div>
</el-collapse-item>

View File

@ -16,7 +16,7 @@
剩余&nbsp;{{ item.remaining }}&nbsp;{{ tag === "day" ? "小时" : "天" }}
</span>
</div>
<el-progress :text-inside="true" :stroke-width="20" :percentage="parseFloat(item.percentage)" />
<el-progress :text-inside="true" :stroke-width="20" :percentage="Number(item.percentage)" />
</div>
<!-- 建站日期 -->
<div v-if="store.siteStartShow" class="capsule-item start">

View File

@ -20,6 +20,9 @@
<script setup>
import { getAdcode, getWeather, getOtherWeather } from "@/api";
import { Error } from "@icon-park/vue-next";
import { mainStore } from "@/store";
import { Speech, stopSpeech, SpeechLocal } from "@/utils/speech";
const store = mainStore();
// Key
const mainKey = import.meta.env.VITE_WEATHER_KEY;

View File

@ -1,6 +1,7 @@
import { createApp } from "vue";
import "@/style/style.scss";
import App from "@/App.vue";
import { mainStore } from "@/store";
import { Speech, stopSpeech, SpeechLocal } from "@/utils/speech";
// 引入 pinia
import { createPinia } from 'pinia';
@ -22,30 +23,32 @@ window.addEventListener("beforeunload", () => {
// 这堆代码原本的意义是在于强制刷新这些本不需要被 pinia 缓存的变量,不知为什么这些变量只会在关闭页面重新输入域名访问才能恢复,导致刷新页面部分模块短时间内出现异常。
// 但是貌似这堆代码也没能解决问题...罢了,先暂且留着叭()
const store = mainStore();
store.imgLoadStatus = false; // 壁纸加载状态
store.innerWidth = null; // 当前窗口宽度
store.musicIsOk = false; // 音乐是否加载完成
store.musicOpenState = false; // 音乐面板开启状态
store.backgroundShow = false; // 壁纸展示状态
store.boxOpenState = false; // 盒子开启状态
store.mobileOpenState = false; // 移动端开启状态
store.mobileFuncState = false; // 移动端功能区开启状态
store.setOpenState = false; // 设置页面开启状态
store.playerState = false; // 当前播放状态
store.playerTitle = null; // 当前播放歌曲名
store.playerArtist = null; // 当前播放歌手名
store.playerLrc = [[true, "猫猫正在翻找歌词..."]]; // 当前播放歌词
store.yrcIndex = -1; // 逐字歌词进度存储
store.yrcTemp = []; // 逐字歌词缓存
store.yrcEnable = true;
store.yrcLoading = false;
Object.assign(store, {
imgLoadStatus: false, // 壁纸加载状态
innerWidth: null, // 当前窗口宽度
musicIsOk: false, // 音乐是否加载完成
musicOpenState: false, // 音乐面板开启状态
backgroundShow: false, // 壁纸展示状态
boxOpenState: false, // 盒子开启状态
mobileOpenState: false, // 移动端开启状态
mobileFuncState: false, // 移动端功能区开启状态
setOpenState: false, // 设置页面开启状态
playerState: false, // 当前播放状态
playerTitle: null, // 当前播放歌曲名
playerArtist: null, // 当前播放歌手名
playerLrc: [[true, "歌词加载中..."]], // 当前播放歌词
yrcIndex: -1, // 逐字歌词进度存储
yrcTemp: [], // 逐字歌词缓存
yrcEnable: true,
yrcLoading: false,
});
});
app.use(pinia);
app.mount("#app");
// PWA
navigator.serviceWorker.addEventListener("controllerchange", () => {
navigator.serviceWorker.addEventListener("controllerchange", async () => {
// 弹出更新提醒
console.log("站点已更新,刷新后生效");
ElMessage("站点已更新,刷新后生效");

View File

@ -24,10 +24,10 @@ export const mainStore = defineStore("main", {
playerAutoplay: true, // 是否自动播放
playerLoop: "all", // 循环播放 "all", "one", "none"
playerOrder: "random", // 循环顺序 "list", "random"
webSpeech: false, // 网页语音交互总开关(包含播报歌名功能)
webSpeech: true, // 网页语音交互总开关(包含播报歌名功能)
playerSpeechName: true, // 播报歌名
playerYrcShow: true, // 逐字歌词解析总开关
playerYrcShowPro: false, // 逐字效果增强开关(更高的性能要求)
playerYrcShowPro: false, // 逐字效果增强开关
playerYrcATDB: true, // 允许接入 AMLL TTML Database
yrcIndex: -1, // 逐字歌词进度存储
yrcTemp: [], // 逐字歌词缓存

View File

@ -110,7 +110,7 @@ export function stopSpeech() {
}
export function SpeechLocal(fileName) {
// 考虑到生成延迟,所以加了这个,仅必要模块调用 api 实时生成,其它模块使用预先生成好的音频
// 考虑到生成延迟,所以加了这个,仅必要模块调用 api 实时生成,其它模块使用预先生成好的音频。记得根据需求更换自己的音频文件哇!
return new Promise((resolve, reject) => {
if (!fileName) {
reject(new Error("No file name provided"));