feat: 支持弹幕显示

This commit is contained in:
sealday 2024-04-05 17:00:16 +08:00
parent 5c6b5b9761
commit 105f23d8a2
8 changed files with 246 additions and 145 deletions

View File

@ -1,33 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* :root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
} */

View File

@ -15,7 +15,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="zh_CN" data-theme="light">
<body className={inter.className}>{children}</body>
</html>
);

96
app/music/danmu.tsx Normal file
View File

@ -0,0 +1,96 @@
'use client';
import { useState, useEffect, useCallback } from "react";
import useWebSocket, { ReadyState } from 'react-use-websocket';
const MOMO_IM_URL = 'wss://live-ws.immomo.com/ws/im'
export const Danmu: React.FC = () => {
//Public API that will echo messages sent to it back to the client
const [socketUrl, setSocketUrl] = useState(MOMO_IM_URL);
const [messageHistory, setMessageHistory] = useState<MessageEvent<any>[]>([]);
// { "msg_id": 1, "client_time": 1712305590234, "type": "Sauth", "data": { "momoid": "404821828", "roomid": "14515460054", "role": 6, "isVisitor": false, "token": "97e721d0bd3bf2e63a9797f9daf50919", "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" } }
const { sendMessage, lastMessage, readyState } = useWebSocket(socketUrl, {
heartbeat:
{
message: () => {
const pingMessage = { "msg_id": 2, "client_time": Date.now(), "type": "Ping", "data": {} }
return JSON.stringify(pingMessage);
},
interval: 5000,
}
});
const momoId = "404821828"
const roomId = "14515460054"
const momoToken = "97e721d0bd3bf2e63a9797f9daf50919"
const clientTime = Date.now()
const data = {
"msg_id": 1,
"client_time": clientTime,
"type": "Sauth",
"data": {
"momoid": momoId, "roomid": roomId,
"role": 6, "isVisitor": false,
"token": momoToken
},
}
useEffect(() => {
if (ReadyState.OPEN === readyState) {
sendMessage(JSON.stringify(data));
}
}, [readyState])
useEffect(() => {
if (lastMessage !== null) {
if (lastMessage.data) {
const message = JSON.parse(lastMessage.data);
if (message.type === 'pong') {
return;
}
setMessageHistory((prev) => prev.concat(message));
}
}
}, [lastMessage]);
const connectionStatus = {
[ReadyState.CONNECTING]: 'Connecting',
[ReadyState.OPEN]: 'Open',
[ReadyState.CLOSING]: 'Closing',
[ReadyState.CLOSED]: 'Closed',
[ReadyState.UNINSTANTIATED]: 'Uninstantiated',
}[readyState];
return (
<div>
{messageHistory.map((message, idx) => (
<MessageDispatcher key={idx} message={message} />
))}
<div className="chat chat-end">
<div className="chat-bubble">You underestimate my power!</div>
</div>
</div>
);
};
// TODO
const MessageDispatcher: React.FC = ({ message }) => {
const messageType = message?.groups?.[0]?.units?.[0]?.type
let nick = 'wait';
let content = 'wait';
if (messageType === 'Message') {
nick = message?.groups?.[0]?.nick;
content = message?.groups?.[0]?.units?.[0]?.['Msg.Message.data']?.text
}
return (<div className="chat chat-start">
<div className="chat-bubble">{nick}: {content}</div>
</div>)
};

View File

@ -1,3 +1,7 @@
import { Danmu } from "./danmu";
export default function Page() {
return <main>hello world</main>
return <main>hello world
<Danmu />
</main>
}

View File

