Repository navigation
feat(player): add a volume control to the desktop player - #60
Merged
Merged
Conversation
Adds a speaker button next to shuffle in the sidebar player that opens a popover with a volume slider. The volume lives in the player store, is applied to the shared audio element (also when it is created) and is remembered in localStorage. The mobile player is unchanged. Closes suitux#49.
suitux
reviewed
Oct 20, 2026
Comment on lines
+12
to
+35
| const VOLUME_STORAGE_KEY = 'tagr-player-volume' | ||
|
|
||
| function clampVolume(value: number): number { | ||
| return Number.isFinite(value) ? Math.min(Math.max(value, 0), 1) : 1 | ||
| } | ||
|
|
||
| function loadVolume(): number { | ||
| if (typeof window === 'undefined') return 1 | ||
| try { | ||
| const stored = window.localStorage.getItem(VOLUME_STORAGE_KEY) | ||
| return stored === null ? 1 : clampVolume(parseFloat(stored)) | ||
| } catch { | ||
| return 1 | ||
| } | ||
| } | ||
|
|
||
| function saveVolume(value: number) { | ||
| try { | ||
| window.localStorage.setItem(VOLUME_STORAGE_KEY, String(value)) | ||
| } catch { | ||
| // Storage may be unavailable (private mode, blocked site data); the volume still applies for this session. | ||
| } | ||
| } | ||
|
|
Owner
There was a problem hiding this comment.
I think this should be moved into src/features/player/volume.ts
What do you think 😄 ?
Contributor
Author
There was a problem hiding this comment.
Good call — done 😄 Moved VOLUME_STORAGE_KEY, clampVolume, loadVolume and saveVolume into src/features/player/volume.ts, the store now imports them from there, and the test pulls the storage key from the module too.
suitux
approved these changes
Oct 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #49
Adds a volume control to the desktop (sidebar) player.
What it does
and the current percentage. The icon reflects the level (muted / low / high).
<audio>element, both when it changes and when the element is created.
localStorage(tagr-player-volume, samenaming as
tagr-edit-count). A missing or invalid value falls back to 100 %;if storage is unavailable (e.g. private mode) it simply isn't remembered.
playback.volumestring, so no newtranslations are needed.
Why not on mobile
I left the mobile player unchanged on purpose: on iOS/iPadOS,
HTMLMediaElement.volumecan't be set from JavaScript (the hardware buttonsalways control the level), and on Android a slider would only duplicate the
volume keys. Happy to add it to the expanded mobile player too if you'd prefer.
Testing
persistence, applying to the audio element) and for the control (opens with
the current value, keyboard changes the volume).
tsc --noEmit, ESLint and the full vitest suite pass, and the Docker imagebuilds.
updates the audio element's volume live, the value survives a reload, and
the icon switches between its three states.
Side note:
.prettierrcusesjsxBracketSameLine, which Prettier 3 no longersupports, so running Prettier reformats existing files. I kept the existing
style (
>on the same line) instead.