diff --git a/package.json b/package.json index 0c46ecb..89b1d70 100644 --- a/package.json +++ b/package.json @@ -12,5 +12,9 @@ "@sveltejs/vite-plugin-svelte": "^6.1.1", "svelte": "^5.38.1", "vite": "^7.1.2" + }, + "dependencies": { + "lucide-svelte": "^0.541.0", + "music-metadata": "^11.8.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59f05d4..73c56b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,13 @@ settings: importers: .: + dependencies: + lucide-svelte: + specifier: ^0.541.0 + version: 0.541.0(svelte@5.38.3) + music-metadata: + specifier: ^11.8.3 + version: 11.8.3 devDependencies: '@sveltejs/vite-plugin-svelte': specifier: ^6.1.1 @@ -20,6 +27,12 @@ importers: packages: + '@borewit/text-codec@0.1.1': + resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + + '@borewit/text-codec@0.2.0': + resolution: {integrity: sha512-X999CKBxGwX8wW+4gFibsbiNdwqmdQEXmUejIWaIqdrHBgS5ARIOOeyiQbHjP9G58xVEPcuvP6VwwH3A0OFTOA==} + '@esbuild/aix-ppc64@0.25.9': resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} engines: {node: '>=18'} @@ -312,6 +325,13 @@ packages: svelte: ^5.0.0 vite: ^6.3.0 || ^7.0.0 + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -332,6 +352,10 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -365,11 +389,21 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + file-type@21.0.0: + resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==} + engines: {node: '>=20'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} @@ -380,12 +414,25 @@ packages: locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + lucide-svelte@0.541.0: + resolution: {integrity: sha512-Jk+LiOYDl62R/0nWkG1s5XL2k6LHmPq3wUfiJ6qtBhb8jGefB4PU10x5HJrAihwaKqVc2vH5wjKMELGjHJenEQ==} + peerDependencies: + svelte: ^3 || ^4 || ^5.0.0-next.42 + magic-string@0.30.18: resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + music-metadata@11.8.3: + resolution: {integrity: sha512-Tgiv4MlCgDb6XzelziB1mmL2xeoHls0KTpCm3Z3qr+LfF4mBEpkuc5vNrc927IT5+S5fv+vzStfI+HYC0igDpA==} + engines: {node: '>=18'} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -411,6 +458,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} + svelte@5.38.3: resolution: {integrity: sha512-ldbPzKdjUy7IALMBn15jzBM/TNxdXMxKeQZ538zzdABUjLg7e7/OIwnlaMQ+OR6s91W7DbDmJYjxHThHH7r9xA==} engines: {node: '>=18'} @@ -419,6 +470,14 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + token-types@6.1.1: + resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + engines: {node: '>=14.16'} + + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + vite@7.1.3: resolution: {integrity: sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -472,6 +531,10 @@ packages: snapshots: + '@borewit/text-codec@0.1.1': {} + + '@borewit/text-codec@0.2.0': {} + '@esbuild/aix-ppc64@0.25.9': optional: true @@ -655,6 +718,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.1 + fflate: 0.8.2 + token-types: 6.1.1 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + '@types/estree@1.0.8': {} acorn@8.15.0: {} @@ -665,6 +738,8 @@ snapshots: clsx@2.1.1: {} + content-type@1.0.5: {} + debug@4.4.1: dependencies: ms: 2.1.3 @@ -710,9 +785,22 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fflate@0.8.2: {} + + file-type@21.0.0: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + fsevents@2.3.3: optional: true + ieee754@1.2.1: {} + is-reference@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -721,12 +809,32 @@ snapshots: locate-character@3.0.0: {} + lucide-svelte@0.541.0(svelte@5.38.3): + dependencies: + svelte: 5.38.3 + magic-string@0.30.18: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + media-typer@1.1.0: {} + ms@2.1.3: {} + music-metadata@11.8.3: + dependencies: + '@borewit/text-codec': 0.2.0 + '@tokenizer/token': 0.3.0 + content-type: 1.0.5 + debug: 4.4.1 + file-type: 21.0.0 + media-typer: 1.1.0 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + nanoid@3.3.11: {} picocolors@1.1.1: {} @@ -767,6 +875,10 @@ snapshots: source-map-js@1.2.1: {} + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + svelte@5.38.3: dependencies: '@jridgewell/remapping': 2.3.5 @@ -789,6 +901,14 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + token-types@6.1.1: + dependencies: + '@borewit/text-codec': 0.1.1 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + uint8array-extras@1.5.0: {} + vite@7.1.3: dependencies: esbuild: 0.25.9 diff --git a/public/music/jifmachinecd.flac b/public/music/jifmachinecd.flac new file mode 100644 index 0000000..703bd10 Binary files /dev/null and b/public/music/jifmachinecd.flac differ diff --git a/public/music/mgxleepe.flac b/public/music/mgxleepe.flac new file mode 100644 index 0000000..e090d48 Binary files /dev/null and b/public/music/mgxleepe.flac differ diff --git a/src/App.svelte b/src/App.svelte index 1ddedef..741aa67 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,4 +1,23 @@ + +
+ + + {#if loading} +
+
+
Loading tracks... {loadingProgress}%
+
+
+
+
+ {:else if error} +
+
+
{error}
+
+ {:else if currentTrack} +
+
+
+
{currentTrack.title}
+
FLAC
+
{currentTrackIndex + 1}/{tracks.length}
+
+
{currentTrack.artist}
+ {#if currentTrack.album} +
[Album] {currentTrack.album}
+ {/if} + {#if currentTrack.year} +
[Year] {currentTrack.year}
+ {/if} + {#if currentTrack.metadata.format} +
+ {Math.round(currentTrack.metadata.format.sampleRate / 1000)}kHz • {currentTrack.metadata.format.bitsPerSample}bit • {currentTrack.metadata.format.bitrate ? Math.round(currentTrack.metadata.format.bitrate / 1000) + 'kbps' : 'Lossless'} +
+ {/if} +
+ {#if currentTrack.metadata.common.picture && currentTrack.metadata.common.picture[0]} + {@const base64Image = arrayBufferToBase64(currentTrack.metadata.common.picture[0].data)} + {#if base64Image} +
+ Album artwork +
+
+ {:else} +
+ +
+ {/if} + {:else} +
+ +
+ {/if} +
+ {/if} + +
+ + + + + + +
+
+ {formatTime(currentTime)} + / + {formatTime(duration)} +
+ {#if duration > 0} +
-{formatTime(duration - currentTime)}
+ {/if} +
+ +
+ +
+ +
{Math.round(volume * 100)}%
+
+
+ + +
+ +
e.key === 'Enter' && seek(e)} + role="slider" + tabindex="0" + aria-label="Seek audio position" + aria-valuenow={duration ? Math.round((currentTime / duration) * 100) : 0} + aria-valuemin="0" + aria-valuemax="100" + > +
+
+
+
+ + {#if showPlaylist && tracks.length > 1} +
+
Playlist ({tracks.length} tracks)
+
+ {#each tracks as track, index} +
switchToTrack(index)} + on:keydown={(e) => e.key === 'Enter' && switchToTrack(index)} + role="button" + tabindex="0" + aria-label="Switch to track {track.title}" + > +
{index + 1}
+
+
{track.title}
+
{track.artist}
+
+
{formatTime(track.duration || 0)}
+
+ {/each} +
+
+ {/if} +