diff --git a/package.json b/package.json index d42c6c2..f421056 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "puppeteer": "^24.10.0", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-i18next": "^15.5.2", + "react-i18next": "^15.5.3", "rehype-format": "^5.0.1", "rehype-highlight": "^7.0.2", "rehype-parse": "^9.0.1", @@ -45,7 +45,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", - "@next/bundle-analyzer": "^15.4.0-canary.81", + "@next/bundle-analyzer": "^15.4.0-canary.83", "@tailwindcss/postcss": "^4", "@types/node": "^24", "@types/react": "^19", diff --git a/src/app/music/[music_id]/components/player.tsx b/src/app/music/[music_id]/components/player.tsx index 390953f..8cc17a5 100644 --- a/src/app/music/[music_id]/components/player.tsx +++ b/src/app/music/[music_id]/components/player.tsx @@ -52,6 +52,7 @@ export function Player({ music, musicList }: PlayerProps) { const [isCircular, setIsCircular] = useState(false); const [eqGains, setEqGains] = useState(equalizerPresets.Flat); // 初期値はFlat const [selectedPreset, setSelectedPreset] = useState("Flat"); + const [volume, setVolume] = useState(1); // 音量の初期値を1(最大)に設定 const audioRef = useRef(null); const audioCtxRef = useRef(null); @@ -383,6 +384,19 @@ export function Player({ music, musicList }: PlayerProps) { setEqGains(equalizerPresets[presetName]); }; + const handleVolumeChange = (e: React.ChangeEvent) => { + if (!audioRef.current) return; + const newVolume = parseFloat(e.target.value); + audioRef.current.volume = newVolume; + setVolume(newVolume); + }; + + useEffect(() => { + if (audioRef.current) { + audioRef.current.volume = volume; + } + }, [volume]); + return (
{music.jacketUrl && ( @@ -499,6 +513,19 @@ export function Player({ music, musicList }: PlayerProps) { {formatTime(duration)}
+
+ {t("volume")} + +
+
{t("equalizer")}
diff --git a/src/app/music/[music_id]/page.module.css b/src/app/music/[music_id]/page.module.css index 8d2de32..2e60f18 100644 --- a/src/app/music/[music_id]/page.module.css +++ b/src/app/music/[music_id]/page.module.css @@ -338,6 +338,42 @@ margin-top: 0.2rem; } +/* ボリュームコントロール */ +.volume-control { + display: flex; + align-items: center; + gap: 1rem; + margin: 1rem 0; +} + +.volume-slider { + flex: 1; + height: 4px; + -webkit-appearance: none; + appearance: none; + background: rgba(255, 255, 255, 0.2); + border-radius: 2px; + cursor: pointer; +} + +.volume-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: #fff; + cursor: pointer; +} + +.volume-slider::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: #fff; + cursor: pointer; + border: none; +} + /* レスポンシブ対応 */ @media (max-width: 600px) { .eq-sliders { diff --git a/src/i18n/en/common.json b/src/i18n/en/common.json index 838be0c..62895dd 100644 --- a/src/i18n/en/common.json +++ b/src/i18n/en/common.json @@ -55,6 +55,7 @@ "artist": "Artist", "duration": "Duration", "equalizer": "Equalizer", + "volume":"Volume", "preset": "Preset", "browserNotSupported": "Your browser does not support audio playback." } diff --git a/src/i18n/ja/common.json b/src/i18n/ja/common.json index 57d0c09..29cfe4a 100644 --- a/src/i18n/ja/common.json +++ b/src/i18n/ja/common.json @@ -55,6 +55,7 @@ "artist": "アーティスト", "duration": "時間", "equalizer": "イコライザ", + "volume": "音量", "preset": "プリセット", "browserNotSupported": "お使いのブラウザは音声再生に対応していません。" } diff --git a/yarn.lock b/yarn.lock index ab91698..397fbe0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,7 +29,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== -"@babel/runtime@^7.23.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.27.1": +"@babel/runtime@^7.23.2", "@babel/runtime@^7.27.1", "@babel/runtime@^7.27.6": version "7.27.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.6.tgz#ec4070a04d76bae8ddbb10770ba55714a417b7c6" integrity sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q== @@ -78,10 +78,10 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint/config-array@^0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.20.0.tgz#7a1232e82376712d3340012a2f561a2764d1988f" - integrity sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ== +"@eslint/config-array@^0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.20.1.tgz#454f89be82b0e5b1ae872c154c7e2f3dd42c3979" + integrity sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw== dependencies: "@eslint/object-schema" "^2.1.6" debug "^4.3.1" @@ -114,10 +114,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.28.0": - version "9.28.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.28.0.tgz#7822ccc2f8cae7c3cd4f902377d520e9ae03f844" - integrity sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg== +"@eslint/js@9.29.0": + version "9.29.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.29.0.tgz#dc6fd117c19825f8430867a662531da36320fe56" + integrity sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ== "@eslint/object-schema@^2.1.6": version "2.1.6" @@ -396,10 +396,10 @@ "@emnapi/runtime" "^1.4.3" "@tybys/wasm-util" "^0.9.0" -"@next/bundle-analyzer@^15.4.0-canary.81": - version "15.4.0-canary.81" - resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-15.4.0-canary.81.tgz#731688001ff4b19fd695a2172c2e545767516433" - integrity sha512-r5Mip5ItgedmF+KKUdikP4yaKLwJEq04ZUl6Nx96NaaVYpWn3Zj9lYhsFFCYPpKvNIT6cDC4YoP9XpiEN8lIBw== +"@next/bundle-analyzer@^15.4.0-canary.83": + version "15.4.0-canary.83" + resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-15.4.0-canary.83.tgz#362d9a47cc8e2dfa8fe96d1f6b8abfc2db6d2dd7" + integrity sha512-JMeQ+BIv9qwqpyCmJCLjNL9OdwVjub+SePE6b4/h6PltMcEJYDcOu9YtVUBPzKrvY3vIKeP9MQyUQ7kHAiBYQw== dependencies: webpack-bundle-analyzer "4.10.1" @@ -1042,9 +1042,9 @@ integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== "@types/node@*", "@types/node@^24": - version "24.0.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.0.0.tgz#14a278ce74dd33993f2c4e5dd614760728c0fba8" - integrity sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg== + version "24.0.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.0.3.tgz#f935910f3eece3a3a2f8be86b96ba833dc286cab" + integrity sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg== dependencies: undici-types "~7.8.0" @@ -2470,7 +2470,7 @@ eslint-plugin-react@^7.37.0: string.prototype.matchall "^4.0.12" string.prototype.repeat "^1.0.0" -eslint-scope@^8.3.0: +eslint-scope@^8.4.0: version "8.4.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== @@ -2489,17 +2489,17 @@ eslint-visitor-keys@^4.2.0, eslint-visitor-keys@^4.2.1: integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== eslint@^9: - version "9.28.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.28.0.tgz#b0bcbe82a16945a40906924bea75e8b4980ced7d" - integrity sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ== + version "9.29.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.29.0.tgz#65e3db3b7e5a5b04a8af541741a0f3648d0a81a6" + integrity sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.20.0" + "@eslint/config-array" "^0.20.1" "@eslint/config-helpers" "^0.2.1" "@eslint/core" "^0.14.0" "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.28.0" + "@eslint/js" "9.29.0" "@eslint/plugin-kit" "^0.3.1" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" @@ -2511,9 +2511,9 @@ eslint@^9: cross-spawn "^7.0.6" debug "^4.3.2" escape-string-regexp "^4.0.0" - eslint-scope "^8.3.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -2529,7 +2529,7 @@ eslint@^9: natural-compare "^1.4.0" optionator "^0.9.3" -espree@^10.0.1, espree@^10.3.0: +espree@^10.0.1, espree@^10.4.0: version "10.4.0" resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== @@ -5423,12 +5423,12 @@ react-dom@^19.1.0: dependencies: scheduler "^0.26.0" -react-i18next@^15.5.2: - version "15.5.2" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.5.2.tgz#2cfbd8e055efea077a7cbd7fbd9528c76d31925e" - integrity sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A== +react-i18next@^15.5.3: + version "15.5.3" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.5.3.tgz#63cae235d540d1b6bde5495b5ebe0c1d5c76de0f" + integrity sha512-ypYmOKOnjqPEJZO4m1BI0kS8kWqkBNsKYyhVUfij0gvjy9xJNoG/VcGkxq5dRlVwzmrmY1BQMAmpbbUBLwC4Kw== dependencies: - "@babel/runtime" "^7.25.0" + "@babel/runtime" "^7.27.6" html-parse-stringify "^3.0.1" react-is@^16.13.1: