Add a volume slider with mute toggle to the player controls
This commit is contained in:
1 file changed
+159
-30
+159
-30
@@ -104,35 +104,72 @@ const playerArtworkOpacity = 0.4;
|
||||
<div
|
||||
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">
|
||||
<button
|
||||
type="button"
|
||||
id="player:controls:previous"
|
||||
aria-label="Previous track"
|
||||
class="rounded-full bg-transparent p-0 hover:cursor-pointer"
|
||||
><Icon name="fa7-solid:backward" class="pointer-events-none" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="player:controls:play"
|
||||
aria-label="Play"
|
||||
class="rounded-full bg-transparent p-0 hover:cursor-pointer"
|
||||
><Icon name="fa7-solid:play" class="pointer-events-none" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="player:controls:pause"
|
||||
aria-label="Pause"
|
||||
class="hidden rounded-full bg-transparent p-0 hover:cursor-pointer"
|
||||
><Icon name="fa7-solid:pause" class="pointer-events-none" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="player:controls:next"
|
||||
aria-label="Next track"
|
||||
class="rounded-full bg-transparent p-0 hover:cursor-pointer"
|
||||
><Icon name="fa7-solid:forward" class="pointer-events-none" />
|
||||
</button>
|
||||
<div class="m-0 flex grow-0 flex-col p-0">
|
||||
<div class="mt-2 flex flex-row items-center space-x-4 text-2xl">
|
||||
<button
|
||||
type="button"
|
||||
id="player:controls:previous"
|
||||
aria-label="Previous track"
|
||||
class="rounded-full bg-transparent p-0 hover:cursor-pointer"
|
||||
><Icon name="fa7-solid:backward" class="pointer-events-none" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="player:controls:play"
|
||||
aria-label="Play"
|
||||
class="rounded-full bg-transparent p-0 hover:cursor-pointer"
|
||||
><Icon name="fa7-solid:play" class="pointer-events-none" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="player:controls:pause"
|
||||
aria-label="Pause"
|
||||
class="hidden rounded-full bg-transparent p-0 hover:cursor-pointer"
|
||||
><Icon name="fa7-solid:pause" class="pointer-events-none" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="player:controls:next"
|
||||
aria-label="Next track"
|
||||
class="rounded-full bg-transparent p-0 hover:cursor-pointer"
|
||||
><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 class="flex flex-grow flex-col text-sm">
|
||||
<span
|
||||
@@ -261,6 +298,40 @@ const playerArtworkOpacity = 0.4;
|
||||
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 */
|
||||
.player-full {
|
||||
transition:
|
||||
@@ -327,6 +398,9 @@ const playerArtworkOpacity = 0.4;
|
||||
this.minimised = window.player?.minimised ?? false;
|
||||
// Seek fraction requested before anything was loaded.
|
||||
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
|
||||
|
||||
@@ -340,7 +414,11 @@ const playerArtworkOpacity = 0.4;
|
||||
pause: $("player:controls:pause"),
|
||||
previous: $("player:controls:previous"),
|
||||
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: {
|
||||
current: $("player:times:current"),
|
||||
@@ -423,6 +501,25 @@ const playerArtworkOpacity = 0.4;
|
||||
e.stopPropagation();
|
||||
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) {
|
||||
event.stopPropagation();
|
||||
@@ -490,6 +587,38 @@ const playerArtworkOpacity = 0.4;
|
||||
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) {
|
||||
this.minimised = minimised;
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user