diff --git a/package-lock.json b/package-lock.json index 154d411..e46fc3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "formidable": "^3.5.4", "gray-matter": "^4.0.3", "highlight.js": "^11.11.1", + "music-metadata": "^11.2.1", "next": "15.3.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -1551,6 +1552,30 @@ "tslib": "^2.8.0" } }, + "node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -2787,7 +2812,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -3932,6 +3956,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3945,6 +3975,24 @@ "node": ">=16.0.0" } }, + "node_modules/file-type": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", + "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4516,6 +4564,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5350,7 +5418,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -5887,6 +5954,35 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/music-metadata": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.2.1.tgz", + "integrity": "sha512-on3rRUrDXZOcWUb+bwRPQw4eZVD4Lmn1kFIa893xbM2pWi3kMTtz1EGA86TVBRgvk3Rcw5CftmTKY/wm1eI2Pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "file-type": "^20.4.1", + "media-typer": "^1.1.0", + "strtok3": "^10.2.2", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -6289,6 +6385,19 @@ "node": ">=8" } }, + "node_modules/peek-readable": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-7.0.0.tgz", + "integrity": "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7356,6 +7465,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strtok3": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.2.2.tgz", + "integrity": "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -7478,6 +7604,23 @@ "node": ">=0.6" } }, + "node_modules/token-types": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz", + "integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -7650,6 +7793,18 @@ "node": ">=14.17" } }, + "node_modules/uint8array-extras": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", + "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", diff --git a/package.json b/package.json index 9bd42c5..60fe42f 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "formidable": "^3.5.4", "gray-matter": "^4.0.3", "highlight.js": "^11.11.1", + "music-metadata": "^11.2.1", "next": "15.3.1", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/src/app/api/music/[track]/cover/route.ts b/src/app/api/music/[track]/cover/route.ts new file mode 100644 index 0000000..d05d080 --- /dev/null +++ b/src/app/api/music/[track]/cover/route.ts @@ -0,0 +1,23 @@ +import { NextRequest } from 'next/server'; +import { resolve } from 'path'; +import * as mm from 'music-metadata'; +export async function GET(req: NextRequest, { params }: { params: { track: string } }) { + try { + const trackName = decodeURIComponent(params.track); + const filePath = resolve(process.cwd(), 'public/music', trackName); + const metadata = await mm.parseFile(filePath); + const picture = metadata.common.picture?.[0]; + if (!picture) { + return new Response('No cover found', { status: 404 }); + } + return new Response(picture.data, { + headers: { + 'Content-Type': picture.format, + 'Content-Length': picture.data.length.toString(), + }, + }); + } catch (err) { + console.log(err); + return new Response('Error retrieving cover', { status: 500 }); + } +} diff --git a/src/components/AudioPlayer/AudioPlayer.tsx b/src/components/AudioPlayer/AudioPlayer.tsx index 2a49f68..56fffd1 100644 --- a/src/components/AudioPlayer/AudioPlayer.tsx +++ b/src/components/AudioPlayer/AudioPlayer.tsx @@ -1,14 +1,21 @@ 'use client'; -import { Box, Typography, IconButton, Slider, useTheme } from '@mui/material'; +import { Box, Typography, IconButton, Slider, useTheme, CardMedia } from '@mui/material'; import { PlayArrow, Pause, VolumeUp } from '@mui/icons-material'; -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; export default function AudioPlayer({ src, title }: { src: string; title: string }) { const audioRef = useRef(null); const [playing, setPlaying] = useState(false); const [volume, setVolume] = useState(100); + const [coverUrl, setCoverUrl] = useState(null); const theme = useTheme(); + const filename = src.split('/').pop(); + + useEffect(() => { + if (filename) + setCoverUrl(`/api/music/${encodeURIComponent(filename)}/cover`) + }, [filename]) const togglePlay = () => { const audio = audioRef.current; @@ -44,6 +51,11 @@ export default function AudioPlayer({ src, title }: { src: string; title: string mx: 'auto', }} > + { + coverUrl && ( + + ) + } {title} @@ -52,7 +64,7 @@ export default function AudioPlayer({ src, title }: { src: string; title: string handleVolumeChange(e, val)} aria-label="Volume" sx={{ width: 100 }} />