commit 2d0d0ff0fb5a5ec5c53889c82579873abef3bc94 Author: 陈乾 Date: Fri Jan 30 19:00:58 2026 +0800 first commit 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..b4c255f --- /dev/null +++ b/index.html @@ -0,0 +1,111 @@ + + + + + + + 网络音乐播放器 + + +
+ +
+ + +
+ + +
+

测试音频源:

+
+ + +
+
+ + + + + + + + + + + +
+ +
+ + + +
+
+ + +
+
+ 00:00 + / + 00:00 +
+
+
+
+
+
+
+
+ + +
+ + +
+ 🔊 + + 70% +
+
+ + + +
+ + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..13fd864 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1105 @@ +{ + "name": "net-audio-player", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "net-audio-player", + "version": "0.0.0", + "devDependencies": { + "vite": "^7.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.1", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "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.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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..e4d23a1 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "net-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..42f9528 --- /dev/null +++ b/src/main.js @@ -0,0 +1,860 @@ +import './style.css' + +class AudioPlayer { + constructor() { + this.audio = document.getElementById('audioElement'); + this.canvas = document.getElementById('visualizer'); + this.ctx = this.canvas.getContext('2d'); + this.audioContext = null; + this.analyser = null; + this.source = null; + this.dataArray = null; + this.bufferLength = null; + this.animationId = null; + this.isPlaying = false; + this.currentVisualization = 'bars'; + + this.initializeElements(); + this.setupEventListeners(); + this.setupCanvas(); + this.initializeAudioContext(); + } + + initializeElements() { + this.elements = { + audioUrl: document.getElementById('audioUrl'), + loadAudio: document.getElementById('loadAudio'), + playPause: document.getElementById('playPause'), + stop: document.getElementById('stop'), + progressBar: document.getElementById('progressBar'), + progress: document.getElementById('progress'), + progressHandle: document.getElementById('progressHandle'), + currentTime: document.getElementById('currentTime'), + duration: document.getElementById('duration'), + volumeSlider: document.getElementById('volumeSlider'), + volumeValue: document.getElementById('volumeValue'), + vizButtons: document.querySelectorAll('.viz-btn'), + playIcon: document.querySelector('.play-icon'), + pauseIcon: document.querySelector('.pause-icon'), + errorMessage: document.getElementById('errorMessage'), + loadingMessage: document.getElementById('loadingMessage'), + loadingTitle: document.getElementById('loadingTitle'), + loadingSubtitle: document.getElementById('loadingSubtitle'), + circularProgress: document.getElementById('circularProgress'), + circularProgressText: document.getElementById('circularProgressText') + }; + } + + setupEventListeners() { + // 加载音频 + this.elements.loadAudio.addEventListener('click', () => this.loadAudio()); + + // 播放控制 + this.elements.playPause.addEventListener('click', () => this.togglePlayPause()); + this.elements.stop.addEventListener('click', () => this.stop()); + + // 进度条控制 + this.elements.progressBar.addEventListener('click', (e) => this.seekTo(e)); + this.elements.progressHandle.addEventListener('mousedown', () => this.startDragging()); + + // 音量控制 + this.elements.volumeSlider.addEventListener('input', () => this.updateVolume()); + + // 音频事件 + this.audio.addEventListener('loadedmetadata', () => this.updateDuration()); + this.audio.addEventListener('timeupdate', () => this.updateProgress()); + this.audio.addEventListener('ended', () => this.onAudioEnded()); + this.audio.addEventListener('error', (e) => this.handleAudioError(e)); + this.audio.addEventListener('loadstart', () => this.showLoadingState()); + this.audio.addEventListener('canplay', () => this.onAudioCanPlay()); + this.audio.addEventListener('canplaythrough', () => this.onAudioCanPlayThrough()); + this.audio.addEventListener('progress', () => this.onAudioProgress()); + + // 可视化切换 + this.elements.vizButtons.forEach(btn => { + btn.addEventListener('click', () => this.switchVisualization(btn.dataset.mode)); + }); + + // 测试音频源按钮 + document.querySelectorAll('.test-source-btn').forEach(btn => { + btn.addEventListener('click', () => { + this.elements.audioUrl.value = btn.dataset.url; + this.loadAudio(); + }); + }); + } + + setupCanvas() { + const resizeCanvas = () => { + const container = this.canvas.parentElement; + const rect = container.getBoundingClientRect(); + this.canvas.width = Math.min(600, rect.width - 40); + this.canvas.height = 300; + }; + + resizeCanvas(); + window.addEventListener('resize', resizeCanvas); + } + + initializeAudioContext() { + try { + this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); + this.analyser = this.audioContext.createAnalyser(); + this.analyser.fftSize = 256; + this.bufferLength = this.analyser.frequencyBinCount; + this.dataArray = new Uint8Array(this.bufferLength); + + // 连接音频源到分析器 + this.source = this.audioContext.createMediaElementSource(this.audio); + this.source.connect(this.analyser); + this.analyser.connect(this.audioContext.destination); + } catch (error) { + console.warn('Web Audio API 不支持或初始化失败:', error); + } + } + + loadAudio() { + const url = this.elements.audioUrl.value.trim(); + if (!url) { + alert('请输入音频地址'); + return; + } + + // 尝试多种跨域解决方案 + this.loadAudioWithCORS(url); + } + + loadAudioWithCORS(url) { + this.hideLoadingState(); + + // 开始加载过程 + this.startLoadingAnimation(); + + // 直接尝试加载音频(跳过URL测试,提高响应速度) + this.audio.crossOrigin = 'anonymous'; + this.audio.src = url; + this.audio.load(); + + // 监听加载进度 + this.setupLoadingProgress(); + + // 如果音频上下文被暂停,恢复它 + if (this.audioContext && this.audioContext.state === 'suspended') { + this.audioContext.resume(); + } + } + + startLoadingAnimation() { + this.showLoadingState(); + this.updateLoadingTitle('正在加载音频'); + this.updateLoadingSubtitle('请稍候...'); + this.updateLoadingProgress(0); + + // 添加加载动画的延迟效果,让用户体验更自然 + setTimeout(() => { + this.updateLoadingProgress(10); + }, 300); + } + + updateLoadingText(text) { + if (this.elements.loadingText) { + this.elements.loadingText.textContent = text; + } + } + + updateLoadingProgress(percent) { + if (this.elements.circularProgress) { + // 圆形进度条计算 + const circumference = 2 * Math.PI * 25; // 半径25 + const offset = circumference - (percent / 100) * circumference; + this.elements.circularProgress.style.strokeDashoffset = offset; + } + if (this.elements.circularProgressText) { + this.elements.circularProgressText.textContent = Math.round(percent) + '%'; + } + } + + setupLoadingProgress() { + let lastProgress = 0; + let loadStartTime = Date.now(); + let timeoutWarningShown = false; + + const checkProgress = () => { + const currentTime = Date.now(); + const elapsedTime = (currentTime - loadStartTime) / 1000; // 秒 + + // 超时警告(30秒) + if (elapsedTime > 30 && !timeoutWarningShown) { + timeoutWarningShown = true; + this.updateLoadingTitle('加载时间较长'); + this.updateLoadingSubtitle('请耐心等待...'); + console.warn('音频加载超过30秒,可能存在网络问题'); + } + + // 超时处理(60秒) + if (elapsedTime > 60) { + console.error('音频加载超时(60秒)'); + this.showError('音频加载超时\n\n可能原因:\n1. 网络连接缓慢\n2. 音频文件过大\n3. 服务器响应缓慢\n\n建议:\n1. 检查网络连接\n2. 尝试其他音频源\n3. 刷新页面重试'); + this.hideLoadingState(); + return; + } + + if (this.audio.buffered.length > 0) { + const bufferedEnd = this.audio.buffered.end(this.audio.buffered.length - 1); + const duration = this.audio.duration || 0; + + if (duration > 0) { + const progress = (bufferedEnd / duration) * 100; + this.updateLoadingProgress(progress); + + // 更新加载文本 - 简化版 + if (progress < 30) { + this.updateLoadingTitle('正在连接...'); + this.updateLoadingSubtitle(''); + } else if (progress < 60) { + this.updateLoadingTitle(`已加载 ${Math.round(progress)}%`); + this.updateLoadingSubtitle(''); + } else if (progress < 90) { + this.updateLoadingTitle('正在缓冲...'); + this.updateLoadingSubtitle(''); + } else { + this.updateLoadingTitle('即将播放'); + this.updateLoadingSubtitle(''); + } + + lastProgress = progress; + } + } + + // 继续检查进度 + if (this.audio.readyState < 4 && this.audio.networkState !== 3) { + setTimeout(checkProgress, 500); + } + }; + + // 开始检查进度 + setTimeout(checkProgress, 100); + } + + isValidAudioUrl(url) { + try { + const urlObj = new URL(url); + // 检查协议 + if (!['http:', 'https:'].includes(urlObj.protocol)) { + return false; + } + // 检查文件扩展名或路径模式 + const audioExtensions = ['.mp3', '.wav', '.ogg', '.m4a', '.flac']; + const hasAudioExtension = audioExtensions.some(ext => + urlObj.pathname.toLowerCase().includes(ext) + ); + // 或者包含音频相关的路径模式 + const hasAudioPattern = /(audio|music|song|media|sound)/i.test(urlObj.href); + + return hasAudioExtension || hasAudioPattern; + } catch (e) { + return false; + } + } + + testAudioUrl(url) { + return new Promise((resolve) => { + const testAudio = new Audio(); + testAudio.crossOrigin = 'anonymous'; + + let resolved = false; + + // 设置超时 + const timeout = setTimeout(() => { + if (!resolved) { + resolved = true; + resolve(false); + } + }, 8000); // 8秒超时 + + testAudio.addEventListener('loadedmetadata', () => { + clearTimeout(timeout); + if (!resolved) { + resolved = true; + resolve(true); + } + }); + + testAudio.addEventListener('error', (e) => { + clearTimeout(timeout); + if (!resolved) { + resolved = true; + console.log('测试音频失败:', e); + resolve(false); + } + }); + + // 添加加载事件监听 + testAudio.addEventListener('loadstart', () => { + console.log('开始测试音频加载:', url); + }); + + testAudio.src = url; + testAudio.load(); + }); + } + + tryProxyLoad(url) { + // 尝试使用代理服务器加载音频 + const proxyUrl = `/DocServer/audio?url=${encodeURIComponent(url)}`; + + console.log('尝试代理加载:', proxyUrl); + + // 直接尝试代理URL + this.audio.crossOrigin = 'anonymous'; + this.audio.src = proxyUrl; + this.audio.load(); + + // 给代理加载设置超时 + setTimeout(() => { + if (this.audio.networkState === 3) { // NETWORK_NO_SOURCE + console.error('代理加载超时'); + this.showError('代理加载失败\n\n可能的原因:\n1. 代理服务器无法访问目标音频\n2. 目标音频需要认证\n3. 音频文件不存在\n\n建议:\n1. 使用支持CORS的音频源\n2. 下载音频到本地服务器\n3. 使用其他音频地址'); + } + }, 10000); // 10秒超时 + } + + togglePlayPause() { + if (this.isPlaying) { + this.pause(); + } else { + this.play(); + } + } + + play() { + if (this.audio.src) { + this.audio.play().then(() => { + this.isPlaying = true; + this.updatePlayButton(); + this.startVisualization(); + document.querySelector('.audio-player').classList.add('playing'); + + // 显示播放成功提示(短暂显示) + console.log('🎵 音频播放开始'); + this.showSuccessMessage('音频加载完成,开始播放!'); + + }).catch(error => { + console.error('播放失败:', error); + this.showError('音频播放失败,请重试'); + this.isPlaying = false; + this.updatePlayButton(); + }); + } + } + + showSuccessMessage(message) { + // 创建成功提示元素 + const successDiv = document.createElement('div'); + successDiv.className = 'success-message'; + successDiv.textContent = message; + successDiv.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #4caf50; + color: white; + padding: 12px 16px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + z-index: 1000; + font-size: 14px; + transform: translateX(100%); + transition: transform 0.3s ease; + `; + + document.body.appendChild(successDiv); + + // 动画显示 + setTimeout(() => { + successDiv.style.transform = 'translateX(0)'; + }, 100); + + // 3秒后自动隐藏 + setTimeout(() => { + successDiv.style.transform = 'translateX(100%)'; + setTimeout(() => { + if (successDiv.parentNode) { + successDiv.parentNode.removeChild(successDiv); + } + }, 300); + }, 3000); + } + + pause() { + this.audio.pause(); + this.isPlaying = false; + this.updatePlayButton(); + this.stopVisualization(); + document.querySelector('.audio-player').classList.remove('playing'); + } + + stop() { + this.audio.pause(); + this.audio.currentTime = 0; + this.isPlaying = false; + this.updatePlayButton(); + this.stopVisualization(); + this.updateProgress(); + document.querySelector('.audio-player').classList.remove('playing'); + } + + updatePlayButton() { + if (this.isPlaying) { + this.elements.playIcon.style.display = 'none'; + this.elements.pauseIcon.style.display = 'inline'; + } else { + this.elements.playIcon.style.display = 'inline'; + this.elements.pauseIcon.style.display = 'none'; + } + } + + updateDuration() { + const duration = this.audio.duration; + this.elements.duration.textContent = this.formatTime(duration); + + // 音频元数据加载完成,更新加载状态 + if (this.elements.loadingMessage.style.display !== 'none') { + this.updateLoadingTitle('加载完成'); + this.updateLoadingSubtitle(''); + this.updateLoadingProgress(80); + console.log('音频元数据加载完成,时长:', this.formatTime(duration)); + } + } + + updateProgress() { + const currentTime = this.audio.currentTime; + const duration = this.audio.duration; + + if (duration) { + const progressPercent = (currentTime / duration) * 100; + this.elements.progress.style.width = progressPercent + '%'; + this.elements.progressHandle.style.left = progressPercent + '%'; + } + + this.elements.currentTime.textContent = this.formatTime(currentTime); + } + + seekTo(e) { + const rect = this.elements.progressBar.getBoundingClientRect(); + const clickX = e.clientX - rect.left; + const width = rect.width; + const percentage = clickX / width; + + if (this.audio.duration) { + this.audio.currentTime = percentage * this.audio.duration; + } + } + + startDragging() { + const handleMouseMove = (e) => { + const rect = this.elements.progressBar.getBoundingClientRect(); + const clickX = e.clientX - rect.left; + const width = rect.width; + let percentage = clickX / width; + + percentage = Math.max(0, Math.min(1, percentage)); + + if (this.audio.duration) { + this.audio.currentTime = percentage * this.audio.duration; + } + }; + + const handleMouseUp = () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + } + + updateVolume() { + const volume = this.elements.volumeSlider.value / 100; + this.audio.volume = volume; + this.elements.volumeValue.textContent = Math.round(volume * 100) + '%'; + } + + onAudioEnded() { + this.isPlaying = false; + this.updatePlayButton(); + this.stopVisualization(); + document.querySelector('.audio-player').classList.remove('playing'); + } + + showError(message) { + const errorDiv = this.elements.errorMessage; + errorDiv.textContent = message; + errorDiv.style.display = 'block'; + + // 5秒后自动隐藏错误信息 + setTimeout(() => { + errorDiv.style.display = 'none'; + }, 8000); + } + + showLoadingState() { + // 显示遮罩层和加载弹出层 + if (this.elements.loadingOverlay) { + this.elements.loadingOverlay.style.display = 'block'; + this.elements.loadingOverlay.classList.add('show'); + } + + this.elements.loadingMessage.style.display = 'block'; + this.elements.loadingMessage.classList.add('fade-in'); + this.elements.errorMessage.style.display = 'none'; + + // 防止页面滚动 + document.body.classList.add('loading-active'); + + const loadBtn = this.elements.loadAudio; + loadBtn.textContent = '加载中...'; + loadBtn.disabled = true; + + // 重置进度 + this.updateLoadingProgress(0); + this.updateLoadingTitle('正在加载音频'); + this.updateLoadingSubtitle('请稍候...'); + + // 启动加载进度监听 + this.setupLoadingProgress(); + } + + hideLoadingState() { + this.elements.loadingMessage.classList.add('fade-out'); + + setTimeout(() => { + this.elements.loadingMessage.style.display = 'none'; + this.elements.loadingMessage.classList.remove('fade-in', 'fade-out'); + + if (this.elements.loadingOverlay) { + this.elements.loadingOverlay.style.display = 'none'; + this.elements.loadingOverlay.classList.remove('show'); + } + + // 恢复页面滚动 + document.body.classList.remove('loading-active'); + }, 300); + + const loadBtn = this.elements.loadAudio; + loadBtn.textContent = '加载音乐'; + loadBtn.disabled = false; + } + + updateLoadingTitle(text) { + if (this.elements.loadingTitle) { + this.elements.loadingTitle.textContent = text; + } + } + + updateLoadingSubtitle(text) { + if (this.elements.loadingSubtitle) { + this.elements.loadingSubtitle.textContent = text; + } + } + + onAudioProgress() { + // 更新加载进度 + if (this.audio.buffered.length > 0 && this.elements.loadingMessage.style.display !== 'none') { + const bufferedEnd = this.audio.buffered.end(this.audio.buffered.length - 1); + const duration = this.audio.duration || 0; + + if (duration > 0) { + const progress = (bufferedEnd / duration) * 100; + this.updateLoadingProgress(progress); + + // 更新加载文本 + if (progress < 30) { + this.updateLoadingText('正在连接音频服务器...'); + } else if (progress < 60) { + this.updateLoadingText('正在接收音频数据...'); + } else if (progress < 90) { + this.updateLoadingText('正在缓冲音频...'); + } else { + this.updateLoadingText('即将开始播放...'); + } + } + } + } + + onAudioCanPlay() { + console.log('音频可以开始播放'); + // 音频已经有足够数据开始播放,但继续显示加载状态 + if (this.elements.loadingMessage.style.display !== 'none') { + this.updateLoadingText('音频缓冲完成,准备播放...'); + this.updateLoadingProgress(95); + } + } + + onAudioCanPlayThrough() { + console.log('音频可以流畅播放'); + // 音频可以流畅播放,隐藏加载状态并自动播放 + + // 显示加载完成状态 + if (this.elements.loadingMessage.style.display !== 'none') { + this.updateLoadingTitle('加载完成'); + this.updateLoadingSubtitle(''); + this.updateLoadingProgress(100); + } + + // 延迟隐藏加载状态,让用户看到完成状态 + setTimeout(() => { + this.hideLoadingState(); + + // 自动开始播放 + if (this.audio.src && !this.isPlaying) { + console.log('自动开始播放'); + this.play(); + } + }, 500); + } + + handleAudioError(error) { + console.error('音频加载错误详情:', { + error: error, + audioError: this.audio.error, + currentSrc: this.audio.currentSrc, + networkState: this.audio.networkState, + readyState: this.audio.readyState + }); + + let errorMessage = '音频加载失败'; + let solutions = []; + + // 获取详细的错误信息 + const audioError = this.audio.error; + const networkState = this.audio.networkState; + const readyState = this.audio.readyState; + + console.log('网络状态:', this.getNetworkStateText(networkState)); + console.log('就绪状态:', this.getReadyStateText(readyState)); + + if (audioError) { + switch (audioError.code) { + case 1: + errorMessage = '音频下载过程中被中断'; + solutions = ['检查网络连接', '重新加载页面', '尝试其他音频地址']; + break; + case 2: + errorMessage = '网络错误 - 音频文件无法下载'; + solutions = ['检查音频地址是否有效', '确认音频文件存在', '检查网络连接']; + break; + case 3: + errorMessage = '音频解码错误 - 文件格式不支持或已损坏'; + solutions = ['确认音频格式为MP3/WAV/OGG', '尝试其他音频文件', '检查文件是否完整']; + break; + case 4: + errorMessage = '音频格式不支持或跨域访问被拒绝'; + solutions = ['检查音频文件格式', '确认服务器支持跨域', '尝试使用代理服务器']; + break; + default: + errorMessage = '未知错误'; + solutions = ['检查音频地址', '确认网络连接', '尝试其他音频源']; + } + } else { + // 没有具体错误码,根据状态判断 + if (networkState === 3) { + errorMessage = '网络错误 - 无法下载音频文件'; + solutions = ['检查音频地址是否有效', '确认音频文件存在', '检查网络连接']; + } else if (readyState === 0) { + errorMessage = '音频文件无法加载'; + solutions = ['检查音频地址', '确认文件可访问', '检查跨域设置']; + } else { + errorMessage = '音频加载失败'; + solutions = ['检查音频地址', '确认网络连接', '尝试其他音频源']; + } + } + + // 显示详细的错误信息 + this.showError(`音频加载失败:${errorMessage}\n\n解决方案:\n${solutions.map((s, i) => `${i + 1}. ${s}`).join('\n')}\n\n调试信息:\n网络状态: ${this.getNetworkStateText(networkState)}\n就绪状态: ${this.getReadyStateText(readyState)}\n错误码: ${audioError?.code || '无'}`); + + this.hideLoadingState(); + } + + getNetworkStateText(state) { + const states = { + 0: 'NETWORK_EMPTY - 网络状态未知', + 1: 'NETWORK_IDLE - 未使用网络', + 2: 'NETWORK_LOADING - 正在加载', + 3: 'NETWORK_NO_SOURCE - 未找到音频源' + }; + return states[state] || `未知状态(${state})`; + } + + getReadyStateText(state) { + const states = { + 0: 'HAVE_NOTHING - 没有音频信息', + 1: 'HAVE_METADATA - 只有元数据', + 2: 'HAVE_CURRENT_DATA - 当前播放位置有数据', + 3: 'HAVE_FUTURE_DATA - 当前及未来数据可用', + 4: 'HAVE_ENOUGH_DATA - 足够数据开始播放' + }; + return states[state] || `未知状态(${state})`; + } + + formatTime(seconds) { + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + + switchVisualization(mode) { + this.currentVisualization = mode; + + // 更新按钮状态 + this.elements.vizButtons.forEach(btn => { + btn.classList.toggle('active', btn.dataset.mode === mode); + }); + } + + startVisualization() { + if (!this.animationId) { + this.animate(); + } + } + + stopVisualization() { + if (this.animationId) { + cancelAnimationFrame(this.animationId); + this.animationId = null; + this.clearCanvas(); + } + } + + clearCanvas() { + this.ctx.fillStyle = '#000'; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + } + + animate() { + if (!this.analyser) return; + + this.animationId = requestAnimationFrame(() => this.animate()); + + this.analyser.getByteFrequencyData(this.dataArray); + + switch (this.currentVisualization) { + case 'bars': + this.drawBars(); + break; + case 'wave': + this.drawWave(); + break; + case 'circle': + this.drawCircle(); + break; + } + } + + drawBars() { + this.clearCanvas(); + + 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 * 0.8; + + const gradient = this.ctx.createLinearGradient(0, this.canvas.height - barHeight, 0, this.canvas.height); + gradient.addColorStop(0, `hsl(${(i / this.bufferLength) * 360}, 100%, 50%)`); + gradient.addColorStop(1, `hsl(${(i / this.bufferLength) * 360}, 100%, 30%)`); + + this.ctx.fillStyle = gradient; + this.ctx.fillRect(x, this.canvas.height - barHeight, barWidth, barHeight); + + x += barWidth + 1; + } + } + + drawWave() { + this.clearCanvas(); + + this.ctx.lineWidth = 2; + this.ctx.strokeStyle = '#00ff88'; + this.ctx.beginPath(); + + const sliceWidth = this.canvas.width / 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.stroke(); + } + + drawCircle() { + this.clearCanvas(); + + const centerX = this.canvas.width / 2; + const centerY = this.canvas.height / 2; + const radius = Math.min(centerX, centerY) - 50; + + for (let i = 0; i < this.bufferLength; i++) { + const angle = (i / this.bufferLength) * Math.PI * 2; + const amplitude = (this.dataArray[i] / 255) * radius; + + const x1 = centerX + Math.cos(angle) * (radius * 0.5); + const y1 = centerY + Math.sin(angle) * (radius * 0.5); + const x2 = centerX + Math.cos(angle) * (radius * 0.5 + amplitude); + const y2 = centerY + Math.sin(angle) * (radius * 0.5 + amplitude); + + this.ctx.beginPath(); + this.ctx.moveTo(x1, y1); + this.ctx.lineTo(x2, y2); + this.ctx.strokeStyle = `hsl(${(i / this.bufferLength) * 360}, 100%, 50%)`; + this.ctx.lineWidth = 2; + this.ctx.stroke(); + } + + // 绘制中心圆 + this.ctx.beginPath(); + this.ctx.arc(centerX, centerY, 30, 0, Math.PI * 2); + this.ctx.fillStyle = 'rgba(102, 126, 234, 0.8)'; + this.ctx.fill(); + + // 中心文字 + this.ctx.fillStyle = 'white'; + this.ctx.font = '12px Arial'; + this.ctx.textAlign = 'center'; + this.ctx.fillText('🎵', centerX, centerY + 4); + } +} + +// 初始化播放器 +document.addEventListener('DOMContentLoaded', () => { + // 检查浏览器兼容性 + if (!window.AudioContext && !window.webkitAudioContext) { + alert('您的浏览器不支持Web Audio API,部分功能可能无法正常使用。\n\n建议使用:\n• Chrome 10+\n• Firefox 25+\n• Safari 6+\n• Edge 12+'); + } + + // 检查是否支持音频 + if (!document.createElement('audio').canPlayType) { + alert('您的浏览器不支持HTML5音频播放。\n\n请升级浏览器或使用现代浏览器。'); + return; + } + + const player = new AudioPlayer(); + + // 显示初始化提示 + console.log('🎵 音频播放器初始化完成'); + console.log('💡 提示:如果遇到跨域问题,请尝试:'); + console.log(' 1. 使用上方的测试音频按钮'); + console.log(' 2. 确保音频地址支持CORS'); + console.log(' 3. 检查网络连接'); +}); \ No newline at end of file diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..6c3469c --- /dev/null +++ b/src/style.css @@ -0,0 +1,878 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + width: 100%; + height: 100%; + min-width: 320px; + min-height: 100vh; + font-family: 'Arial', sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + justify-content: center; + align-items: center; + color: #333; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.audio-player { + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + padding: 30px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); + width: 800px; + margin: 20px; +} + +/* URL输入区域 */ +.url-input-section { + margin-bottom: 30px; + display: flex; + gap: 10px; +} + +#audioUrl { + flex: 1; + padding: 12px 16px; + border: 2px solid #e0e0e0; + border-radius: 10px; + font-size: 14px; + outline: none; + transition: border-color 0.3s ease; +} + +#audioUrl:focus { + border-color: #667eea; +} + +#loadAudio { + padding: 12px 20px; + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + border-radius: 10px; + cursor: pointer; + font-size: 14px; + font-weight: bold; + transition: transform 0.2s ease; +} + +#loadAudio:hover { + transform: translateY(-2px); +} + +/* 可视化区域 */ +.visualization-section { + margin-bottom: 30px; + text-align: center; +} + +#visualizer { + border: 2px solid #e0e0e0; + border-radius: 15px; + background: #000; + width: 100%; + height: 320px; +} + +.visualization-controls { + margin-top: 15px; + display: flex; + justify-content: center; + gap: 10px; +} + +.viz-btn { + padding: 8px 16px; + border: 2px solid #e0e0e0; + background: white; + border-radius: 20px; + cursor: pointer; + font-size: 12px; + transition: all 0.3s ease; +} + +.viz-btn.active { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border-color: #667eea; +} + +.viz-btn:hover { + border-color: #667eea; + transform: translateY(-1px); +} + +/* 播放控制 */ +.player-controls { + margin-bottom: 25px; +} + +.time-display { + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + margin-bottom: 15px; + font-size: 14px; + font-weight: bold; + color: #666; +} + +.progress-container { + position: relative; + margin-bottom: 20px; +} + +.progress-bar { + width: 100%; + height: 8px; + background: #e0e0e0; + border-radius: 4px; + cursor: pointer; + position: relative; + overflow: hidden; +} + +.progress { + height: 100%; + background: linear-gradient(135deg, #667eea, #764ba2); + border-radius: 4px; + width: 0%; + transition: width 0.1s ease; +} + +.progress-handle { + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + width: 16px; + height: 16px; + background: white; + border: 3px solid #667eea; + border-radius: 50%; + cursor: pointer; + left: 0%; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + transition: transform 0.2s ease; +} + +.progress-handle:hover { + transform: translate(-50%, -50%) scale(1.2); +} + +/* 控制按钮 */ +.control-buttons { + display: flex; + justify-content: center; + align-items: center; + gap: 20px; +} + +.control-btn { + width: 50px; + height: 50px; + border: none; + border-radius: 50%; + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + cursor: pointer; + font-size: 18px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.control-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); +} + +#playPause { + width: 60px; + height: 60px; + font-size: 24px; +} + +/* 音量控制 */ +.volume-control { + display: flex; + align-items: center; + gap: 10px; + margin-left: 20px; +} + +.volume-icon { + font-size: 20px; +} + +.volume-slider { + width: 100px; + height: 6px; + background: #e0e0e0; + border-radius: 3px; + outline: none; + cursor: pointer; + -webkit-appearance: none; +} + +.volume-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + background: linear-gradient(135deg, #667eea, #764ba2); + border-radius: 50%; + cursor: pointer; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +.volume-slider::-moz-range-thumb { + width: 16px; + height: 16px; + background: linear-gradient(135deg, #667eea, #764ba2); + border-radius: 50%; + cursor: pointer; + border: none; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +#volumeValue { + font-size: 12px; + font-weight: bold; + color: #666; + min-width: 30px; +} + +/* 加载完成动画 */ +@keyframes loading-complete { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } + 100% { + transform: scale(1); + } +} + +/* 加载遮罩层 - 简洁设计 */ +.loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.2); + backdrop-filter: blur(1px); + z-index: 999; + display: none; +} + +.loading-overlay.show { + display: block; + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +/* 加载弹出层悬浮效果 */ +.loading-message { + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.15), + 0 0 0 1px rgba(102, 126, 234, 0.1); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} +/* 加载弹出层悬浮效果 */ +.loading-message:hover { + transform: translate(-50%, -50%) scale(1.02); + box-shadow: + 0 25px 50px rgba(0, 0, 0, 0.2), + 0 0 0 1px rgba(102, 126, 234, 0.2); +} + +/* 加载弹出层响应式设计 */ +@media (max-width: 480px) { + .loading-message { + min-width: 180px; + max-width: 240px; + padding: 20px; + } + + .circular-progress { + width: 60px; + height: 60px; + } + + .loading-text .loading-title { + font-size: 14px; + } + + .loading-text .loading-subtitle { + font-size: 11px; + } +} + +/* 加载弹出层细节优化 */ +.loading-message { + will-change: transform, opacity; +} + +.circular-progress .progress-bar { + will-change: stroke-dashoffset; +} + +.loading-text .loading-title { + will-change: contents; +} + +/* 防止加载时页面滚动 */ +body.loading-active { + overflow: hidden; +} 加载遮罩层 - 简洁设计 */ +.loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.2); + backdrop-filter: blur(1px); + z-index: 999; + display: none; +} + +.loading-overlay.show { + display: block; + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +/* 加载弹出层 - 简洁优雅 */ +.loading-message { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(255, 255, 255, 0.95); + border-radius: 16px; + padding: 24px; + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15); + backdrop-filter: blur(8px); + z-index: 1000; + text-align: center; + min-width: 200px; + max-width: 280px; + display: none; + border: 1px solid rgba(102, 126, 234, 0.1); +} + +/* 成功消息动画 */ +@keyframes slide-in-right { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slide-out-right { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(100%); + opacity: 0; + } +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .audio-player { + padding: 20px; + margin: 10px; + } + + .url-input-section { + flex-direction: column; + } + + #visualizer { + height: 200px; + } + + .control-buttons { + flex-direction: column; + gap: 15px; + } + + .volume-control { + margin-left: 0; + margin-top: 10px; + } +} + +/* 测试音频源 */ +.test-audio-sources { + margin-bottom: 20px; +} + +.test-source-btn { + padding: 6px 12px; + background: #f5f5f5; + border: 1px solid #ddd; + border-radius: 15px; + cursor: pointer; + font-size: 12px; + transition: all 0.3s ease; +} + +.test-source-btn:hover { + background: #e0e0e0; + border-color: #667eea; + transform: translateY(-1px); +} + +/* 加载弹出层 - 简洁优雅 */ +.loading-message { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(255, 255, 255, 0.95); + border-radius: 16px; + padding: 24px; + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15); + backdrop-filter: blur(8px); + z-index: 1000; + text-align: center; + min-width: 200px; + max-width: 280px; + display: none; + border: 1px solid rgba(102, 126, 234, 0.1); +} + +/* 圆形加载动画 */ +.loading-animation { + display: flex; + justify-content: center; + margin-bottom: 20px; +} + +.circular-loader { + position: relative; + width: 80px; + height: 80px; +} + +.circular-loader .loader-circle { + width: 100%; + height: 100%; + border: 4px solid #f3f3f3; + border-top: 4px solid #667eea; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +.circular-loader .loader-inner { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 60px; + height: 60px; + border: 3px solid transparent; + border-top: 3px solid #764ba2; + border-radius: 50%; + animation: spin 1.5s linear infinite reverse; +} + +/* 加载信息 - 简洁 */ +.loading-info { + margin-bottom: 12px; +} + +.loading-info .loading-title { + font-size: 16px; + font-weight: 600; + color: #333; + margin-bottom: 4px; +} + +.loading-info .loading-subtitle { + font-size: 12px; + color: #888; +} + +/* 迷你频谱 - 简洁 */ +.mini-spectrum { + display: flex; + justify-content: center; + align-items: flex-end; + height: 20px; + gap: 2px; +} + +.spectrum-bar { + width: 3px; + background: linear-gradient(to top, #667eea, #764ba2); + border-radius: 1.5px; + animation: spectrum-pulse 1s ease-in-out infinite; +} + +.spectrum-bar:nth-child(1) { + height: 12px; + animation-delay: 0s; +} + +.spectrum-bar:nth-child(2) { + height: 16px; + animation-delay: 0.2s; +} + +.spectrum-bar:nth-child(3) { + height: 14px; + animation-delay: 0.4s; +} + +.spectrum-bar:nth-child(4) { + height: 18px; + animation-delay: 0.6s; +} + +@keyframes spectrum-pulse { + 0%, 100% { + transform: scaleY(0.7); + opacity: 0.7; + } + 50% { + transform: scaleY(1); + opacity: 1; + } +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* 主要加载器 - 简洁 */ +.main-loader { + display: flex; + justify-content: center; + margin-bottom: 16px; +} + +/* 简化圆形进度条 */ +.circular-progress-simple { + position: relative; + width: 80px; + height: 80px; + margin: 0 auto; +} + +.circular-progress-simple svg { + width: 100%; + height: 100%; + transform: rotate(-90deg); +} + +.circular-progress-simple .progress-bg { + fill: none; + stroke: #f5f5f5; + stroke-width: 6; +} + +.circular-progress-simple .progress-bar { + fill: none; + stroke: url(#gradient); + stroke-width: 6; + stroke-linecap: round; + stroke-dasharray: 220; /* 2 * π * 35 */ + stroke-dashoffset: 220; + transition: stroke-dashoffset 0.4s ease; +} + +.circular-progress-simple .progress-text { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 14px; + font-weight: 600; + color: #667eea; +} + +.loading-text { + text-align: center; + margin-bottom: 10px; +} + +.loading-text .loading-title { + font-size: 16px; + font-weight: 600; + color: #333; + margin-bottom: 5px; +} + +.loading-text .loading-subtitle { + font-size: 12px; + color: #666; +} + +/* 加载状态切换动画 - 更平滑 */ +.loading-message.fade-in { + animation: fadeInScale 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.loading-message.fade-out { + animation: fadeOutScale 0.3s ease-in; +} + +@keyframes fadeInScale { + from { + opacity: 0; + transform: translate(-50%, -50%) scale(0.7); + } + to { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } +} + +@keyframes fadeOutScale { + from { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + to { + opacity: 0; + transform: translate(-50%, -50%) scale(0.7); + } +} + +/* 圆形进度条填充动画 */ +@keyframes progress-fill { + from { + stroke-dashoffset: 220; + } + to { + stroke-dashoffset: 0; + } +} + +.circular-progress-simple .progress-bar.loading { + animation: progress-fill 2s ease-out; +} + +/* 加载弹出层响应式设计 */ +@media (max-width: 480px) { + .loading-message { + min-width: 180px; + max-width: 240px; + padding: 20px; + } + + .circular-progress-simple { + width: 60px; + height: 60px; + } + + .loading-info .loading-title { + font-size: 14px; + } + + .loading-info .loading-subtitle { + font-size: 11px; + } +} + +/* 错误提示和加载状态 */ +.error-message { + background: #ffebee; + border: 1px solid #f44336; + border-radius: 8px; + padding: 12px; + margin: 15px 0; + color: #c62828; + font-size: 14px; + display: none; + white-space: pre-line; +} + +.loading-message { + background: #e3f2fd; + border: 1px solid #2196f3; + border-radius: 8px; + padding: 12px; + margin: 15px 0; + color: #1565c0; + font-size: 14px; + text-align: center; + display: none; + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.15), + 0 0 0 1px rgba(102, 126, 234, 0.1); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.loading-message:hover { + transform: translate(-50%, -50%) scale(1.02); + box-shadow: + 0 25px 50px rgba(0, 0, 0, 0.2), + 0 0 0 1px rgba(102, 126, 234, 0.2); +} + +/* 圆形进度条动画 */ +@keyframes progress-glow { + 0%, 100% { + filter: drop-shadow(0 0 2px rgba(102, 126, 234, 0.3)); + } + 50% { + filter: drop-shadow(0 0 8px rgba(102, 126, 234, 0.6)); + } +} + +.circular-progress .progress-bar { + animation: progress-glow 2s ease-in-out infinite; +} + +/* 音乐频谱脉冲效果 */ +@keyframes pulse-glow { + 0%, 100% { + opacity: 0.8; + } + 50% { + opacity: 1; + } +} + +.music-bars-small { + animation: pulse-glow 1.5s ease-in-out infinite; +} + +/* 加载文字动画 */ +@keyframes text-shimmer { + 0% { + background-position: -100% 0; + } + 100% { + background-position: 100% 0; + } +} + +.loading-title { + background: linear-gradient(90deg, #667eea, #764ba2, #667eea); + background-size: 200% 100%; + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + animation: text-shimmer 3s ease-in-out infinite; +} + +.loading-message span { + display: inline-block; + animation: pulse 1.5s infinite; +} + +/* 动画效果 */ +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); } + 100% { transform: scale(1); } +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.loading-message::before { + content: "🎵"; + display: inline-block; + margin-right: 8px; + animation: spin 2s linear infinite; +} + +.playing .control-btn { + animation: pulse 2s infinite; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} \ No newline at end of file diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..5de32dc --- /dev/null +++ b/vite.config.js @@ -0,0 +1,24 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + server: { + port: 3000, + host: true, + cors: true, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization, Range', + 'Access-Control-Allow-Credentials': 'true', + 'Access-Control-Expose-Headers': 'Content-Length, Content-Range' + }, + proxy: { + '/DocServer': { + target: 'http://192.168.1.201:8080', + changeOrigin: true, + secure: false, + rewrite: (path) => path.replace(/^\/DocServer/, '/DocServer') + } + } + } +}) \ No newline at end of file