Files
BYW-VoiceHub/vendor/amll-core/src/test.ts
T
Henry_Du 4ce4649aaf
Build VoiceHub FPK / build-fpk (push) Failing after 56m5s
Build and Push Docker Image / build-and-push (push) Canceled after 1h9m35s
初次提交
2026-09-06 08:19:02 +08:00

391 lines
9.9 KiB
TypeScript

/**
* @fileoverview
* 此处是一个简易的组件加载测试脚本,用来调试歌词
*
* @author SteveXMH
*/
import * as lyrics from "@applemusic-like-lyrics/lyric";
import {
parseLrc,
parseLys,
parseQrc,
parseTTML,
parseYrc,
type LyricLine as RawLyricLine,
} from "@applemusic-like-lyrics/lyric";
import GUI from "lil-gui";
import Stats from "stats.js";
import type { LyricLine } from ".";
import {
BackgroundRender,
MeshGradientRenderer,
PixiRenderer,
} from "./bg-render";
import { DomLyricPlayer, type LyricLineMouseEvent } from "./lyric-player";
import type { SpringParams } from "./utils/spring";
(window as any).lyrics = lyrics;
const audio = document.createElement("audio");
audio.volume = 0.5;
audio.preload = "auto";
const debugValues = {
lyric: new URL(location.href).searchParams.get("lyric") || "",
music: new URL(location.href).searchParams.get("music") || "",
album: new URL(location.href).searchParams.get("album") || "",
enableSpring: true,
bgFPS: 60,
bgMode: new URL(location.href).searchParams.get("bg") || "mg",
bgScale: 1,
bgFlowSpeed: 2,
bgPlaying: true,
bgStaticMode: true,
currentTime: 0,
enableBlur: true,
playing: false,
async mockPlay() {
this.playing = true;
const startTime = Date.now();
const baseTime = this.currentTime * 1000;
while (this.playing && this.currentTime < 300) {
const time = Date.now() - startTime;
this.currentTime = (baseTime + time) / 1000;
progress.updateDisplay();
lyricPlayer.setCurrentTime(baseTime + time);
await waitFrame();
}
},
play() {
this.playing = true;
audio.load();
audio.play();
},
pause() {
this.playing = false;
if (audio.paused) {
audio.play();
lyricPlayer.resume();
} else {
audio.pause();
lyricPlayer.pause();
}
},
fadeWidth: 0.5,
lineSprings: {
posX: {
mass: 1,
damping: 10,
stiffness: 100,
soft: false,
} as SpringParams,
posY: {
mass: 1,
damping: 15,
stiffness: 100,
soft: false,
} as SpringParams,
scale: {
mass: 1,
damping: 20,
stiffness: 100,
soft: false,
} as SpringParams,
},
};
function recreateBGRenderer(mode: string) {
window.globalBackground?.dispose();
if (mode === "pixi") {
window.globalBackground = BackgroundRender.new(PixiRenderer);
} else if (mode === "mg") {
window.globalBackground = BackgroundRender.new(MeshGradientRenderer);
} else {
throw new Error("Unknown renderer mode");
}
const bg = window.globalBackground;
bg.setFPS(debugValues.bgFPS);
bg.setRenderScale(debugValues.bgScale);
bg.setStaticMode(debugValues.bgStaticMode);
bg.getElement().style.position = "absolute";
bg.getElement().style.top = "0";
bg.getElement().style.left = "0";
bg.getElement().style.width = "100%";
bg.getElement().style.height = "100%";
bg.setAlbum(debugValues.album);
}
audio.src = debugValues.music;
audio.load();
const gui = new GUI();
gui.close();
gui.title("AMLL 歌词测试页面");
gui
.add(debugValues, "lyric")
.name("歌词文件")
.onFinishChange(async (url: string) => {
lyricPlayer.setLyricLines(
parseTTML(await (await fetch(url)).text()).lines.map(mapTTMLLyric),
);
});
gui
.add(debugValues, "music")
.name("歌曲")
.onFinishChange((v: string) => {
audio.src = v;
});
gui
.add(debugValues, "album")
.name("专辑图片")
.onFinishChange((v: string) => {
window.globalBackground.setAlbum(v);
});
const bgGui = gui.addFolder("背景");
bgGui
.add(debugValues, "bgPlaying")
.name("播放")
.onFinishChange((v: boolean) => {
if (v) {
window.globalBackground.resume();
} else {
window.globalBackground.pause();
}
});
bgGui
.add(debugValues, "bgMode", ["pixi", "mg"])
.name("背景渲染器")
.onFinishChange((v: string) => {
recreateBGRenderer(v);
});
bgGui
.add(debugValues, "bgScale", 0.01, 1, 0.01)
.name("分辨率比率")
.onChange((v: number) => {
window.globalBackground.setRenderScale(v);
});
bgGui
.add(debugValues, "bgFPS", 1, 60, 1)
.name("帧率")
.onFinishChange((v: number) => {
window.globalBackground.setFPS(v);
});
bgGui
.add(debugValues, "bgFlowSpeed", 0, 10, 0.1)
.name("流动速度")
.onFinishChange((v: number) => {
window.globalBackground.setFlowSpeed(v);
});
bgGui
.add(debugValues, "bgStaticMode")
.name("静态模式")
.onFinishChange((v: boolean) => {
window.globalBackground.setStaticMode(v);
});
{
const animation = gui.addFolder("歌词行动画/效果");
animation
.add(debugValues, "fadeWidth", 0, 10, 0.01)
.name("歌词渐变宽度")
.onChange((v: number) => {
lyricPlayer.setWordFadeWidth(v);
});
animation
.add(debugValues, "enableBlur")
.name("启用歌词模糊")
.onChange((v: boolean) => {
lyricPlayer.setEnableBlur(v);
});
animation
.add(debugValues, "enableSpring")
.name("使用弹簧动画")
.onChange((v: boolean) => {
lyricPlayer.setEnableSpring(v);
});
function addSpringDbg(name: string, obj: SpringParams, onChange: () => void) {
const x = animation.addFolder(name);
x.close();
x.add(obj, "mass").name("质量").onFinishChange(onChange);
x.add(obj, "damping").name("阻力").onFinishChange(onChange);
x.add(obj, "stiffness").name("弹性").onFinishChange(onChange);
x.add(obj, "soft")
.name("强制软弹簧(当阻力小于 1 时有用)")
.onFinishChange(onChange);
}
addSpringDbg("水平位移弹簧", debugValues.lineSprings.posX, () => {
lyricPlayer.setLinePosXSpringParams(debugValues.lineSprings.posX);
});
addSpringDbg("垂直位移弹簧", debugValues.lineSprings.posY, () => {
lyricPlayer.setLinePosYSpringParams(debugValues.lineSprings.posY);
});
addSpringDbg("缩放弹簧", debugValues.lineSprings.scale, () => {
lyricPlayer.setLineScaleSpringParams(debugValues.lineSprings.scale);
});
}
const playerGui = gui.addFolder("音乐播放器");
const progress = playerGui
.add(debugValues, "currentTime")
.min(0)
.step(1)
.name("当前进度")
.onChange((v: number) => {
audio.currentTime = v;
lyricPlayer.setCurrentTime(v * 1000, true);
});
playerGui.add(debugValues, "play").name("加载/播放");
playerGui.add(debugValues, "pause").name("暂停/继续");
const lyricPlayer = new DomLyricPlayer();
lyricPlayer.addEventListener("line-click", (evt) => {
const e = evt as LyricLineMouseEvent;
evt.preventDefault();
evt.stopImmediatePropagation();
evt.stopPropagation();
console.log(e.line, e.lineIndex);
audio.currentTime = e.line.getLine().startTime / 1000;
});
const stats = new Stats();
stats.showPanel(0);
document.body.appendChild(stats.dom);
let lastTime = -1;
const frame = (time: number) => {
stats.end();
if (lastTime === -1) {
lastTime = time;
}
if (!audio.paused) {
const time = (audio.currentTime * 1000) | 0;
debugValues.currentTime = (time / 1000) | 0;
progress.max(audio.duration | 0);
progress.updateDisplay();
lyricPlayer.setCurrentTime(time);
}
lyricPlayer.update(time - lastTime);
lastTime = time;
stats.begin();
requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
declare global {
interface Window {
globalLyricPlayer: DomLyricPlayer;
globalBackground:
| BackgroundRender<PixiRenderer>
| BackgroundRender<MeshGradientRenderer>;
}
}
(window as any).globalLyricPlayer = lyricPlayer;
const waitFrame = (): Promise<number> =>
new Promise((resolve) => requestAnimationFrame(resolve));
const mapLyric = (
line: RawLyricLine,
_i: number,
_lines: RawLyricLine[],
): LyricLine => ({
words: line.words.map((word) => ({ obscene: false, romanWord: "", ...word })),
startTime: line.words[0]?.startTime ?? 0,
endTime:
line.words[line.words.length - 1]?.endTime ?? Number.POSITIVE_INFINITY,
translatedLyric: "",
romanLyric: "",
isBG: false,
isDuet: false,
});
const mapTTMLLyric = (line: RawLyricLine): LyricLine => ({
...line,
words: line.words.map((word) => ({ obscene: false, romanWord: "", ...word })),
romanLyric: "",
});
async function loadLyric() {
const lyricFile = debugValues.lyric;
const content = await (await fetch(lyricFile)).text();
if (lyricFile.endsWith(".ttml")) {
lyricPlayer.setLyricLines(parseTTML(content).lines.map(mapTTMLLyric));
} else if (lyricFile.endsWith(".lrc")) {
lyricPlayer.setLyricLines(parseLrc(content).map(mapLyric));
} else if (lyricFile.endsWith(".yrc")) {
lyricPlayer.setLyricLines(parseYrc(content).map(mapLyric));
} else if (lyricFile.endsWith(".lys")) {
lyricPlayer.setLyricLines(parseLys(content).map(mapLyric));
} else if (lyricFile.endsWith(".qrc")) {
lyricPlayer.setLyricLines(parseQrc(content).map(mapLyric));
} else if (lyricFile === "bug") {
const buildLyricLines = (
lyric: string,
startTime = 1000,
otherParams: Partial<LyricLine> = {},
): LyricLine => {
let curTime = startTime;
const words = [];
for (const word of lyric.split("|")) {
const [text, duration] = word.split(",");
const endTime = curTime + Number.parseInt(duration);
words.push({
word: text,
romanWord: "",
startTime: curTime,
endTime,
obscene: false,
});
curTime = endTime;
}
return {
startTime,
endTime: curTime + 3000,
translatedLyric: "",
romanLyric: "",
isBG: false,
isDuet: false,
words,
...otherParams,
};
};
const DEMO_LYRIC: LyricLine[] = [
buildLyricLines(
"Apple ,750|Music ,500|Like ,500|Ly,400|ri,500|cs ,250",
1000,
),
buildLyricLines("BG ,750|Lyrics ,1000", 2000, {
isBG: true,
}),
buildLyricLines("Next ,1000|Lyrics,1000", 2500, {
// isDuet: true,
}),
];
lyricPlayer.setLyricLines(DEMO_LYRIC);
}
}
(async () => {
recreateBGRenderer(debugValues.bgMode);
audio.style.display = "none";
// lyricPlayer.getBottomLineElement().innerHTML = "Test Bottom Line";
const player = document.getElementById("player");
if (player) {
player.appendChild(audio);
player.appendChild(window.globalBackground.getElement());
player.appendChild(lyricPlayer.getElement());
}
if (!debugValues.enableSpring) {
lyricPlayer.setEnableSpring(false);
}
await loadLyric();
// debugValues.play();
// debugValues.currentTime = 34;
// debugValues.mockPlay();
})();