feat: Option to restore the last set "App Volume" on next app launch#509
Conversation
- 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.
📝 WalkthroughWalkthroughThis PR introduces persistent volume control for the audio player by creating a reusable ChangesVolume Settings and Slider Refactoring
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
mobile/src/modules/audio/_components/PlaybackParameterSlider.tsx (1)
63-63: PlaybackParameterSlider currently shows the numeric label fromstoredValue, notliveValue(likely non-live during drag).
displayedValueis computed fromstoredValue(displayedValue={${numberFormatter.format(storedValue)}x}) while the slider’s live updates go intocachedValuevialiveValue. SincePlaybackParameterSliderdoesn’t mirrorcachedValueinto React state (unlikeVolumeSettings, which usesuseAnimatedReactionto update_volumeand then rendersdisplayedValuefrom_volume), the text won’t update during dragging—only when the debouncedonChange/onCompleteupdates the store.If live numeric updates are intended, mirror
VolumeSettings’useAnimatedReactionpattern inPlaybackParameterSliderand derivedisplayedValuefrom 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 addingliveValuefor consistency with other audio sliders.PreAmpSlider passes only
initValuetoAudioEffectSlider, whileVolumeSettingsandPlaybackParameterSliderpass bothinitValueandliveValue(aSharedValue). This means external updates topreAmpValuewill re-render the slider but transitions will be instant rather than animated. If smooth animations are desired to match the pattern used elsewhere, addconst cachedValue = useSharedValue(preAmpValue)and pass it asliveValue.🤖 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 winClarify 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 configmin={0} max={1} step={0.01}in VolumeSettings.tsx), not a percentage0–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
📒 Files selected for processing (12)
mobile/src/modules/audio/_components/AudioEffectSlider.tsxmobile/src/modules/audio/_components/PlaybackParameterSlider.tsxmobile/src/modules/audio/_components/VolumeSettings.tsxmobile/src/modules/audio/_screens.tsxmobile/src/modules/audio/replayGain/components/PreAmpSlider.tsxmobile/src/modules/i18n/translations/en.jsonmobile/src/modules/scanning/hooks/useSetup.tsmobile/src/navigation/screens/now-playing/sheets/PlaybackOptionsSheet.tsxmobile/src/stores/Playback/constants.tsmobile/src/stores/Playback/store.tsmobile/src/stores/Session/constants.tsmobile/src/stores/Session/store.ts
💤 Files with no reviewable changes (2)
- mobile/src/stores/Session/store.ts
- mobile/src/stores/Session/constants.ts
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
volumevalue to the "Playback" store and either restore it or reset it back to1in theuseSetup()hook. We've also added a volume slider under theRestore App Volumesetting as it enables the user to set & forget the volume if they enabledRestore App Volume.Other Changes
AudioEffectSlidercomponent which is the horizontal slider design we used on the "Audio Effects" screen.Checklist
pnpm sync:licenses.pnpm android:prod.Summary by CodeRabbit
New Features
Improvements