Add a volume slider with mute toggle to the player controls
This commit is contained in:
1 file changed
+131
-2
+131
-2
@@ -104,7 +104,8 @@ 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">
|
||||||
|
<div class="mt-2 flex flex-row items-center space-x-4 text-2xl">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id="player:controls:previous"
|
id="player:controls:previous"
|
||||||
@@ -134,6 +135,42 @@ const playerArtworkOpacity = 0.4;
|
|||||||
><Icon name="fa7-solid:forward" class="pointer-events-none" />
|
><Icon name="fa7-solid:forward" class="pointer-events-none" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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">
|
<div class="flex flex-grow flex-col text-sm">
|
||||||
<span
|
<span
|
||||||
id="player:metadata:title"
|
id="player:metadata:title"
|
||||||
@@ -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 {
|
||||||
|
|||||||
Reference in new issue
Block a user