danmu-sim/app/danmu/page.tsx

192 lines
5.6 KiB
TypeScript
Raw Normal View History

2024-04-06 02:12:38 +08:00
/* eslint-disable no-console -- debug */
"use client";
import { useRef, useState, useEffect } from "react";
import { query } from "@/app/lib/actions/lyrics";
const useLyricsRunner = (runner: (lyric: string) => void, interval: number) => {
const [lyrics, setLyrics] = useState<string[]>([]);
const [offset, setOffset] = useState<number>(0);
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
const startTime = useRef<number>(Date.now());
const ref = useRef<{ lyrics: string[]; offset: number }>({ lyrics, offset });
const clearTimer = (): void => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
const doLyric = (): void => {
console.log("testing ...", ref.current.lyrics.length);
if (ref.current.lyrics.length > 0) {
const currentTime = Date.now() - startTime.current;
const found = ref.current.lyrics.find((lyric) => {
const m = Number(lyric.slice(1, 3));
const s = Number(lyric.slice(4, 6));
const ms10 = Number(lyric.slice(7, 9));
const ms = (m * 60 + s) * 1000 + ms10 * 10;
return (
currentTime > ms + ref.current.offset &&
ms + ref.current.offset > currentTime - interval
);
});
if (found) {
runner(found);
}
}
};
useEffect(() => {
ref.current.offset = offset;
ref.current.lyrics = lyrics;
}, [offset, lyrics]);
const start = (): void => {
clearTimer();
startTime.current = Date.now();
timerRef.current = setInterval(doLyric, interval);
};
const pause = (): void => {
clearTimer();
};
const resume = (): void => {
clearTimer();
timerRef.current = setInterval(doLyric, interval);
};
return {
offset,
setOffset,
setLyrics,
resume,
start,
pause,
};
};
interface LyricItem {
timestamp: string;
content: string;
}
export default function Page(): JSX.Element {
const ref = useRef<HTMLDialogElement | null>(null);
const [history, setHistory] = useState<LyricItem[]>([]);
const { start, resume, pause, setLyrics, offset, setOffset } =
useLyricsRunner((line) => {
setHistory((items) => [
{
content: line.slice(0, 10),
timestamp: line.slice(10),
},
...items,
]);
console.log(line);
}, 200);
return (
<div className="mockup-browser border">
<div className="mockup-browser-toolbar">
<div className="input"></div>
</div>
<div className="flex justify-center px-4 py-16 border-t border-base-300">
<div className="columns-1">
<div className="w-full">
<div className="join">
<div className="stat join-item"> {offset / 1000} </div>
<button
className="btn join-item"
type="button"
onClick={() => {
pause();
}}
>
</button>
<button
className="btn join-item"
type="button"
onClick={() => {
resume();
}}
>
</button>
<button
className="btn join-item"
type="button"
onClick={() => {
setOffset(offset + 1000);
}}
>
+1
</button>
<button
className="btn join-item"
type="button"
onClick={() => {
setOffset(offset - 1000);
}}
>
-1
</button>
<button
className="btn join-item"
onClick={() => {
query({
name: "红豆",
})
.then((result) => {
// eslint-disable-next-line no-console -- temp
console.log(result);
if (result) {
setLyrics(result.lyrics.split("\n"));
2024-04-06 02:23:18 +08:00
setHistory([]);
2024-04-06 02:12:38 +08:00
start();
}
})
.catch((err) => {
// eslint-disable-next-line no-console -- temp
console.error(err);
});
}}
type="button"
>
</button>
</div>
</div>
<div className="w-full mt-8">
<ul className="timeline timeline-vertical">
{history.map((item, i) => (
<li key={i}>
<div className="timeline-start">{item.timestamp}</div>
<div className="timeline-middle">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="w-5 h-5"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="timeline-end timeline-box">
{item.content}
</div>
<hr />
</li>
))}
</ul>
</div>
</div>
</div>
</div>
);
}