radio styling changes (add/fix mobile responsivity); WIP, player still does not function on iOS

This commit is contained in:
2025-07-17 11:30:14 -04:00
parent 3b2fac133a
commit 814bbbff85
2 changed files with 300 additions and 256 deletions

View File

@ -1,263 +1,268 @@
div > img { /* Universal box-sizing for consistency */
transition: 300ms; *,
} *::before,
div > img:hover { *::after {
opacity: 0.7; box-sizing: border-box;
cursor: pointer;
}
div {
box-sizing: border-box;
}
img {
width: 100%;
height: 100%;
}
.album-cover > img {
height: 100% !important;
width: 100% !important;
object-fit: cover;
aspect-ratio: 1;
}
p {
margin-top: 0;
margin-bottom: 0.3em;
} }
body { body {
font-family: "Mukta", sans-serif; font-family: "Mukta", sans-serif;
width: 100%; width: 100%;
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
min-width: 100vw; min-width: 100vw;
min-height: 100vh; min-height: 100vh;
/* background: linear-gradient(-45deg, #FFCDD2 50%, #B2EBF2 50%); */ /* background: linear-gradient(-45deg, #FFCDD2 50%, #B2EBF2 50%); */
}
div,
section {
box-sizing: border-box;
}
.c-containter {
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
width: 100%;
height: 30vh;
padding: 1em;
}
.station-tabs {
top: 4rem;
} }
/* Container for the player and album cover */
.music-container { .music-container {
display: flex; width: 800px; /* fixed desktop width */
justify-content: center; max-width: 90vw; /* prevent overflow on smaller screens */
align-items: center; height: 290px;
position: absolute; margin: 0 auto 120px auto; /* increased bottom margin */
/* top: 20%; overflow-x: visible; /* allow horizontal overflow if needed */
left: 30%; */ overflow-y: hidden;
width: 40%; position: relative;
max-width: 700px; display: flex;
box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, 0.3); justify-content: center;
max-height: 290px; align-items: center;
margin-bottom: 50px; box-sizing: border-box;
padding: 1rem;
box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, 0.3);
} }
/* Album cover section */
.album-cover { .album-cover {
flex: 1 0 30%; flex: 0 0 30%; /* Fixed ~30% width */
} max-width: 30%;
.album-cover img { height: 100%;
width: 100%; overflow: hidden; /* Prevent image spill */
height: 100%; min-width: 0; /* Fix flex overflow */
} }
.arrow { /* Album cover image */
position: absolute; .album-cover > img {
top: calc(50% - 2.5em); width: 100%;
background: rgba(255, 255, 255, 0.3); height: 100%;
border: 0; object-fit: cover;
width: 5em; aspect-ratio: 1;
height: 5em; transition: 300ms;
cursor: pointer;
}
.arrow:hover {
background: rgba(255, 255, 255, 0.5);
}
.arrow img {
display: block;
width: 20px;
margin: 0 auto;
}
.arrow.left {
left: -5em;
}
.arrow.right {
right: -5em;
} }
.album-cover > img:hover {
opacity: 0.7;
cursor: pointer;
}
/* Player info and controls */
.music-player { .music-player {
display: flex; flex: 1 1 70%; /* Take remaining ~70% */
flex-flow: column wrap; max-width: 70%;
justify-content: center; width: auto;
background: inherit; height: 100%; /* Match container height */
padding: 1em; padding: 1em;
text-align: center; text-align: center;
width: 500px; display: flex;
max-width: 500px; flex-direction: column;
justify-content: center;
background: inherit;
box-sizing: border-box;
min-width: 0; /* Fix flex overflow */
flex-shrink: 0; /* Prevent shrinking that hides content */
} }
/* Text styling */
.music-player__title { .music-player__title {
color: inherit; color: inherit;
font-size: 0.9rem; font-size: 0.9rem;
margin: 0 0 0.1em 0; margin: 0 0 0.1em 0;
} }
.music-player__header { .music-player__header {
color: inherit; color: inherit;
font-size: 1rem; font-size: 1rem;
margin: 1 0 0.1em 0; margin: 1 0 0.1em 0;
} }
.music-player__author { .music-player__author {
color: inherit; color: inherit;
font-size: 0.9rem; font-size: 0.9rem;
margin: 0 0 0.5em 0; margin: 0 0 0.5em 0;
} }
.music-player__genre { .music-player__genre {
color: inherit; color: inherit;
font-size: 0.9rem; font-size: 0.9rem;
margin: 0 0 0.5em 0; margin: 0 0 0.5em 0;
} }
.music-player__genre:before { .music-player__genre:before {
font-weight: bold; font-weight: bold;
content: "Genre: " content: "Genre: ";
} }
.station-tabs {
padding-top: 2%;
}
/* Music bar and progress */
.music-bar { .music-bar {
background: #efefef; background: #efefef;
stroke-width: 1; stroke-width: 1;
height: 8px; height: 8px;
width: 100%; width: 100%;
} cursor: pointer;
.music-bar:hover {
cursor: pointer;
} }
.music-bar #length { .music-bar #length {
width: 0%; width: 0%;
background: #2196F3; background: #2196F3;
height: 100%; height: 100%;
transition: width linear 200ms; transition: width linear 200ms;
} }
/* Time display */
.music-time { .music-time {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
} }
.music-time__last { .music-time__last {
margin-left: auto; margin-left: auto;
} }
/* Playback order icons */
.music-order { .music-order {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
} }
.music-order__shuffle, .music-order__loop {
width: 1.2em; .music-order__shuffle,
height: 1.2em; .music-order__loop {
opacity: 0.2; width: 1.2em;
margin: 0.3em 0; height: 1.2em;
opacity: 0.2;
margin: 0.3em 0;
} }
.music-order__shuffle.is-loop, .music-order__loop.is-loop {
opacity: 1 !important; .music-order__shuffle.is-loop,
} .music-order__loop.is-loop,
.music-order__shuffle.is-loop-one, .music-order__loop.is-loop-one { .music-order__shuffle.is-loop-one,
opacity: 1 !important; .music-order__loop.is-loop-one {
opacity: 1 !important;
} }
.music-order__shuffle { .music-order__shuffle {
margin-left: auto; margin-left: auto;
} }
/* Controls container */
.music-control { .music-control {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 2em; height: 2em;
} }
.music-control__play { .music-control__play {
width: 3em; width: 3em;
height: 3em; height: 3em;
margin: 0 1em; margin: 0 1em;
}
.music-control__backward, .music-control__forward {
width: 1.5em;
height: 1.5em;
} }
.music-control__backward,
.music-control__forward {
width: 1.5em;
height: 1.5em;
}
/* Arrows for navigation */
.arrow {
position: absolute;
top: calc(50% - 2.5em);
background: rgba(255, 255, 255, 0.3);
border: 0;
width: 5em;
height: 5em;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: background 0.3s ease;
}
.arrow:hover {
background: rgba(255, 255, 255, 0.5);
}
.arrow img {
display: block;
width: 20px;
margin: 0 auto;
}
.arrow.left {
left: -5em;
}
.arrow.right {
right: -5em;
}
/* Mobile override */
@media all and (max-width: 960px) { @media all and (max-width: 960px) {
.c-containter { .music-container {
display: block; flex-direction: column !important;
overflow: auto; width: 100% !important;
max-height: inherit; max-width: 100% !important;
max-width: 90%; height: auto !important;
} margin-bottom: 50px !important;
overflow: visible !important;
padding: 1rem !important;
}
.music-container { .album-cover {
display: flex; flex: none !important;
justify-content: center; width: 100% !important;
flex-flow: column wrap; max-width: 100% !important;
margin: 0 auto; height: auto !important;
padding-left: 25%; margin-bottom: 1rem !important;
overflow: auto; min-width: 0 !important;
} }
.music-player__author { .album-cover > img {
font-size: 0.4rem; aspect-ratio: unset !important;
} height: auto !important;
width: 100% !important;
}
.music-player__title { .music-player {
font-size: 0.6rem; flex: none !important;
} width: 100% !important;
max-width: 100% !important;
height: auto !important;
padding: 0 !important;
min-width: 0 !important;
flex-shrink: 0 !important;
}
.music-player__genre { .arrow.left,
display: none; .arrow.right {
} position: relative !important;
top: auto !important;
left: auto !important;
right: auto !important;
width: auto !important;
height: auto !important;
background: transparent !important;
cursor: default !important;
margin: 0 1em !important;
}
.music-player { .arrow img {
width: 100%; width: 24px !important;
max-width: 100%; }
}
.album-cover {
position: relative;
flex: 1 1 100%;
max-width: 200px;
max-height: 200px;
}
.arrow {
position: absolute;
top: calc(50% - 1.5em);
width: 3em;
height: 3em;
}
.arrow.left {
left: 0;
}
.arrow.right {
right: 0;
}
.music-control__play {
width: 2.2em;
height: 2.2em;
}
} }

