Initial design based off original website, some things still to do

This commit is contained in:
2025-08-08 17:48:11 +09:30
parent d38c8fc694
commit ae3c38be17
185 changed files with 6799 additions and 1877 deletions

425
src/components/Player.astro Normal file
View File

@@ -0,0 +1,425 @@
---
import { Icon } from "astro-icon/components";
interface Props {
height?: string;
}
const { height = "h-28" } = Astro.props as Props;
---
<div
id="player"
class:list={["fixed right-0 bottom-0 left-0 z-50 bg-black shadow-lg", height]}
transition:persist=""
transition:name="player"
transition:animate="none"
>
<div
id="player:time:progress"
class="relative top-0 right-0 left-0 m-0 h-2 w-full bg-gray-900 p-0"
>
<div
id="player:time:progress:buffered"
class="absolute top-0 left-0 m-0 h-full bg-gray-700 p-0"
>
</div>
<div
id="player:time:progress:played"
class="bg-primary absolute top-0 left-0 m-0 h-full p-0"
>
</div>
</div>
<div class="p-2 text-white">
<div class="grid grid-cols-2 text-xs">
<span id="player:time:current" class="col-span-1 text-left"></span>
<span id="player:time:total" class="col-span-1 text-right"></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:backward" />
</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:forward" />
</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>
<div class:list={[height]}></div>
<script src="/js/howler.min.js" is:inline></script>
<!-- Retrieve elements -->
<script is:inline>
// numbers
const player_playerTimeCurrent = document.getElementById(
"player:time:current"
);
const player_playerTimeTotal = document.getElementById("player:time:total");
// progress bars
const player_playerTimeProgress = document.getElementById(
"player:time:progress"
);
const player_playerTimeProgressPlayed = document.getElementById(
"player:time:progress:played"
);
const player_playerTimeProgressBuffered = document.getElementById(
"player:time:progress:buffered"
);
// metadata
const player_playerMetadataTitle = document.getElementById(
"player:metadata:title"
);
const player_playerMetadataSubtitle = document.getElementById(
"player:metadata:subtitle"
);
const player_playerMetadataExtra = document.getElementById(
"player:metadata:extra"
);
// controls
const player_playerControlsPlay = document.getElementById(
"player:controls:play"
);
const player_playerControlsPause = document.getElementById(
"player:controls:pause"
);
const player_playerControlsForward = document.getElementById(
"player:controls:forward"
);
const player_playerControlsBackward = document.getElementById(
"player:controls:backward"
);
</script>
<!-- Add listeners -->
<script is:inline>
if (player_playerTimeProgress) {
player_playerTimeProgress.addEventListener("click", function (event) {
const x = event.offsetX;
const percentage =
x / player_playerTimeProgress.getBoundingClientRect().width;
player_seekTrack(percentage);
});
}
if (player_playerControlsPlay) {
player_playerControlsPlay.addEventListener("click", function (event) {
player_playPause();
});
}
if (player_playerControlsPause) {
player_playerControlsPause.addEventListener("click", function (event) {
player_playPause();
});
}
if (player_playerControlsForward) {
player_playerControlsForward.addEventListener("click", function (event) {
player_playForward();
});
}
if (player_playerControlsBackward) {
player_playerControlsBackward.addEventListener("click", function (event) {
player_playBackward();
});
}
</script>
<!-- Playing control logic -->
<script is:inline>
function player_seekTrack(percentage) {
const currentTrack = player_getCurrentTrack();
if (currentTrack) {
const duration = currentTrack.howl.duration();
const seconds = duration * percentage;
const ID = player_getCurrentTrackID();
currentTrack.howl.seek(seconds, ID);
}
}
function player_playPause() {
const currentTrack = player_getCurrentTrack();
if (!currentTrack) {
return;
}
const ID = player_getCurrentTrackID();
if (currentTrack.howl.playing(ID)) {
currentTrack.howl.pause(ID);
return;
}
// Case 2: Valid ID but not playing
if (typeof ID === "number" && ID >= 0) {
currentTrack.howl.play(ID);
return;
}
// Case 3: No valid ID yet (first play)
player_nextIndex++;
player_currentID = currentTrack.howl.play();
}
function player_playForward() {
player_playNext();
}
function player_playBackward() {
player_playPrevious();
}
</script>
<!-- Handle UI -->
<script is:inline>
function player_formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
// Pad with 0s if less than 10
const paddedMinutes = String(minutes).padStart(2, "0");
const paddedSecs = String(secs).padStart(2, "0");
return `${paddedMinutes}:${paddedSecs}`;
}
function player_getBufferedAmount(track) {
const audioNode = track._sounds[0]._node;
if (audioNode && audioNode.buffered.length) {
// Get the highest buffered point
return audioNode.buffered.end(0);
}
return 0;
}
function player_updateProgressAndDuration(
current,
total,
progress,
buffered
) {
if (player_playerTimeCurrent) {
player_playerTimeCurrent.textContent = player_formatTime(current);
}
if (player_playerTimeTotal) {
player_playerTimeTotal.textContent = player_formatTime(total);
}
if (player_playerTimeProgressPlayed) {
player_playerTimeProgressPlayed.style.width = `${(progress * 100).toString()}%`;
}
if (player_playerTimeProgressBuffered) {
let bufferedPercentage = (total / buffered) * 100;
if (Math.abs(100 - bufferedPercentage) < 1) {
bufferedPercentage = 100;
}
player_playerTimeProgressBuffered.style.width = `${bufferedPercentage.toString()}%`;
}
}
function player_updateMetadata(title, subtitle, extra, artist) {
if (player_playerMetadataTitle) {
player_playerMetadataTitle.textContent = title;
}
if (player_playerMetadataSubtitle) {
player_playerMetadataSubtitle.textContent = subtitle;
}
if (player_playerMetadataExtra) {
player_playerMetadataExtra.textContent = extra;
}
}
function player_updatePlayPause() {
if (player_playerControlsPlay && player_playerControlsPause) {
const currentTrack = player_getCurrentTrack();
if (currentTrack) {
if (currentTrack.howl.playing()) {
player_playerControlsPause.classList.remove("hidden");
player_playerControlsPlay.classList.add("hidden");
} else {
player_playerControlsPause.classList.add("hidden");
player_playerControlsPlay.classList.remove("hidden");
}
}
}
}
function player_updateUIElements() {
const currentTrack = player_getCurrentTrack();
if (currentTrack) {
// progress bar and times
const current = currentTrack.howl.seek();
const duration = currentTrack.howl.duration();
const progress = current / duration;
const buffered = player_getBufferedAmount(currentTrack.howl);
player_updateProgressAndDuration(current, duration, progress, buffered);
// metadata
const title = currentTrack.metadata.title;
const subtitle = currentTrack.metadata.subtitle || "";
const extra = currentTrack.metadata.extra || "";
const artist = currentTrack.metadata.artist || "";
player_updateMetadata(title, subtitle, extra, artist);
player_updatePlayPause();
}
requestAnimationFrame(player_updateUIElements);
}
requestAnimationFrame(player_updateUIElements);
</script>
<!-- Main logic -->
<script is:inline>
const player_playlist = [];
let player_currentID = -999;
let player_nextIndex = 0;
function player_getCurrentTrack() {
if (player_getPlaylistSize() === 0) {
return null;
}
return player_playlist[player_getCurrentTrackIndex()];
}
function player_getCurrentTrackNumber() {
return player_nextIndex;
}
function player_getCurrentTrackIndex() {
if (player_nextIndex === 0) {
return player_nextIndex;
}
return player_nextIndex - 1;
}
function player_getCurrentTrackID() {
return player_currentID;
}
function player_getPlaylistSize() {
return player_playlist.length;
}
function player_playNext() {
if (player_getPlaylistSize() === 0) {
// the playlist is empty, just exit
return;
}
const currentTrack = player_getCurrentTrack();
const currentID = player_getCurrentTrackID();
if (currentTrack && currentTrack.howl.playing()) {
currentTrack.howl.stop(currentID);
currentTrack.howl.seek(0, currentID);
}
if (player_nextIndex >= player_getPlaylistSize()) {
// wrap around
player_nextIndex = 0;
}
const newTrack = player_playlist[player_nextIndex];
player_nextIndex++;
player_currentID = newTrack.howl.play();
}
function player_playPrevious() {
if (player_getPlaylistSize() === 0) {
// the playlist is empty, just exit
return;
}
const currentTrack = player_getCurrentTrack();
const ID = player_getCurrentTrackID();
if (
currentTrack &&
currentTrack.howl.playing() &&
currentTrack.howl.seek() >= 2
) {
// there's currently a track playing and we're more than two seconds into it
// play it from the start rather than going back to the previous
currentTrack.howl.seek(0, ID);
return;
}
if (currentTrack && currentTrack.howl.playing(ID)) {
currentTrack.howl.stop(ID);
}
// Rewind index
player_nextIndex -= 2;
if (player_nextIndex < 0) {
player_nextIndex = player_getPlaylistSize() - 1;
}
const newTrack = player_playlist[player_nextIndex];
player_nextIndex++;
player_currentID = newTrack.howl.play();
}
</script>
<!-- Player utilities -->
<script is:inline>
function player_constructTrack(trackData) {
trackData.howl = new Howl({
src: [`/audio/${trackData.src}`],
html5: true,
preload: "metadata",
volume: 1,
onend: player_playNext
});
return trackData;
}
function player_addToPlaylist(track, autoPlay = true) {
const currentTrack = player_getCurrentTrack();
if (
currentTrack &&
currentTrack.metadata.title === track.metadata.title &&
currentTrack.metadata.subtitle === track.metadata.subtitle
) {
player_playPause();
return;
}
if (autoPlay) {
player_playlist.splice(player_nextIndex, 0, track);
player_playNext();
} else {
player_playlist.push(track);
}
}
</script>