From d0c216fdafd0ac8babe0ec63db304bac0f920cff Mon Sep 17 00:00:00 2001 From: alikia2x Date: Thu, 24 Oct 2024 05:03:18 +0800 Subject: [PATCH] merge: branch 'ref-lyric' into dev --- .gitignore | 3 +- package.json | 2 +- src/app.css | 7 + src/lib/components/lyrics/lyricLine.svelte | 162 +++++++++++ src/lib/components/{ => lyrics}/lyrics.svelte | 199 +++++++------ src/lib/components/lyrics/newLyrics.svelte | 260 +++++++++++++++++ src/lib/components/lyrics/type.d.ts | 4 + src/lib/graphics/spring/derivative.ts | 8 + src/lib/graphics/spring/index.ts | 17 ++ src/lib/graphics/spring/spring.ts | 147 ++++++++++ src/lib/lyrics/LRCparser.ts | 41 +-- src/lib/lyrics/lrc/index.ts | 0 src/lib/lyrics/lrc/parser.ts | 270 ++++++++++++++++++ src/lib/lyrics/lrc/type.d.ts | 11 + src/lib/lyrics/ttml/index.ts | 21 ++ src/lib/lyrics/ttml/parser.ts | 169 +++++++++++ src/lib/lyrics/ttml/ttml-types.ts | 26 ++ src/lib/lyrics/ttml/writer.ts | 254 ++++++++++++++++ src/lib/lyrics/type.d.ts | 36 +++ src/routes/import/[id]/lyric/+page.svelte | 2 +- src/routes/play/[id]/+page.svelte | 63 ++-- src/test/lrcParser.test.ts | 2 +- 22 files changed, 1543 insertions(+), 161 deletions(-) create mode 100644 src/lib/components/lyrics/lyricLine.svelte rename src/lib/components/{ => lyrics}/lyrics.svelte (70%) create mode 100644 src/lib/components/lyrics/newLyrics.svelte create mode 100644 src/lib/components/lyrics/type.d.ts create mode 100644 src/lib/graphics/spring/derivative.ts create mode 100644 src/lib/graphics/spring/index.ts create mode 100644 src/lib/graphics/spring/spring.ts create mode 100644 src/lib/lyrics/lrc/index.ts create mode 100644 src/lib/lyrics/lrc/parser.ts create mode 100644 src/lib/lyrics/lrc/type.d.ts create mode 100644 src/lib/lyrics/ttml/index.ts create mode 100644 src/lib/lyrics/ttml/parser.ts create mode 100644 src/lib/lyrics/ttml/ttml-types.ts create mode 100644 src/lib/lyrics/ttml/writer.ts create mode 100644 src/lib/lyrics/type.d.ts diff --git a/.gitignore b/.gitignore index f28b76f..b967bbb 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ node_modules !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* -data/pending \ No newline at end of file +data/pending +.vscode \ No newline at end of file diff --git a/package.json b/package.json index ceb53a6..237679c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aquavox", - "version": "2.0.3", + "version": "2.3.0", "private": false, "scripts": { "dev": "vite dev", diff --git a/src/app.css b/src/app.css index 03b75b4..6ceabcd 100644 --- a/src/app.css +++ b/src/app.css @@ -29,3 +29,10 @@ h2 { .text-shadow-none { text-shadow: none; } + +body, +html { + position: fixed; + overflow: hidden; + overscroll-behavior: none; +} diff --git a/src/lib/components/lyrics/lyricLine.svelte b/src/lib/components/lyrics/lyricLine.svelte new file mode 100644 index 0000000..dedc193 --- /dev/null +++ b/src/lib/components/lyrics/lyricLine.svelte @@ -0,0 +1,162 @@ + + + + +
{ + clickMask.style.backgroundColor = "rgba(255,255,255,.3)"; + }} + on:touchend={() => { + clickMask.style.backgroundColor = "transparent"; + }} + on:click={() => { + lyricClick(index); + }} +> + + + + {#if debugMode} + + {index}: duration: {(line.end - line.start).toFixed(3)}, {line.start.toFixed(3)}~{line.end.toFixed(3)} + + {/if} + + {line.text} + + {#if line.translation} +
+ + {line.translation} + + {/if} +
diff --git a/src/lib/components/lyrics.svelte b/src/lib/components/lyrics/lyrics.svelte similarity index 70% rename from src/lib/components/lyrics.svelte rename to src/lib/components/lyrics/lyrics.svelte index eef572d..a115667 100644 --- a/src/lib/components/lyrics.svelte +++ b/src/lib/components/lyrics/lyrics.svelte @@ -2,9 +2,11 @@ import userAdjustingProgress from '$lib/state/userAdjustingProgress'; import createLyricsSearcher from '$lib/lyrics/lyricSearcher'; import progressBarRaw from '$lib/state/progressBarRaw'; - import type { LrcJsonData } from '$lib/lyrics/LRCparser'; + import type { LrcJsonData } from '$lib/lyrics/type'; + import progressBarSlideValue from '$lib/state/progressBarSlideValue'; import nextUpdate from '$lib/state/nextUpdate'; - import truncate from '$lib/utils/truncate'; + import truncate from '$lib/truncate'; + import { blur } from 'svelte/transition'; // Component input properties export let lyrics: string[]; @@ -18,15 +20,13 @@ let showTranslation = false; if (localStorage.getItem('debugMode') == null) { localStorage.setItem('debugMode', 'false'); - } - else { - debugMode = localStorage.getItem('debugMode')!.toLowerCase() === "true"; + } else { + debugMode = localStorage.getItem('debugMode')!.toLowerCase() === 'true'; } if (localStorage.getItem('showTranslation') == null) { localStorage.setItem('showTranslation', 'false'); - } - else { - showTranslation = localStorage.getItem('showTranslation')!.toLowerCase() === "true"; + } else { + showTranslation = localStorage.getItem('showTranslation')!.toLowerCase() === 'true'; } let currentLyricIndex = -1; let currentPositionIndex = -1; @@ -45,18 +45,16 @@ $: refs = _refs.filter(Boolean); $: getLyricIndex = createLyricsSearcher(originalLyrics); - // handle KeyDown event function onKeyDown(e: KeyboardEvent) { if (e.altKey && e.shiftKey && (e.metaKey || e.key === 'OS') && e.key === 'Enter') { debugMode = !debugMode; localStorage.setItem('debugMode', debugMode ? 'true' : 'false'); - } - else if (e.key === 't') { + } else if (e.key === 't') { showTranslation = !showTranslation; localStorage.setItem('showTranslation', showTranslation ? 'true' : 'false'); setTimeout(() => { - scrollToLyric(refs[currentPositionIndex]) + scrollToLyric(refs[currentPositionIndex]); }, 50); } } @@ -65,7 +63,7 @@ function extractTranslateValue(s: string): string | null { const regex = /translateY\((-?\d*px)\)/; let arr = regex.exec(s); - return arr==null ? null : arr[1]; + return arr == null ? null : arr[1]; } // Helper function to get CSS class for a lyric based on its index and progress @@ -78,7 +76,7 @@ // Function to move the lyrics up smoothly async function moveToNextLine(h: number) { - console.debug(new Date().getTime() , 'moveToNextLine', h); + console.debug(new Date().getTime(), 'moveToNextLine', h); // the line that's going to process (like a pointer) // by default, it's "the next line" after the lift let processingLineIndex = currentPositionIndex + 2; @@ -86,21 +84,24 @@ // modify translateY of all lines in viewport one by one to lift them up for (let i = processingLineIndex; i < refs.length; i++) { const lyric = refs[i]; - lyric.style.transition = - `transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, + lyric.style.transition = `transform .5s cubic-bezier(.16,.02,.38,.98), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease`; lyric.style.transform = `translateY(${-h}px)`; processingLineIndex = i; await sleep(75); const twoLinesAhead = refs[i - 2]; - if (lyricsContainer && twoLinesAhead.getBoundingClientRect().top > lyricsContainer.getBoundingClientRect().height) break; + if ( + lyricsContainer && + twoLinesAhead.getBoundingClientRect().top > lyricsContainer.getBoundingClientRect().height + ) + break; } if (refs.length - processingLineIndex < 3) { for (let i = processingLineIndex; i < refs.length; i++) { const lyric = refs[i]; lyric.style.transition = - 'transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease'; + 'transform .5s cubic-bezier(.16,.02,.38,.98), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease'; lyric.style.transform = `translateY(${-h}px)`; processingLineIndex = i; await sleep(75); @@ -199,9 +200,13 @@ const currentLyric = refs[currentPositionIndex]; if ($userAdjustingProgress || scrolling || currentLyric.getBoundingClientRect().top < 0) return; - for (let i = 0; i < scripts.length; i++) { + for (let i = 0; i < refs.length; i++) { const offset = Math.abs(i - currentPositionIndex); - const blurRadius = Math.min(offset * 0.96, 16); + let blurRadius = Math.min(offset * 1.25, 16); + const rect = refs[i].getBoundingClientRect(); + if (rect.top + rect.height < 0 || rect.top > lyricsContainer.getBoundingClientRect().height) { + blurRadius = 0; + } if (refs[i]) { refs[i].style.filter = `blur(${blurRadius}px)`; } @@ -209,15 +214,30 @@ })(); } + function getViewportRange() { + let min = 0; + let max = 0; + for (let i = 0; i < refs.length; i++) { + const element = refs[i]; + if (element.getBoundingClientRect().top < 0) { + min = i; + } else if (element.getBoundingClientRect().bottom < 0) { + max = i; + return [min, max]; + } + } + } + // Main function that control's lyrics update during playing // triggered by nextUpdate's update - async function lyricsUpdate(){ + async function lyricsUpdate() { if ( currentPositionIndex < 0 || currentPositionIndex === currentAnimationIndex || $userAdjustingProgress === true || scrolling - ) return; + ) + return; const currentLyric = refs[currentPositionIndex]; const currentLyricRect = currentLyric.getBoundingClientRect(); @@ -227,18 +247,20 @@ const offsetHeight = truncate(currentLyricRect.top - currentLyricTopMargin, 0, Infinity); // prepare current line - currentLyric.style.transition = `transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, + currentLyric.style.transition = `transform .5s cubic-bezier(.16,.02,.38,.98), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease`; currentLyric.style.transform = `translateY(${-offsetHeight}px)`; + // prepare past lines for (let i = currentPositionIndex - 1; i >= 0; i--) { - refs[i].style.transition = `transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, + refs[i].style.transition = `transform .5s cubic-bezier(.16,.02,.38,.98), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease`; refs[i].style.transform = `translateY(${-offsetHeight}px)`; } + await sleep(75); if (currentPositionIndex + 1 < refs.length) { const nextLyric = refs[currentPositionIndex + 1]; - nextLyric.style.transition = `transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, + nextLyric.style.transition = `transform .5s cubic-bezier(.16,.02,.38,.98), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease`; nextLyric.style.transform = `translateY(${-offsetHeight}px)`; await moveToNextLine(offsetHeight); @@ -246,8 +268,7 @@ currentAnimationIndex = currentPositionIndex; } - - nextUpdate.subscribe(lyricsUpdate) + nextUpdate.subscribe(lyricsUpdate); // Process while user is adjusting progress userAdjustingProgress.subscribe((adjusting) => { @@ -281,63 +302,77 @@ }); function lyricClick(lyricIndex: number) { - if (player===null || originalLyrics.scripts === undefined) return; + if (player === null || originalLyrics.scripts === undefined) return; player.currentTime = originalLyrics.scripts[lyricIndex].start; - player.play() + player.play(); } - -{#if debugMode && lyricsContainer} -
-

- LyricIndex: {currentLyricIndex} PositionIndex: {currentPositionIndex} - AnimationIndex:{currentAnimationIndex} - NextUpdate: {$nextUpdate} - Progress: {progress.toFixed(2)} - scrollPosition: {lyricsContainer.scrollTop} -

-
-{/if} +
+ {#if debugMode && lyricsContainer} +
+

+ LyricIndex: {currentLyricIndex} PositionIndex: {currentPositionIndex} + AnimationIndex:{currentAnimationIndex} + NextUpdate: {$nextUpdate} + Progress: {progress.toFixed(2)} + scrollPosition: {lyricsContainer.scrollTop} +

+
+ {/if} - -{#if lyrics && originalLyrics && originalLyrics.scripts} -
- {#each lyrics as lyric, i} - - -
{lyricClick(i)}} > - {#if debugMode && refs[i] && refs[i].style !== undefined} - {i}   - {originalLyrics.scripts[i].start} ~ {originalLyrics.scripts[i].end} - tY: {extractTranslateValue(refs[i].style.transform)} - top: {Math.round(refs[i].getBoundingClientRect().top)}px - - {/if} - -

- {#if originalLyrics.scripts[i].singer} - {originalLyrics.scripts[i].singer} + bind:this={lyricsContainer} + on:scroll={scrollHandler} + > + {#each lyrics as lyric, i} + + +

{ + lyricClick(i); + }} + > + {#if debugMode && refs[i] && refs[i].style !== undefined} + {i}   + {originalLyrics.scripts[i].start} ~ {originalLyrics.scripts[i].end} + tY: {extractTranslateValue(refs[i].style.transform)} + top: {Math.round(refs[i].getBoundingClientRect().top)}px + {/if} - {lyric} -

- {#if originalLyrics.scripts[i].translation && showTranslation} -
{originalLyrics.scripts[i].translation}
- {/if} -
- {/each} -
-
-{/if} + +

+ {#if originalLyrics.scripts[i].singer} + {originalLyrics.scripts[i].singer} + {/if} + {lyric} +

+ {#if originalLyrics.scripts[i].translation && showTranslation} +
+ {originalLyrics.scripts[i].translation} +
+ {/if} +
+ {/each} +
+
+ {/if} +