diff --git a/plugins/AudioVisualizer/desktop/AudioVisualizerDesktop.cpp b/plugins/AudioVisualizer/desktop/AudioVisualizerDesktop.cpp index be80ea04..40156751 100644 --- a/plugins/AudioVisualizer/desktop/AudioVisualizerDesktop.cpp +++ b/plugins/AudioVisualizer/desktop/AudioVisualizerDesktop.cpp @@ -18,6 +18,8 @@ AudioVisualizerDesktop::~AudioVisualizerDesktop() = default; void AudioVisualizerDesktop::render() { + std::lock_guard lock(stateMutex); + ImPlot::SetCurrentContext(implotContext); addConnectionSettings(); @@ -154,10 +156,6 @@ void AudioVisualizerDesktop::addDeviceSettings() { bool isSelected = (cfg.deviceName == DEFAULT_LOOPBACK_DEVICE_NAME); if (ImGui::Selectable((DEFAULT_LOOPBACK_DEVICE_NAME + "##default_loopback").c_str(), isSelected)) { cfg.deviceName = DEFAULT_LOOPBACK_DEVICE_NAME; - recorder->stopRecording(); - int loopbackIdx = AudioRecorder::Recorder::getDefaultOutputLoopbackIndex(); - if (loopbackIdx >= 0) - recorder->startRecording(loopbackIdx); } if (isSelected) ImGui::SetItemDefaultFocus(); @@ -174,8 +172,6 @@ void AudioVisualizerDesktop::addDeviceSettings() { #endif if (ImGui::Selectable(deviceNameWithId.c_str())) { cfg.deviceName = device.name; - recorder->stopRecording(); - recorder->startRecording(device.index); } if (cfg.deviceName == device.name) @@ -362,6 +358,21 @@ std::optional > AudioVisualize if (sceneName != "audio_spectrum") return std::nullopt; + std::lock_guard stateLock(stateMutex); + + if (cfg.deviceName != currentDeviceName) { + recorder->stopRecording(); + currentDeviceName = cfg.deviceName; + } + + if (cfg.deviceName == DEFAULT_LOOPBACK_DEVICE_NAME) { + int expectedLoopback = AudioRecorder::Recorder::getDefaultOutputLoopbackIndex(); + if (recorder->isRecording() && expectedLoopback >= 0 && expectedLoopback != recorder->getCurrentDeviceIndex()) { + spdlog::info("Default output device changed, switching loopback to device {}", expectedLoopback); + recorder->stopRecording(); + } + } + if (!recorder->isRecording()) { int deviceIndex = -1; @@ -403,17 +414,6 @@ std::optional > AudioVisualize } recorder->startRecording(deviceIndex); } - else if (cfg.deviceName == DEFAULT_LOOPBACK_DEVICE_NAME) - { - // Auto-switch: check if the default output device changed - int expectedLoopback = AudioRecorder::Recorder::getDefaultOutputLoopbackIndex(); - if (expectedLoopback >= 0 && expectedLoopback != recorder->getCurrentDeviceIndex()) - { - spdlog::info("Default output device changed, switching loopback to device {}", expectedLoopback); - recorder->stopRecording(); - recorder->startRecording(expectedLoopback); - } - } auto samplesOpt = recorder->getLastSamples(); if (!samplesOpt.has_value()) diff --git a/plugins/AudioVisualizer/desktop/AudioVisualizerDesktop.h b/plugins/AudioVisualizer/desktop/AudioVisualizerDesktop.h index 49f9bbba..2faa7179 100644 --- a/plugins/AudioVisualizer/desktop/AudioVisualizerDesktop.h +++ b/plugins/AudioVisualizer/desktop/AudioVisualizerDesktop.h @@ -51,6 +51,10 @@ class AudioVisualizerDesktop final : public Plugins::DesktopPlugin std::shared_mutex lastErrorMutex; std::string lastError; + // Concurrency protection + std::mutex stateMutex; + std::string currentDeviceName; + // Beat detection std::unique_ptr beatDetector; bool beat_detected; diff --git a/react-web/package.json b/react-web/package.json index 8af12415..476f2815 100644 --- a/react-web/package.json +++ b/react-web/package.json @@ -27,7 +27,7 @@ "lodash": "^4.18.1", "lucide-react": "^0.525.0", "react": "^19.2.6", - "react-colorful": "^5.6.1", + "react-colorful": "^5.7.0", "react-dom": "^19.2.6", "react-router-dom": "^7.15.0", "sonner": "^2.0.7", @@ -37,7 +37,7 @@ }, "devDependencies": { "@types/lodash": "^4.17.24", - "@types/node": "^25.6.0", + "@types/node": "^25.7.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@types/uuid": "^10.0.0", diff --git a/react-web/pnpm-lock.yaml b/react-web/pnpm-lock.yaml index 9894f6d8..8d234e9a 100644 --- a/react-web/pnpm-lock.yaml +++ b/react-web/pnpm-lock.yaml @@ -60,8 +60,8 @@ importers: specifier: ^19.2.6 version: 19.2.6 react-colorful: - specifier: ^5.6.1 - version: 5.6.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + specifier: ^5.7.0 + version: 5.7.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react-dom: specifier: ^19.2.6 version: 19.2.6(react@19.2.6) @@ -85,8 +85,8 @@ importers: specifier: ^4.17.24 version: 4.17.24 '@types/node': - specifier: ^25.6.0 - version: 25.6.0 + specifier: ^25.7.0 + version: 25.7.0 '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -98,7 +98,7 @@ importers: version: 10.0.0 '@vitejs/plugin-react': specifier: ^4.7.0 - version: 4.7.0(vite@6.4.2(@types/node@25.6.0)(jiti@1.21.7)(terser@5.46.2)) + version: 4.7.0(vite@6.4.2(@types/node@25.7.0)(jiti@1.21.7)(terser@5.47.1)) autoprefixer: specifier: ^10.5.0 version: 10.5.0(postcss@8.5.14) @@ -113,10 +113,10 @@ importers: version: 5.9.3 vite: specifier: ^6.4.2 - version: 6.4.2(@types/node@25.6.0)(jiti@1.21.7)(terser@5.46.2) + version: 6.4.2(@types/node@25.7.0)(jiti@1.21.7)(terser@5.47.1) vite-plugin-pwa: specifier: ^0.21.2 - version: 0.21.2(vite@6.4.2(@types/node@25.6.0)(jiti@1.21.7)(terser@5.46.2))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0) + version: 0.21.2(vite@6.4.2(@types/node@25.7.0)(jiti@1.21.7)(terser@5.47.1))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0) packages: @@ -1495,11 +1495,14 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/lodash@4.17.24': resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} - '@types/node@25.6.0': - resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} + '@types/node@25.7.0': + resolution: {integrity: sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==} '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} @@ -1598,8 +1601,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.10.27: - resolution: {integrity: sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==} + baseline-browser-mapping@2.10.29: + resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -1610,8 +1613,8 @@ packages: brace-expansion@2.1.0: resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} braces@3.0.3: @@ -1744,8 +1747,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.351: - resolution: {integrity: sha512-9D7Iqx8RImSvCnOsj86rCH6eQjZFQoM04Jn6HnZVM0Nu/G58/gmKYQ1d12MZTbjQbQSTGI8nwEy07ErsA2slLA==} + electron-to-chromium@1.5.353: + resolution: {integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==} es-abstract@1.24.2: resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} @@ -2165,8 +2168,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - node-releases@2.0.38: - resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} + node-releases@2.0.44: + resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -2298,8 +2301,8 @@ packages: randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - react-colorful@5.6.1: - resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==} + react-colorful@5.7.0: + resolution: {integrity: sha512-fuesYIemttah97XmsIHmz4OORDHiSFzyc9HMAIrCHJou2jaRQmL8cFJ76K4zQhhj8jzwOBlOi4BaGTjjOZCfTg==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' @@ -2574,8 +2577,8 @@ packages: resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} engines: {node: '>=10'} - terser@5.46.2: - resolution: {integrity: sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==} + terser@5.47.1: + resolution: {integrity: sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==} engines: {node: '>=10'} hasBin: true @@ -2632,8 +2635,8 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} - undici-types@7.19.2: - resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + undici-types@7.21.0: + resolution: {integrity: sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==} unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} @@ -4120,7 +4123,7 @@ snapshots: dependencies: serialize-javascript: 6.0.2 smob: 1.6.1 - terser: 5.46.2 + terser: 5.47.1 optionalDependencies: rollup: 2.80.0 @@ -4133,7 +4136,7 @@ snapshots: '@rollup/pluginutils@5.3.0(rollup@2.80.0)': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-walker: 2.0.2 picomatch: 4.0.4 optionalDependencies: @@ -4246,11 +4249,13 @@ snapshots: '@types/estree@1.0.8': {} + '@types/estree@1.0.9': {} + '@types/lodash@4.17.24': {} - '@types/node@25.6.0': + '@types/node@25.7.0': dependencies: - undici-types: 7.19.2 + undici-types: 7.21.0 '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: @@ -4266,7 +4271,7 @@ snapshots: '@types/uuid@10.0.0': {} - '@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@25.6.0)(jiti@1.21.7)(terser@5.46.2))': + '@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@25.7.0)(jiti@1.21.7)(terser@5.47.1))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -4274,7 +4279,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.4.2(@types/node@25.6.0)(jiti@1.21.7)(terser@5.46.2) + vite: 6.4.2(@types/node@25.7.0)(jiti@1.21.7)(terser@5.47.1) transitivePeerDependencies: - supports-color @@ -4362,7 +4367,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.10.27: {} + baseline-browser-mapping@2.10.29: {} binary-extensions@2.3.0: {} @@ -4370,7 +4375,7 @@ snapshots: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.5: + brace-expansion@5.0.6: dependencies: balanced-match: 4.0.4 @@ -4380,10 +4385,10 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.27 + baseline-browser-mapping: 2.10.29 caniuse-lite: 1.0.30001792 - electron-to-chromium: 1.5.351 - node-releases: 2.0.38 + electron-to-chromium: 1.5.353 + node-releases: 2.0.44 update-browserslist-db: 1.2.3(browserslist@4.28.2) buffer-from@1.1.2: {} @@ -4505,7 +4510,7 @@ snapshots: dependencies: jake: 10.9.4 - electron-to-chromium@1.5.351: {} + electron-to-chromium@1.5.353: {} es-abstract@1.24.2: dependencies: @@ -4961,7 +4966,7 @@ snapshots: minimatch@10.2.5: dependencies: - brace-expansion: 5.0.5 + brace-expansion: 5.0.6 minimatch@5.1.9: dependencies: @@ -4979,7 +4984,7 @@ snapshots: nanoid@3.3.12: {} - node-releases@2.0.38: {} + node-releases@2.0.44: {} normalize-path@3.0.0: {} @@ -5078,7 +5083,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - react-colorful@5.6.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + react-colorful@5.7.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -5446,7 +5451,7 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 - terser@5.46.2: + terser@5.47.1: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.16.0 @@ -5522,7 +5527,7 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - undici-types@7.19.2: {} + undici-types@7.21.0: {} unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -5568,18 +5573,18 @@ snapshots: uuid@14.0.0: {} - vite-plugin-pwa@0.21.2(vite@6.4.2(@types/node@25.6.0)(jiti@1.21.7)(terser@5.46.2))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0): + vite-plugin-pwa@0.21.2(vite@6.4.2(@types/node@25.7.0)(jiti@1.21.7)(terser@5.47.1))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0): dependencies: debug: 4.4.3 pretty-bytes: 6.1.1 tinyglobby: 0.2.16 - vite: 6.4.2(@types/node@25.6.0)(jiti@1.21.7)(terser@5.46.2) + vite: 6.4.2(@types/node@25.7.0)(jiti@1.21.7)(terser@5.47.1) workbox-build: 7.4.0(@types/babel__core@7.20.5) workbox-window: 7.4.0 transitivePeerDependencies: - supports-color - vite@6.4.2(@types/node@25.6.0)(jiti@1.21.7)(terser@5.46.2): + vite@6.4.2(@types/node@25.7.0)(jiti@1.21.7)(terser@5.47.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) @@ -5588,10 +5593,10 @@ snapshots: rollup: 4.60.3 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 25.6.0 + '@types/node': 25.7.0 fsevents: 2.3.3 jiti: 1.21.7 - terser: 5.46.2 + terser: 5.47.1 webidl-conversions@4.0.2: {}