feature: volume persistent, better lyric scroll
This commit is contained in:
parent
1cd5892ffe
commit
61bb6654d5
@ -3,7 +3,7 @@
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"printWidth": 120,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "aquavox",
|
||||
"version": "1.8.3",
|
||||
"version": "1.9.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
|
@ -34,6 +34,7 @@
|
||||
|
||||
function volumeBarOnChange(e: any) {
|
||||
adjustVolume(e.target.value);
|
||||
localStorage.setItem('volume', e.target.value);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@ -43,9 +44,7 @@
|
||||
});
|
||||
|
||||
$: {
|
||||
console.log(songInfoTopContainer, songInfoTopContent);
|
||||
if (songInfoTopContainer && songInfoTopContent) {
|
||||
console.log(songInfoTopContent.offsetWidth, songInfoTopContainer.offsetWidth);
|
||||
isInfoTopOverflowing =
|
||||
songInfoTopContent.offsetWidth > songInfoTopContainer.offsetWidth;
|
||||
}
|
||||
@ -77,7 +76,7 @@
|
||||
bind:this={songInfoTopContent}>{name}</span
|
||||
>
|
||||
</div>
|
||||
<span class="song-author">{singer}</span>
|
||||
<span class="song-author text-shadow-lg">{singer}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
@ -3,23 +3,75 @@
|
||||
export let lyrics: string[];
|
||||
export let originalLyrics: Line[];
|
||||
export let progress: number;
|
||||
function userSlideProgress() {
|
||||
systemCouldScrollSince = 0;
|
||||
};
|
||||
export { userSlideProgress };
|
||||
|
||||
let currentScrollPos = '';
|
||||
let currentLyric: Line;
|
||||
let currentLyricIndex = -1;
|
||||
let lyricsContainer: HTMLDivElement;
|
||||
let systemScrolling = false;
|
||||
let systemCouldScrollSince = 0;
|
||||
let lastScroll = 0;
|
||||
|
||||
let refs = [];
|
||||
let _refs: any[] = [];
|
||||
$: refs = _refs.filter(Boolean);
|
||||
|
||||
function smoothScrollTo(element: HTMLElement, to: number, duration: number, timingFunction: Function) {
|
||||
if (systemCouldScrollSince > Date.now()) return;
|
||||
systemScrolling = true;
|
||||
const start = element.scrollTop;
|
||||
const change = to - start;
|
||||
const startTime = performance.now();
|
||||
|
||||
function animateScroll(timestamp: number) {
|
||||
const elapsedTime = timestamp - startTime;
|
||||
const progress = Math.min(elapsedTime / duration, 1);
|
||||
const easedProgress = timingFunction(progress, 1.1, 0, 1, 1);
|
||||
element.scrollTop = start + change * easedProgress;
|
||||
|
||||
console.log(elapsedTime);
|
||||
if (elapsedTime < duration) {
|
||||
requestAnimationFrame(animateScroll);
|
||||
} else {
|
||||
console.log('?');
|
||||
systemScrolling = false;
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(animateScroll);
|
||||
}
|
||||
|
||||
// Define your custom Bézier curve function
|
||||
function customBezier(progress: number, p1x: number, p1y: number, p2x: number, p2y: number) {
|
||||
function cubicBezier(t: number, p1: number, p2: number) {
|
||||
const c = 3 * p1;
|
||||
const b = 3 * (p2 - p1) - c;
|
||||
const a = 1 - c - b;
|
||||
return a * Math.pow(t, 3) + b * Math.pow(t, 2) + c * t;
|
||||
}
|
||||
|
||||
return cubicBezier(progress, p1x, p2x);
|
||||
}
|
||||
|
||||
function getClass(lyricIndex: number, progress: number) {
|
||||
if (!currentLyric) return 'after-lyric';
|
||||
if (lyricIndex === currentLyricIndex) return 'current-lyric';
|
||||
else if (progress > currentLyric.endSeconds) return 'after-lyric';
|
||||
else return 'previous-lyric';
|
||||
}
|
||||
|
||||
function inRange(x: number, min: number, max: number) {
|
||||
return (x - min) * (x - max) <= 0;
|
||||
}
|
||||
|
||||
$: {
|
||||
if (originalLyrics) {
|
||||
if (originalLyrics && lyricsContainer) {
|
||||
let found = false;
|
||||
let finallyFound = false;
|
||||
for (let i = 0; i < originalLyrics.length; i++) {
|
||||
let l = originalLyrics[i];
|
||||
if (progress >= l.startSeconds && progress <= l.endSeconds) {
|
||||
@ -28,20 +80,53 @@
|
||||
found = true;
|
||||
const currentRef = refs[i];
|
||||
if (currentRef && currentScrollPos !== currentLyric.text) {
|
||||
currentRef.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
const targetScroll = lyricsContainer.scrollTop + currentRef.getBoundingClientRect().top - 320;
|
||||
const duration = 700;
|
||||
smoothScrollTo(lyricsContainer, targetScroll, duration, customBezier);
|
||||
lastScroll = 0;
|
||||
currentScrollPos = currentLyric.text;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < lyrics.length; i++) {
|
||||
const offset = Math.abs(i - currentLyricIndex);
|
||||
const blurRadius = Math.min(offset * 1, 16);
|
||||
if (refs[i]) {
|
||||
refs[i].style.filter = `blur(${blurRadius}px)`;
|
||||
if (!found) {
|
||||
for (let i = 0; i < originalLyrics.length; i++) {
|
||||
let l = originalLyrics[i];
|
||||
let nl = i + 1 < originalLyrics.length ? originalLyrics[i + 1] : originalLyrics[i];
|
||||
if (
|
||||
progress >= l.endSeconds &&
|
||||
progress < nl.startSeconds &&
|
||||
inRange(lastScroll, l.endSeconds, nl.startSeconds) === false
|
||||
) {
|
||||
const currentRef = refs[i];
|
||||
const targetScroll = lyricsContainer.scrollTop + currentRef.getBoundingClientRect().top - 320;
|
||||
const duration = 700;
|
||||
currentLyricIndex = i - 0.1;
|
||||
currentLyric = {
|
||||
id: '-1',
|
||||
startTime: '00:00:00,000',
|
||||
startSeconds: l.endSeconds + 0.01,
|
||||
endTime: '00:00:00,000',
|
||||
endSeconds: nl.startSeconds - 0.01,
|
||||
text: ''
|
||||
};
|
||||
smoothScrollTo(lyricsContainer, targetScroll, duration, customBezier);
|
||||
lastScroll = progress;
|
||||
finallyFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
if (systemCouldScrollSince < Date.now()) {
|
||||
for (let i = 0; i < lyrics.length; i++) {
|
||||
const offset = Math.abs(i - currentLyricIndex);
|
||||
const blurRadius = Math.min(offset * 1, 16);
|
||||
if (refs[i]) {
|
||||
refs[i].style.filter = `blur(${blurRadius}px)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found == false && finallyFound == false) {
|
||||
currentLyric = {
|
||||
id: '-1',
|
||||
startTime: '00:00:00,000',
|
||||
@ -58,7 +143,17 @@
|
||||
{#if lyrics && originalLyrics}
|
||||
<div
|
||||
class="absolute top-[6.5rem] md:top-36 xl:top-0 w-screen xl:w-[52vw] px-6 md:px-12 lg:px-[7.5rem] xl:left-[45vw] xl:px-[3vw] h-[calc(100vh-17rem)] xl:h-screen font-sans
|
||||
text-left scroll-smooth no-scrollbar overflow-y-auto z-[1] lyrics"
|
||||
text-left no-scrollbar overflow-y-auto z-[1] lyrics py-16"
|
||||
bind:this={lyricsContainer}
|
||||
on:scroll={(e) => {
|
||||
if (systemScrolling == false) {
|
||||
console.log('yes');
|
||||
for (let i = 0; i < lyrics.length; i++) {
|
||||
refs[i].style.filter = `blur(0px)`;
|
||||
}
|
||||
systemCouldScrollSince = Date.now() + 5000;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#each lyrics as lyric, i}
|
||||
<p bind:this={_refs[i]} class={getClass(i, progress)}>
|
||||
@ -70,7 +165,12 @@
|
||||
|
||||
<style>
|
||||
.lyrics {
|
||||
mask-image: linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,1) 2rem, rgba(0,0,0,1) calc(100% - 5rem), rgba(0,0,0,0) 100%);
|
||||
mask-image: linear-gradient(
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
rgba(0, 0, 0, 1) 2rem,
|
||||
rgba(0, 0, 0, 1) calc(100% - 5rem),
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
);
|
||||
}
|
||||
.no-scrollbar {
|
||||
scrollbar-width: none;
|
||||
@ -92,7 +192,7 @@
|
||||
position: relative;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 600;
|
||||
font-size: 2.2rem;
|
||||
font-size: 2.3rem;
|
||||
line-height: 2.7rem;
|
||||
filter: blur(1px);
|
||||
top: 1rem;
|
||||
@ -103,7 +203,7 @@
|
||||
position: relative;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 600;
|
||||
font-size: 2.2rem;
|
||||
font-size: 2.3rem;
|
||||
line-height: 2.7rem;
|
||||
filter: blur(1px);
|
||||
top: 1rem;
|
||||
@ -117,12 +217,12 @@
|
||||
margin: 2.4rem 0rem;
|
||||
}
|
||||
.after-lyric {
|
||||
font-size: 2.8rem;
|
||||
font-size: 3rem;
|
||||
line-height: 3.3rem;
|
||||
margin: 2.4rem 0rem;
|
||||
}
|
||||
.previous-lyric {
|
||||
font-size: 2.8rem;
|
||||
font-size: 3em;
|
||||
line-height: 3.3rem;
|
||||
margin: 2.4rem 0rem;
|
||||
}
|
||||
|
@ -77,7 +77,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
AquaVox 1.8.3 · 早期公开预览 · 源代码参见
|
||||
AquaVox 1.9.0 · 早期公开预览 · 源代码参见
|
||||
<a href="https://github.com/alikia2x/aquavox">GitHub</a>
|
||||
</p>
|
||||
<a href="/import">导入音乐</a> <br />
|
||||
|
@ -27,6 +27,7 @@
|
||||
let lyricsText: string[] = [];
|
||||
let onAdjustingProgress = false;
|
||||
let hasLyrics: boolean;
|
||||
let lyricComp: any;
|
||||
const coverPath = writable('');
|
||||
let mainInterval: ReturnType<typeof setInterval>;
|
||||
|
||||
@ -134,12 +135,14 @@
|
||||
if (audioPlayer) {
|
||||
audioPlayer.currentTime = duration * progress;
|
||||
currentProgress = duration * progress;
|
||||
lyricComp.userSlideProgress();
|
||||
}
|
||||
}
|
||||
|
||||
function adjustRealProgress(progress: number) {
|
||||
if (audioPlayer) {
|
||||
currentProgress = duration * progress;
|
||||
lyricComp.userSlideProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,9 +166,15 @@
|
||||
) {
|
||||
currentProgress = audioPlayer.currentTime;
|
||||
}
|
||||
}, 100);
|
||||
}, 17);
|
||||
}
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(() => {
|
||||
audioPlayer.volume = localStorage.getItem('volume') ? Number(localStorage.getItem('volume')) : 1;
|
||||
});
|
||||
|
||||
$: {
|
||||
if (audioPlayer) {
|
||||
paused = audioPlayer.paused;
|
||||
@ -206,7 +215,7 @@
|
||||
{hasLyrics}
|
||||
/>
|
||||
|
||||
<Lyrics lyrics={lyricsText} {originalLyrics} progress={currentProgress} />
|
||||
<Lyrics lyrics={lyricsText} {originalLyrics} progress={currentProgress} bind:this={lyricComp}/>
|
||||
|
||||
<audio
|
||||
bind:this={audioPlayer}
|
||||
|
Loading…
Reference in New Issue
Block a user