From 38b1c613b53e6e64fd1aa60199a05bd4bcf22f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E4=B9=BE?= Date: Fri, 30 Jan 2026 14:18:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 24 + index.html | 77 +++ package-lock.json | 1105 +++++++++++++++++++++++++++++++++++++ package.json | 14 + public/vite.svg | 1 + src/counter.js | 9 + src/javascript.svg | 1 + src/main.js | 773 ++++++++++++++++++++++++++ src/style.css | 583 +++++++++++++++++++ src/waveformVisualizer.js | 224 ++++++++ vite.config.js | 13 + 11 files changed, 2824 insertions(+) create mode 100644 .gitignore create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/vite.svg create mode 100644 src/counter.js create mode 100644 src/javascript.svg create mode 100644 src/main.js create mode 100644 src/style.css create mode 100644 src/waveformVisualizer.js create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/index.html b/index.html new file mode 100644 index 0000000..cd934a9 --- /dev/null +++ b/index.html @@ -0,0 +1,77 @@ + + + + + + + 音乐播放器 - 波形图可视化 + + +
+
+

音乐播放器

+ + +
+ + + + +
+ + +
+

请选择音乐文件

+
+ + +
+ +
+ + + + + +
+ + + +
+
+
+
+
+
+ 0:00 / 0:00 +
+
+ +
+ + +
+ +
+ + 列表循环 +
+ + +
+ + 条形图 +
+ + +
+ + + 50% +
+
+
+
+ + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c4d88b6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1105 @@ +{ + "name": "audio-player", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "audio-player", + "version": "0.0.0", + "devDependencies": { + "vite": "^7.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.0.tgz", + "integrity": "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.0.tgz", + "integrity": "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.0.tgz", + "integrity": "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.0.tgz", + "integrity": "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.0.tgz", + "integrity": "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.0.tgz", + "integrity": "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.0.tgz", + "integrity": "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.0.tgz", + "integrity": "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.0.tgz", + "integrity": "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.0.tgz", + "integrity": "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.0.tgz", + "integrity": "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.0.tgz", + "integrity": "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.0.tgz", + "integrity": "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.0.tgz", + "integrity": "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.0.tgz", + "integrity": "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.0.tgz", + "integrity": "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.0.tgz", + "integrity": "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.0.tgz", + "integrity": "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.0.tgz", + "integrity": "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.0.tgz", + "integrity": "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.0.tgz", + "integrity": "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.0.tgz", + "integrity": "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.0.tgz", + "integrity": "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.0.tgz", + "integrity": "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz", + "integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.0.tgz", + "integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.0", + "@rollup/rollup-android-arm64": "4.57.0", + "@rollup/rollup-darwin-arm64": "4.57.0", + "@rollup/rollup-darwin-x64": "4.57.0", + "@rollup/rollup-freebsd-arm64": "4.57.0", + "@rollup/rollup-freebsd-x64": "4.57.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", + "@rollup/rollup-linux-arm-musleabihf": "4.57.0", + "@rollup/rollup-linux-arm64-gnu": "4.57.0", + "@rollup/rollup-linux-arm64-musl": "4.57.0", + "@rollup/rollup-linux-loong64-gnu": "4.57.0", + "@rollup/rollup-linux-loong64-musl": "4.57.0", + "@rollup/rollup-linux-ppc64-gnu": "4.57.0", + "@rollup/rollup-linux-ppc64-musl": "4.57.0", + "@rollup/rollup-linux-riscv64-gnu": "4.57.0", + "@rollup/rollup-linux-riscv64-musl": "4.57.0", + "@rollup/rollup-linux-s390x-gnu": "4.57.0", + "@rollup/rollup-linux-x64-gnu": "4.57.0", + "@rollup/rollup-linux-x64-musl": "4.57.0", + "@rollup/rollup-openbsd-x64": "4.57.0", + "@rollup/rollup-openharmony-arm64": "4.57.0", + "@rollup/rollup-win32-arm64-msvc": "4.57.0", + "@rollup/rollup-win32-ia32-msvc": "4.57.0", + "@rollup/rollup-win32-x64-gnu": "4.57.0", + "@rollup/rollup-win32-x64-msvc": "4.57.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8c98dd4 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "audio-player", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^7.2.4" + } +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/counter.js b/src/counter.js new file mode 100644 index 0000000..881e2d7 --- /dev/null +++ b/src/counter.js @@ -0,0 +1,9 @@ +export function setupCounter(element) { + let counter = 0 + const setCounter = (count) => { + counter = count + element.innerHTML = `count is ${counter}` + } + element.addEventListener('click', () => setCounter(counter + 1)) + setCounter(0) +} diff --git a/src/javascript.svg b/src/javascript.svg new file mode 100644 index 0000000..f9abb2b --- /dev/null +++ b/src/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..dfe2177 --- /dev/null +++ b/src/main.js @@ -0,0 +1,773 @@ +import './style.css' +import WaveformVisualizer from './waveformVisualizer.js' + +// 音频播放器类 +class AudioPlayer { + constructor() { + this.audio = document.getElementById('audio'); + this.canvas = document.getElementById('waveform'); + this.ctx = this.canvas.getContext('2d'); + this.audioContext = null; + this.analyser = null; + this.source = null; + this.dataArray = null; + this.animationId = null; + this.waveformVisualizer = null; + + // 播放列表相关 + this.playlist = []; + this.currentTrackIndex = 0; + this.isPlayingFolder = false; + this.playbackMode = 'loop'; // 默认列表循环模式:'single', 'loop', 'random' + + // Web Audio API警告标志 + this.webAudioApiWarningShown = false; + + // 自动播放状态跟踪 + this.shouldAutoPlay = false; + + this.initializeElements(); + this.setupEventListeners(); + this.setupCanvas(); + this.setupKeyboardShortcuts(); + this.initializePlayerState(); + } + + initializeElements() { + // 获取所有DOM元素 + this.fileInput = document.getElementById('audioFile'); + this.folderInput = document.getElementById('audioFolder'); + this.playPauseBtn = document.getElementById('playPauseBtn'); + this.prevBtn = document.getElementById('prevBtn'); + this.nextBtn = document.getElementById('nextBtn'); + this.stopBtn = document.getElementById('stopBtn'); + this.progress = document.getElementById('progress'); + this.currentTime = document.getElementById('currentTime'); + this.duration = document.getElementById('duration'); + this.volumeSlider = document.getElementById('volumeSlider'); + this.volumeValue = document.getElementById('volumeValue'); + this.trackName = document.getElementById('trackName'); + this.muteBtn = document.getElementById('muteBtn'); + this.visualizationBtn = document.getElementById('visualizationBtn'); + this.visualizationMode = document.getElementById('visualizationMode'); + this.playbackModeBtn = document.getElementById('playbackModeBtn'); + this.playbackModeText = document.getElementById('playbackModeText'); + this.progressBar = document.getElementById('progressBar'); + this.progressHandle = document.getElementById('progressHandle'); + + // 初始化音频音量和状态 + this.audio.volume = 0.5; // 设置默认音量为50% + this.previousVolume = 0.5; // 用于静音前的音量记忆 + this.isMuted = false; + this.isDraggingProgress = false; // 进度条拖动状态 + } + + setupCanvas() { + // 设置canvas尺寸 + const rect = this.canvas.getBoundingClientRect(); + this.canvas.width = rect.width * window.devicePixelRatio; + this.canvas.height = rect.height * window.devicePixelRatio; + this.ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + this.canvas.style.width = rect.width + 'px'; + this.canvas.style.height = rect.height + 'px'; + } + + setupEventListeners() { + // 文件选择 + this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e)); + this.folderInput.addEventListener('change', (e) => this.handleFolderSelect(e)); + + // 音频结束事件(用于自动播放下一首) + this.audio.addEventListener('ended', () => this.handleTrackEnd()); + + // 播放控制 + this.playPauseBtn.addEventListener('click', () => this.togglePlayPause()); + this.prevBtn.addEventListener('click', () => this.previousTrack()); + this.nextBtn.addEventListener('click', () => this.nextTrack()); + this.stopBtn.addEventListener('click', () => this.stop()); + + // 播放模式切换 + this.playbackModeBtn.addEventListener('click', () => this.togglePlaybackMode()); + + // 音频事件 + this.audio.addEventListener('loadedmetadata', () => this.updateDuration()); + this.audio.addEventListener('timeupdate', () => this.updateProgress()); + this.audio.addEventListener('ended', () => this.stop()); + this.audio.addEventListener('volumechange', () => this.syncVolumeSlider()); + + // 音量控制 - 双向联动 + this.volumeSlider.addEventListener('input', (e) => this.updateVolume(e)); + + // 静音按钮 + this.muteBtn.addEventListener('click', () => this.toggleMute()); + + // Canvas点击切换可视化模式(不再用于跳转进度) + this.canvas.addEventListener('click', () => this.switchVisualization()); + + // 可视化模式切换按钮 + this.visualizationBtn.addEventListener('click', () => this.switchVisualization()); + + // 进度条拖动功能 + this.setupProgressDrag(); + } + + setupProgressDrag() { + // 进度条拖动功能 + let isDragging = false; + + const handleProgressDrag = (e) => { + if (!this.audio.duration) return; + + const rect = this.progressBar.getBoundingClientRect(); + const x = e.clientX - rect.left; + const percentage = Math.max(0, Math.min(1, x / rect.width)); + const newTime = percentage * this.audio.duration; + + this.audio.currentTime = newTime; + this.updateProgressBar(percentage); + }; + + const startDrag = (e) => { + isDragging = true; + this.isDraggingProgress = true; + document.addEventListener('mousemove', handleProgressDrag); + document.addEventListener('mouseup', stopDrag); + handleProgressDrag(e); + }; + + const stopDrag = () => { + isDragging = false; + this.isDraggingProgress = false; + document.removeEventListener('mousemove', handleProgressDrag); + document.removeEventListener('mouseup', stopDrag); + }; + + // 进度条点击 + this.progressBar.addEventListener('click', handleProgressDrag); + + // 拖动手柄 + this.progressHandle.addEventListener('mousedown', startDrag); + + // 防止拖动时选中文本 + this.progressHandle.addEventListener('selectstart', (e) => e.preventDefault()); + } + + updateProgressBar(percentage) { + // 更新进度条显示(不依赖于timeupdate事件) + this.progress.style.width = (percentage * 100) + '%'; + + // 更新手柄位置 + if (this.progressHandle && this.progressBar) { + const handleX = percentage * this.progressBar.offsetWidth - 8; // 8是手柄宽度的一半 + this.progressHandle.style.left = Math.max(-8, Math.min(this.progressBar.offsetWidth - 8, handleX)) + 'px'; + } + } + + handleFileSelect(event) { + const file = event.target.files[0]; + if (file) { + // 单个文件播放模式 + this.isPlayingFolder = false; + this.playlist = []; + this.currentTrackIndex = 0; + + // 清理旧的URL + if (this.audio.src) { + URL.revokeObjectURL(this.audio.src); + } + + const url = URL.createObjectURL(file); + this.audio.src = url; + this.trackName.textContent = file.name; + + // 确保音量设置保持一致 + const currentVolume = this.volumeSlider.value / 100; + this.audio.volume = currentVolume; + + // 设置音频上下文(如果尚未初始化) + this.setupAudioContext(); + } + } + + handleFolderSelect(event) { + const files = Array.from(event.target.files); + if (files.length > 0) { + // 筛选音频文件 - 支持更多格式 + const audioExtensions = ['mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac', 'wma', 'opus', 'webm']; + const audioFiles = files.filter(file => { + const extension = file.name.split('.').pop().toLowerCase(); + return file.type.startsWith('audio/') || audioExtensions.includes(extension); + }); + + if (audioFiles.length > 0) { + // 按文件名排序 + audioFiles.sort((a, b) => a.name.localeCompare(b.name)); + + this.playlist = audioFiles; + this.currentTrackIndex = 0; + this.isPlayingFolder = true; + + // 确保播放模式显示正确(默认列表循环) + this.playbackMode = 'loop'; + this.updatePlaybackModeDisplay(); + + // 确保音频上下文已初始化 + this.setupAudioContext(); + + // 播放第一首 + this.loadTrack(0); + console.log(`已加载文件夹,共${audioFiles.length}首音乐`); + + // 显示成功提示 + this.showFolderLoadedFeedback(audioFiles.length); + } else { + alert('文件夹中没有找到音频文件'); + } + } + } + + showFolderLoadedFeedback(count) { + const feedback = document.createElement('div'); + feedback.textContent = `已加载 ${count} 首音乐`; + feedback.className = 'visualization-feedback'; + feedback.style.background = 'rgba(76, 175, 80, 0.9)'; + + document.body.appendChild(feedback); + + setTimeout(() => { + if (feedback.parentNode) { + feedback.parentNode.removeChild(feedback); + } + }, 2000); + } + + loadTrack(index, shouldAutoPlay = true) { + if (index < 0 || index >= this.playlist.length) return; + + this.currentTrackIndex = index; + const file = this.playlist[index]; + + // 停止当前播放和可视化 + this.stopVisualization(); + + // 清理旧的URL + if (this.audio.src) { + URL.revokeObjectURL(this.audio.src); + } + + const url = URL.createObjectURL(file); + this.audio.src = url; + this.trackName.textContent = `${index + 1}/${this.playlist.length}: ${file.name}`; + + // 确保音量设置保持一致 + const currentVolume = this.volumeSlider.value / 100; + this.audio.volume = currentVolume; + + // 设置音频上下文(如果尚未初始化) + this.setupAudioContext(); + + this.updateNavigationButtons(); + + // 设置自动播放状态 + this.shouldAutoPlay = shouldAutoPlay; + + // 等待音频数据加载完成后播放 + const tryAutoPlay = () => { + if (this.audio.readyState >= 2 && (!this.audio.paused || this.shouldAutoPlay)) { + this.play(); + } + }; + this.audio.addEventListener('loadedmetadata', tryAutoPlay, { once: true }); + this.audio.addEventListener('canplay', tryAutoPlay, { once: true }); + this.audio.addEventListener('loadeddata', tryAutoPlay, { once: true }); + } + + handleTrackEnd() { + if (!this.isPlayingFolder || this.playlist.length === 0) return; + + let nextIndex; + switch (this.playbackMode) { + case 'single': + // 单曲循环 + nextIndex = this.currentTrackIndex; + break; + case 'random': + // 随机播放 + nextIndex = Math.floor(Math.random() * this.playlist.length); + break; + case 'loop': + default: + // 列表循环 + nextIndex = (this.currentTrackIndex + 1) % this.playlist.length; + break; + } + + // 自动播放下一首 + this.loadTrack(nextIndex, true); + } + + nextTrack() { + if (!this.isPlayingFolder || this.playlist.length === 0) return; + + const nextIndex = (this.currentTrackIndex + 1) % this.playlist.length; + this.loadTrack(nextIndex, true); // 自动播放 + } + + previousTrack() { + if (!this.isPlayingFolder || this.playlist.length === 0) return; + + const prevIndex = this.currentTrackIndex === 0 ? this.playlist.length - 1 : this.currentTrackIndex - 1; + this.loadTrack(prevIndex, true); // 自动播放 + } + + setupAudioContext() { + try { + // 首次创建音频上下文 + if (!this.audioContext) { + this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); + this.analyser = this.audioContext.createAnalyser(); + this.analyser.fftSize = 2048; + + const bufferLength = this.analyser.frequencyBinCount; + this.dataArray = new Uint8Array(bufferLength); + + // 初始化高级可视化器 + this.waveformVisualizer = new WaveformVisualizer(this.canvas, this.audioContext, this.analyser); + console.log('音频上下文和可视化器初始化成功'); + } + + // 连接音频源(只在首次或需要重新连接时) + if (this.audioContext && this.analyser && !this.source) { + this.source = this.audioContext.createMediaElementSource(this.audio); + this.source.connect(this.analyser); + this.analyser.connect(this.audioContext.destination); + console.log('音频源连接成功'); + } + } catch (error) { + console.error('音频上下文初始化失败:', error); + if (!this.webAudioApiWarningShown) { + alert('您的浏览器不支持Web Audio API,部分功能可能无法正常使用'); + this.webAudioApiWarningShown = true; + } + } + } + + play() { + if (this.audio.src) { + // 确保音频上下文处于运行状态 + if (this.audioContext && this.audioContext.state === 'suspended') { + this.audioContext.resume(); + } + + this.audio.play().then(() => { + this.startVisualization(); + this.updatePlayPauseButton(true); + document.querySelector('.audio-player').classList.add('playing'); + }).catch(error => { + console.error('播放失败:', error); + alert('播放失败,请检查音频文件'); + }); + } + } + + pause() { + this.audio.pause(); + this.stopVisualization(); + this.updatePlayPauseButton(false); + document.querySelector('.audio-player').classList.remove('playing'); + } + + // 移除旧的togglePlayPause方法,因为已经移到了上面 + + updatePlayPauseButton(isPlaying) { + if (this.playPauseBtn) { + this.playPauseBtn.textContent = isPlaying ? '⏸️' : '▶️'; + } + } + + stop() { + this.audio.pause(); + this.audio.currentTime = 0; + this.stopVisualization(); + this.clearCanvas(); + this.updatePlayPauseButton(false); + document.querySelector('.audio-player').classList.remove('playing'); + } + + updateDuration() { + const duration = this.audio.duration; + this.duration.textContent = this.formatTime(duration); + } + + updateProgress() { + // 只在不拖动时更新进度条 + if (this.isDraggingProgress) return; + const currentTime = this.audio.currentTime; + const duration = this.audio.duration; + const progressPercent = (currentTime / duration) * 100; + this.progress.style.width = progressPercent + '%'; + this.currentTime.textContent = this.formatTime(currentTime); + // 更新手柄位置 + if (this.progressHandle && this.progressBar) { + const handleX = (currentTime / duration) * this.progressBar.offsetWidth - 8; + this.progressHandle.style.left = Math.max(-8, Math.min(this.progressBar.offsetWidth - 8, handleX)) + 'px'; + } + } + + updateVolume(event) { + const volume = event.target.value / 100; + this.audio.volume = volume; + this.volumeValue.textContent = event.target.value + '%'; + + // 更新静音状态 + if (volume > 0 && this.isMuted) { + this.isMuted = false; + } else if (volume === 0 && !this.isMuted) { + this.isMuted = true; + } + + // 如果音量大于0且不是静音状态,保存当前音量 + if (volume > 0 && !this.isMuted) { + this.previousVolume = volume; + } + + this.updateVolumeIcon(Math.round(volume * 100)); + console.log('音量设置为:', volume, '静音状态:', this.isMuted); + } + + syncVolumeSlider() { + // 同步滑块位置与音频实际音量(处理音频元素自身的音量变化) + const currentVolume = Math.round(this.audio.volume * 100); + this.volumeSlider.value = currentVolume; + this.volumeValue.textContent = currentVolume + '%'; + this.updateVolumeIcon(currentVolume); + } + + updateVolumeIcon(volume) { + if (this.muteBtn) { + if (volume === 0 || this.isMuted) { + this.muteBtn.textContent = '🔇'; + this.muteBtn.classList.add('muted'); + } else if (volume < 30) { + this.muteBtn.textContent = '🔈'; + this.muteBtn.classList.remove('muted'); + } else if (volume < 70) { + this.muteBtn.textContent = '🔉'; + this.muteBtn.classList.remove('muted'); + } else { + this.muteBtn.textContent = '🔊'; + this.muteBtn.classList.remove('muted'); + } + } + } + + toggleMute() { + if (this.isMuted) { + // 取消静音,恢复到之前的音量 + this.audio.volume = this.previousVolume; + this.volumeSlider.value = Math.round(this.previousVolume * 100); + this.volumeValue.textContent = Math.round(this.previousVolume * 100) + '%'; + this.isMuted = false; + } else { + // 静音,保存当前音量 + this.previousVolume = this.audio.volume; + this.audio.volume = 0; + this.volumeSlider.value = 0; + this.volumeValue.textContent = '0%'; + this.isMuted = true; + } + this.updateVolumeIcon(this.audio.volume); + console.log(this.isMuted ? '已静音' : '取消静音', '音量:', this.audio.volume); + } + + formatTime(seconds) { + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins}:${secs.toString().padStart(2, '0')}`; + } + + startVisualization() { + if (!this.waveformVisualizer) { + console.warn('可视化器未初始化,使用备用方案'); + this.startBasicVisualization(); + return; + } + + const draw = () => { + this.animationId = requestAnimationFrame(draw); + this.waveformVisualizer.draw(); + }; + + draw(); + } + + startBasicVisualization() { + // 备用基础可视化方案 + if (!this.analyser) return; + + const draw = () => { + this.animationId = requestAnimationFrame(draw); + + this.analyser.getByteFrequencyData(this.dataArray); + + this.ctx.fillStyle = 'rgb(0, 0, 0)'; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + + const barWidth = (this.canvas.width / this.dataArray.length) * 2.5; + let barHeight; + let x = 0; + + for (let i = 0; i < this.dataArray.length; i++) { + barHeight = (this.dataArray[i] / 255) * this.canvas.height; + + const r = barHeight + (25 * (i / this.dataArray.length)); + const g = 250 * (i / this.dataArray.length); + const b = 50; + + this.ctx.fillStyle = `rgb(${r},${g},${b})`; + this.ctx.fillRect(x, this.canvas.height - barHeight, barWidth, barHeight); + + x += barWidth + 1; + } + }; + + draw(); + } + + stopVisualization() { + if (this.animationId) { + cancelAnimationFrame(this.animationId); + this.animationId = null; + } + } + + clearCanvas() { + if (this.waveformVisualizer) { + this.ctx.fillStyle = 'rgb(0, 0, 0)'; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + } + } + + switchVisualization() { + if (this.waveformVisualizer) { + this.waveformVisualizer.nextVisualization(); + const currentMode = this.waveformVisualizer.getCurrentVisualization(); + const modeNames = { + 'bars': '条形图', + 'wave': '波形图', + 'circular': '圆形图' + }; + if (this.visualizationMode) { + this.visualizationMode.textContent = modeNames[currentMode] || '条形图'; + console.log('切换到可视化模式:', currentMode); + } + + // 添加视觉反馈 + this.showVisualizationFeedback(currentMode); + } else { + console.warn('可视化器未初始化'); + } + } + + showVisualizationFeedback(mode) { + // 创建切换反馈效果 + const feedback = document.createElement('div'); + feedback.textContent = this.getModeDisplayName(mode); + feedback.className = 'visualization-feedback'; + + document.body.appendChild(feedback); + + // 1.5秒后移除反馈元素 + setTimeout(() => { + if (feedback.parentNode) { + feedback.parentNode.removeChild(feedback); + } + }, 1500); + } + + getModeDisplayName(mode) { + const modeNames = { + 'bars': '🔊 条形图模式', + 'wave': '〰️ 波形图模式', + 'circular': '⭕ 圆形图模式' + }; + return modeNames[mode] || '条形图模式'; + } + + // 键盘快捷键相关方法 + setupKeyboardShortcuts() { + // 键盘快捷键支持 + document.addEventListener('keydown', (e) => { + // 防止在输入框中触发快捷键 + if (e.target.tagName === 'INPUT') return; + + switch (e.key.toLowerCase()) { + case ' ': + e.preventDefault(); + this.togglePlayPause(); + break; + case 'm': + this.toggleMute(); + break; + case 'arrowup': + e.preventDefault(); + this.adjustVolume(5); + break; + case 'arrowdown': + e.preventDefault(); + this.adjustVolume(-5); + break; + case 'arrowleft': + e.preventDefault(); + if (e.ctrlKey && this.isPlayingFolder) { + this.previousTrack(); + } else { + this.skipTime(-5); + } + break; + case 'arrowright': + e.preventDefault(); + if (e.ctrlKey && this.isPlayingFolder) { + this.nextTrack(); + } else { + this.skipTime(5); + } + break; + case 'n': + if (this.isPlayingFolder) { + this.nextTrack(); + } + break; + case 'p': + if (this.isPlayingFolder) { + this.previousTrack(); + } + break; + } + }); + } + + initializePlayerState() { + // 初始化播放器状态 + this.updatePlayPauseButton(false); + document.querySelector('.audio-player').classList.remove('playing'); + + // 初始化播放模式显示(直接设置为列表循环) + this.playbackMode = 'loop'; + this.updatePlaybackModeDisplay(); + + // 初始状态下禁用导航按钮 + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const hasPlaylist = this.isPlayingFolder && this.playlist.length > 0; + this.prevBtn.disabled = !hasPlaylist; + this.nextBtn.disabled = !hasPlaylist; + this.playbackModeBtn.disabled = !hasPlaylist; + + if (!hasPlaylist) { + this.prevBtn.style.opacity = '0.5'; + this.nextBtn.style.opacity = '0.5'; + this.playbackModeBtn.style.opacity = '0.5'; + } else { + this.prevBtn.style.opacity = '1'; + this.nextBtn.style.opacity = '1'; + this.playbackModeBtn.style.opacity = '1'; + } + } + + togglePlayPause() { + if (this.audio.paused) { + this.play(); + } else { + this.pause(); + } + } + + adjustVolume(change) { + const currentVolume = Math.round(this.audio.volume * 100); + const newVolume = Math.max(0, Math.min(100, currentVolume + change)); + + this.volumeSlider.value = newVolume; + this.audio.volume = newVolume / 100; + this.volumeValue.textContent = newVolume + '%'; + + // 更新静音状态 + if (newVolume > 0 && this.isMuted) { + this.isMuted = false; + } else if (newVolume === 0 && !this.isMuted) { + this.isMuted = true; + } + + if (newVolume > 0 && !this.isMuted) { + this.previousVolume = newVolume / 100; + } + + this.updateVolumeIcon(newVolume); + console.log('键盘调节音量:', newVolume); + } + + skipTime(seconds) { + if (this.audio.duration) { + const newTime = Math.max(0, Math.min(this.audio.duration, this.audio.currentTime + seconds)); + this.audio.currentTime = newTime; + } + } + + updatePlaybackModeDisplay() { + const modeIcons = { + 'loop': '🔁', + 'single': '🔂', + 'random': '🔀' + }; + + const modeTexts = { + 'loop': '列表循环', + 'single': '单曲循环', + 'random': '随机播放' + }; + + if (this.playbackModeBtn && this.playbackModeText) { + this.playbackModeBtn.textContent = modeIcons[this.playbackMode]; + this.playbackModeText.textContent = modeTexts[this.playbackMode]; + } + } + + togglePlaybackMode() { + const modes = ['loop', 'single', 'random']; + const currentIndex = modes.indexOf(this.playbackMode); + this.playbackMode = modes[(currentIndex + 1) % modes.length]; + + this.updatePlaybackModeDisplay(); + + const modeTexts = { + 'loop': '列表循环', + 'single': '单曲循环', + 'random': '随机播放' + }; + + // 添加视觉反馈 + this.showPlaybackModeFeedback(modeTexts[this.playbackMode]); + } + + showPlaybackModeFeedback(modeText) { + const feedback = document.createElement('div'); + feedback.textContent = `播放模式: ${modeText}`; + feedback.className = 'visualization-feedback'; + feedback.style.background = 'rgba(102, 126, 234, 0.9)'; + + document.body.appendChild(feedback); + + setTimeout(() => { + if (feedback.parentNode) { + feedback.parentNode.removeChild(feedback); + } + }, 1500); + } +} + +// 初始化播放器 +document.addEventListener('DOMContentLoaded', () => { + new AudioPlayer(); +}); \ No newline at end of file diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..8c6b0da --- /dev/null +++ b/src/style.css @@ -0,0 +1,583 @@ +/* 音乐播放器样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +.audio-player { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border-radius: 20px; + padding: 30px; + box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); + border: 1px solid rgba(255, 255, 255, 0.18); + width: 100%; + max-width: 900px; + color: white; +} + +.audio-player h1 { + text-align: center; + margin-bottom: 20px; + font-size: 2.5em; + font-weight: 300; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); +} + +/* 文件选择器 */ +.file-selector { + margin-bottom: 15px; + text-align: center; + display: flex; + justify-content: center; + gap: 15px; + flex-wrap: wrap; +} + +.folder-label { + background: linear-gradient(45deg, #667eea, #764ba2); +} + +.file-selector input[type="file"] { + display: none; +} + +.file-label { + display: inline-block; + padding: 12px 30px; + background: linear-gradient(45deg, #ff6b6b, #ee5a24); + color: white; + border-radius: 25px; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 500; + box-shadow: 0 4px 15px rgba(238, 90, 36, 0.4); +} + +.file-label:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(238, 90, 36, 0.6); +} + +/* 波形图容器 */ +.waveform-container { + margin: 20px 0; + text-align: center; + border-radius: 15px; + overflow: hidden; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); + height: 320px; +} + +#waveform { + width: 100%; + height: 100%; + background: #000; + border-radius: 15px; + cursor: pointer; +} + +#waveform:hover { + transform: scale(1.01); +} + +/* 播放控制和进度条容器 */ +.playback-progress-container { + display: flex; + align-items: center; + gap: 15px; + margin: 20px 0; +} + +.control-btn { + padding: 12px 25px; + border: none; + border-radius: 25px; + background: linear-gradient(45deg, #4facfe, #00f2fe); + color: white; + font-size: 16px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(79, 172, 254, 0.4); + white-space: nowrap; +} + +/* 播放/暂停按钮 */ +.play-pause-btn { + padding: 12px; + border-radius: 50%; + width: 40px; + height: 40px; + font-size: 20px; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(90deg, #ff6b6b, #4ecdc4); + box-shadow: 0 3px 12px rgba(255, 107, 107, 0.4); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border: 2px solid rgba(255, 255, 255, 0.3); +} + +.play-pause-btn:hover { + box-shadow: 0 5px 18px rgba(255, 107, 107, 0.6); + transform: scale(1.08); + border-color: rgba(255, 255, 255, 0.5); +} + +.play-pause-btn:active { + transform: scale(0.95); +} + +/* 停止按钮 */ +.stop-btn { + padding: 10px; + border-radius: 50%; + width: 40px; + height: 40px; + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(90deg, #667eea, #764ba2); + box-shadow: 0 2px 10px rgba(102, 126, 234, 0.4); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border: 2px solid rgba(255, 255, 255, 0.2); +} + +.stop-btn:hover { + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.6); + transform: scale(1.08); + border-color: rgba(255, 255, 255, 0.4); +} + +.stop-btn:active { + transform: scale(0.95); +} + +/* 导航按钮(上一首/下一首) */ +.nav-btn { + padding: 10px; + border-radius: 50%; + width: 40px; + height: 40px; + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(45deg, #4facfe, #00f2fe); + box-shadow: 0 2px 10px rgba(79, 172, 254, 0.4); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border: 2px solid rgba(255, 255, 255, 0.2); +} + +.nav-btn:hover { + box-shadow: 0 4px 15px rgba(79, 172, 254, 0.6); + transform: scale(1.08); + border-color: rgba(255, 255, 255, 0.4); +} + +.nav-btn:active { + transform: scale(0.95); +} + +.nav-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none !important; +} + +.control-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(79, 172, 254, 0.6); +} + +.control-btn:active { + transform: translateY(0); +} + +/* 进度条 */ +.progress-container { + flex: 1; + user-select: none; /* 防止拖动时选中文本 */ + margin-top: 15px; +} + +.progress-bar { + width: 100%; + height: 6px; + background: rgba(255, 255, 255, 0.15); + border-radius: 3px; + position: relative; + cursor: pointer; + margin-bottom: 8px; + transition: all 0.3s ease; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); +} + +.progress-bar:hover { + height: 8px; + box-shadow: 0 2px 8px rgba(255, 255, 255, 0.2), inset 0 1px 3px rgba(0, 0, 0, 0.2); +} + +.progress { + height: 100%; + background: linear-gradient(90deg, #ff6b6b, #4ecdc4); + width: 0%; + transition: width 0.1s ease; + border-radius: 3px; + position: relative; + box-shadow: 0 1px 3px rgba(255, 107, 107, 0.4); +} + +.progress-handle { + position: absolute; + top: 50%; + right: -6px; + transform: translateY(-50%); + width: 12px; + height: 12px; + background: linear-gradient(45deg, #ff6b6b, #4ecdc4); + border-radius: 50%; + cursor: grab; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + transition: all 0.2s ease; + z-index: 10; + border: 1px solid rgba(255, 255, 255, 0.8); +} + +.progress-handle:hover { + transform: translateY(-50%) scale(1.4); + box-shadow: 0 3px 8px rgba(255, 107, 107, 0.4); + border-width: 2px; +} + +.progress-handle:active { + cursor: grabbing; + transform: translateY(-50%) scale(1.2); + box-shadow: 0 2px 6px rgba(255, 107, 107, 0.6); +} + +.time-display { + text-align: center; + font-size: 14px; + color: rgba(255, 255, 255, 0.8); +} + +/* 底部控制栏 */ +.bottom-controls { + display: flex; + justify-content: space-between; + align-items: center; + gap: 20px; + margin: 20px 0; +} + +/* 播放模式控制 */ +.playback-mode-control { + display: flex; + align-items: center; + gap: 10px; + background: rgba(255, 255, 255, 0.05); + padding: 8px 12px; + border-radius: 20px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +#playbackModeBtn { + background: linear-gradient(45deg, #ff6b6b, #ee5a24); + padding: 8px 12px; + border-radius: 50%; + width: 36px; + height: 36px; + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; + border: none; + min-width: 36px; +} + +#playbackModeText { + font-size: 14px; + font-weight: 500; + white-space: nowrap; +} + +/* 可视化控制 */ +.visualization-control { + display: flex; + align-items: center; + gap: 15px; +} + +#visualizationBtn { + background: linear-gradient(45deg, #667eea, #764ba2); +} + +#visualizationMode { + font-weight: 500; + color: rgba(255, 255, 255, 0.9); +} + +/* 音量控制 */ +.volume-control { + display: flex; + align-items: center; + gap: 10px; +} + +.volume-control label { + font-weight: 500; + white-space: nowrap; +} + +#volumeSlider { + width: 120px; + height: 5px; + background: rgba(255, 255, 255, 0.3); + border-radius: 5px; + outline: none; + -webkit-appearance: none; +} + +#volumeSlider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + background: linear-gradient(45deg, #ff6b6b, #4ecdc4); + border-radius: 50%; + cursor: pointer; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); +} + +#volumeSlider::-moz-range-thumb { + width: 20px; + height: 20px; + background: linear-gradient(45deg, #ff6b6b, #4ecdc4); + border-radius: 50%; + cursor: pointer; + border: none; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); +} + +#volumeValue { + min-width: 40px; + font-weight: 500; +} + +/* 音量按钮 */ +.volume-btn { + background: linear-gradient(45deg, #667eea, #764ba2); + padding: 8px; + font-size: 16px; + border-radius: 50%; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + border: none; + min-width: 36px; +} + +.volume-btn:hover { + transform: scale(1.1); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.volume-btn:active { + transform: scale(0.95); +} + +.volume-btn.muted { + background: linear-gradient(45deg, #ff6b6b, #ee5a24); +} + +.volume-btn.muted:hover { + box-shadow: 0 4px 12px rgba(255, 107, 107, 0.4); +} + +/* 曲目信息 */ +.track-info { + text-align: center; + margin: 15px 0; + padding: 10px; + background: rgba(255, 255, 255, 0.1); + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.2); +} + +#trackName { + font-size: 16px; + font-weight: 500; + color: rgba(255, 255, 255, 0.9); + margin: 0; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .audio-player { + padding: 20px; + margin: 10px; + } + + .audio-player h1 { + font-size: 2em; + } + + .playback-progress-container { + flex-direction: row; + align-items: center; + gap: 10px; + } + + .play-pause-btn { + width: 42px; + height: 42px; + font-size: 16px; + } + + .stop-btn { + width: 32px; + height: 32px; + font-size: 12px; + } + + .waveform-container { + height: 250px; + } + + .bottom-controls { + flex-direction: column; + gap: 15px; + } + + .playback-mode-control { + justify-content: center; + width: 100%; + } + + .visualization-control, + .volume-control { + justify-content: center; + width: 100%; + } + + #volumeSlider { + width: 150px; + } + + .control-btn { + font-size: 14px; + padding: 10px 20px; + } +} + +@media (max-width: 480px) { + .audio-player { + padding: 15px; + } + + .audio-player h1 { + font-size: 1.8em; + } + + .waveform-container { + height: 200px; + } + + .play-pause-btn { + width: 45px; + height: 45px; + font-size: 18px; + } + + .stop-btn { + width: 35px; + height: 35px; + font-size: 14px; + } + + #volumeSlider { + width: 120px; + } +} + +/* 动画效果 */ +@keyframes pulse { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } + 100% { + transform: scale(1); + } +} + +@keyframes glow { + 0% { + box-shadow: 0 0 5px rgba(255, 107, 107, 0.5); + } + 50% { + box-shadow: 0 0 20px rgba(255, 107, 107, 0.8), 0 0 30px rgba(255, 107, 107, 0.6); + } + 100% { + box-shadow: 0 0 5px rgba(255, 107, 107, 0.5); + } +} + +.audio-player.playing .play-pause-btn { + animation: glow 2s infinite; + background: linear-gradient(90deg, #ff6b6b, #ff8e53); +} + +@keyframes fadeInOut { + 0% { + opacity: 0; + transform: translate(-50%, -50%) scale(0.8); + } + 20% { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + 80% { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + 100% { + opacity: 0; + transform: translate(-50%, -50%) scale(0.8); + } +} + +.visualization-feedback { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 15px 25px; + border-radius: 25px; + font-size: 18px; + font-weight: bold; + z-index: 1000; + pointer-events: none; + animation: fadeInOut 1.5s ease-in-out; +} \ No newline at end of file diff --git a/src/waveformVisualizer.js b/src/waveformVisualizer.js new file mode 100644 index 0000000..3f891f5 --- /dev/null +++ b/src/waveformVisualizer.js @@ -0,0 +1,224 @@ +// 高级波形图可视化器 +class WaveformVisualizer { + constructor(canvas, audioContext, analyser) { + this.canvas = canvas; + this.ctx = canvas.getContext('2d'); + this.audioContext = audioContext; + this.analyser = analyser; + + this.bufferLength = this.analyser.frequencyBinCount; + this.dataArray = new Uint8Array(this.bufferLength); + + this.setupCanvas(); + this.visualizationTypes = ['bars', 'wave', 'circular']; + this.currentVisualization = 0; + } + + setupCanvas() { + const rect = this.canvas.getBoundingClientRect(); + this.canvas.width = rect.width * window.devicePixelRatio; + this.canvas.height = rect.height * window.devicePixelRatio; + this.ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + this.canvas.style.width = rect.width + 'px'; + this.canvas.style.height = rect.height + 'px'; + } + + drawBars() { + this.analyser.getByteFrequencyData(this.dataArray); + + this.ctx.fillStyle = 'rgb(0, 0, 0)'; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + + const barWidth = (this.canvas.width / this.bufferLength) * 2.5; + let barHeight; + let x = 0; + + for (let i = 0; i < this.bufferLength; i++) { + barHeight = (this.dataArray[i] / 255) * this.canvas.height; + + // 创建渐变效果 + const gradient = this.ctx.createLinearGradient(0, this.canvas.height - barHeight, 0, this.canvas.height); + const r = barHeight + (25 * (i / this.bufferLength)); + const g = 250 * (i / this.bufferLength); + const b = 50; + + gradient.addColorStop(0, `rgb(${r}, ${g}, ${b})`); + gradient.addColorStop(1, `rgb(${Math.floor(r/2)}, ${Math.floor(g/2)}, ${Math.floor(b/2)})`); + + this.ctx.fillStyle = gradient; + this.ctx.fillRect(x, this.canvas.height - barHeight, barWidth, barHeight); + + x += barWidth + 1; + } + } + + drawWave() { + this.analyser.getByteTimeDomainData(this.dataArray); + + this.ctx.fillStyle = 'rgb(0, 0, 0)'; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.ctx.lineWidth = 2; + this.ctx.strokeStyle = 'rgb(0, 255, 0)'; + this.ctx.beginPath(); + + const sliceWidth = this.canvas.width * 1.0 / this.bufferLength; + let x = 0; + + for (let i = 0; i < this.bufferLength; i++) { + const v = this.dataArray[i] / 128.0; + const y = v * this.canvas.height / 2; + + if (i === 0) { + this.ctx.moveTo(x, y); + } else { + this.ctx.lineTo(x, y); + } + + x += sliceWidth; + } + + this.ctx.lineTo(this.canvas.width, this.canvas.height / 2); + this.ctx.stroke(); + } + + drawCircular() { + this.drawCompactCircular(); + } + + drawCompactCircular() { + // 更紧凑的圆形图版本 - 圆心在画布中央,大小适中 + this.analyser.getByteFrequencyData(this.dataArray); + + this.ctx.fillStyle = 'rgb(0, 0, 0)'; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + + // 获取画布的实际显示尺寸 + const displayWidth = this.canvas.width / window.devicePixelRatio; + const displayHeight = this.canvas.height / window.devicePixelRatio; + + // 圆心位置在画布中央 + const centerX = displayWidth / 2; + const centerY = displayHeight / 2; + + // 适中的圆形图大小 - 占画布最小尺寸的30-35% + const baseRadius = Math.min(centerX, centerY) * 0.32; + const maxAmplitude = Math.min(centerX, centerY) * 0.22; + + // 绘制频谱线条 - 平衡视觉效果和性能 + const step = Math.max(1, Math.floor(this.bufferLength / 128)); + + for (let i = 0; i < this.bufferLength; i += step) { + const angle = (i / this.bufferLength) * Math.PI * 2; + const amplitude = (this.dataArray[i] / 255) * maxAmplitude; + + // 起始点(基础圆上) + const x1 = centerX + Math.cos(angle) * baseRadius; + const y1 = centerY + Math.sin(angle) * baseRadius; + + // 结束点(根据频率数据延伸) + const x2 = centerX + Math.cos(angle) * (baseRadius + amplitude); + const y2 = centerY + Math.sin(angle) * (baseRadius + amplitude); + + // 根据频率强度计算颜色 + const intensity = this.dataArray[i] / 255; + const hue = (i / this.bufferLength) * 360; + const saturation = 75 + (intensity * 25); + const lightness = 50 + (intensity * 30); + + this.ctx.strokeStyle = `hsl(${hue}, ${saturation}%, ${lightness}%)`; + this.ctx.lineWidth = 1 + (intensity * 1.5); + this.ctx.beginPath(); + this.ctx.moveTo(x1, y1); + this.ctx.lineTo(x2, y2); + this.ctx.stroke(); + } + } + + drawCircular() { + this.analyser.getByteFrequencyData(this.dataArray); + + this.ctx.fillStyle = 'rgb(0, 0, 0)'; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + + // 获取画布的实际显示尺寸(考虑设备像素比) + const displayWidth = this.canvas.width / window.devicePixelRatio; + const displayHeight = this.canvas.height / window.devicePixelRatio; + + // 圆心位置在画布中央 + const centerX = displayWidth / 2; + const centerY = displayHeight / 2; + + // 缩小圆形图大小,使用较小的半径 + const baseRadius = Math.min(centerX, centerY) * 0.5; // 从0.8改为0.5,缩小一半 + const maxAmplitude = Math.min(centerX, centerY) * 0.5; // 最大振幅也相应缩小 + + // 绘制内部基础圆形(可选,增加层次感) + this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; + this.ctx.lineWidth = 1; + this.ctx.beginPath(); + this.ctx.arc(centerX, centerY, baseRadius, 0, 2 * Math.PI); + this.ctx.stroke(); + + // 绘制频谱线条 + const step = Math.max(1, Math.floor(this.bufferLength / 128)); // 减少线条数量,提高性能 + + for (let i = 0; i < this.bufferLength; i += step) { + const angle = (i / this.bufferLength) * Math.PI * 2; + const amplitude = (this.dataArray[i] / 255) * maxAmplitude; + + // 起始点(基础圆上) + const x1 = centerX + Math.cos(angle) * baseRadius; + const y1 = centerY + Math.sin(angle) * baseRadius; + + // 结束点(根据频率数据延伸) + const x2 = centerX + Math.cos(angle) * (baseRadius + amplitude); + const y2 = centerY + Math.sin(angle) * (baseRadius + amplitude); + + // 根据频率数据计算颜色 + const intensity = this.dataArray[i] / 255; + const hue = (i / this.bufferLength) * 360; // 彩虹色调 + const saturation = 80 + (intensity * 20); // 饱和度随强度变化 + const lightness = 40 + (intensity * 40); // 亮度随强度变化 + + this.ctx.strokeStyle = `hsl(${hue}, ${saturation}%, ${lightness}%)`; + this.ctx.lineWidth = 1.5 + (intensity * 1.5); // 线条宽度随强度变化 + this.ctx.beginPath(); + this.ctx.moveTo(x1, y1); + this.ctx.lineTo(x2, y2); + this.ctx.stroke(); + } + + // 绘制中心点(可选,增加视觉焦点) + this.ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; + this.ctx.beginPath(); + this.ctx.arc(centerX, centerY, 2, 0, 2 * Math.PI); + this.ctx.fill(); + } + + draw() { + switch (this.visualizationTypes[this.currentVisualization]) { + case 'bars': + this.drawBars(); + break; + case 'wave': + this.drawWave(); + break; + case 'circular': + this.drawCircular(); + break; + default: + this.drawBars(); + } + } + + nextVisualization() { + this.currentVisualization = (this.currentVisualization + 1) % this.visualizationTypes.length; + } + + getCurrentVisualization() { + return this.visualizationTypes[this.currentVisualization]; + } +} + +export default WaveformVisualizer; \ No newline at end of file diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..5979927 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + // Vite配置选项 + server: { + port: 3000, + open: true + }, + build: { + outDir: 'dist', + assetsDir: 'assets' + } +}) \ No newline at end of file