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,24 @@
<!DOCTYPE html>
<html>
<head>
<title>AMLL React Wrapper Test</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="src/test.tsx" type="module" defer></script>
<style>
:root {
font-family:
-apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC", system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
body {
background: #222;
margin: 0;
}
</style>
</head>
<body style="height: 100vh; overflow: hidden;">
<div id="root" style="height: 100vh; overflow: hidden;"></div>
</body>
</html>

View File

@@ -0,0 +1,31 @@
{
"name": "@applemusic-like-lyrics/playground-react",
"private": true,
"version": "0.0.0",
"type": "module",
"nx": {
"tags": [
"playground"
]
},
"scripts": {
"dev": "vite dev"
},
"devDependencies": {
"@applemusic-like-lyrics/core": "workspace:^",
"@applemusic-like-lyrics/lyric": "workspace:^",
"@applemusic-like-lyrics/react": "workspace:^",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"vite": "catalog:",
"@pixi/app": "^7.4.3",
"@pixi/core": "^7.4.3",
"@pixi/display": "^7.4.3",
"@pixi/filter-blur": "^7.4.3",
"@pixi/filter-color-matrix": "^7.4.3",
"@pixi/sprite": "^7.4.3"
}
}

View File

@@ -0,0 +1,165 @@
import type { LyricLine } from "@applemusic-like-lyrics/core";
import {
parseTTML,
type LyricLine as RawLyricLine,
} from "@applemusic-like-lyrics/lyric";
import {
BackgroundRender,
LyricPlayer,
type LyricPlayerRef,
} from "@applemusic-like-lyrics/react";
import type { FC } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
const mapTTMLLyric = (line: RawLyricLine): LyricLine => ({
...line,
words: line.words.map((word) => ({ obscene: false, ...word })),
});
export const App: FC = () => {
const audioRef = useRef<HTMLAudioElement>(null);
const [audioUrl, setAudioUrl] = useState("");
const [albumUrl, setAlbumUrl] = useState("");
const [albumIsVideo, setAlbumIsVideo] = useState(false);
const lyricPlayerRef = useRef<LyricPlayerRef>(null);
const [lyricLines, setLyricLines] = useState<LyricLine[]>([]);
const onClickOpenAudio = useCallback(() => {
const input = document.createElement("input");
input.type = "file";
input.accept = "audio/*";
input.onchange = () => {
const file = input.files?.[0];
if (file) {
setAudioUrl((old) => {
if (old.trim().length > 0) {
URL.revokeObjectURL(old);
}
return URL.createObjectURL(file);
});
}
};
input.click();
}, []);
const onClickOpenAlbumImage = useCallback(() => {
const input = document.createElement("input");
input.type = "file";
input.accept = "image/*,video/*";
input.onchange = () => {
const file = input.files?.[0];
if (file) {
setAlbumIsVideo(file.type.startsWith("video/"));
setAlbumUrl((old) => {
if (old.trim().length > 0) {
URL.revokeObjectURL(old);
}
return URL.createObjectURL(file);
});
}
};
input.click();
}, []);
const onClickOpenTTMLLyric = useCallback(() => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".ttml,text/*";
input.onchange = async () => {
const file = input.files?.[0];
if (file) {
const text = await file.text();
setLyricLines(parseTTML(text).lines.map(mapTTMLLyric));
}
};
input.click();
}, []);
useEffect(() => {
if (audioRef.current) {
let lastTime = -1;
const onFrame = (time: number) => {
if (audioRef.current && !audioRef.current.paused) {
if (lastTime === -1) {
lastTime = time;
}
lyricPlayerRef.current?.lyricPlayer?.update(time - lastTime);
lastTime = time;
lyricPlayerRef.current?.lyricPlayer?.setCurrentTime(
(audioRef.current.currentTime * 1000) | 0,
);
requestAnimationFrame(onFrame);
}
};
const onPlay = () => onFrame(0);
audioRef.current.addEventListener("play", onPlay);
return () => {
audioRef.current?.removeEventListener("play", onPlay);
};
}
return;
}, []);
useEffect(() => {
if (lyricPlayerRef.current) {
// biome-ignore lint/suspicious/noExplicitAny: 调试用途,暴露到 Window
(window as any).lyricPlayer = lyricPlayerRef.current;
}
}, []);
return (
<>
<BackgroundRender
style={{
position: "absolute",
top: "0",
left: "0",
width: "100%",
height: "100%",
}}
album={albumUrl}
albumIsVideo={albumIsVideo}
/>
<LyricPlayer
style={{
position: "absolute",
top: "0",
left: "0",
width: "100%",
height: "100%",
maxWidth: "100%",
maxHeight: "100%",
contain: "paint layout",
overflow: "hidden",
mixBlendMode: "plus-lighter",
}}
ref={lyricPlayerRef}
alignAnchor="center"
lyricLines={lyricLines}
/>
<div
style={{
position: "absolute",
right: "0",
bottom: "0",
backgroundColor: "#0004",
margin: "1rem",
padding: "1rem",
borderRadius: "0.5rem",
color: "white",
display: "flex",
flexDirection: "column",
gap: "0.5rem",
}}
>
<div>AMLL React </div>
<div></div>
<div>使 Core </div>
<button type="button" onClick={onClickOpenAudio}>
</button>
<button type="button" onClick={onClickOpenAlbumImage}>
/
</button>
<button type="button" onClick={onClickOpenTTMLLyric}>
</button>
<audio controls ref={audioRef} src={audioUrl} preload="auto" />
</div>
</>
);
};

View File

@@ -0,0 +1,9 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./test-app";
createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<App />
</StrictMode>,
);

View File

@@ -0,0 +1,4 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src"]
}

View File

@@ -0,0 +1,24 @@
import path from "node:path";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
resolve: {
alias: {
"@applemusic-like-lyrics/core/style.css": path.resolve(
__dirname,
"../../core/src/styles/index.css",
),
"@applemusic-like-lyrics/react": path.resolve(
__dirname,
"../../react/src",
),
"@applemusic-like-lyrics/core": path.resolve(__dirname, "../../core/src"),
"@applemusic-like-lyrics/lyric": path.resolve(
__dirname,
"../../lyric/src",
),
},
},
plugins: [react()],
});