@ -3,115 +3,122 @@ import SignoutButton from "@/app/components/signout-button";
import Link from "next/link";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
return <main>
<SignoutButton />
<Link className="btn" href="/music"></Link>
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
Get started by editing&nbsp;
<code className="font-mono font-bold">app/page.tsx</code>
</p>
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
<a
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
By{" "}
<Image
src="/vercel.svg"
alt="Vercel Logo"
className="dark:invert"
width={100}
height={24}
priority
/>
</a>
</div>
</div>
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-full sm:before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full sm:after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
<Image
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
<div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Docs{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Find in-depth information about Next.js features and API.
</p>
</a>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Learn{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Learn about Next.js in an interactive course with&nbsp;quizzes!
</p>
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Templates{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Explore starter templates for Next.js.
</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Deploy{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50 text-balance`}>
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
</main>
);
}
// export default function Home() {
// return (
// <main className="flex min-h-screen flex-col items-center justify-between p-24">
// <SignoutButton />
// <Link className="btn" href="/music">弹幕测试</Link>
// <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
// <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
// Get started by editing&nbsp;
// <code className="font-mono font-bold">app/page.tsx</code>
// </p>
// <div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
// <a
// className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
// href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
// target="_blank"
// rel="noopener noreferrer"
// >
// By{" "}
// <Image
// src="/vercel.svg"
// alt="Vercel Logo"
// className="dark:invert"
// width={100}
// height={24}
// priority
// />
// </a>
// </div>
// </div>
// <div className="relative flex place-items-center before:absolute before:h-[300px] before:w-full sm:before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full sm:after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
// <Image
// className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
// src="/next.svg"
// alt="Next.js Logo"
// width={180}
// height={37}
// priority
// />
// </div>
// <div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
// <a
// href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
// className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
// target="_blank"
// rel="noopener noreferrer"
// >
// <h2 className={`mb-3 text-2xl font-semibold`}>
// Docs{" "}
// <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
// -&gt;
// </span>
// </h2>
// <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
// Find in-depth information about Next.js features and API.
// </p>
// </a>
// <a
// href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
// target="_blank"
// rel="noopener noreferrer"
// >
// <h2 className={`mb-3 text-2xl font-semibold`}>
// Learn{" "}
// <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
// -&gt;
// </span>
// </h2>
// <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
// Learn about Next.js in an interactive course with&nbsp;quizzes!
// </p>
// </a>
// <a
// href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
// className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
// target="_blank"
// rel="noopener noreferrer"
// >
// <h2 className={`mb-3 text-2xl font-semibold`}>
// Templates{" "}
// <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
// -&gt;
// </span>
// </h2>
// <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
// Explore starter templates for Next.js.
// </p>
// </a>
// <a
// href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
// className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
// target="_blank"
// rel="noopener noreferrer"
// >
// <h2 className={`mb-3 text-2xl font-semibold`}>
// Deploy{" "}
// <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
// -&gt;
// </span>
// </h2>
// <p className={`m-0 max-w-[30ch] text-sm opacity-50 text-balance`}>
// Instantly deploy your Next.js site to a shareable URL with Vercel.
// </p>
// </a>
// </div>
// </main>
// );
// }

View File

@ -12,7 +12,8 @@
"axios": "^1.6.8",
"next": "14.1.4",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"react-use-websocket": "^4.8.1"
},
"devDependencies": {
"@types/node": "^20",

View File

@ -17,6 +17,9 @@ dependencies:
react-dom:
specifier: ^18
version: 18.2.0(react@18.2.0)
react-use-websocket:
specifier: ^4.8.1
version: 4.8.1(react-dom@18.2.0)(react@18.2.0)
devDependencies:
'@types/node':
@ -2393,6 +2396,16 @@ packages:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
dev: true
/react-use-websocket@4.8.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-FTXuG5O+LFozmu1BRfrzl7UIQngECvGJmL7BHsK4TYXuVt+mCizVA8lT0hGSIF0Z0TedF7bOo1nRzOUdginhDw==}
peerDependencies:
react: '>= 18.0.0'
react-dom: '>= 18.0.0'
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/react@18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}

View File

@ -17,5 +17,15 @@ const config: Config = {
},
},
plugins: [daisyui],
daisyui: {
themes: false, // false: only light + dark | true: all themes | array: specific themes like this ["light", "dark", "cupcake"]
darkTheme: "dark", // name of one of the included themes for dark mode
base: true, // applies background color and foreground color for root element by default
styled: true, // include daisyUI colors and design decisions for all components
utils: true, // adds responsive and modifier utility classes
prefix: "", // prefix for daisyUI classnames (components, modifiers and responsive class names. Not colors)
logs: true, // Shows info about daisyUI version and used config in the console when building your CSS
themeRoot: ":root", // The element that receives theme color CSS variables
},
};
export default config;