mirror of
https://github.com/imsyy/home.git
synced 2025-05-21 21:50:15 +09:00
弃用依赖替代,逐字样式升级,调用AMLL歌词库默认使用加速镜像源(可在设置内关闭),语音提示添加延迟时间避免洪水
This commit is contained in:
parent
3aa28f6a9c
commit
f18146c54d
35
package.json
35
package.json
@ -16,16 +16,15 @@
|
||||
"dependencies": {
|
||||
"@worstone/vue-aplayer": "^1.0.7",
|
||||
"aplayer": "^1.10.1",
|
||||
"axios": "^1.7.8",
|
||||
"axios": "^1.7.9",
|
||||
"dayjs": "^1.11.13",
|
||||
"element-plus": "^2.8.8",
|
||||
"element-plus": "^2.9.1",
|
||||
"fetch-jsonp": "^1.3.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"pinia": "^2.2.7",
|
||||
"pinia-plugin-persistedstate": "^4.1.3",
|
||||
"pinia": "^2.3.0",
|
||||
"pinia-plugin-persistedstate-2": "^2.0.27",
|
||||
"swiper": "^11.1.15",
|
||||
"three": "^0.170.0",
|
||||
"three": "^0.171.0",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -37,15 +36,23 @@
|
||||
"@vicons/tabler": "^0.12.0",
|
||||
"@vicons/utils": "^0.1.4",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-plugin-vue": "^9.31.0",
|
||||
"prettier": "^3.4.1",
|
||||
"sass": "^1.81.0",
|
||||
"terser": "^5.36.0",
|
||||
"unplugin-auto-import": "^0.18.6",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"prettier": "^3.4.2",
|
||||
"sass": "^1.83.0",
|
||||
"terser": "^5.37.0",
|
||||
"unplugin-auto-import": "^0.19.0",
|
||||
"unplugin-vue-components": "^0.27.5",
|
||||
"vite": "^5.4.11",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-pwa": "^0.20.5"
|
||||
"vite": "^6.0.3",
|
||||
"vite-plugin-compression2": "^1.3.3",
|
||||
"vite-plugin-pwa": "^0.21.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"glob": "^9.0.1",
|
||||
"@jridgewell/sourcemap-codec":"^1.5.0",
|
||||
"magic-string":"^0.30.15",
|
||||
"workbox-build":"^7.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1735
pnpm-lock.yaml
generated
1735
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -134,7 +134,7 @@ const siteUrl = computed(() => {
|
||||
|
||||
&.fade-in-start {
|
||||
text-shadow: 0px 0px 2px rgba(255, 240, 245, 1);
|
||||
opacity: 0.6;
|
||||
opacity: 0.6; // 初始显示的透明度
|
||||
transform: translateY(1px);
|
||||
transition:
|
||||
color 0.5s linear,
|
||||
@ -238,7 +238,7 @@ const siteUrl = computed(() => {
|
||||
}
|
||||
|
||||
to {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: rgba(255, 240, 245, 1);
|
||||
opacity: 1;
|
||||
text-shadow: 3px 3px 7px rgba(255, 240, 245, 1),
|
||||
0px 0px 12px rgba(255, 182, 193, 1),
|
||||
@ -259,7 +259,8 @@ const siteUrl = computed(() => {
|
||||
position: absolute;
|
||||
width: auto;
|
||||
opacity: 0.6;
|
||||
text-shadow: 0 0 6px rgba(255, 240, 245, 1),
|
||||
color: rgba(255, 240, 245, 1);
|
||||
text-shadow: 0 0 6px rgba(0, 191, 255, 1),
|
||||
0px 0px 2px rgba(176, 224, 230, 1),
|
||||
0px 0px 2px rgba(230, 230, 250, 1);
|
||||
font-family: MiSans-Regular;
|
||||
@ -278,7 +279,7 @@ const siteUrl = computed(() => {
|
||||
opacity: 1;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.8),
|
||||
text-shadow: 0 0 6px rgba(255, 240, 245, 1),
|
||||
0 0 2px rgba(255, 165, 0, 1),
|
||||
0 0 2px rgba(255, 179, 71, 1);
|
||||
font-family: MiSans-Regular;
|
||||
@ -290,6 +291,7 @@ const siteUrl = computed(() => {
|
||||
// End
|
||||
|
||||
|
||||
|
||||
#footer {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
|
@ -2,11 +2,11 @@
|
||||
<APlayer v-if="playList[0]" ref="player" :audio="playList" :autoplay="store.playerAutoplay" :theme="theme"
|
||||
:autoSwitch="false" :loop="store.playerLoop" :order="store.playerOrder" :volume="volume" :showLrc="true"
|
||||
:listFolded="listFolded" :listMaxHeight="listMaxHeight" :noticeSwitch="false" @play="onPlay" @pause="onPause"
|
||||
@error="loadMusicError" />
|
||||
@timeupdate="onTimeUp" @error="loadMusicError" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { MusicOne, PlayWrong } from "@icon-park/vue-next";
|
||||
import { Float, MusicOne, PlayWrong } from "@icon-park/vue-next";
|
||||
import { getPlayerList } from "@/api";
|
||||
import { mainStore } from "@/store";
|
||||
import APlayer from "@worstone/vue-aplayer";
|
||||
@ -14,7 +14,9 @@ import { Speech, stopSpeech, SpeechLocal } from "@/utils/speech";
|
||||
import { decodeYrc } from "../utils/decodeYrc";
|
||||
|
||||
const store = mainStore();
|
||||
let showYrcRunning = 0;
|
||||
let lastTimestamp = Date.now();
|
||||
let nowLineStart = -1;
|
||||
|
||||
// 获取播放器 DOM
|
||||
const player = ref(null);
|
||||
@ -173,10 +175,10 @@ const onPause = () => {
|
||||
store.setPlayerState(player.value.audioRef.paused);
|
||||
};
|
||||
|
||||
let nowLineStart = -1
|
||||
// 音频时间更新事件
|
||||
function showYrc() {
|
||||
// 至于为什么所有源的逐字都叫 YRC 呢...因为逐字功能本来是打算写网易云音乐独占的,但好像有些偏心了(bushi)...看了一下 qrc 和 yrc 没什么大区别,就顺带捏一起了。但是就懒得改变量名了!
|
||||
showYrcRunning = 1;
|
||||
try {
|
||||
// 至于为什么要 try 呢?问得好!因为网易云接口时不时会返回一些令人费解的东西,比如没有时间轴、时间轴为负数([20720,-4200])、时间轴乱码,这些东西会造成模块直接卡死,除非刷新页面。暂时没有那么多纠错逻辑,为了防止模块死掉,就先加个 try 在这里复活自己。咕咕咕!
|
||||
if (player.value == null) {
|
||||
@ -221,10 +223,15 @@ function showYrc() {
|
||||
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`
|
||||
};
|
||||
const songUrlInfUrl = store.playerYrcATDBF
|
||||
? {
|
||||
'netease': `https://ghp.ci/https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/ncm-lyrics/${songId}.yrc`,
|
||||
'tencent': `https://ghp.ci/https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/qq-lyrics/${songId}.qrc`
|
||||
}
|
||||
: {
|
||||
'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;
|
||||
};
|
||||
@ -263,10 +270,15 @@ function showYrc() {
|
||||
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`
|
||||
};
|
||||
const songUrlInfwurl = store.playerYrcATDBF
|
||||
? {
|
||||
'netease': `https://ghp.ci/https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/ncm-lyrics/${songId}.lrc`,
|
||||
'tencent': `https://ghp.ci/https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/qq-lyrics/${songId}.lrc`
|
||||
}
|
||||
: {
|
||||
'netease': `https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/ncm-lyrics/${songId}.lrc`,
|
||||
'tencent': `https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/main/qq-lyrics/${songId}.lrc`
|
||||
};
|
||||
if (!['netease', 'tencent'].includes(songServerlrc)) {
|
||||
return;
|
||||
};
|
||||
@ -365,13 +377,18 @@ function showYrc() {
|
||||
};
|
||||
nowLineStart = yrcFiltered.slice(-1)[0][0];
|
||||
};
|
||||
requestAnimationFrame(showYrc);
|
||||
return requestAnimationFrame(showYrc);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return requestAnimationFrame(showYrc);
|
||||
};
|
||||
};
|
||||
|
||||
const onTimeUp = () => {
|
||||
if (showYrcRunning == 0) {
|
||||
requestAnimationFrame(showYrc);
|
||||
};
|
||||
};
|
||||
requestAnimationFrame(showYrc);
|
||||
|
||||
// 切换播放暂停事件
|
||||
const playToggle = () => {
|
||||
@ -479,6 +496,7 @@ defineExpose({ playToggle, changeVolume, changeSong, toggleList });
|
||||
#fff 85%,
|
||||
hsla(0deg, 0%, 100%, 0.6) 90%,
|
||||
hsla(0deg, 0%, 100%, 0));
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
display: none;
|
||||
|
@ -54,6 +54,10 @@
|
||||
不稳定的网络中可能导致歌词载入速度变慢)</span>
|
||||
<el-switch v-model="playerYrcATDB" inline-prompt :active-icon="CheckSmall" :inactive-icon="CloseSmall" />
|
||||
</div>
|
||||
<div v-if="playerLrcShow && playerYrcATDB" class="item">
|
||||
<span class="text" white-space="pre">调用 AMLL TTML Database 时使用镜像加速</span>
|
||||
<el-switch v-model="playerYrcATDBF" inline-prompt :active-icon="CheckSmall" :inactive-icon="CloseSmall" />
|
||||
</div>
|
||||
<div v-if="playerLrcShow" class="item">
|
||||
<span class="text">逐字歌词解析总开关</span>
|
||||
<el-switch v-model="playerYrcShow" inline-prompt :active-icon="CheckSmall" :inactive-icon="CloseSmall" />
|
||||
@ -101,6 +105,7 @@ const {
|
||||
playerYrcShow,
|
||||
playerYrcShowPro,
|
||||
playerYrcATDB,
|
||||
playerYrcATDBF,
|
||||
} = storeToRefs(store);
|
||||
|
||||
// 默认选中项
|
||||
|
@ -29,6 +29,7 @@ export const mainStore = defineStore("main", {
|
||||
playerYrcShow: true, // 逐字歌词解析总开关
|
||||
playerYrcShowPro: false, // 逐字效果增强开关
|
||||
playerYrcATDB: true, // 允许接入 AMLL TTML Database
|
||||
playerYrcATDBF: true, // 接入 AMLL TTML Database 时使用镜像加速
|
||||
yrcIndex: -1, // 逐字歌词进度存储
|
||||
yrcTemp: [], // 逐字歌词缓存
|
||||
yrcEnable: true,
|
||||
@ -100,6 +101,7 @@ export const mainStore = defineStore("main", {
|
||||
"playerYrcShow",
|
||||
"playerYrcShowPro",
|
||||
"playerYrcATDB",
|
||||
"playerYrcATDBF",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
@ -1,13 +1,14 @@
|
||||
let currentAudio = null;
|
||||
let audioQueue = [];
|
||||
let isPlaying = false;
|
||||
let controller = null; // 用于取消请求
|
||||
let controller = null;
|
||||
let timeoutId = null;
|
||||
|
||||
/**
|
||||
* Speech
|
||||
* Made by NanoRocky
|
||||
* 使用指定参数生成语音并播放音频。
|
||||
* 该功能原为 Azure 设计,理应兼容大部分使用 post 传参的 api 。请自行根据要求修改!如果也使用 Azure ,您可直接使用 https://github.com/NanoRocky/AzureSpeechAPI-by-PHP 完成 API 部署
|
||||
* 该功能原为 Azure 设计,理应兼容大部分使用 post 传参的 api 。请自行根据要求修改!
|
||||
* https://learn.microsoft.com/zh-cn/azure/ai-services/speech-service/speech-synthesis-markup-voice
|
||||
*
|
||||
* @param {string} text - 朗读的文本
|
||||
@ -16,6 +17,7 @@ let controller = null; // 用于取消请求
|
||||
* @param {string} [role="Boy"] - 讲话角色扮演(默认为“Boy”)
|
||||
* @param {string} [rate="1"] - 语速(默认为“1”)
|
||||
* @param {string} [volume="100"] - 音量(默认为“100”)
|
||||
* @param {number} [delay=300] - 等待时间【毫秒】后发出请求,防止频繁点击产生请求洪水(默认为等待300毫秒)
|
||||
* @returns {Promise<void>} - 一个 Promise,在语音播放完成时解析或出现错误时拒绝
|
||||
*/
|
||||
export function Speech(
|
||||
@ -25,15 +27,24 @@ export function Speech(
|
||||
role = "Boy",
|
||||
rate = "1",
|
||||
volume = "100",
|
||||
delay = 300,
|
||||
) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 如果有现有的等待,取消之前的 timeout
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
};
|
||||
if (currentAudio) {
|
||||
currentAudio.pause();
|
||||
currentAudio = null;
|
||||
};
|
||||
// 创建新的 AbortController 实例,并中断旧请求
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
}
|
||||
};
|
||||
controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("text", text);
|
||||
formData.append("voice", voice);
|
||||
@ -41,26 +52,136 @@ export function Speech(
|
||||
formData.append("role", role);
|
||||
formData.append("rate", rate);
|
||||
formData.append("volume", volume);
|
||||
// 在指定的 delay 后开始请求
|
||||
timeoutId = setTimeout(async () => {
|
||||
try {
|
||||
const speechapi = import.meta.env.VITE_TTS_API;
|
||||
const response = await fetch(speechapi, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
signal, // 传递 AbortSignal
|
||||
});
|
||||
|
||||
try {
|
||||
const speechapi = import.meta.env.VITE_TTS_API;
|
||||
const response = await fetch(speechapi, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
signal, // 传递 AbortSignal
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error);
|
||||
};
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error);
|
||||
const blob = await response.blob();
|
||||
const audioUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 将新的音频对象添加到队列
|
||||
audioQueue.push(audioUrl);
|
||||
|
||||
if (!isPlaying) {
|
||||
playNext();
|
||||
};
|
||||
|
||||
function playNext() {
|
||||
if (audioQueue.length === 0) {
|
||||
isPlaying = false;
|
||||
return;
|
||||
};
|
||||
|
||||
isPlaying = true;
|
||||
|
||||
const nextAudioUrl = audioQueue.shift();
|
||||
if (currentAudio) {
|
||||
currentAudio.pause();
|
||||
currentAudio = null;
|
||||
};
|
||||
|
||||
const audio = new Audio();
|
||||
audio.src = nextAudioUrl;
|
||||
audio.play();
|
||||
|
||||
// 在音频播放结束时解析 Promise
|
||||
audio.onended = () => {
|
||||
resolve();
|
||||
playNext();
|
||||
};
|
||||
|
||||
// 如果发生错误,拒绝 Promise
|
||||
audio.onerror = (error) => {
|
||||
reject(error);
|
||||
playNext();
|
||||
};
|
||||
|
||||
// 将当前播放的语音赋值给全局变量
|
||||
currentAudio = audio;
|
||||
};
|
||||
} catch (error) {
|
||||
if (error.name === "AbortError") {
|
||||
console.log("Request canceled");
|
||||
} else {
|
||||
console.error("Error:", error.message);
|
||||
reject(error);
|
||||
};
|
||||
};
|
||||
}, delay);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止当前播放的语音,并清空播放队列。
|
||||
*/
|
||||
export function stopSpeech() {
|
||||
if (currentAudio) {
|
||||
currentAudio.pause();
|
||||
currentAudio = null;
|
||||
};
|
||||
audioQueue = [];
|
||||
isPlaying = false;
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
controller = null;
|
||||
};
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* SpeechLocal
|
||||
* Made by NanoRocky
|
||||
* 播放本地预生成的语音音频。
|
||||
* 考虑到生成延迟,所以加了这个,仅必要模块调用 api 实时生成,其它模块使用预先生成好的音频。记得根据需求更换自己的音频文件哇!
|
||||
*
|
||||
* @param {string} fileName - 音频文件名 + 文件拓展名(请将文件放在指定路径)
|
||||
* @param {number} [delay=0] - 等待时间【毫秒】后发出请求,防止频繁点击产生请求洪水(默认提前生成的不等待)
|
||||
* @returns {Promise<void>} - 一个 Promise,在语音播放完成时解析或出现错误时拒绝
|
||||
*/
|
||||
export function SpeechLocal(fileName, delay = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!fileName) {
|
||||
reject(new Error("No file name provided"));
|
||||
return;
|
||||
}
|
||||
|
||||
const audioUrl = `https://file.nanorocky.top/home/speechlocal/${fileName}`;
|
||||
|
||||
// 如果有现有的等待,取消之前的 timeout
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
};
|
||||
// 清除之前的音频
|
||||
if (currentAudio) {
|
||||
currentAudio.pause();
|
||||
currentAudio = null;
|
||||
}
|
||||
timeoutId = setTimeout(async () => {
|
||||
// 停止当前正在播放的语音
|
||||
audioQueue = [];
|
||||
isPlaying = false;
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
controller = null;
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const audioUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 将新的音频对象添加到队列
|
||||
// 添加新音频到队列并播放
|
||||
audioQueue.push(audioUrl);
|
||||
|
||||
if (!isPlaying) {
|
||||
playNext();
|
||||
}
|
||||
@ -74,14 +195,14 @@ export function Speech(
|
||||
isPlaying = true;
|
||||
|
||||
const nextAudioUrl = audioQueue.shift();
|
||||
if (currentAudio) {
|
||||
currentAudio.pause();
|
||||
currentAudio = null;
|
||||
}
|
||||
|
||||
const audio = new Audio();
|
||||
audio.src = nextAudioUrl;
|
||||
audio.play();
|
||||
|
||||
// 确保新的音频对象没有被中途替换
|
||||
audio.oncanplaythrough = () => {
|
||||
currentAudio = audio;
|
||||
currentAudio.play();
|
||||
};
|
||||
|
||||
// 在音频播放结束时解析 Promise
|
||||
audio.onended = () => {
|
||||
@ -94,104 +215,7 @@ export function Speech(
|
||||
reject(error);
|
||||
playNext();
|
||||
};
|
||||
|
||||
// 将当前播放的语音赋值给全局变量
|
||||
currentAudio = audio;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.name === "AbortError") {
|
||||
console.log("Request canceled");
|
||||
} else {
|
||||
console.error("Error:", error.message);
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, delay);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止当前播放的语音,并清空播放队列。
|
||||
*/
|
||||
export function stopSpeech() {
|
||||
if (currentAudio) {
|
||||
currentAudio.pause();
|
||||
currentAudio = null;
|
||||
}
|
||||
audioQueue = [];
|
||||
isPlaying = false;
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
controller = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SpeechLocal
|
||||
* Made by NanoRocky
|
||||
* 播放本地预生成的语音音频。
|
||||
* 考虑到生成延迟,所以加了这个,仅必要模块调用 api 实时生成,其它模块使用预先生成好的音频。记得根据需求更换自己的音频文件哇!
|
||||
*
|
||||
* @param {string} fileName - 音频文件名 + 文件拓展名(请将文件放在指定路径)
|
||||
* @returns {Promise<void>} - 一个 Promise,在语音播放完成时解析或出现错误时拒绝
|
||||
*/
|
||||
export function SpeechLocal(fileName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!fileName) {
|
||||
reject(new Error("No file name provided"));
|
||||
return;
|
||||
}
|
||||
|
||||
const audioUrl = `/speechlocal/${fileName}`;
|
||||
|
||||
// 清除之前的音频
|
||||
if (currentAudio) {
|
||||
currentAudio.pause();
|
||||
currentAudio = null;
|
||||
}
|
||||
|
||||
// 停止当前正在播放的语音
|
||||
audioQueue = [];
|
||||
isPlaying = false;
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
controller = null;
|
||||
}
|
||||
|
||||
// 添加新音频到队列并播放
|
||||
audioQueue.push(audioUrl);
|
||||
if (!isPlaying) {
|
||||
playNext();
|
||||
}
|
||||
|
||||
function playNext() {
|
||||
if (audioQueue.length === 0) {
|
||||
isPlaying = false;
|
||||
return;
|
||||
}
|
||||
|
||||
isPlaying = true;
|
||||
|
||||
const nextAudioUrl = audioQueue.shift();
|
||||
const audio = new Audio();
|
||||
audio.src = nextAudioUrl;
|
||||
|
||||
// 确保新的音频对象没有被中途替换
|
||||
audio.oncanplaythrough = () => {
|
||||
currentAudio = audio;
|
||||
currentAudio.play();
|
||||
};
|
||||
|
||||
// 在音频播放结束时解析 Promise
|
||||
audio.onended = () => {
|
||||
resolve();
|
||||
playNext();
|
||||
};
|
||||
|
||||
// 如果发生错误,拒绝 Promise
|
||||
audio.onerror = (error) => {
|
||||
reject(error);
|
||||
playNext();
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ import { VitePWA } from "vite-plugin-pwa";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import viteCompression from "vite-plugin-compression";
|
||||
import viteCompression from "vite-plugin-compression2";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default ({ mode }) =>
|
||||
@ -107,7 +107,7 @@ export default ({ mode }) =>
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
charset: false,
|
||||
additionalData: `@use "./src/style/global.scss" as global;`,
|
||||
additionalData: `@use "@/style/global.scss" as global;`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user