realese: AquaVox 1.8.0
This commit is contained in:
parent
8d87d9b6db
commit
dc5b27ddd0
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "aquavox",
|
"name": "aquavox",
|
||||||
"version": "0.0.1",
|
"version": "1.8.0",
|
||||||
"private": true,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<!-- <link rel="icon" href="%sveltekit.assets%/favicon.png" /> -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link
|
<link
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
export let coverPath: Writable<string>;
|
export let coverPath: Writable<string>;
|
||||||
|
export let hasLyrics: boolean;
|
||||||
let path: string = '';
|
let path: string = '';
|
||||||
|
|
||||||
coverPath.subscribe((p) => {
|
coverPath.subscribe((p) => {
|
||||||
@ -8,6 +9,18 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<img class="absolute shadow-md select-none z-10 object-cover rounded-2xl max-h-[calc(94vh-18rem)] md:max-h-[calc(94vh-20rem)] xl:w-auto max-w-[90%] md:max-w-[75%] xl:max-w-[37vw]
|
{#if hasLyrics}
|
||||||
bottom-72 md:bottom-80
|
<img
|
||||||
left-1/2 translate-x-[-50%] xl:left-[25vw]" src={path} alt="封面" />
|
class="absolute shadow-md select-none z-10 object-cover rounded-lg md:rounded-2xl max-md:h-20 max-xl:h-32 max-xl:top-6 md:max-h-[calc(94vh-20rem)] xl:w-auto max-w-[90%] xl:max-w-[37vw]
|
||||||
|
md:bottom-80 left-6 md:left-[calc(7vw-1rem)] lg:left-[calc(12vw-1rem)] xl:translate-x-[-50%] xl:left-[25vw]"
|
||||||
|
src={path}
|
||||||
|
alt="封面"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<img
|
||||||
|
class="absolute shadow-md select-none z-10 object-cover rounded-2xl max-h-[calc(94vh-18rem)] md:max-h-[calc(94vh-20rem)] xl:w-auto max-w-[90%] md:max-w-[75%] xl:max-w-[37vw]
|
||||||
|
bottom-72 md:bottom-80 left-1/2 translate-x-[-50%]"
|
||||||
|
src={path}
|
||||||
|
alt="封面"
|
||||||
|
/>
|
||||||
|
{/if}
|
@ -6,7 +6,7 @@
|
|||||||
import { fileListState } from '$lib/state/fileList.state';
|
import { fileListState } from '$lib/state/fileList.state';
|
||||||
import AddIcon from './addIcon.svelte';
|
import AddIcon from './addIcon.svelte';
|
||||||
const fileItems = useAtom(fileListState);
|
const fileItems = useAtom(fileListState);
|
||||||
export let accept: string = "audio/*";
|
export let accept: string = ".aac, .mp3, .wav, .ogg, .flac";
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
audioFiles.addEventListener('change', function (e: any) {
|
audioFiles.addEventListener('change', function (e: any) {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<a href={dest}>
|
<a href={dest}>
|
||||||
<div
|
<div
|
||||||
class="cursor-pointer flex relative min-h-20 h-fit p-4 w-full m-4 border-2 border-zinc-400 dark:border-neutral-700 rounded-lg"
|
class="cursor-pointer flex relative min-h-20 h-fit p-4 w-full my-4 lg:m-4 border-2 border-zinc-400 dark:border-neutral-700 rounded-lg"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col justify-center text-4xl">
|
<div class="flex flex-col justify-center text-4xl">
|
||||||
<Icon {icon} />
|
<Icon {icon} />
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import formatDuration from '$lib/formatDuration';
|
import formatDuration from '$lib/formatDuration';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
export let name: string;
|
export let name: string;
|
||||||
export let singer: string = '';
|
export let singer: string = '';
|
||||||
@ -13,8 +14,12 @@
|
|||||||
export let adjustVolume: Function;
|
export let adjustVolume: Function;
|
||||||
export let onSlide: boolean;
|
export let onSlide: boolean;
|
||||||
export let setOnSlide: Function;
|
export let setOnSlide: Function;
|
||||||
|
export let hasLyrics: boolean;
|
||||||
|
|
||||||
let progressBar: HTMLInputElement;
|
let progressBar: HTMLInputElement;
|
||||||
let volumeBar: HTMLInputElement;
|
let volumeBar: HTMLInputElement;
|
||||||
|
let showInfoTop: boolean = false;
|
||||||
|
const mql = window.matchMedia('(max-width: 1280px)');
|
||||||
|
|
||||||
function progressBarOnChange(e: any) {
|
function progressBarOnChange(e: any) {
|
||||||
adjustProgress(e.target.value / (duration + 0.001));
|
adjustProgress(e.target.value / (duration + 0.001));
|
||||||
@ -27,13 +32,37 @@
|
|||||||
function volumeBarOnChange(e: any) {
|
function volumeBarOnChange(e: any) {
|
||||||
adjustVolume(e.target.value);
|
adjustVolume(e.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
mql.addEventListener('change', (e) => {
|
||||||
|
showInfoTop = e.matches && hasLyrics;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$: {
|
||||||
|
showInfoTop = mql.matches && hasLyrics;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="absolute select-none bottom-2 h-60 w-[86vw] left-[7vw] lg:w-[76vw] lg:left-[12vw] xl:w-[37vw] xl:left-[7vw]">
|
{#if showInfoTop}
|
||||||
|
<div class="absolute top-6 md:top-12 left-28 md:left-48 lg:left-64 flex-col">
|
||||||
|
<span class="song-name text-shadow">{name}</span><br />
|
||||||
|
<span class="song-author">{singer}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={"absolute select-none bottom-2 h-60 w-[86vw] left-[7vw] z-10 " + (hasLyrics
|
||||||
|
? "lg:w-[76vw] lg:left-[12vw] xl:w-[37vw] xl:left-[7vw]"
|
||||||
|
: "lg:w-[76vw] lg:left-[12vw] xl:w-[37vw] xl:left-[31.5vw]")}
|
||||||
|
>
|
||||||
|
{#if !showInfoTop}
|
||||||
<div class="song-info">
|
<div class="song-info">
|
||||||
<span class="song-name text-shadow">{name}</span><br />
|
<span class="song-name text-shadow">{name}</span><br />
|
||||||
<span class="song-author">{singer}</span>
|
<span class="song-author">{singer}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="progress top-16">
|
<div class="progress top-16">
|
||||||
<div class="time-indicator text-shadow-md time-current">{formatDuration(progress)}</div>
|
<div class="time-indicator text-shadow-md time-current">{formatDuration(progress)}</div>
|
||||||
<input
|
<input
|
||||||
|
@ -57,8 +57,8 @@
|
|||||||
|
|
||||||
{#if lyrics && originalLyrics}
|
{#if lyrics && originalLyrics}
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 w-screen xl:w-[52vw] xl:left-[45vw] xl:px-[3vw] 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"
|
text-left scroll-smooth no-scrollbar overflow-y-auto z-[1] lyrics"
|
||||||
>
|
>
|
||||||
{#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)}>
|
||||||
@ -69,6 +69,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<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%);
|
||||||
|
}
|
||||||
.no-scrollbar {
|
.no-scrollbar {
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
@ -79,39 +82,67 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 2rem;
|
font-size: 2.3rem;
|
||||||
line-height: 2.5rem;
|
line-height: 2.7rem;
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
margin: 2rem 0.3rem;
|
margin: 1rem 0.3rem;
|
||||||
}
|
}
|
||||||
.previous-lyric {
|
.previous-lyric {
|
||||||
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: 3rem;
|
font-size: 2.2rem;
|
||||||
line-height: 3.5rem;
|
line-height: 2.7rem;
|
||||||
filter: blur(1px);
|
filter: blur(1px);
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
margin: 2rem 0rem;
|
margin: 1rem 0rem;
|
||||||
}
|
}
|
||||||
.after-lyric {
|
.after-lyric {
|
||||||
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: 3rem;
|
font-size: 2.2rem;
|
||||||
line-height: 3.5rem;
|
line-height: 2.7rem;
|
||||||
filter: blur(1px);
|
filter: blur(1px);
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
margin: 2rem 0rem;
|
margin: 1rem 0rem;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.current-lyric {
|
||||||
|
font-size: 3rem;
|
||||||
|
line-height: 4rem;
|
||||||
|
margin: 2.4rem 0rem;
|
||||||
|
}
|
||||||
|
.after-lyric {
|
||||||
|
font-size: 2.8rem;
|
||||||
|
line-height: 3.3rem;
|
||||||
|
margin: 2.4rem 0rem;
|
||||||
|
}
|
||||||
|
.previous-lyric {
|
||||||
|
font-size: 2.8rem;
|
||||||
|
line-height: 3.3rem;
|
||||||
|
margin: 2.4rem 0rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
.current-lyric {
|
.current-lyric {
|
||||||
font-size: 3.5rem;
|
font-size: 3.5rem;
|
||||||
line-height: 4.5rem;
|
line-height: 4.5rem;
|
||||||
|
margin: 2rem 0rem;
|
||||||
|
}
|
||||||
|
.after-lyric {
|
||||||
|
font-size: 3rem;
|
||||||
|
line-height: 3.5rem;
|
||||||
|
margin: 2rem 0rem;
|
||||||
|
}
|
||||||
|
.previous-lyric {
|
||||||
|
font-size: 3rem;
|
||||||
|
line-height: 3.5rem;
|
||||||
|
margin: 2rem 0rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -37,22 +37,33 @@
|
|||||||
musicList[id].coverUrl = URL.createObjectURL(v);
|
musicList[id].coverUrl = URL.createObjectURL(v);
|
||||||
}
|
}
|
||||||
idList = Object.keys(musicList);
|
idList = Object.keys(musicList);
|
||||||
console.log(musicList);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
localforage.clear();
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="absolute w-screen md:w-2/3 lg:w-1/2 left-0 md:left-[16.6667%] lg:left-1/4 px-[3%] md:px-0 top-16">
|
<svelte:head>
|
||||||
|
<title>Aquavox - 音乐库</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="absolute w-screen md:w-2/3 lg:w-1/2 left-0 md:left-[16.6667%] lg:left-1/4 px-[3%] md:px-0 top-16"
|
||||||
|
>
|
||||||
<h1>AquaVox</h1>
|
<h1>AquaVox</h1>
|
||||||
<h2>音乐库</h2>
|
<h2>音乐库</h2>
|
||||||
<div>
|
<div>
|
||||||
<ul
|
<ul class="mt-4 relative w-full">
|
||||||
class="mt-4 relative w-full"
|
|
||||||
>
|
|
||||||
{#each idList as id}
|
{#each idList as id}
|
||||||
<a href={`/play/${id}`}>
|
<a class="!no-underline !text-black dark:!text-white" href={`/play/${id}`}>
|
||||||
<li class="relative my-4 p-4 bg-zinc-300 dark:bg-zinc-600 rounded-lg">
|
<li
|
||||||
<span>{musicList[id].name}</span> <br />
|
class="relative my-4 p-4 duration-150 bg-zinc-200 hover:bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600 rounded-lg"
|
||||||
<span>{toHumanSize(musicList[id].size)}</span>
|
>
|
||||||
|
<span class="font-bold">{musicList[id].name}</span> <br />
|
||||||
|
<span>{toHumanSize(musicList[id].size)}</span> ·
|
||||||
|
<a href={`/import/${id}/lyric`}>导入歌词</a>
|
||||||
{#if musicList[id].coverUrl}
|
{#if musicList[id].coverUrl}
|
||||||
<img
|
<img
|
||||||
class="h-16 w-16 object-cover absolute rounded-lg right-2 top-2"
|
class="h-16 w-16 object-cover absolute rounded-lg right-2 top-2"
|
||||||
@ -65,4 +76,19 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<p>
|
||||||
|
AquaVox 1.8.0 · 早期公开预览 · 源代码参见
|
||||||
|
<a href="https://github.com/alikia2x/aquavox">GitHub</a>
|
||||||
|
</p>
|
||||||
|
<a href="/import">导入音乐</a> <br />
|
||||||
|
<button
|
||||||
|
on:click={() => window.confirm('确定要删除本地数据库中所有内容吗?') && clear()}
|
||||||
|
class="text-white bg-red-500 px-4 py-2 mt-4 rounded-md">一键清除</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
a {
|
||||||
|
@apply text-red-500 hover:text-red-400 duration-150 underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<h1 class="text-3xl text-red-500"><a href="/">AquaVox</a></h1>
|
||||||
<h1>导入</h1>
|
<h1>导入</h1>
|
||||||
<p class="leading-10">希望从哪里导入你的歌曲?</p>
|
<p class="leading-10">希望从哪里导入你的歌曲?</p>
|
||||||
<SourceCard title="本地" dest="/import/local" details="从本地导入歌曲添加到 AquaVox 的本地音乐库。
|
<SourceCard title="本地" dest="/import/local" details="从本地导入歌曲添加到 AquaVox 的本地音乐库。
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
let status = "";
|
let status = "";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<h1 class="text-3xl text-red-500"><a href="/">AquaVox</a></h1>
|
||||||
<h1>歌词导入</h1>
|
<h1>歌词导入</h1>
|
||||||
<p>当前为 <span class="text-zinc-700 dark:text-zinc-400">{audioId}</span> 导入歌词</p>
|
<p>当前为 <span class="text-zinc-700 dark:text-zinc-400">{audioId}</span> 导入歌词</p>
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
const success = useAtom(localImportSuccess);
|
const success = useAtom(localImportSuccess);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<h1 class="text-3xl text-red-500"><a href="/">AquaVox</a></h1>
|
||||||
<h1>本地导入向导</h1>
|
<h1>本地导入向导</h1>
|
||||||
<p>欢迎使用本地导入向导!</p>
|
<p>欢迎使用本地导入向导!</p>
|
||||||
<p>
|
<p>
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import srtParser2 from 'srt-parser-2';
|
import srtParser2 from 'srt-parser-2';
|
||||||
import type { Line } from 'srt-parser-2';
|
import type { Line } from 'srt-parser-2';
|
||||||
|
import type { IAudioMetadata } from 'music-metadata-browser';
|
||||||
|
|
||||||
const audioId = $page.params.id;
|
const audioId = $page.params.id;
|
||||||
let audioPlayer: HTMLAudioElement;
|
let audioPlayer: HTMLAudioElement;
|
||||||
@ -25,6 +26,7 @@
|
|||||||
let originalLyrics: Line[];
|
let originalLyrics: Line[];
|
||||||
let lyricsText: string[] = [];
|
let lyricsText: string[] = [];
|
||||||
let onAdjustingProgress = false;
|
let onAdjustingProgress = false;
|
||||||
|
let hasLyrics: boolean;
|
||||||
const coverPath = writable('');
|
const coverPath = writable('');
|
||||||
let mainInterval: ReturnType<typeof setInterval>;
|
let mainInterval: ReturnType<typeof setInterval>;
|
||||||
|
|
||||||
@ -64,8 +66,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function readDB() {
|
function readDB() {
|
||||||
getAudioIDMetadata(audioId, (metadata: any) => {
|
getAudioIDMetadata(audioId, (metadata: IAudioMetadata | null) => {
|
||||||
|
if (!metadata) return;
|
||||||
duration = metadata.format.duration ? metadata.format.duration : 0;
|
duration = metadata.format.duration ? metadata.format.duration : 0;
|
||||||
|
console.log(metadata);
|
||||||
|
singer = metadata.common.artist ? metadata.common.artist : '未知歌手';
|
||||||
prepared.push('duration');
|
prepared.push('duration');
|
||||||
});
|
});
|
||||||
localforage.getItem(`${audioId}-cover`, function (err, file) {
|
localforage.getItem(`${audioId}-cover`, function (err, file) {
|
||||||
@ -168,6 +173,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (originalLyrics) {
|
||||||
|
hasLyrics = true;
|
||||||
|
} else {
|
||||||
|
hasLyrics = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
readDB();
|
readDB();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -176,7 +189,7 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<Background coverId={audioId} />
|
<Background coverId={audioId} />
|
||||||
<Cover {coverPath} />
|
<Cover {coverPath} {hasLyrics} />
|
||||||
<InteractiveBox
|
<InteractiveBox
|
||||||
{name}
|
{name}
|
||||||
{singer}
|
{singer}
|
||||||
@ -190,6 +203,7 @@
|
|||||||
{adjustRealProgress}
|
{adjustRealProgress}
|
||||||
onSlide={onAdjustingProgress}
|
onSlide={onAdjustingProgress}
|
||||||
{setOnSlide}
|
{setOnSlide}
|
||||||
|
{hasLyrics}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Lyrics lyrics={lyricsText} {originalLyrics} progress={currentProgress} />
|
<Lyrics lyrics={lyricsText} {originalLyrics} progress={currentProgress} />
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB |
Loading…
Reference in New Issue
Block a user