Add a volume slider with mute toggle to the player controls

This commit is contained in:
encie22 committed 2026-10-15 17:17:19 +10:30
1 parent 4624fe722b
commit da65d52319
1 file changed
+159 -30
+159 -30
View File
@@ -104,35 +104,72 @@ const playerArtworkOpacity = 0.4;
<div <div
class="flex h-full flex-row items-center justify-center align-middle" class="flex h-full flex-row items-center justify-center align-middle"
> >
<div class="m-0 flex grow-0 flex-row space-x-2 p-0 text-3xl"> <div class="m-0 flex grow-0 flex-col p-0">
<button <div class="mt-2 flex flex-row items-center space-x-4 text-2xl">
type="button" <button
id="player:controls:previous" type="button"
aria-label="Previous track" id="player:controls:previous"
class="rounded-full bg-transparent p-0 hover:cursor-pointer" aria-label="Previous track"
><Icon name="fa7-solid:backward" class="pointer-events-none" /> class="rounded-full bg-transparent p-0 hover:cursor-pointer"
</button> ><Icon name="fa7-solid:backward" class="pointer-events-none" />
<button </button>
type="button" <button
id="player:controls:play" type="button"
aria-label="Play" id="player:controls:play"
class="rounded-full bg-transparent p-0 hover:cursor-pointer" aria-label="Play"
><Icon name="fa7-solid:play" class="pointer-events-none" /> class="rounded-full bg-transparent p-0 hover:cursor-pointer"
</button> ><Icon name="fa7-solid:play" class="pointer-events-none" />
<button </button>
type="button" <button
id="player:controls:pause" type="button"
aria-label="Pause" id="player:controls:pause"
class="hidden rounded-full bg-transparent p-0 hover:cursor-pointer" aria-label="Pause"
><Icon name="fa7-solid:pause" class="pointer-events-none" /> class="hidden rounded-full bg-transparent p-0 hover:cursor-pointer"
</button> ><Icon name="fa7-solid:pause" class="pointer-events-none" />
<button </button>
type="button" <button
id="player:controls:next" type="button"
aria-label="Next track" id="player:controls:next"
class="rounded-full bg-transparent p-0 hover:cursor-pointer" aria-label="Next track"
><Icon name="fa7-solid:forward" class="pointer-events-none" /> class="rounded-full bg-transparent p-0 hover:cursor-pointer"
</button> ><Icon name="fa7-solid:forward" class="pointer-events-none" />
</button>
</div>
<div
class="mt-3 flex hidden flex-row items-center space-x-2 pt-1 text-sm md:flex"
>
<span class="flex w-9 items-center justify-center">
<button
type="button"
id="player:controls:mute"
aria-label="Mute"
aria-pressed="false"
class="rounded-full bg-transparent p-0 hover:cursor-pointer"
>
<Icon
name="fa7-solid:volume-high"
id="player:controls:volume-on"
class="pointer-events-none"
/>
<Icon
name="fa7-solid:volume-xmark"
id="player:controls:volume-off"
class="pointer-events-none hidden"
/>
</button>
</span>
<span class="flex w-14 items-center md:w-16">
<input
type="range"
id="player:controls:volume"
aria-label="Volume"
min="0"
max="100"
value="100"
class="accent-primary w-full cursor-pointer"
/>
</span>
</div>
</div> </div>
<div class="flex flex-grow flex-col text-sm"> <div class="flex flex-grow flex-col text-sm">
<span <span
@@ -261,6 +298,40 @@ const playerArtworkOpacity = 0.4;
display: none; display: none;
} }
/* Slim volume slider */
#player\:controls\:volume {
-webkit-appearance: none;
appearance: none;
background: transparent;
height: 12px;
}
#player\:controls\:volume::-webkit-slider-runnable-track {
height: 3px;
border-radius: 9999px;
background: rgba(255, 255, 255, 0.25);
}
#player\:controls\:volume::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 10px;
height: 10px;
margin-top: -3.5px;
border-radius: 9999px;
background: #ff2d00;
}
#player\:controls\:volume::-moz-range-track {
height: 3px;
border-radius: 9999px;
background: rgba(255, 255, 255, 0.25);
}
#player\:controls\:volume::-moz-range-thumb {
width: 10px;
height: 10px;
border: none;
border-radius: 9999px;
background: #ff2d00;
}
/* Outgoing layer fades fast, incoming fades in late */ /* Outgoing layer fades fast, incoming fades in late */
.player-full { .player-full {
transition: transition:
@@ -327,6 +398,9 @@ const playerArtworkOpacity = 0.4;
this.minimised = window.player?.minimised ?? false; this.minimised = window.player?.minimised ?? false;
// Seek fraction requested before anything was loaded. // Seek fraction requested before anything was loaded.
this.pendingSeek = null; this.pendingSeek = null;
// Playback volume (0-1); restored from storage below.
this.volume = 1;
this.lastVolume = 1;
this.RING_C = 182.2; // 2 * PI * 29, must match dasharray in markup this.RING_C = 182.2; // 2 * PI * 29, must match dasharray in markup
@@ -340,7 +414,11 @@ const playerArtworkOpacity = 0.4;
pause: $("player:controls:pause"), pause: $("player:controls:pause"),
previous: $("player:controls:previous"), previous: $("player:controls:previous"),
next: $("player:controls:next"), next: $("player:controls:next"),
minimise: $("player:controls:minimise") minimise: $("player:controls:minimise"),
mute: $("player:controls:mute"),
volumeOn: $("player:controls:volume-on"),
volumeOff: $("player:controls:volume-off"),
volume: $("player:controls:volume")
}, },
times: { times: {
current: $("player:times:current"), current: $("player:times:current"),
@@ -423,6 +501,25 @@ const playerArtworkOpacity = 0.4;
e.stopPropagation(); e.stopPropagation();
window.player.setminimised(true); window.player.setminimised(true);
}); });
this.UI.controls.mute.addEventListener("click", (e) => {
e.stopPropagation();
window.player.toggleMute();
});
this.UI.controls.volume.addEventListener("input", (e) => {
e.stopPropagation();
window.player.setVolume(e.target.value / 100);
});
try {
const stored = parseFloat(localStorage.getItem("player-volume"));
if (Number.isFinite(stored)) {
this.volume = Math.min(Math.max(stored, 0), 1);
this.lastVolume = this.volume > 0 ? this.volume : 1;
}
} catch (e) {
/* ignore */
}
this.syncVolumeUI();
this.UI.progress.bar.addEventListener("click", function (event) { this.UI.progress.bar.addEventListener("click", function (event) {
event.stopPropagation(); event.stopPropagation();
@@ -490,6 +587,38 @@ const playerArtworkOpacity = 0.4;
window.playerIsInitialised = true; window.playerIsInitialised = true;
} }
syncVolumeUI() {
Howler.volume(this.volume);
this.UI.controls.volume.value = String(Math.round(this.volume * 100));
const muted = this.volume <= 0;
this.UI.controls.volumeOn.classList.toggle("hidden", muted);
this.UI.controls.volumeOff.classList.toggle("hidden", !muted);
this.UI.controls.mute.setAttribute(
"aria-label",
muted ? "Unmute" : "Mute"
);
this.UI.controls.mute.setAttribute(
"aria-pressed",
muted ? "true" : "false"
);
try {
localStorage.setItem("player-volume", String(this.volume));
} catch (e) {
/* ignore */
}
}
setVolume(volume) {
if (!Number.isFinite(volume)) return;
this.volume = Math.min(Math.max(volume, 0), 1);
if (this.volume > 0) this.lastVolume = this.volume;
this.syncVolumeUI();
}
toggleMute() {
this.setVolume(this.volume > 0 ? 0 : this.lastVolume || 1);
}
setminimised(minimised) { setminimised(minimised) {
this.minimised = minimised; this.minimised = minimised;
try { try {