Skip to content

feat: Option to restore the last set "App Volume" on next app launch#509

Merged
cyanChill merged 5 commits into
devfrom
feat/restore-volume
Jun 7, 2026
Merged

feat: Option to restore the last set "App Volume" on next app launch#509
cyanChill merged 5 commits into
devfrom
feat/restore-volume

Conversation

@cyanChill

@cyanChill cyanChill commented Jun 7, 2026

Copy link
Copy Markdown
Member

Why

This PR adds the ability to persist the volume set in the app as it doesn't modify the device volume, but plays a percentage of the device volume.

This requires us to move the volume value to the "Playback" store and either restore it or reset it back to 1 in the useSetup() hook. We've also added a volume slider under the Restore App Volume setting as it enables the user to set & forget the volume if they enabled Restore App Volume.

Other Changes

  • Created a reusable AudioEffectSlider component which is the horizontal slider design we used on the "Audio Effects" screen.

Checklist

  • Documentation is up to date to reflect these changes.
  • Ensure dependency licenses are up-to-date by running pnpm sync:licenses.
  • This diff will work correctly for pnpm android:prod.

Summary by CodeRabbit

  • New Features

    • Added volume restoration option to preserve app volume settings between sessions.
    • Added dedicated volume control to audio settings with percentage display.
  • Improvements

    • Consolidated audio effect slider UI for consistent display across playback and replay gain controls.
    • Improved volume state management for better persistence and reliability.

cyanChill added 5 commits June 6, 2026 22:09
- The volume slider in our app allows for further granularity, separate from the device volume.
- We reset the volume to `1` in `useSetup` if the flag is disabled as it sometimes doesn't get saved when we do it in the `onServiceKilled` event.
- We've decided that we're going to also display a slider.
- The initial implementation prevents it from being used with the `PreAmpSlider`.
- Mainly for if the user enables "Restore App Volume", they can immediately set & forget the volume on the same screen.
@cyanChill cyanChill added the enhancement New feature or request label Jun 7, 2026
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces persistent volume control for the audio player by creating a reusable AudioEffectSlider component, moving volume state from temporary session storage to persistent playback store, and refactoring existing sliders to use the new component foundation. Volume restoration is applied during app startup.

Changes

Volume Settings and Slider Refactoring

Layer / File(s) Summary
AudioEffectSlider component foundation
mobile/src/modules/audio/_components/AudioEffectSlider.tsx
Reusable slider component combining CachedSlider with fixed-width value display and optional icon, serving as UI foundation for volume and effect sliders.
Playback store schema and defaults
mobile/src/stores/Playback/constants.ts, mobile/src/stores/Playback/store.ts
Adds volume and restoreVolume fields to PlaybackStore interface, initializes defaults, and marks both fields for persistence.
Remove volume from SessionStore
mobile/src/stores/Session/constants.ts, mobile/src/stores/Session/store.ts
Removes volume field from SessionStore interface and initial state, completing migration to PlaybackStore.
VolumeSettings component and helpers
mobile/src/modules/audio/_components/VolumeSettings.tsx
New component reading/updating restoreVolume and volume from playback store, rendering AudioEffectSlider with animated value sync and percentage display; includes toggleRestoreVolume and setVolume helpers.
PlaybackParameterSlider refactoring
mobile/src/modules/audio/_components/PlaybackParameterSlider.tsx
Converts to memoized component, replaces inline CachedSlider rendering with AudioEffectSlider, passes formatted displayedValue and preset buttons.
PreAmpSlider refactoring
mobile/src/modules/audio/replayGain/components/PreAmpSlider.tsx
Replaces CachedSlider and separate dB display with AudioEffectSlider, moving formatted dB text into displayedValue prop.
Store consumer updates and integration
mobile/src/navigation/screens/now-playing/sheets/PlaybackOptionsSheet.tsx, mobile/src/modules/scanning/hooks/useSetup.ts, mobile/src/modules/audio/_screens.tsx
Updates PlaybackOptionsSheet to use playbackStore for volume; applies volume restoration in useSetup on startup; integrates VolumeSettings into audio effects screen.
Localization
mobile/src/modules/i18n/translations/en.json
Adds English translation string "restoreVolume": "Restore App Volume".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • MissingCore/Music#507: Both PRs modify PlaybackParameterSlider implementation; this PR refactors it to use AudioEffectSlider while the other adds pitch slider wiring and presets.
  • MissingCore/Music#504: Both PRs update PreAmpSlider to replace its slider UI implementation with the newer AudioEffectSlider component.
  • MissingCore/Music#505: Both PRs modify audio effects screen wiring and touch shared audio-effect UI components like PreAmpSlider and playback options.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main feature added: persistence of the last set "App Volume" on app launch, which directly aligns with the changeset's objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
