diff --git a/package.json b/package.json index 9ea2937..eb179b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aquavox", - "version": "2.9.1", + "version": "2.9.2", "private": false, "module": "index.ts", "type": "module", diff --git a/packages/core/components/lyrics/lyricLine.svelte b/packages/core/components/lyrics/lyricLine.svelte index e521e62..9556d2a 100644 --- a/packages/core/components/lyrics/lyricLine.svelte +++ b/packages/core/components/lyrics/lyricLine.svelte @@ -3,9 +3,11 @@ import type { LyricWord, ScriptItem } from '@core/lyrics/type'; import type { LyricPos } from './type'; import type { Spring } from '@core/graphics/spring/spring'; + import userAdjustingProgress from '@core/state/userAdjustingProgress'; const viewportWidth = document.documentElement.clientWidth; const blurRatio = viewportWidth > 640 ? 1.2 : 1.4; + const scrollDuration = 0.2; export let line: ScriptItem; export let index: number; @@ -23,6 +25,10 @@ let positionY: number = 0; let blur = 0; let stopped = false; + let we_are_scrolling = false; + let scrollTarget: number | undefined = undefined; + let scrollFrom: number | undefined = undefined; + let scrollingStartTime: number | undefined = undefined; let lastPosX: number | undefined = undefined; let lastPosY: number | undefined = undefined; let lastUpdateY: number | undefined = undefined; @@ -39,7 +45,7 @@ time = (new Date().getTime() - lastUpdateY) / 1000; springY.update(time); positionY = springY.getCurrentPosition(); - if (!springY.arrived() && !stopped) { + if (!springY.arrived() && !stopped && !we_are_scrolling) { requestAnimationFrame(updateY); } lastUpdateY = new Date().getTime(); @@ -87,6 +93,7 @@ blurRadius = Math.min(offset * blurRatio, 16); } if (scrolling) blurRadius = 0; + if ($userAdjustingProgress) blurRadius = 0; blur = blurRadius } } @@ -107,6 +114,26 @@ lastPosY = pos.y; }; + function updateScroll(timestamp: number) { + const elapsedTime = (new Date().getTime() - scrollingStartTime!) / 1000; + const percentage = Math.min(elapsedTime / scrollDuration, 1); + positionY = scrollFrom! + (scrollTarget! - scrollFrom!) * percentage; + if (percentage < 1) { + requestAnimationFrame(updateScroll); + } + } + + export const scrollTo = (targetY: number) => { + scrollFrom = positionY; + scrollTarget = targetY; + scrollingStartTime = new Date().getTime(); + we_are_scrolling = true; + requestAnimationFrame(updateScroll); + springY!.setPosition(targetY); + we_are_scrolling = false; + }; + + export const getInfo = () => { return { x: positionX, diff --git a/packages/core/components/lyrics/newLyrics.svelte b/packages/core/components/lyrics/newLyrics.svelte index ce8694b..c3539cf 100644 --- a/packages/core/components/lyrics/newLyrics.svelte +++ b/packages/core/components/lyrics/newLyrics.svelte @@ -3,7 +3,7 @@ import { onMount } from 'svelte'; import LyricLine from './lyricLine.svelte'; import createLyricsSearcher from '@core/lyrics/lyricSearcher'; - import { createRule } from 'eslint-plugin-svelte/lib/utils'; + import userAdjustingProgress from '@core/state/userAdjustingProgress'; // constants const viewportHeight = document.documentElement.clientHeight; @@ -48,7 +48,8 @@ function initLyricComponents() { initLyricTopList(); for (let i = 0; i < lyricComponents.length; i++) { - lyricComponents[i].init({ x: 0, y: lyricTopList[i] }); + const currentLyric = lyricComponents[i]; + currentLyric.init({ x: 0, y: lyricTopList[i] }); } } @@ -85,6 +86,16 @@ } } + function seekForward() { + if (!originalLyrics.scripts) return; + const relativeOrigin = lyricTopList[currentLyricIndex] - currentLyricTop; + for (let i = 0; i < lyricElements.length; i++) { + const currentLyricComponent = lyricComponents[i]; + currentLyricComponent.scrollTo(lyricTopList[i] - relativeOrigin); + } + lastSeekForward = new Date().getTime(); + } + $effect(() => { if (!originalLyrics || !originalLyrics.scripts) return; lyricLines = originalLyrics.scripts!; @@ -171,69 +182,40 @@ scrollEventAdded = true; }); - let lastTriggered = $state(0); let lastEventLyricIndex = $state(0); let lastEventProgress = $state(0); + let lastSeekForward = $state(0); $effect(() => { - const progressDelta = progress - lastEventProgress; - const deltaInRange = 0 <= progressDelta && progressDelta <= 0.15; - const deltaTooBig = progressDelta > 0.15; - const deltaIsNegative = progressDelta < 0; - const lyricChanged = currentLyricIndex !== lastEventLyricIndex; - const lyricIndexDeltaTooBig = Math.abs(currentLyricIndex - lastEventLyricIndex) > 1; - if (lyricChanged && !lyricIndexDeltaTooBig && deltaInRange) { - console.log("Event: regular move"); - } - else if (deltaTooBig && lyricChanged) { - console.log("Event: seek forward"); - } - else if (deltaIsNegative && lyricChanged) { - console.log("Event: seek backward"); - } - lastEventLyricIndex = currentLyricIndex; - lastEventProgress = progress; - }); + const progressDelta = progress - lastEventProgress; + const deltaInRange = 0 <= progressDelta && progressDelta <= 0.15; + const deltaTooBig = progressDelta > 0.15; + const deltaIsNegative = progressDelta < 0; + const lyricChanged = currentLyricIndex !== lastEventLyricIndex; + const lyricIndexDeltaTooBig = Math.abs(currentLyricIndex - lastEventLyricIndex) > 1; - $effect(() => { - if (!lyricsContainer || lyricComponents.length < 0) return; - if (progress >= nextUpdate - 0.5 && !scrolling) { + lastEventLyricIndex = currentLyricIndex; + lastEventProgress = progress; + if (!lyricChanged) return; + if (!lyricIndexDeltaTooBig && deltaInRange) { + console.log("Event: regular move"); + console.log(new Date().getTime() , lastSeekForward); computeLayout(); } - if (Math.abs(lastProgress - progress) > 0.5) { - scrolling = false; - } - if (lastProgress - progress > 0) { - computeLayout(); - nextUpdate = progress; - } else { - const lyricLength = originalLyrics.scripts!.length; - const currentEnd = originalLyrics.scripts![currentLyricIndex].end; - const nextStart = originalLyrics.scripts![Math.min(currentLyricIndex + 1, lyricLength - 1)].start; - if (currentEnd !== nextStart) { - nextUpdate = currentEnd; - } else { - nextUpdate = nextStart; + else if ($userAdjustingProgress) { + if (deltaTooBig && lyricChanged) { + console.log("Event: seek forward"); + seekForward(); + } else if (deltaIsNegative && lyricChanged) { + console.log("Event: seek backward"); + seekForward(); } } - lastProgress = progress; + else { + console.log("Event: regular move"); + computeLayout(); + } }); - // $: { - // for (let i = 0; i < lyricElements.length; i++) { - // const s = originalLyrics.scripts![i].start; - // const t = originalLyrics.scripts![i].end; - // // Explain: - // // The `currentLyricIndex` is also used for locating & layout computing, - // // so when the current progress is in the interlude between two lyrics, - // // `currentLyricIndex` still needs to have a valid value to ensure that - // // the style and scrolling position are calculated correctly. - // // But in that situation, the “current lyric index” does not exist. - // const isCurrent = i == currentLyricIndex && s <= progress && progress <= t; - // const currentLyricComponent = lyricComponents[i]; - // currentLyricComponent.setCurrent(isCurrent); - // } - // } - onMount(() => { // Initialize if (localStorage.getItem('debugMode') == null) { @@ -258,16 +240,17 @@ function lyricClick(lyricIndex: number) { if (player === null || originalLyrics.scripts === undefined) return; player.currentTime = originalLyrics.scripts[lyricIndex].start; + userAdjustingProgress.set(false); player.play(); } - + {#if debugMode} - progress: {progress.toFixed(2)}, nextUpdate: {nextUpdate}, scrolling: {scrolling}, current: {currentLyricIndex} + progress: {progress.toFixed(2)}, nextUpdate: {nextUpdate}, scrolling: {scrolling}, current: {currentLyricIndex}, uap: {$userAdjustingProgress} {/if}