198 lines
5.7 KiB
TypeScript
198 lines
5.7 KiB
TypeScript
"use client";
|
|
import { useRef, useState, useEffect, useContext } from "react";
|
|
import { query } from "@/lib/actions/lyrics";
|
|
import { DanmuContext } from "./provider";
|
|
|
|
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 Controls(): JSX.Element {
|
|
const ref = useRef<HTMLDialogElement | null>(null);
|
|
const [history, setHistory] = useState<LyricItem[]>([]);
|
|
const { songId, songName, songArtist, listener } = useContext(DanmuContext);
|
|
|
|
const { start, resume, pause, setLyrics, offset, setOffset } =
|
|
useLyricsRunner((line) => {
|
|
setHistory((items) => [
|
|
{
|
|
timestamp: line.slice(0, 10),
|
|
content: line.slice(10),
|
|
},
|
|
...items,
|
|
]);
|
|
if (listener) {
|
|
// 测试用
|
|
// listener('666');
|
|
// 实际输出歌词
|
|
listener(line.slice(10));
|
|
}
|
|
console.log(line);
|
|
}, 200);
|
|
|
|
return (
|
|
<div className="flex justify-center p-4 border-t border-base-300">
|
|
<div className="columns-1">
|
|
<div className="w-full">
|
|
<div className="join">
|
|
<button
|
|
className="btn join-item btn-sm"
|
|
type="button"
|
|
onClick={() => {
|
|
pause();
|
|
}}
|
|
>
|
|
暂停
|
|
</button>
|
|
<button
|
|
className="btn join-item btn-sm"
|
|
type="button"
|
|
onClick={() => {
|
|
resume();
|
|
}}
|
|
>
|
|
恢复
|
|
</button>
|
|
<button
|
|
className="btn join-item btn-sm"
|
|
type="button"
|
|
onClick={() => {
|
|
setOffset(offset + 1000);
|
|
}}
|
|
>
|
|
+1秒
|
|
</button>
|
|
<button
|
|
className="btn join-item btn-sm"
|
|
type="button"
|
|
onClick={() => {
|
|
setOffset(offset - 1000);
|
|
}}
|
|
>
|
|
-1秒
|
|
</button>
|
|
<button
|
|
className="btn join-item btn-sm"
|
|
onClick={() => {
|
|
query({
|
|
name: songName,
|
|
artist: songArtist,
|
|
})
|
|
.then((result) => {
|
|
// eslint-disable-next-line no-console -- temp
|
|
console.log(result);
|
|
|
|
if (result) {
|
|
setLyrics(result.lyrics.split("\n"));
|
|
setHistory([]);
|
|
start();
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
// eslint-disable-next-line no-console -- temp
|
|
console.error(err);
|
|
});
|
|
}}
|
|
type="button"
|
|
disabled={!songId}
|
|
>
|
|
开始
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div
|
|
className="w-full mt-1 pt-0 pb-1 rounded-xl bg-gray-100 border-t-gray-100"
|
|
role="alert"
|
|
>
|
|
<div className="text-center">当前延迟 {offset / 1000} 秒</div>
|
|
</div>
|
|
<div className="w-full h-[20vh] overflow-auto">
|
|
<ul className="timeline timeline-vertical">
|
|
{history.map((item, i) => (
|
|
<li key={i}>
|
|
<div className="timeline-middle">{item.timestamp}{item.content}</div>
|
|
{/* <div className="timeline-end">
|
|
<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> */}
|
|
<hr />
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|