switch to HLS streams for desktop as well
This commit is contained in:
@@ -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),
|
hls.loadSource(streamUrl);
|
||||||
onplayerror: (_, err) => {
|
hls.attachMedia(audioRef.current);
|
||||||
console.error("Play error", err);
|
hlsRef.current = hls;
|
||||||
setTimeout(() => newHowl.play(), 1000);
|
} else {
|
||||||
},
|
console.error("HLS is not supported in this browser.");
|
||||||
|
}
|
||||||
|
|
||||||
|
audioRef.current.play().then(() => setIsPlaying(true)).catch((e) => {
|
||||||
|
console.error("Playback failed:", e);
|
||||||
});
|
});
|
||||||
|
|
||||||
soundRef.current = newHowl;
|
|
||||||
newHowl.play();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const togglePlayback = () => {
|
const togglePlayback = () => {
|
||||||
if (isPlaying) {
|
if (!audioRef.current) return;
|
||||||
soundRef.current?.pause();
|
|
||||||
setIsPlaying(false);
|
if (isPlaying) {
|
||||||
} else {
|
audioRef.current.pause();
|
||||||
if (!soundRef.current) {
|
setIsPlaying(false);
|
||||||
playStream();
|
|
||||||
} else {
|
} else {
|
||||||
soundRef.current.play();
|
audioRef.current.play().then(() => setIsPlaying(true));
|
||||||
}
|
}
|
||||||
setIsPlaying(true);
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (soundRef.current) {
|
if (audioRef.current) {
|
||||||
soundRef.current.stop();
|
audioRef.current.pause();
|
||||||
soundRef.current.unload();
|
audioRef.current.src = "";
|
||||||
soundRef.current = null;
|
setIsPlaying(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPlaying) {
|
|
||||||
playStream();
|
playStream();
|
||||||
}
|
|
||||||
|
|
||||||
}, [activeStation]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (hlsRef.current) {
|
||||||
|
hlsRef.current.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [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,21 +162,17 @@ 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 =
|
|
||||||
progress >= 90
|
|
||||||
? "bg-red-500 dark:bg-red-400"
|
|
||||||
: progress >= 75 || remaining <= 20
|
|
||||||
? "bg-yellow-400 dark:bg-yellow-300"
|
|
||||||
: "bg-blue-500 dark:bg-blue-400";
|
|
||||||
|
|
||||||
|
|
||||||
|
const progressColorClass =
|
||||||
|
progress >= 90
|
||||||
|
? "bg-red-500 dark:bg-red-400"
|
||||||
|
: progress >= 75 || remaining <= 20
|
||||||
|
? "bg-yellow-400 dark:bg-yellow-300"
|
||||||
|
: "bg-blue-500 dark:bg-blue-400";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -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}
|
||||||
className="cover"
|
</div>
|
||||||
title={trackAlbum ? `"${trackAlbum}" Cover Art` : "Cover Art"}
|
<img
|
||||||
alt={trackAlbum ? `"${trackAlbum}" Cover Art` : "Cover Art"} />
|
src={coverArt}
|
||||||
|
className="cover"
|
||||||
|
title={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
|
<div className="w-full h-2 rounded bg-neutral-300 dark:bg-neutral-700 overflow-hidden">
|
||||||
className={`h-full transition-all duration-200 ${progressColorClass}`}
|
<div
|
||||||
style={{ width: `${progress}%` }}
|
className={`h-full transition-all duration-200 ${progressColorClass}`}
|
||||||
></div>
|
style={{ width: `${progress}%` }}
|
||||||
</div>
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="music-control">
|
<div className="music-control">
|
||||||
<div
|
<div
|
||||||
className="music-control__play"
|
className="music-control__play"
|
||||||
@@ -246,7 +239,9 @@ const progressColorClass =
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<audio ref={audioRef} preload="none" />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user