mobile/src/modules/audio/_components/PlaybackParameterSlider.tsx (1)

63-63: PlaybackParameterSlider currently shows the numeric label from storedValue, not liveValue (likely non-live during drag).

displayedValue is computed from storedValue (displayedValue={${numberFormatter.format(storedValue)}x}) while the slider’s live updates go into cachedValue via liveValue. Since PlaybackParameterSlider doesn’t mirror cachedValue into React state (unlike VolumeSettings, which uses useAnimatedReaction to update _volume and then renders displayedValue from _volume), the text won’t update during dragging—only when the debounced onChange / onComplete updates the store.

If live numeric updates are intended, mirror VolumeSettingsuseAnimatedReaction pattern in PlaybackParameterSlider and derive displayedValue from that synced live value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/src/modules/audio/_components/PlaybackParameterSlider.tsx` at line 63,
PlaybackParameterSlider currently renders displayedValue from storedValue so the
label doesn't update while dragging; change it to mirror the live cachedValue
into React state like VolumeSettings does by using useAnimatedReaction to
subscribe to liveValue/cachedValue (the same source updated during onChange) and
set a local synced value (e.g., _playbackRate) then compute displayedValue from
that synced value instead of storedValue; update references to displayedValue,
storedValue, liveValue, cachedValue and ensure the reaction is cleaned up and
debounced behaviour remains handled by the existing onChange/onComplete.
mobile/src/modules/audio/replayGain/components/PreAmpSlider.tsx (1)

21-34: Consider adding liveValue for consistency with other audio sliders.

PreAmpSlider passes only initValue to AudioEffectSlider, while VolumeSettings and PlaybackParameterSlider pass both initValue and liveValue (a SharedValue). This means external updates to preAmpValue will re-render the slider but transitions will be instant rather than animated. If smooth animations are desired to match the pattern used elsewhere, add const cachedValue = useSharedValue(preAmpValue) and pass it as liveValue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/src/modules/audio/replayGain/components/PreAmpSlider.tsx` around lines
21 - 34, The PreAmpSlider currently passes only initValue to AudioEffectSlider
so external changes jump instantly; create a SharedValue to animate changes by
adding const cachedValue = useSharedValue(preAmpValue) inside PreAmpSlider,
update cachedValue whenever preAmpValue changes, and pass cachedValue as the
liveValue prop to AudioEffectSlider (retain initValue and other props);
reference the PreAmpSlider component, the preAmpValue variable, and
AudioEffectSlider's liveValue prop when making this change.
mobile/src/stores/Playback/constants.ts (1)

75-76: ⚡ Quick win

Clarify the comment to reflect the actual value range.

The comment states "Percentage of device volume" but the value is a decimal multiplier in the range 0–1 (as evidenced by the slider config min={0} max={1} step={0.01} in VolumeSettings.tsx), not a percentage 0–100. The UI displays it as a percentage (Math.round(_volume * 100)%), but the stored value is a fraction.

