Skip to content

Conversation

@official-notfishvr
Copy link

i dont think 99.99% of users will need this but i do lol

@qodo-code-review
Copy link

Review Summary by Qodo

Add volume boost feature with Web Audio API integration

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add volume boost feature with 1.0× to 10.0× range control
• Implement Web Audio API integration for audio gain manipulation
• Create IPC handlers for getting and setting volume boost values
• Add settings UI slider with real-time label display
• Auto-detect and hook media elements for boost application
Diagram
flowchart LR
  A["Settings UI<br/>Slider Input"] -->|invoke| B["IPC Handler<br/>set-volume-boost"]
  B -->|store value| C["SimpleStore<br/>volumeBoost"]
  B -->|send event| D["Preload Script<br/>applyBoostValue"]
  D -->|set gain| E["Web Audio API<br/>Master Gain Node"]
  E -->|amplify| F["Media Elements<br/>audio/video"]
  G["DOMContentLoaded"] -->|invoke| H["IPC Handler<br/>get-volume-boost"]
  H -->|load value| D
  I["MutationObserver"] -->|detect new| F
  F -->|hook for boost| E
Loading

Grey Divider

File Changes

1. src/main/main.js ✨ Enhancement +37/-0

Add IPC handlers for volume boost persistence

• Add get-volume-boost IPC handler to retrieve stored boost value with validation and clamping
 (1.0-10.0 range)
• Add set-volume-boost IPC handler to persist boost value and broadcast changes to renderer
 process
• Initialize volumeBoost: 1.0 in SimpleStore defaults

src/main/main.js


2. src/preload/preload-settings.js ✨ Enhancement +3/-0

Expose volume boost IPC methods to settings context

• Expose getVolumeBoost() method to invoke IPC handler
• Expose setVolumeBoost(value) method to invoke IPC handler

src/preload/preload-settings.js


3. src/preload/preload.js ✨ Enhancement +117/-0

Implement Web Audio API volume boost system

• Create Web Audio API context and master gain node for audio amplification
• Implement applyBoostValue() to validate and apply gain multiplier with clamping
• Implement ensureAudioGraph() to lazily initialize AudioContext and gain nodes
• Implement hookMediaElementForBoost() to connect media elements to gain node and track volume
 changes
• Implement scanAndHookMediaElements() to find and hook existing audio/video elements
• Add MutationObserver to automatically hook newly added media elements
• Listen for volume-boost-changed IPC events to update gain in real-time
• Call setupVolumeBoost() on DOMContentLoaded to initialize feature

src/preload/preload.js


View more (2)
4. src/settings/settings.js ✨ Enhancement +46/-0

Add volume boost slider UI and event handling

• Add DOM element references for volume boost slider and label
• Load initial volume boost value from IPC and update slider/label display
• Add input event listener to slider for real-time value changes with validation
• Persist slider changes via IPC and update UI with applied value

src/settings/settings.js


5. src/settings/settings.html ✨ Enhancement +21/-0

Add volume boost slider UI component

• Add new settings section with volume boost slider (range 1-10, step 0.1)
• Add descriptive title and help text explaining boost functionality
• Add label element to display current boost multiplier value

src/settings/settings.html


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Feb 9, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

✅ 1. error.message returned to UI 📘 Rule violation ⛨ Security
Description
• The set-volume-boost IPC handler returns error.message directly to the renderer on failure.
• This can expose internal implementation details (and potentially sensitive environment info) to
  end-users, contrary to secure user-facing error guidance.
• The detailed error should stay in internal logs, while the renderer receives a generic message
  (optionally with an error code).
Code

src/main/main.js[R1046-1049]

+  } catch (error) {
+    console.error('Failed to set volume boost:', error);
+    return { success: false, error: error.message };
+  }
Evidence
Compliance requires user-facing errors to be generic and avoid exposing internal details. The IPC
handler currently returns the raw exception message (error.message) to the renderer, which is a
user-facing surface in an Electron app.

Rule 4: Generic: Secure Error Handling
src/main/main.js[1046-1049]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`set-volume-boost` returns `error.message` to the renderer, which can leak internal details.
## Issue Context
Renderer-visible IPC responses should use generic error text (and optionally stable error codes). Detailed stack traces/messages should remain only in internal logs.
## Fix Focus Areas
- src/main/main.js[1046-1049]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 2. Empty catch swallows errors 📘 Rule violation ⛯ Reliability
Description
scanAndHookMediaElements() uses an empty catch {} block, causing failures (e.g., DOM/query
  issues) to be silently ignored.
• This prevents diagnosis in production and violates robust error handling expectations (no context,
  no logging, no fallback behavior).
• At minimum, log a warning with safe context or explicitly justify/handle the failure path.
Code

src/preload/preload.js[R203-209]

+const scanAndHookMediaElements = () => {
+  try {
+    const mediaEls = document.querySelectorAll('audio, video');
+    mediaEls.forEach((el) => hookMediaElementForBoost(el));
+  } catch {
+  }
+};
Evidence
The robust error-handling rule forbids silent failures and swallowed exceptions without logging. The
empty catch {} block guarantees errors are discarded with no context.

Rule 3: Generic: Robust Error Handling and Edge Case Management
src/preload/preload.js[203-209]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An empty `catch {}` silently swallows errors, making failures impossible to debug.
## Issue Context
This code runs in the preload context; failures should be observable via safe warnings (without leaking sensitive data).
## Fix Focus Areas
- src/preload/preload.js[203-209]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 3. Sync writes on drag 🐞 Bug ➹ Performance
Description
• The settings slider uses the high-frequency input event and invokes setVolumeBoost for every
  tick while dragging.