View File

@ -16,7 +16,12 @@ const STATIONS = {
pop: { label: "Pop", streamPath: "/pop.ogg" }, pop: { label: "Pop", streamPath: "/pop.ogg" },
}; };
// Global interval tracking (required for Astro + client:only) // Detect iOS user agent (can refine if needed)
const isIOS = (() => {
if (typeof window === "undefined") return false;
return /iP(ad|hone|od)/.test(navigator.userAgent);
})();
let activeInterval = null; let activeInterval = null;
let currentStationForInterval = null; let currentStationForInterval = null;
@ -50,13 +55,19 @@ export function Player() {
const progress = duration > 0 ? (elapsed / duration) * 100 : 0; const progress = duration > 0 ? (elapsed / duration) * 100 : 0;
// Create Howl instance on activeStation change // UseEffect: create Howl on station change (as before)
useEffect(() => { useEffect(() => {
if (soundRef.current) { if (soundRef.current) {
soundRef.current.unload(); soundRef.current.unload();
soundRef.current = null; soundRef.current = null;
} }
// On iOS we defer creation until user plays, so skip creating Howl here
if (isIOS) {
setIsPlaying(false);
return;
}
const streamUrl = "https://stream.codey.lol" + STATIONS[activeStation].streamPath; const streamUrl = "https://stream.codey.lol" + STATIONS[activeStation].streamPath;
const howl = new Howl({ const howl = new Howl({
@ -92,47 +103,80 @@ export function Player() {
}; };
}, [activeStation]); }, [activeStation]);
const togglePlayback = () => { // Toggle playback handler
if (isPlaying) { const togglePlayback = () => {
if (soundRef.current) { if (isPlaying) {
soundRef.current.pause(); if (soundRef.current) {
} soundRef.current.pause();
setIsPlaying(false); }
} else { setIsPlaying(false);
// If a previous Howl exists, unload it } else {
if (soundRef.current) { if (isIOS) {
soundRef.current.stop(); // On iOS, defer creation until user explicitly plays
soundRef.current.unload(); if (soundRef.current) {
soundRef.current.stop();
soundRef.current.unload();
soundRef.current = null;
}
const streamUrl =
"https://stream.codey.lol" +
STATIONS[activeStation].streamPath +
`?t=${Date.now()}`; // Cache-busting param
const newHowl = new Howl({
src: [streamUrl],
html5: true,
onend: () => newHowl.play(),
onplay: () => {
setIsPlaying(true);
},
onpause: () => setIsPlaying(false),
onstop: () => setIsPlaying(false),
onloaderror: (_, err) => console.error("Load error", err),
onplayerror: (_, err) => {
console.error("Play error", err);
setTimeout(() => newHowl.play(), 1000);
},
});
soundRef.current = newHowl;
newHowl.play();
} else {
// Desktop browsers: current logic
if (soundRef.current) {
soundRef.current.stop();
soundRef.current.unload();
}
const streamUrl =
"https://stream.codey.lol" +
STATIONS[activeStation].streamPath +
`?t=${Date.now()}`; // Cache-busting param
const newHowl = new Howl({
src: [streamUrl],
html5: true,
onend: () => newHowl.play(),
onplay: () => {
setIsPlaying(true);
},
onpause: () => setIsPlaying(false),
onstop: () => setIsPlaying(false),
onloaderror: (_, err) => console.error("Load error", err),
onplayerror: (_, err) => {
console.error("Play error", err);
setTimeout(() => newHowl.play(), 1000);
},
});
soundRef.current = newHowl;
newHowl.play();
}
} }
};
const streamUrl = // Metadata fetcher (unchanged)
"https://stream.codey.lol" +
STATIONS[activeStation].streamPath +
`?t=${Date.now()}`; // Cache-busting param
const newHowl = new Howl({
src: [streamUrl],
html5: true,
onend: () => newHowl.play(),
onplay: () => {
setIsPlaying(true);
},
onpause: () => setIsPlaying(false),
onstop: () => setIsPlaying(false),
onloaderror: (_, err) => console.error("Load error", err),
onplayerror: (_, err) => {
console.error("Play error", err);
setTimeout(() => newHowl.play(), 1000);
},
});
soundRef.current = newHowl;
newHowl.play();
}
};
// Metadata fetcher: global-safe
useEffect(() => { useEffect(() => {
clearGlobalMetadataInterval(); clearGlobalMetadataInterval();
@ -145,7 +189,6 @@ const togglePlayback = () => {
}); });
const data = await response.json(); const data = await response.json();
// Ignore stale interval calls
if (currentStationForInterval !== activeStation) return; if (currentStationForInterval !== activeStation) return;
if (data.artist === "N/A" && data.song === "N/A") { if (data.artist === "N/A" && data.song === "N/A") {
@ -227,12 +270,8 @@ const togglePlayback = () => {
<div id="length" style={{ width: `${progress}%` }}></div> <div id="length" style={{ width: `${progress}%` }}></div>
</div> </div>
<div className="music-control"> <div className="music-control">
<div className="music-control__play" id="play"> <div className="music-control__play" id="play" onClick={togglePlayback} role="button" tabIndex={0} aria-pressed={isPlaying}>
{!isPlaying ? ( {!isPlaying ? <Play /> : <Pause />}
<Play onClick={togglePlayback} />
) : (
<Pause onClick={togglePlayback} />
)}
</div> </div>
</div> </div>
</section> </section>