📝 Suggested clarification
-  /** Percentage of device volume audio will be outputted with. */
+  /** Fraction of device volume (0–1) that audio will be output at. */
   volume: number;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/src/stores/Playback/constants.ts` around lines 75 - 76, Update the
JSDoc for the volume field to state it is a decimal multiplier in the range 0–1
(not 0–100), e.g., "volume: number" represents a fraction of device output (0.0
= muted, 1.0 = full), and reference that VolumeSettings.tsx uses slider min={0}
max={1} step={0.01} and the UI displays it as Math.round(_volume * 100)%; change
the comment above the volume property in constants.ts accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mobile/src/modules/audio/_components/VolumeSettings.tsx`:
- Line 18: The local _volume state is hardcoded to 1 causing a UI mismatch until
useAnimatedReaction updates it; initialize it from the store's current value
instead. Replace the useState(1) initializer for _volume/_setVolume with a
function that reads the current cachedValue (or the store's volume) so the
initial render matches the store (e.g., useState(() => cachedValue ?? 1)); keep
useAnimatedReaction as-is to keep syncing thereafter. Ensure you reference and
import the same cachedValue/store variable used inside useAnimatedReaction so
there is no mismatch.

In `@mobile/src/modules/scanning/hooks/useSetup.ts`:
- Around line 104-105: When restoreVolume is false the code only calls
playbackStore.setState({ volume: 1 }) but doesn't update the actual audio
output; call AudioBrowser.setVolume(1) as well so the UI store and audio engine
stay in sync. Update the branch that handles restoreVolume (the block using
restoreVolume, AudioBrowser.setVolume and playbackStore.setState) to set both
the playbackStore volume and call AudioBrowser.setVolume(1), mirroring how
VolumeSettings.setVolume updates both the store and AudioBrowser.

---

Nitpick comments:
In `@mobile/src/modules/audio/_components/PlaybackParameterSlider.tsx`:
- Line 63: PlaybackParameterSlider currently renders displayedValue from
storedValue so the label doesn't update while dragging; change it to mirror the
live cachedValue into React state like VolumeSettings does by using
useAnimatedReaction to subscribe to liveValue/cachedValue (the same source
updated during onChange) and set a local synced value (e.g., _playbackRate) then
compute displayedValue from that synced value instead of storedValue; update
references to displayedValue, storedValue, liveValue, cachedValue and ensure the
reaction is cleaned up and debounced behaviour remains handled by the existing
onChange/onComplete.

In `@mobile/src/modules/audio/replayGain/components/PreAmpSlider.tsx`:
- Around line 21-34: The PreAmpSlider currently passes only initValue to
AudioEffectSlider so external changes jump instantly; create a SharedValue to
animate changes by adding const cachedValue = useSharedValue(preAmpValue) inside
PreAmpSlider, update cachedValue whenever preAmpValue changes, and pass
cachedValue as the liveValue prop to AudioEffectSlider (retain initValue and
other props); reference the PreAmpSlider component, the preAmpValue variable,
and AudioEffectSlider's liveValue prop when making this change.

In `@mobile/src/stores/Playback/constants.ts`:
- Around line 75-76: Update the JSDoc for the volume field to state it is a
decimal multiplier in the range 0–1 (not 0–100), e.g., "volume: number"
represents a fraction of device output (0.0 = muted, 1.0 = full), and reference
that VolumeSettings.tsx uses slider min={0} max={1} step={0.01} and the UI
displays it as Math.round(_volume * 100)%; change the comment above the volume
property in constants.ts accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a6d6bbd5-cdff-4285-9f3d-edf60e961c6c

📥 Commits

Reviewing files that changed from the base of the PR and between 507dfe3 and f04a0b2.

📒 Files selected for processing (12)
  • mobile/src/modules/audio/_components/AudioEffectSlider.tsx
  • mobile/src/modules/audio/_components/PlaybackParameterSlider.tsx
  • mobile/src/modules/audio/_components/VolumeSettings.tsx
  • mobile/src/modules/audio/_screens.tsx
  • mobile/src/modules/audio/replayGain/components/PreAmpSlider.tsx
  • mobile/src/modules/i18n/translations/en.json
  • mobile/src/modules/scanning/hooks/useSetup.ts
  • mobile/src/navigation/screens/now-playing/sheets/PlaybackOptionsSheet.tsx
  • mobile/src/stores/Playback/constants.ts
  • mobile/src/stores/Playback/store.ts
  • mobile/src/stores/Session/constants.ts
  • mobile/src/stores/Session/store.ts
💤 Files with no reviewable changes (2)
  • mobile/src/stores/Session/store.ts
  • mobile/src/stores/Session/constants.ts

Comment thread mobile/src/modules/audio/_components/VolumeSettings.tsx
Comment thread mobile/src/modules/scanning/hooks/useSetup.ts
@cyanChill cyanChill merged commit 20af1c3 into dev Jun 7, 2026
1 check passed
@cyanChill cyanChill deleted the feat/restore-volume branch June 7, 2026 03:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant