fork(fix): Clone AMLL 并修复 BUG

- 将AMLL Clone到本以地进行修复和优化(emm虽然这很不优雅但是暂时无时间做子模块和Fork)
- 修复在当前播放歌词行不可见的视口Seek会出现滚动偏移的问题
This commit is contained in:
lqtmcstudio
2026-06-07 00:02:14 +08:00
parent 783d2c3dee
commit 72f4510dc8
458 changed files with 86075 additions and 1665 deletions

View File

@@ -0,0 +1,161 @@
import type { LyricLine } from "@applemusic-like-lyrics/core";
import "@applemusic-like-lyrics/core/style.css";
import { LyricPlayer } from "@applemusic-like-lyrics/react";
import { useLayoutEffect, useRef, useState } from "react";
const buildLyricLines = (
lyric: string,
startTime: number,
otherParams: Partial<LyricLine> = {},
): LyricLine => {
let curTime = startTime;
const words = [];
for (const word of lyric.split("|")) {
const [text, duration] = word.split(",");
const endTime = curTime + Number(duration);
words.push({
word: text,
startTime: curTime,
endTime,
obscene: false,
});
curTime = endTime;
}
return {
startTime,
endTime: curTime + 500,
translatedLyric: "",
romanLyric: "",
isBG: false,
isDuet: false,
words,
...otherParams,
};
};
const DEMO_LYRICS: LyricLine[][] = [
[
buildLyricLines(
"Apple ,350|Music ,300|Like ,300|Ly,500|ri,900|cs ,250",
2000,
{ translatedLyric: "类苹果歌词" },
),
// A lyric component library for the web
buildLyricLines(
"A ,200|ly,100|ric ,250|com,200|po,200|nent ,200|li,100|bra,200|ry ,100|for ,100|the ,200|web ,600",
5000,
{ translatedLyric: "为 Web 而生的歌词组件库" },
),
// Brought to you with
buildLyricLines("Brought ,300|to ,250|you ,800|with ,600", 8000, {
translatedLyric: "为你带来",
}),
// Background Lyric Line
buildLyricLines("Background ,750|lyric ,300|line ,500", 8500, {
translatedLyric: "背景歌词行",
isBG: true,
}),
// And Duet Lyric Line
buildLyricLines("And ,300|Duet ,300|lyric ,500|line ,650", 10500, {
translatedLyric: "还有对唱歌词行",
isDuet: true,
}),
],
];
export const AMLLPreview = () => {
const [lyricLines, setLyricLines] = useState<LyricLine[]>([]);
const [currentTime, setCurrentTime] = useState(0);
const wRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
let selectedDemo = DEMO_LYRICS.length - 1;
let endTime = 0;
let startTime = 0;
let canceled = false;
const onFrame = (time: number) => {
if (canceled) return;
if (time - startTime > endTime) {
const w = wRef.current;
if (!w) {
if (canceled) return;
return;
}
if (canceled) return;
setCurrentTime(0);
w.animate(
{
opacity: 0,
filter: "blur(10px)",
},
{
duration: 500,
easing: "ease-in-out",
fill: "forwards",
},
).onfinish = () => {
if (canceled) return;
selectedDemo = (selectedDemo + 1) % DEMO_LYRICS.length;
setLyricLines(JSON.parse(JSON.stringify(DEMO_LYRICS[selectedDemo])));
endTime = DEMO_LYRICS[selectedDemo].reduce(
(acc, v) => Math.max(acc, v.endTime),
0,
);
startTime = time;
setTimeout(() => {
if (canceled) return;
w.animate(
{
opacity: 1,
filter: "blur(0px)",
},
{
duration: 300,
easing: "ease-in-out",
fill: "forwards",
},
).onfinish = () => {
if (canceled) return;
requestAnimationFrame(onFrame);
};
}, 600);
};
} else {
setCurrentTime((time - startTime) | 0);
requestAnimationFrame(onFrame);
}
};
requestAnimationFrame(onFrame);
return () => {
canceled = true;
};
}, []);
return (
<div
style={{
height: "100%",
maskImage:
"linear-gradient(to bottom, transparent 0%, white 5%, white 95%, transparent 100%)",
transition: "opacity 0.5s, filter 0.5s",
}}
ref={wRef}
>
<LyricPlayer
currentTime={currentTime}
lyricLines={lyricLines}
alignAnchor="top"
alignPosition={0.05}
style={{
height: "100%",
}}
/>
</div>
);
};

View File

@@ -0,0 +1,30 @@
---
import { Code, TabItem, Tabs } from "@astrojs/starlight/components";
interface Props {
packages: string | string[];
dev?: boolean;
syncKey?: string;
}
const { packages, dev = false, syncKey = "pkg" } = Astro.props;
const packageList = Array.isArray(packages)
? packages
: packages.split(/\s+/).filter(Boolean);
const packageNames = packageList.join(" ");
const devFlag = dev ? " -D" : "";
const commands = [
{ label: "npm", command: `npm install${devFlag} ${packageNames}` },
{ label: "pnpm", command: `pnpm add${devFlag} ${packageNames}` },
{ label: "Yarn", command: `yarn add${devFlag} ${packageNames}` },
];
---
<Tabs syncKey={syncKey}>
{commands.map(({ label, command }) => (
<TabItem label={label}>
<Code code={command} lang="sh" />
</TabItem>
))}
</Tabs>