diff options
| author | Pinapelz <yukais@pinapelz.com> | 2023-11-14 19:08:14 -0800 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2023-11-14 19:08:14 -0800 |
| commit | 7b6d5f1666e428c37c936bd6b01323c3a6399ac6 (patch) | |
| tree | 981126f91f26c7419600a7496e76a16af9fa6988 /src | |
| parent | a7701e6183956531a6930fac484acb52e22baf85 (diff) | |
Initial commit - barebone lrc player
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/App.tsx | 91 | ||||
| -rw-r--r-- | src/app/components/Control.tsx | 42 | ||||
| -rw-r--r-- | src/app/components/Video.tsx | 0 | ||||
| -rw-r--r-- | src/app/data.ts | 66 | ||||
| -rw-r--r-- | src/app/globals.css | 23 | ||||
| -rw-r--r-- | src/app/layout.tsx | 14 | ||||
| -rw-r--r-- | src/app/page.tsx | 116 | ||||
| -rw-r--r-- | src/app/use_timer.ts | 33 |
8 files changed, 240 insertions, 145 deletions
diff --git a/src/app/App.tsx b/src/app/App.tsx new file mode 100644 index 0000000..ebe2d33 --- /dev/null +++ b/src/app/App.tsx @@ -0,0 +1,91 @@ +import { CSSProperties, useCallback, useRef, useEffect, useState } from "react"; +import styled, { css } from "styled-components"; +import { Lrc, LrcLine } from "react-lrc"; +import { LRC } from "./data"; + +const Root = styled.div` + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + + display: flex; + flex-direction: column; +`; +const lrcStyle: CSSProperties = { + flex: 1, + minHeight: 0, + overflow: 'hidden !important' +}; +const Line = styled.div<{ $active: boolean; $next: boolean }>` + min-height: 10px; + padding: 14px 30px; + + font-size: 40px; + font-family : "Roboto", sans-serif; + font-weight: 500; + text-align: center; + color: rgb(72,72,72); + + background: linear-gradient(to right, rgba(0,0,0,0) 50%, rgb(200, 190, 190) 50%); + background-size: 200% 100%; + background-position: right bottom; + + ${({ $active }) => $active && css` + color: black; + font-weight: 700; + background-position: left bottom; + color: rgb(50, 50, 50); + `} +`; + +function App() { + const [currentMillisecond, setCurrentMillisecond] = useState(0); + + const videoRef = useRef<HTMLVideoElement>(null); + + useEffect(() => { + const video = videoRef.current; + if (!video) return; + + const syncLrcWithVideo = () => { + const offset = 400; + setCurrentMillisecond((video.currentTime * 1000)+offset); + }; + + video.addEventListener('timeupdate', syncLrcWithVideo); + + return () => { + video.removeEventListener('timeupdate', syncLrcWithVideo); + }; + }, [setCurrentMillisecond]); + + const lineRenderer = useCallback( + ({ active, line: { content } }: { active: boolean; line: LrcLine }) => { + const next = active && content === ''; + return <Line $active={active} $next={next}>{content}</Line> + }, + [] + ); + + return ( + <Root> + <div style={{ display: 'flex', width: '100%', height: '100vh' }}> + <Lrc + lrc={LRC} + lineRenderer={lineRenderer} + currentMillisecond={currentMillisecond} + style={lrcStyle} + recoverAutoScrollInterval={0} + /> + <div style={{ flex: 1 }}> + <video ref={videoRef} src="https://cdn.pinapelz.com/VTuber%20Covers%20Archive/pj9yqqTYa-E.webm" controls style={{ width: '100%', height: '100%' }} /> + + </div> + </div> + </Root> + ); +} + +export default App; diff --git a/src/app/components/Control.tsx b/src/app/components/Control.tsx new file mode 100644 index 0000000..2ad2339 --- /dev/null +++ b/src/app/components/Control.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +function Control({ + onPlay, + onPause, + onReset, + current, + setCurrent, + recoverAutoScrollImmediately, +}: { + onPlay: () => void; + onPause: () => void; + onReset: () => void; + current: number; + setCurrent: (c: number) => void; + recoverAutoScrollImmediately: () => void; +}) { + return ( + <div className="flex items-center space-x-2 p-2 bg-gray-200"> + <button type="button" onClick={onPlay} className="btn"> + play + </button> + <button type="button" onClick={onPause} className="btn"> + pause + </button> + <button type="button" onClick={onReset} className="btn"> + reset + </button> + <input + type="number" + value={current} + onChange={(event) => setCurrent(Number(event.target.value))} + className="input" + /> + <button type="button" onClick={recoverAutoScrollImmediately} className="btn"> + recover auto scroll immediately + </button> + </div> + ); +} + +export default Control;
\ No newline at end of file diff --git a/src/app/components/Video.tsx b/src/app/components/Video.tsx new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/components/Video.tsx diff --git a/src/app/data.ts b/src/app/data.ts new file mode 100644 index 0000000..eff48cf --- /dev/null +++ b/src/app/data.ts @@ -0,0 +1,66 @@ +export const LRC = `[00:11.53]Another day, I wander +[00:13.85]Without escape, I ponder +[00:15.98]A million questions +[00:17.25]I don't need to find an answer for +[00:20.59]Like, is it worth the hassle? +[00:22.78]Or, is it worth the pain? +[00:24.23]Look inside the mirror and say to myself +[00:28.17]Am I enough? +[00:30.81]Oh, am I worth it? +[00:35.570] +[00:37.670]I see the way you notice +[00:39.980]All of my fragile moments +[00:42.210]A part of me is still uncertain I should let you in +[00:46.670]But as it flows and passes +[00:48.800]The time will just confirm +[00:50.150]That when you're here with me +[00:51.940]I can just let go +[00:54.560]Now suddenly, the clouds clear out +[00:59.070]All my worries disappear +[01:01.070]And all the stars, they feel so near +[01:03.340]I could almost reach out right now +[01:07.580]Because of you, feel myself again +[01:11.980]You helped me realize what was here to do +[01:16.480]Because of you, I'm feeling real again +[01:20.840]Now I know where I should go +[01:22.940]You got me walking back to hope +[01:25.090]One step at a time +[01:34.560]I can't explain the feeling +[01:36.630]A sort of, kind of healing +[01:38.980]A different wave of love that travels +[01:41.530]Through your precious words +[01:43.370]At times, I circle back, but +[01:45.600]I guess I just forget +[01:46.980]That when I'm down and low +[01:48.940]I could count on you +[01:51.460]Now suddenly, the clouds clear out +[01:55.760]All my worries disappear +[01:57.810]And all the stars, they feel so near +[02:00.300]I could almost reach out right now +[02:05.530]Because of you, I feel myself again +[02:09.930]You helped me realize what I was here to do +[02:14.370]Because of you, I'm feeling real again +[02:18.520]Now I know where I should go +[02:20.640]You got me walking back to hope +[02:22.250]A step at a time +[02:25.940]♪ +[02:41.030]What can I say but thank you +[02:43.400]Surrounded by some angels +[02:45.190]Honestly, it's hard for me to live without +[02:49.800]Tomorrow's bringing something new +[02:53.160]I know for certain, it's worth it +[02:56.560]All thanks to you +[03:08.840]Because of you, I feel myself again +[03:13.240]You helped me realize what I was here to do +[03:17.450]Because of you, I'm feeling real again +[03:21.930]Now I know where I should go +[03:23.980]You got me walking back to hope +[03:26.390]Because of you, I feel myself again +[03:30.700]You helped me realize what I was here to do +[03:34.940]Because of you, I'm feeling real again +[03:39.310]Now I know where I should go +[03:41.570]You got me walking back to hope +[03:43.860]One step at a time +[03:51.980]A little closer +[03:55.580]Every step gets closer +[03:58.330]`; diff --git a/src/app/globals.css b/src/app/globals.css index fd81e88..a90f074 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,26 +2,3 @@ @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)); -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 40e027f..a14e64f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,12 +1,6 @@ -import type { Metadata } from 'next' -import { Inter } from 'next/font/google' -import './globals.css' - -const inter = Inter({ subsets: ['latin'] }) - -export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', +export const metadata = { + title: 'Next.js', + description: 'Generated by Next.js', } export default function RootLayout({ @@ -16,7 +10,7 @@ export default function RootLayout({ }) { return ( <html lang="en"> - <body className={inter.className}>{children}</body> + <body>{children}</body> </html> ) } diff --git a/src/app/page.tsx b/src/app/page.tsx index b973266..e4a8169 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,113 +1,5 @@ -import Image from 'next/image' +"use client" +import React from 'react'; +import App from './App'; -export default function Home() { - return ( - <main className="flex min-h-screen flex-col items-center justify-between p-24"> - <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 - <code className="font-mono font-bold">src/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-[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-[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"> - -> - </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"> - -> - </span> - </h2> - <p className={`m-0 max-w-[30ch] text-sm opacity-50`}> - Learn about Next.js in an interactive course with 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"> - -> - </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"> - -> - </span> - </h2> - <p className={`m-0 max-w-[30ch] text-sm opacity-50`}> - Instantly deploy your Next.js site to a shareable URL with Vercel. - </p> - </a> - </div> - </main> - ) -} +export default App;
\ No newline at end of file diff --git a/src/app/use_timer.ts b/src/app/use_timer.ts new file mode 100644 index 0000000..487591d --- /dev/null +++ b/src/app/use_timer.ts @@ -0,0 +1,33 @@ +import { useEffect, useState, useCallback } from "react"; + +function useTimer(speed = 1) { + const [paused, setPaused] = useState(true); + const play = useCallback(() => setPaused(false), []); + const pause = useCallback(() => setPaused(true), []); + + const [currentMillisecond, setCurrentMillisecond] = useState(0); + const reset = useCallback(() => setCurrentMillisecond(0), []); + + useEffect(() => { + if (!paused) { + let last = Date.now(); + const timer = window.setInterval(() => { + const now = Date.now(); + setCurrentMillisecond((cm) => cm + (now - last) * speed); + last = now; + }, 97); + return () => window.clearInterval(timer); + } + }, [paused, speed]); + + return { + currentMillisecond, + setCurrentMillisecond, + reset, + paused, + play, + pause + }; +} + +export default useTimer;
\ No newline at end of file |