• The main process persists every update via SimpleStore.set() which performs synchronous
  fs.writeFileSync, potentially blocking the main process repeatedly and causing UI jank.
• This also creates avoidable disk churn/wear; persisting at most on change (release) or via
  debounced/throttled updates would be safer.
Code

src/settings/settings.js[R199-224]

+// Handle volume boost slider change
+if (volumeBoostSlider) {
+  volumeBoostSlider.addEventListener('input', async (event) => {
+    const raw = Number(event.target.value);
+    let numeric = Number.isFinite(raw) && raw > 0 ? raw : 1.0;
+    numeric = Math.min(Math.max(numeric, 1.0), 10.0);
+
+    if (volumeBoostLabel) {
+      volumeBoostLabel.textContent = `${numeric.toFixed(1)}×`;
+    }
+
+    try {
+      const result = await window.settings.setVolumeBoost(numeric);
+      if (result && result.success && typeof result.value === 'number') {
+        const applied = Math.min(Math.max(result.value, 1.0), 10.0);
+        if (volumeBoostSlider.value !== applied.toString()) {
+          volumeBoostSlider.value = applied.toString();
+        }
+        if (volumeBoostLabel) {
+          volumeBoostLabel.textContent = `${applied.toFixed(1)}×`;
+        }
+      }
+    } catch (error) {
+      console.error('Failed to update volume boost:', error);
+    }
+  });
Evidence
The renderer sends IPC updates on every slider input event; the main handler calls store.set,
which synchronously writes settings to disk on each call.

src/settings/settings.js[199-225]
src/main/main.js[1026-1044]
src/main/storage.js[24-39]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The volume boost slider persists changes on every `input` tick. In the current architecture, each IPC call results in a synchronous disk write (`fs.writeFileSync`) on the main process, which can block the app repeatedly during dragging.
## Issue Context
- `settings.js` sends `setVolumeBoost()` for every `input` event.
- `main.js` persists the value with `store.set()`.
- `SimpleStore.save()` uses synchronous I/O.
## Fix Focus Areas
- src/settings/settings.js[199-225]
- src/main/main.js[1026-1044]
- src/main/storage.js[24-39]
## Suggested approach
1. Keep UI responsiveness: update the label immediately on `input`.
2. Reduce persistence frequency:
 - Option A: call `setVolumeBoost` only on `change` (mouse/touch release).
 - Option B: debounce IPC calls (e.g., 200–500ms) so dragging produces fewer writes.
3. (Optional, stronger) Buffer settings writes in `SimpleStore`:
 - replace `writeFileSync` with async write and/or coalesce multiple `set()` calls into a single write with a timer.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. No audio boost cleanup 🐞 Bug ⛯ Reliability
Description
• The preload volume-boost implementation creates an AudioContext, per-media-element nodes, and
  attaches event listeners, but does not disconnect/close them.
• A MutationObserver is started on the entire document subtree and is never disconnected.
• Over time (navigations/reloads, SPAs, dynamic media elements), this can leak media elements/audio
  nodes and keep observers running, increasing memory/CPU usage.
Code

src/preload/preload.js[R225-252]

+  try {
+    const observer = new MutationObserver((mutations) => {
+      for (const mutation of mutations) {
+        for (const node of mutation.addedNodes) {
+          if (!(node instanceof Element)) continue;
+          if (node.matches('audio, video')) {
+            hookMediaElementForBoost(node);
+          }
+          const nested = node.querySelectorAll && node.querySelectorAll('audio, video');
+          if (nested && nested.length) {
+            nested.forEach((el) => hookMediaElementForBoost(el));
+          }
+        }
+      }
+    });
+
+    observer.observe(document.documentElement, {
+      childList: true,
+      subtree: true,
+    });
+  } catch (error) {
+    console.warn('[P-Stream] Failed to observe media elements for volume boost:', error);
+  }
+};
+
window.addEventListener('DOMContentLoaded', () => {
 observeThemeChanges();
 const intervalId = setInterval(sendThemeColor, 10000);
Evidence
The code installs long-lived observers and listeners and allocates audio resources, but the only
unload cleanup clears an unrelated interval; there is no observer disconnect, listener removal, node
disconnect, or AudioContext close.

src/preload/preload.js[149-167]
src/preload/preload.js[169-201]
src/preload/preload.js[225-245]
src/preload/preload.js[250-262]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The preload volume boost implementation allocates long-lived resources (AudioContext, MutationObserver, event listeners, AudioNodes) but does not clean them up. This can cause memory/CPU leaks across navigations/reloads and with dynamic media elements.
## Issue Context
- `setupVolumeBoost()` creates a MutationObserver and observes the entire document subtree.
- `hookMediaElementForBoost()` creates MediaElementSource/Gain nodes and attaches listeners to each media element.
- The existing `beforeunload` handler only clears a theme interval; it does not clean up boost resources.
## Fix Focus Areas
- src/preload/preload.js[135-262]
## Suggested approach
1. Keep references:
 - Store the MutationObserver in a module-level variable.
 - Track per-element state in a `WeakMap&amp;lt;HTMLMediaElement, { sourceNode, gainNode, onPlay, onVolumeChange }&amp;gt;`.
2. Cleanup on unload:
 - In `beforeunload`, call `observer.disconnect()`.
 - For each tracked element, `removeEventListener` and `disconnect()` nodes.
 - Call `pstreamAudioContext.close()` (best-effort) and null out globals.
3. (Optional) Handle removals:
 - Extend MutationObserver to process `removedNodes` and cleanup media elements that leave the DOM.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant