feature: volume persistent, better lyric scroll
This commit is contained in:
parent
1cd5892ffe
commit
61bb6654d5
@ -3,7 +3,7 @@
|
|||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"printWidth": 100,
|
"printWidth": 120,
|
||||||
"plugins": ["prettier-plugin-svelte"],
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aquavox",
|
"name": "aquavox",
|
||||||
"version": "1.8.3",
|
"version": "1.9.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
function volumeBarOnChange(e: any) {
|
function volumeBarOnChange(e: any) {
|
||||||
adjustVolume(e.target.value);
|
adjustVolume(e.target.value);
|
||||||
|
localStorage.setItem('volume', e.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@ -43,9 +44,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
console.log(songInfoTopContainer, songInfoTopContent);
|
|
||||||
if (songInfoTopContainer && songInfoTopContent) {
|
if (songInfoTopContainer && songInfoTopContent) {
|
||||||
console.log(songInfoTopContent.offsetWidth, songInfoTopContainer.offsetWidth);
|
|
||||||
isInfoTopOverflowing =
|
isInfoTopOverflowing =
|
||||||
songInfoTopContent.offsetWidth > songInfoTopContainer.offsetWidth;
|
songInfoTopContent.offsetWidth > songInfoTopContainer.offsetWidth;
|
||||||
}
|
}
|
||||||
@ -77,7 +76,7 @@
|
|||||||
bind:this={songInfoTopContent}>{name}</span
|
bind:this={songInfoTopContent}>{name}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<span class="song-author">{singer}</span>
|
<span class="song-author text-shadow-lg">{singer}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -3,23 +3,75 @@
|
|||||||
export let lyrics: string[];
|
export let lyrics: string[];
|
||||||
export let originalLyrics: Line[];
|
export let originalLyrics: Line[];
|
||||||
export let progress: number;
|
export let progress: number;
|
||||||
|
function userSlideProgress() {
|
||||||
|
systemCouldScrollSince = 0;
|
||||||
|
};
|
||||||
|
export { userSlideProgress };
|
||||||
|
|
||||||
let currentScrollPos = '';
|
let currentScrollPos = '';
|
||||||
let currentLyric: Line;
|
let currentLyric: Line;
|
||||||
let currentLyricIndex = -1;
|
let currentLyricIndex = -1;
|
||||||
|
let lyricsContainer: HTMLDivElement;
|
||||||
|
let systemScrolling = false;
|
||||||
|
let systemCouldScrollSince = 0;
|
||||||
|
let lastScroll = 0;
|
||||||
|
|
||||||
let refs = [];
|
let refs = [];
|
||||||
let _refs: any[] = [];
|
let _refs: any[] = [];
|
||||||
$: refs = _refs.filter(Boolean);
|
$: 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) {
|
function getClass(lyricIndex: number, progress: number) {
|
||||||
|
if (!currentLyric) return 'after-lyric';
|
||||||
if (lyricIndex === currentLyricIndex) return 'current-lyric';
|
if (lyricIndex === currentLyricIndex) return 'current-lyric';
|
||||||
else if (progress > currentLyric.endSeconds) return 'after-lyric';
|
else if (progress > currentLyric.endSeconds) return 'after-lyric';
|
||||||
else return 'previous-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 found = false;
|
||||||
|
let finallyFound = false;
|
||||||
for (let i = 0; i < originalLyrics.length; i++) {
|
for (let i = 0; i < originalLyrics.length; i++) {
|
||||||
let l = originalLyrics[i];
|
let l = originalLyrics[i];
|
||||||
if (progress >= l.startSeconds && progress <= l.endSeconds) {
|
if (progress >= l.startSeconds && progress <= l.endSeconds) {
|
||||||
@ -28,20 +80,53 @@
|
|||||||
found = true;
|
found = true;
|
||||||
const currentRef = refs[i];
|
const currentRef = refs[i];
|
||||||
if (currentRef && currentScrollPos !== currentLyric.text) {
|
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;
|
currentScrollPos = currentLyric.text;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < lyrics.length; i++) {
|
if (!found) {
|
||||||
const offset = Math.abs(i - currentLyricIndex);
|
for (let i = 0; i < originalLyrics.length; i++) {
|
||||||
const blurRadius = Math.min(offset * 1, 16);
|
let l = originalLyrics[i];
|
||||||
if (refs[i]) {
|
let nl = i + 1 < originalLyrics.length ? originalLyrics[i + 1] : originalLyrics[i];
|
||||||
refs[i].style.filter = `blur(${blurRadius}px)`;
|
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 = {
|
currentLyric = {
|
||||||
id: '-1',
|
id: '-1',
|
||||||
startTime: '00:00:00,000',
|
startTime: '00:00:00,000',
|
||||||
@ -58,7 +143,17 @@
|
|||||||
{#if lyrics && originalLyrics}
|
{#if lyrics && originalLyrics}
|
||||||
<div
|
<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
|
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}
|
{#each lyrics as lyric, i}
|
||||||
<p bind:this={_refs[i]} class={getClass(i, progress)}>
|
<p bind:this={_refs[i]} class={getClass(i, progress)}>
|
||||||
@ -70,7 +165,12 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.lyrics {
|
.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 {
|
.no-scrollbar {
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
@ -92,7 +192,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 2.2rem;
|
font-size: 2.3rem;
|
||||||
line-height: 2.7rem;
|
line-height: 2.7rem;
|
||||||
filter: blur(1px);
|
filter: blur(1px);
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
@ -103,7 +203,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 2.2rem;
|
font-size: 2.3rem;
|
||||||
line-height: 2.7rem;
|
line-height: 2.7rem;
|
||||||
filter: blur(1px);
|
filter: blur(1px);
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
@ -117,12 +217,12 @@
|
|||||||
margin: 2.4rem 0rem;
|
margin: 2.4rem 0rem;
|
||||||
}
|
}
|
||||||
.after-lyric {
|
.after-lyric {
|
||||||
font-size: 2.8rem;
|
font-size: 3rem;
|
||||||
line-height: 3.3rem;
|
line-height: 3.3rem;
|
||||||
margin: 2.4rem 0rem;
|
margin: 2.4rem 0rem;
|
||||||
}
|
}
|
||||||
.previous-lyric {
|
.previous-lyric {
|
||||||
font-size: 2.8rem;
|
font-size: 3em;
|
||||||
line-height: 3.3rem;
|
line-height: 3.3rem;
|
||||||
margin: 2.4rem 0rem;
|
margin: 2.4rem 0rem;
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
AquaVox 1.8.3 · 早期公开预览 · 源代码参见
|
AquaVox 1.9.0 · 早期公开预览 · 源代码参见
|
||||||
<a href="https://github.com/alikia2x/aquavox">GitHub</a>
|
<a href="https://github.com/alikia2x/aquavox">GitHub</a>
|
||||||
</p>
|
</p>
|
||||||
<a href="/import">导入音乐</a> <br />
|
<a href="/import">导入音乐</a> <br />
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
let lyricsText: string[] = [];
|
let lyricsText: string[] = [];
|
||||||
let onAdjustingProgress = false;
|
let onAdjustingProgress = false;
|
||||||
let hasLyrics: boolean;
|
let hasLyrics: boolean;
|
||||||
|
let lyricComp: any;
|
||||||
const coverPath = writable('');
|
const coverPath = writable('');
|
||||||
let mainInterval: ReturnType<typeof setInterval>;
|
let mainInterval: ReturnType<typeof setInterval>;
|
||||||
|
|
||||||
@ -134,12 +135,14 @@
|
|||||||
if (audioPlayer) {
|
if (audioPlayer) {
|
||||||
audioPlayer.currentTime = duration * progress;
|
audioPlayer.currentTime = duration * progress;
|
||||||
currentProgress = duration * progress;
|
currentProgress = duration * progress;
|
||||||
|
lyricComp.userSlideProgress();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustRealProgress(progress: number) {
|
function adjustRealProgress(progress: number) {
|
||||||
if (audioPlayer) {
|
if (audioPlayer) {
|
||||||
currentProgress = duration * progress;
|
currentProgress = duration * progress;
|
||||||
|
lyricComp.userSlideProgress();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,9 +166,15 @@
|
|||||||
) {
|
) {
|
||||||
currentProgress = audioPlayer.currentTime;
|
currentProgress = audioPlayer.currentTime;
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 17);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
audioPlayer.volume = localStorage.getItem('volume') ? Number(localStorage.getItem('volume')) : 1;
|
||||||
|
});
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (audioPlayer) {
|
if (audioPlayer) {
|
||||||
paused = audioPlayer.paused;
|
paused = audioPlayer.paused;
|
||||||
@ -206,7 +215,7 @@
|
|||||||
{hasLyrics}
|
{hasLyrics}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Lyrics lyrics={lyricsText} {originalLyrics} progress={currentProgress} />
|
<Lyrics lyrics={lyricsText} {originalLyrics} progress={currentProgress} bind:this={lyricComp}/>
|
||||||
|
|
||||||
<audio
|
<audio
|
||||||
bind:this={audioPlayer}
|
bind:this={audioPlayer}
|
||||||
|
Loading…
Reference in New Issue
Block a user