switch to HLS streams for desktop as well

This commit is contained in:
2025-07-30 07:58:44 -04:00
parent c61bf226d2
commit a722a404dc

View File

@@ -1,20 +1,18 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { Howl, Howler } from "howler"; import Hls from "hls.js";
import { metaData } from "../config"; import { metaData } from "../config";
import Play from "@mui/icons-material/PlayArrow"; import Play from "@mui/icons-material/PlayArrow";
import Pause from "@mui/icons-material/Pause"; import Pause from "@mui/icons-material/Pause";
import "@styles/player.css"; import "@styles/player.css";
const API_URL = "https://api.codey.lol"; const API_URL = "https://api.codey.lol";
Howler.html5PoolSize = 32;
const isIOS = /iP(ad|hone|od)/.test(navigator.userAgent);
const STATIONS = { const STATIONS = {
main: { label: "Main", streamPath: "/sfm.ogg" }, main: { label: "Main" },
rock: { label: "Rock", streamPath: "/rock.ogg" }, rock: { label: "Rock" },
rap: { label: "Rap", streamPath: "/rap.ogg" }, rap: { label: "Rap" },
electronic: { label: "Electronic", streamPath: "/electronic.ogg" }, electronic: { label: "Electronic" },
classical: { label: "Classical", streamPath: "/classical.ogg" }, classical: { label: "Classical" },
pop: { label: "Pop", streamPath: "/pop.ogg" }, pop: { label: "Pop" },
}; };
let activeInterval = null; let activeInterval = null;
@@ -39,7 +37,8 @@ export function Player() {
const [elapsed, setElapsed] = useState(0); const [elapsed, setElapsed] = useState(0);
const [duration, setDuration] = useState(0); const [duration, setDuration] = useState(0);
const soundRef = useRef(null); const audioRef = useRef(null);
const hlsRef = useRef(null);
const uuidRef = useRef(null); const uuidRef = useRef(null);
const lastStationData = useRef(null); const lastStationData = useRef(null);
@@ -52,77 +51,67 @@ export function Player() {
const progress = duration > 0 ? (elapsed / duration) * 100 : 0; const progress = duration > 0 ? (elapsed / duration) * 100 : 0;
const playStream = () => { const playStream = () => {
let streamPath = STATIONS[activeStation].streamPath; const lcStation = activeStation.toLowerCase();
if (isIOS) { const streamUrl = `https://stream.codey.lol/hls/${lcStation}/${lcStation}.m3u8?t=${Date.now()}`;
let lcStation = activeStation.toLowerCase();
streamPath = `/hls/${lcStation}/${lcStation}.m3u8`;
console.log(`Replaced streamPath: ${streamPath}`);
}
const streamUrl =
"https://stream.codey.lol:9992" +
streamPath +
`?t=${Date.now()}`;
if (soundRef.current) { if (hlsRef.current) {
soundRef.current.stop(); hlsRef.current.destroy();
soundRef.current.unload(); hlsRef.current = null;
} }
const newHowl = new Howl({ if (audioRef.current.canPlayType("application/vnd.apple.mpegurl")) {
src: [streamUrl], // Native HLS support (Safari)
html5: true, audioRef.current.src = streamUrl;
onend: () => newHowl.play(), } else if (Hls.isSupported()) {
onplay: () => setIsPlaying(true), const hls = new Hls({
onpause: () => setIsPlaying(false), maxBufferLength: 60,
onstop: () => setIsPlaying(false),
onloaderror: (_, err) => console.error("Load error", err),
onplayerror: (_, err) => {
console.error("Play error", err);
setTimeout(() => newHowl.play(), 1000);
},
}); });
hls.loadSource(streamUrl);
hls.attachMedia(audioRef.current);
hlsRef.current = hls;
} else {
console.error("HLS is not supported in this browser.");
}
soundRef.current = newHowl; audioRef.current.play().then(() => setIsPlaying(true)).catch((e) => {
newHowl.play(); console.error("Playback failed:", e);
});
}; };
const togglePlayback = () => { const togglePlayback = () => {
if (!audioRef.current) return;
if (isPlaying) { if (isPlaying) {
soundRef.current?.pause(); audioRef.current.pause();
setIsPlaying(false); setIsPlaying(false);
} else { } else {
if (!soundRef.current) { audioRef.current.play().then(() => setIsPlaying(true));
}
};
useEffect(() => {
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.src = "";
setIsPlaying(false);
}
playStream(); playStream();
} else {
soundRef.current.play(); return () => {
if (hlsRef.current) {
hlsRef.current.destroy();
} }
setIsPlaying(true); };
} }, [activeStation]);
};
useEffect(() => {
if (soundRef.current) {
soundRef.current.stop();
soundRef.current.unload();
soundRef.current = null;
}
if (isPlaying) {
playStream();
}
}, [activeStation]);
useEffect(() => { useEffect(() => {
clearGlobalMetadataInterval(); clearGlobalMetadataInterval();
currentStationForInterval = activeStation; currentStationForInterval = activeStation;
const setPageTitle = (artist, song) => { const setPageTitle = (artist, song) => {
document.title = metaData.title + ` - Radio - ${artist} - ${song} [${activeStation}]`; document.title = `${metaData.title} - Radio - ${artist} - ${song} [${activeStation}]`;
} };
const fetchTrackData = async () => { const fetchTrackData = async () => {
try { try {
@@ -173,22 +162,18 @@ useEffect(() => {
fetchTrackData(); fetchTrackData();
activeInterval = setInterval(fetchTrackData, 1000); activeInterval = setInterval(fetchTrackData, 1000);
return () => { return () => clearGlobalMetadataInterval();
clearGlobalMetadataInterval();
};
}, [activeStation]); }, [activeStation]);
const remaining = duration - elapsed; const remaining = duration - elapsed;
const progressColorClass = const progressColorClass =
progress >= 90 progress >= 90
? "bg-red-500 dark:bg-red-400" ? "bg-red-500 dark:bg-red-400"
: progress >= 75 || remaining <= 20 : progress >= 75 || remaining <= 20
? "bg-yellow-400 dark:bg-yellow-300" ? "bg-yellow-400 dark:bg-yellow-300"
: "bg-blue-500 dark:bg-blue-400"; : "bg-blue-500 dark:bg-blue-400";
return ( return (
<> <>
<div className="station-tabs flex gap-2 justify-center mb-4 flex-wrap z-10 relative"> <div className="station-tabs flex gap-2 justify-center mb-4 flex-wrap z-10 relative">
@@ -211,27 +196,35 @@ const progressColorClass =
<div className="c-containter"> <div className="c-containter">
<div className="music-container mt-8"> <div className="music-container mt-8">
<section className="album-cover"> <section className="album-cover">
<div class="music-player__album" title="Album">{trackAlbum}</div> <div className="music-player__album" title="Album">
<img src={coverArt} {trackAlbum}
</div>
<img
src={coverArt}
className="cover" className="cover"
title={trackAlbum ? `"${trackAlbum}" Cover Art` : "Cover Art"} title={trackAlbum ? `"${trackAlbum}" Cover Art` : "Cover Art"}
alt={trackAlbum ? `"${trackAlbum}" Cover Art` : "Cover Art"} /> alt={trackAlbum ? `"${trackAlbum}" Cover Art` : "Cover Art"}
/>
</section> </section>
<section className="music-player"> <section className="music-player">
<h1 className="music-player__header">serious.FM</h1> <h1 className="music-player__header">serious.FM</h1>
<h1 className="music-player__title">{trackTitle}</h1> <h1 className="music-player__title">{trackTitle}</h1>
<h2 className="music-player__author">{trackArtist}</h2> <h2 className="music-player__author">{trackArtist}</h2>
{trackGenre && <h2 className="music-player__genre">{trackGenre}</h2>} {trackGenre && <h2 className="music-player__genre">{trackGenre}</h2>}
<div className="music-time"> <div className="music-time">
<p className="music-time__current">{formatTime(elapsed)}</p> <p className="music-time__current">{formatTime(elapsed)}</p>
<p className="music-time__last">{formatTime(duration - elapsed)}</p> <p className="music-time__last">{formatTime(duration - elapsed)}</p>
</div> </div>
<div className="w-full h-2 rounded bg-neutral-300 dark:bg-neutral-700 overflow-hidden">
<div className="w-full h-2 rounded bg-neutral-300 dark:bg-neutral-700 overflow-hidden">
<div <div
className={`h-full transition-all duration-200 ${progressColorClass}`} className={`h-full transition-all duration-200 ${progressColorClass}`}
style={{ width: `${progress}%` }} style={{ width: `${progress}%` }}
></div> ></div>
</div> </div>
<div className="music-control"> <div className="music-control">
<div <div
className="music-control__play" className="music-control__play"
@@ -246,6 +239,8 @@ const progressColorClass =
</div> </div>
</section> </section>
</div> </div>
<audio ref={audioRef} preload="none" />
</div> </div>
</> </>
); );