Add minimised player option, remove transition on about image due to transition bug, make about image bigger
Build and Deploy to Web Server / deploy (push) Successful in 6m21s
Build and Deploy to Web Server / deploy (push) Successful in 6m21s
This commit is contained in:
3 files changed
+442
-119
No files matched your search
+440
-115
@@ -39,75 +39,250 @@ const playerArtworkBlur = "10px";
|
||||
const playerArtworkOpacity = 0.4;
|
||||
---
|
||||
|
||||
<!-- Morphing player shell: bar <-> circle, layers crossfade inside -->
|
||||
<div
|
||||
id="player"
|
||||
id="player-shell"
|
||||
class:list={[
|
||||
"fixed right-0 bottom-0 left-0 z-100 overflow-hidden bg-black shadow-lg/75 md:right-3 md:bottom-3 md:left-3 md:rounded",
|
||||
"group fixed z-100 overflow-hidden bg-black shadow-lg/75",
|
||||
height
|
||||
]}
|
||||
transition:persist=""
|
||||
transition:name="player"
|
||||
transition:animate="none"
|
||||
>
|
||||
<div class="absolute inset-0 -z-90 overflow-hidden">
|
||||
<div
|
||||
id="player:artwork:background"
|
||||
class="animate-bg-perlin absolute top-0 right-0 bottom-0 left-0 -z-90"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="player:time:progress"
|
||||
class="relative top-0 right-0 left-0 m-0 h-2 w-full rounded-t p-0 hover:cursor-pointer"
|
||||
>
|
||||
<div
|
||||
id="player:time:progress:background"
|
||||
class="absolute top-0 left-0 m-0 h-full w-full bg-gray-900 p-0 opacity-50"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
id="player:time:progress:buffered"
|
||||
class="absolute top-0 left-0 m-0 h-full bg-gray-700 p-0 opacity-50"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
id="player:time:progress:played"
|
||||
class="from-primary to-primary-50 absolute top-0 left-0 m-0 h-full bg-gradient-to-r p-0 opacity-100"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-2 pt-2 pb-2 text-white md:px-3 md:pb-3">
|
||||
<div class="grid grid-cols-2 text-xs">
|
||||
<span id="player:times:current" class="col-span-1 text-left">0:00</span>
|
||||
<span id="player:times:duration" class="col-span-1 text-right">0:00</span>
|
||||
</div>
|
||||
<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">
|
||||
<span>
|
||||
<Icon name="fa7-solid:backward" id="player:controls:previous" />
|
||||
</span>
|
||||
<span>
|
||||
<Icon name="fa7-solid:play" id="player:controls:play" />
|
||||
<Icon
|
||||
name="fa7-solid:pause"
|
||||
id="player:controls:pause"
|
||||
class="hidden"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<Icon name="fa7-solid:forward" id="player:controls:next" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-grow flex-col text-sm">
|
||||
<span id="player:metadata:title" class="w-full text-right font-semibold"
|
||||
></span>
|
||||
<span id="player:metadata:subtitle" class="w-full text-right"></span>
|
||||
<span id="player:metadata:extra" class="w-full text-right"></span>
|
||||
<!-- Full-bar layer -->
|
||||
<div class="player-full absolute inset-0">
|
||||
<!-- Blurred artwork backdrop (static: animations restart on view transitions) -->
|
||||
<div class="absolute inset-0 -z-90 overflow-hidden">
|
||||
<div
|
||||
id="player:artwork:background"
|
||||
class="absolute top-0 right-0 bottom-0 left-0 -z-90"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="player:time:progress"
|
||||
class="relative top-0 right-0 left-0 m-0 h-2 w-full rounded-t p-0 hover:cursor-pointer"
|
||||
>
|
||||
<div
|
||||
id="player:time:progress:background"
|
||||
class="absolute top-0 left-0 m-0 h-full w-full bg-gray-900 p-0 opacity-50"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
id="player:time:progress:buffered"
|
||||
class="absolute top-0 left-0 m-0 h-full bg-gray-700 p-0 opacity-50"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
id="player:time:progress:played"
|
||||
class="from-primary to-primary-50 absolute top-0 left-0 m-0 h-full bg-gradient-to-r p-0 opacity-100"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-2 pt-2 pb-2 text-white md:px-3 md:pb-3">
|
||||
<div class="grid grid-cols-[1fr_auto_1fr] items-center text-xs">
|
||||
<span id="player:times:current" class="col-span-1 text-left">0:00</span>
|
||||
<button
|
||||
id="player:controls:minimise"
|
||||
type="button"
|
||||
title="Minimise player"
|
||||
aria-label="Minimise player"
|
||||
class="col-span-1 rounded-full px-3 py-1 text-gray-400 hover:cursor-pointer hover:text-white"
|
||||
>
|
||||
<Icon name="fa7-solid:chevron-down" class="pointer-events-none" />
|
||||
</button>
|
||||
<span id="player:times:duration" class="col-span-1 text-right"
|
||||
>0:00</span
|
||||
>
|
||||
</div>
|
||||
<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">
|
||||
<span>
|
||||
<Icon name="fa7-solid:backward" id="player:controls:previous" />
|
||||
</span>
|
||||
<span>
|
||||
<Icon name="fa7-solid:play" id="player:controls:play" />
|
||||
<Icon
|
||||
name="fa7-solid:pause"
|
||||
id="player:controls:pause"
|
||||
class="hidden"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<Icon name="fa7-solid:forward" id="player:controls:next" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-grow flex-col text-sm">
|
||||
<span
|
||||
id="player:metadata:title"
|
||||
class="w-full text-right font-semibold"></span>
|
||||
<span id="player:metadata:subtitle" class="w-full text-right"></span>
|
||||
<span id="player:metadata:extra" class="w-full text-right"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Circle face: artwork + progress ring -->
|
||||
<div class="player-face absolute inset-0" aria-hidden="true">
|
||||
<div class="absolute inset-0 rounded-full bg-black ring-1 ring-white/10">
|
||||
</div>
|
||||
<img
|
||||
id="player:face:artwork"
|
||||
alt=""
|
||||
class="absolute inset-[5px] h-[calc(100%-10px)] w-[calc(100%-10px)] rounded-full bg-gray-900 object-cover"
|
||||
/>
|
||||
<div
|
||||
id="player:face:fallback"
|
||||
class="absolute inset-[5px] flex items-center justify-center rounded-full bg-gray-900 text-xl text-gray-400"
|
||||
>
|
||||
<Icon name="fa7-solid:music" class="pointer-events-none" />
|
||||
</div>
|
||||
<svg
|
||||
class="absolute inset-0 h-full w-full -rotate-90"
|
||||
viewBox="0 0 64 64"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle
|
||||
cx="32"
|
||||
cy="32"
|
||||
r="29"
|
||||
stroke="rgba(255,255,255,0.15)"
|
||||
stroke-width="4"></circle>
|
||||
<circle
|
||||
id="player:face:progress:buffered"
|
||||
cx="32"
|
||||
cy="32"
|
||||
r="29"
|
||||
stroke="rgba(255,255,255,0.35)"
|
||||
stroke-width="4"
|
||||
stroke-linecap="round"
|
||||
stroke-dasharray="182.2"
|
||||
stroke-dashoffset="182.2"></circle>
|
||||
<circle
|
||||
id="player:face:progress:played"
|
||||
cx="32"
|
||||
cy="32"
|
||||
r="29"
|
||||
stroke="#ff2d00"
|
||||
stroke-width="4"
|
||||
stroke-linecap="round"
|
||||
stroke-dasharray="182.2"
|
||||
stroke-dashoffset="182.2"></circle>
|
||||
</svg>
|
||||
<button
|
||||
id="player:face:controls:toggle"
|
||||
type="button"
|
||||
title="Play / pause"
|
||||
aria-label="Play / pause"
|
||||
class="absolute top-1/2 left-1/2 flex h-8 w-8 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-black/55 text-white opacity-100 backdrop-blur-sm hover:cursor-pointer hover:bg-black/70 md:h-9 md:w-9 md:opacity-0 md:group-focus-within:opacity-100 md:group-hover:opacity-100"
|
||||
>
|
||||
<Icon
|
||||
name="fa7-solid:play"
|
||||
id="player:face:controls:play"
|
||||
class="pointer-events-none text-sm drop-shadow"
|
||||
/>
|
||||
<Icon
|
||||
name="fa7-solid:pause"
|
||||
id="player:face:controls:pause"
|
||||
class="pointer-events-none hidden text-sm drop-shadow"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class:list={[height, "bg-gray-900"]}></div>
|
||||
<div id="player:spacer" class:list={[height, "bg-gray-900"]}></div>
|
||||
|
||||
<style>
|
||||
#player-shell {
|
||||
--ease-slide: cubic-bezier(0.22, 1, 0.36, 1);
|
||||
--ease-round: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
border-radius: 0;
|
||||
transition:
|
||||
left 0.55s var(--ease-slide),
|
||||
width 0.55s var(--ease-slide),
|
||||
bottom 0.55s var(--ease-slide),
|
||||
height 0.55s var(--ease-slide),
|
||||
border-radius 0.6s var(--ease-round);
|
||||
}
|
||||
#player-shell.player--minimised {
|
||||
left: calc(100% - 9rem);
|
||||
width: 8rem;
|
||||
bottom: 1rem;
|
||||
height: 8rem;
|
||||
border-radius: 4rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
#player-shell {
|
||||
left: 0.75rem;
|
||||
width: calc(100% - 1.5rem);
|
||||
bottom: 0.75rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
#player-shell.player--minimised {
|
||||
left: calc(100% - 9.5rem);
|
||||
width: 8rem;
|
||||
bottom: 1.5rem;
|
||||
height: 8rem;
|
||||
}
|
||||
}
|
||||
|
||||
#player\:spacer.player-spacer--hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Outgoing layer fades fast, incoming fades in late */
|
||||
.player-full {
|
||||
transition:
|
||||
opacity 0.25s ease 0.3s,
|
||||
visibility 0s linear 0s;
|
||||
}
|
||||
#player-shell.player--minimised .player-full {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 0.15s ease 0s,
|
||||
visibility 0s linear 0.15s;
|
||||
}
|
||||
.player-face {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 0.15s ease 0s,
|
||||
visibility 0s linear 0.15s;
|
||||
}
|
||||
#player-shell.player--minimised .player-face {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
transition:
|
||||
opacity 0.25s ease 0.3s,
|
||||
visibility 0s linear 0.3s;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#player-shell,
|
||||
.player-full,
|
||||
.player-face {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* No morph on first paint */
|
||||
#player-shell.player--no-motion,
|
||||
#player-shell.player--no-motion .player-full,
|
||||
#player-shell.player--no-motion .player-face {
|
||||
transition: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline src="/js/howler.min.js"></script>
|
||||
<script
|
||||
@@ -119,34 +294,57 @@ const playerArtworkOpacity = 0.4;
|
||||
* Includes all methods for playing, skipping, updating the display, etc.
|
||||
* @param {Array} playlist Array of objects with playlist song details ({title, file, howl}).
|
||||
*/
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
class Player {
|
||||
constructor(playlist) {
|
||||
this.playlist = window.player?.playlist ?? playlist;
|
||||
this.index = window.player?.index ?? 0;
|
||||
this.minimised = window.player?.minimised ?? false;
|
||||
|
||||
this.RING_C = 182.2; // 2 * PI * 29, must match dasharray in markup
|
||||
|
||||
this.UI = {
|
||||
root: $("player-shell"),
|
||||
spacer: $("player:spacer"),
|
||||
full: document.querySelector("#player-shell .player-full"),
|
||||
face: document.querySelector("#player-shell .player-face"),
|
||||
controls: {
|
||||
play: document.getElementById("player:controls:play"),
|
||||
pause: document.getElementById("player:controls:pause"),
|
||||
previous: document.getElementById("player:controls:previous"),
|
||||
next: document.getElementById("player:controls:next")
|
||||
play: $("player:controls:play"),
|
||||
pause: $("player:controls:pause"),
|
||||
previous: $("player:controls:previous"),
|
||||
next: $("player:controls:next"),
|
||||
minimise: $("player:controls:minimise")
|
||||
},
|
||||
times: {
|
||||
current: document.getElementById("player:times:current"),
|
||||
duration: document.getElementById("player:times:duration")
|
||||
current: $("player:times:current"),
|
||||
duration: $("player:times:duration")
|
||||
},
|
||||
metadata: {
|
||||
title: document.getElementById("player:metadata:title"),
|
||||
subtitle: document.getElementById("player:metadata:subtitle"),
|
||||
extra: document.getElementById("player:metadata:extra")
|
||||
title: $("player:metadata:title"),
|
||||
subtitle: $("player:metadata:subtitle"),
|
||||
extra: $("player:metadata:extra")
|
||||
},
|
||||
progress: {
|
||||
bar: document.getElementById("player:time:progress"),
|
||||
buffered: document.getElementById("player:time:progress:buffered"),
|
||||
played: document.getElementById("player:time:progress:played")
|
||||
bar: $("player:time:progress"),
|
||||
buffered: $("player:time:progress:buffered"),
|
||||
played: $("player:time:progress:played")
|
||||
},
|
||||
artwork: {
|
||||
background: document.getElementById("player:artwork:background")
|
||||
background: $("player:artwork:background")
|
||||
},
|
||||
faceArtwork: {
|
||||
img: $("player:face:artwork"),
|
||||
fallback: $("player:face:fallback")
|
||||
},
|
||||
faceProgress: {
|
||||
buffered: $("player:face:progress:buffered"),
|
||||
played: $("player:face:progress:played")
|
||||
},
|
||||
faceControls: {
|
||||
toggle: $("player:face:controls:toggle"),
|
||||
play: $("player:face:controls:play"),
|
||||
pause: $("player:face:controls:pause")
|
||||
}
|
||||
};
|
||||
|
||||
@@ -158,38 +356,161 @@ const playerArtworkOpacity = 0.4;
|
||||
Math.round(playlist[this.index].duration)
|
||||
);
|
||||
|
||||
if (playlist[this.index].metadata.artwork) {
|
||||
this.UI.artwork.background.style.backgroundImage = `url('${playlist[this.index].metadata.artwork.src}')`;
|
||||
this.UI.artwork.background.style.backgroundSize = "cover";
|
||||
this.UI.artwork.background.style.backgroundPosition = "center";
|
||||
this.UI.artwork.background.style.filter = `blur(${playerArtworkBlur})`;
|
||||
this.UI.artwork.background.style.opacity = playerArtworkOpacity;
|
||||
} else {
|
||||
this.UI.artwork.background.style.backgroundImage = "";
|
||||
this.UI.artwork.background.style.filter = "";
|
||||
this.UI.artwork.background.style.opacity = "0";
|
||||
}
|
||||
this.updateArtwork(playlist[this.index]);
|
||||
this.syncPlayPauseUI(false);
|
||||
|
||||
this.UI.controls.play.addEventListener("click", function () {
|
||||
try {
|
||||
const stored = localStorage.getItem("player-minimised");
|
||||
if (stored !== null && window.player === undefined) {
|
||||
this.minimised = stored === "1";
|
||||
}
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
// No morph on first paint.
|
||||
this.UI.root.classList.add("player--no-motion");
|
||||
this.applyminimised();
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
this.UI.root?.classList.remove("player--no-motion");
|
||||
});
|
||||
});
|
||||
|
||||
this.UI.controls.play.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
window.player.play();
|
||||
});
|
||||
this.UI.controls.pause.addEventListener("click", function () {
|
||||
this.UI.controls.pause.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
window.player.pause();
|
||||
});
|
||||
this.UI.controls.previous.addEventListener("click", function () {
|
||||
this.UI.controls.previous.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
window.player.skip("prev");
|
||||
});
|
||||
this.UI.controls.next.addEventListener("click", function () {
|
||||
this.UI.controls.next.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
window.player.skip("next");
|
||||
});
|
||||
this.UI.controls.minimise.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
window.player.setminimised(true);
|
||||
});
|
||||
|
||||
this.UI.progress.bar.addEventListener("click", function (event) {
|
||||
event.stopPropagation();
|
||||
window.player.seek(event.clientX / window.innerWidth);
|
||||
});
|
||||
|
||||
// Tap the circle to expand (not the play button).
|
||||
const expand = (e) => {
|
||||
if (!window.player.minimised) return;
|
||||
if (
|
||||
e.target instanceof Element &&
|
||||
this.UI.faceControls.toggle.contains(e.target)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (e.type === "keydown" && e.key !== "Enter" && e.key !== " ") return;
|
||||
e.preventDefault();
|
||||
window.player.setminimised(false);
|
||||
};
|
||||
this.UI.root.addEventListener("click", expand);
|
||||
this.UI.root.addEventListener("keydown", expand);
|
||||
this.UI.faceControls.toggle.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const sound = window.player.playlist[window.player.index].howl;
|
||||
if (sound && sound.playing()) {
|
||||
window.player.pause();
|
||||
} else {
|
||||
window.player.play();
|
||||
}
|
||||
});
|
||||
|
||||
window.playerIsInitialised = true;
|
||||
}
|
||||
|
||||
setminimised(minimised) {
|
||||
this.minimised = minimised;
|
||||
try {
|
||||
localStorage.setItem("player-minimised", minimised ? "1" : "0");
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
this.applyminimised();
|
||||
}
|
||||
|
||||
// One class drives the morph; re-applying is a no-op.
|
||||
applyminimised() {
|
||||
if (!this.UI.root) return;
|
||||
if (this.minimised) {
|
||||
this.UI.root.classList.add("player--minimised");
|
||||
this.UI.root.setAttribute("role", "button");
|
||||
this.UI.root.setAttribute("tabindex", "0");
|
||||
this.UI.root.setAttribute(
|
||||
"title",
|
||||
"Expand player — click to restore full size"
|
||||
);
|
||||
this.UI.root.setAttribute(
|
||||
"aria-label",
|
||||
"Expand player — click to restore full size"
|
||||
);
|
||||
this.UI.spacer?.classList.add("player-spacer--hidden");
|
||||
this.UI.full?.setAttribute("aria-hidden", "true");
|
||||
this.UI.face?.setAttribute("aria-hidden", "false");
|
||||
} else {
|
||||
this.UI.root.classList.remove("player--minimised");
|
||||
this.UI.root.removeAttribute("role");
|
||||
this.UI.root.removeAttribute("tabindex");
|
||||
this.UI.root.removeAttribute("title");
|
||||
this.UI.root.removeAttribute("aria-label");
|
||||
this.UI.spacer?.classList.remove("player-spacer--hidden");
|
||||
this.UI.full?.removeAttribute("aria-hidden");
|
||||
this.UI.face?.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
}
|
||||
|
||||
updateArtwork(data) {
|
||||
const artworkSrc = data.metadata.artwork?.src;
|
||||
const bg = this.UI.artwork.background;
|
||||
const img = this.UI.faceArtwork.img;
|
||||
const fallback = this.UI.faceArtwork.fallback;
|
||||
|
||||
bg.style.backgroundImage = artworkSrc ? `url('${artworkSrc}')` : "";
|
||||
bg.style.backgroundSize = "cover";
|
||||
bg.style.backgroundPosition = "center";
|
||||
bg.style.filter = artworkSrc ? `blur(${playerArtworkBlur})` : "";
|
||||
bg.style.opacity = artworkSrc ? playerArtworkOpacity : "0";
|
||||
|
||||
if (!img) return;
|
||||
if (artworkSrc) {
|
||||
const alt =
|
||||
data.metadata.artwork?.alt || data.metadata.title || "Track artwork";
|
||||
img.src = artworkSrc;
|
||||
img.style.display = "";
|
||||
img.alt = typeof alt === "string" ? alt : "";
|
||||
if (fallback) fallback.style.display = "none";
|
||||
} else {
|
||||
img.removeAttribute("src");
|
||||
img.style.display = "none";
|
||||
img.alt = "";
|
||||
if (fallback) fallback.style.display = "";
|
||||
}
|
||||
}
|
||||
|
||||
syncPlayPauseUI(isPlaying) {
|
||||
for (const set of [this.UI.controls, this.UI.faceControls]) {
|
||||
set.pause.classList.toggle("hidden", !isPlaying);
|
||||
set.play.classList.toggle("hidden", isPlaying);
|
||||
}
|
||||
}
|
||||
|
||||
setRingProgress(el, per) {
|
||||
if (!el) return;
|
||||
const clamped = Math.min(1, Math.max(0, per || 0));
|
||||
el.style.strokeDashoffset = String(this.RING_C * (1 - clamped));
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a song in the playlist.
|
||||
* @param {Number} index Index of the song in the playlist (leave empty to play the first or current).
|
||||
@@ -217,21 +538,14 @@ const playerArtworkOpacity = 0.4;
|
||||
// Start updating the progress of the track.
|
||||
requestAnimationFrame(this.step.bind(this));
|
||||
|
||||
this.UI.controls.pause.classList.remove("hidden");
|
||||
this.UI.controls.play.classList.add("hidden");
|
||||
this.syncPlayPauseUI(true);
|
||||
},
|
||||
// onload: () => {
|
||||
// loading.style.display = "none";
|
||||
// },
|
||||
onend: () => {
|
||||
this.skip("next");
|
||||
},
|
||||
onpause: () => {
|
||||
this.UI.controls.pause.classList.add("hidden");
|
||||
this.UI.controls.play.classList.remove("hidden");
|
||||
this.syncPlayPauseUI(false);
|
||||
},
|
||||
// onstop: () => {
|
||||
// },
|
||||
onseek: () => {
|
||||
// Start updating the progress of the track.
|
||||
requestAnimationFrame(this.step.bind(this));
|
||||
@@ -250,18 +564,10 @@ const playerArtworkOpacity = 0.4;
|
||||
Math.round(data.duration)
|
||||
);
|
||||
this.UI.progress.buffered.style.width = "0%";
|
||||
this.setRingProgress(this.UI.faceProgress.buffered, 0);
|
||||
this.setRingProgress(this.UI.faceProgress.played, 0);
|
||||
|
||||
if (data.metadata.artwork) {
|
||||
this.UI.artwork.background.style.backgroundImage = `url('${data.metadata.artwork.src}')`;
|
||||
this.UI.artwork.background.style.backgroundSize = "cover";
|
||||
this.UI.artwork.background.style.backgroundPosition = "center";
|
||||
this.UI.artwork.background.style.filter = `blur(${playerArtworkBlur})`;
|
||||
this.UI.artwork.background.style.opacity = playerArtworkOpacity;
|
||||
} else {
|
||||
this.UI.artwork.background.style.backgroundImage = "";
|
||||
this.UI.artwork.background.style.filter = "";
|
||||
this.UI.artwork.background.style.opacity = "0";
|
||||
}
|
||||
this.updateArtwork(data);
|
||||
|
||||
// Keep track of the index we are currently playing.
|
||||
this.index = index;
|
||||
@@ -276,8 +582,7 @@ const playerArtworkOpacity = 0.4;
|
||||
|
||||
sound.pause();
|
||||
|
||||
this.UI.controls.pause.classList.add("hidden");
|
||||
this.UI.controls.play.classList.remove("hidden");
|
||||
this.syncPlayPauseUI(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -316,6 +621,7 @@ const playerArtworkOpacity = 0.4;
|
||||
|
||||
// Reset progress.
|
||||
this.UI.progress.played.style.width = "0%";
|
||||
this.setRingProgress(this.UI.faceProgress.played, 0);
|
||||
this.UI.times.current.textContent = this.formatTime(0);
|
||||
|
||||
// Play the new track.
|
||||
@@ -345,10 +651,16 @@ const playerArtworkOpacity = 0.4;
|
||||
|
||||
var seek = sound.seek() || 0;
|
||||
this.UI.times.current.textContent = this.formatTime(Math.round(seek));
|
||||
this.UI.progress.played.style.width =
|
||||
((seek / sound.duration()) * 100 || 0) + "%";
|
||||
const playedPer = seek / sound.duration() || 0;
|
||||
this.UI.progress.played.style.width = playedPer * 100 + "%";
|
||||
this.setRingProgress(this.UI.faceProgress.played, playedPer);
|
||||
|
||||
this.UI.progress.buffered.style.width = `${this.getBufferedAmount(sound).bufferedPercent}%`;
|
||||
const bufferedPercent = this.getBufferedAmount(sound).bufferedPercent;
|
||||
this.UI.progress.buffered.style.width = `${bufferedPercent}%`;
|
||||
this.setRingProgress(
|
||||
this.UI.faceProgress.buffered,
|
||||
bufferedPercent / 100
|
||||
);
|
||||
|
||||
// If the sound is still playing, continue stepping.
|
||||
if (sound.playing()) {
|
||||
@@ -378,13 +690,14 @@ const playerArtworkOpacity = 0.4;
|
||||
|
||||
getBufferedAmount(sound) {
|
||||
if (sound._sounds.length > 0) {
|
||||
const audioNode = sound._sounds[0]._node; // The underlying HTML5 Audio element
|
||||
const audioNode = sound._sounds[0]._node; // Underlying HTML5 Audio element
|
||||
if (audioNode.buffered.length > 0) {
|
||||
const bufferedEnd = audioNode.buffered.end(0); // Time (seconds) where buffering ends
|
||||
const bufferedEnd = audioNode.buffered.end(0);
|
||||
const duration = audioNode.duration;
|
||||
const bufferedSeconds = bufferedEnd;
|
||||
const bufferedPercent = duration ? (bufferedEnd / duration) * 100 : 0;
|
||||
return { bufferedSeconds, bufferedPercent };
|
||||
return {
|
||||
bufferedSeconds: bufferedEnd,
|
||||
bufferedPercent: duration ? (bufferedEnd / duration) * 100 : 0
|
||||
};
|
||||
}
|
||||
}
|
||||
return { bufferedSeconds: 0, bufferedPercent: 0 };
|
||||
@@ -411,5 +724,17 @@ const playerArtworkOpacity = 0.4;
|
||||
|
||||
if (!window.playerIsInitialised) {
|
||||
window.player = new Player(initialQueue);
|
||||
} else {
|
||||
// View-transition restore: re-grab nodes and re-apply.
|
||||
try {
|
||||
const p = window.player;
|
||||
p.UI.root = $("player-shell");
|
||||
p.UI.spacer = $("player:spacer");
|
||||
p.UI.full = document.querySelector("#player-shell .player-full");
|
||||
p.UI.face = document.querySelector("#player-shell .player-face");
|
||||
p.applyminimised();
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -19,9 +19,8 @@ import { Image } from "astro:assets";
|
||||
<Image
|
||||
src={aboutImage}
|
||||
alt="A photo of Nathan conducting the Woodville Concert Band"
|
||||
class="mx-auto mb-8 size-96 rounded-full object-cover shadow-lg/75 transition ease-in-out md:hover:scale-150"
|
||||
transition:name="aboutImage"
|
||||
height={576}
|
||||
class="mx-auto mb-8 size-125 rounded-full object-cover shadow-lg/75 transition ease-in-out md:hover:scale-150"
|
||||
height={750}
|
||||
/>
|
||||
<SectionTitle class="py-8">Dr Nathan Cummins</SectionTitle>
|
||||
<H2>{site.tagline}</H2>
|
||||
|
||||
@@ -93,7 +93,6 @@ const tracks = (
|
||||
src={aboutImage}
|
||||
alt="Nathan conducting the Woodville Concert Band"
|
||||
class="mx-auto mt-8 size-96 rounded-full object-cover shadow-lg/75 transition ease-in-out md:hover:scale-150"
|
||||
transition:name="aboutImage"
|
||||
height={576}
|
||||
/>
|
||||
<span class="block w-full text-center"
|
||||
|
||||
Reference in new issue
Block a user