feat: Update AudioPlayer and LyricSearch components for improved functionality and user experience

This commit is contained in:
2025-11-25 13:05:37 -05:00
parent 8500cd6e67
commit ee25ad243c
5 changed files with 133 additions and 122 deletions

View File

@@ -217,16 +217,16 @@ export default function Player({ user }) {
}
const hls = new Hls({
lowLatencyMode: true,
lowLatencyMode: false,
abrEnabled: false,
liveSyncDuration: 0.5, // seconds behind live edge target
liveSyncDuration: 0.6, // seconds behind live edge target
liveMaxLatencyDuration: 3.0, // max allowed latency before catchup
liveCatchUpPlaybackRate: 1.02,
maxBufferLength: 30,
maxMaxBufferLength: 60,
maxBufferHole: 0.5,
manifestLoadingTimeOut: 4000,
fragLoadingTimeOut: 10000, // playback speed when catching up
// maxBufferLength: 30,
// maxMaxBufferLength: 60,
// maxBufferHole: 2.0,
// manifestLoadingTimeOut: 5000,
// fragLoadingTimeOut: 10000, // playback speed when catching up
});
hlsInstance.current = hls;

View File

@@ -55,6 +55,8 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
const [inputStatus, setInputStatus] = useState("hint");
const searchToastRef = useRef(null);
const autoCompleteRef = useRef(null);
const autoCompleteInputRef = useRef(null);
const searchButtonRef = useRef(null);
const [theme, setTheme] = useState(document.documentElement.getAttribute("data-theme") || "light");
const statusLabels = {
hint: "Format: Artist - Song",
@@ -196,6 +198,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
if (autoCompleteRef.current) {
autoCompleteRef.current.hide();
}
autoCompleteInputRef.current?.blur(); // blur early so the suggestion panel closes immediately
const evaluation = evaluateSearchValue(searchValue);
if (!evaluation?.valid) {
@@ -209,13 +212,19 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
setLyricsResult(null);
setShowLyrics(false);
if (!toast.isActive('lyrics-searching-toast')) {
searchToastRef.current = toast.info("Searching...", {
toastId: "lyrics-searching-toast"
});
}
const toastId = "lyrics-searching-toast";
toast.dismiss(toastId);
searchToastRef.current = toast.info("Searching...", {
toastId,
});
const startTime = Date.now();
const dismissSearchToast = () => {
if (searchToastRef.current) {
toast.dismiss(searchToastRef.current);
searchToastRef.current = null;
}
};
try {
const res = await fetch(`${API_URL}/lyric/search`, {
@@ -230,6 +239,10 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
}),
});
if (res.status === 429) {
throw new Error("Too many requests. Please wait a moment and try again.");
}
const data = await res.json();
if (!res.ok || !data.lyrics) {
throw new Error(data.errorText || "Unknown error.");
@@ -244,21 +257,22 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
const hash = `#${encodeURIComponent(data.artist)}/${encodeURIComponent(data.song)}`;
window.history.pushState(null, '', hash);
toast.update(searchToastRef.current, {
type: "success",
render: `Found! (Took ${duration}s)`,
className: "Toastify__toast--success",
dismissSearchToast();
toast.success(`Found! (Took ${duration}s)`, {
autoClose: 2500,
toastId: `lyrics-success-${Date.now()}`,
});
} catch (error) {
toast.update(searchToastRef.current, {
type: "error",
render: error.message,
dismissSearchToast();
toast.error(error.message, {
icon: () => "😕",
autoClose: 5000,
});
} finally {
setIsLoading(false);
autoCompleteInputRef.current?.blur();
searchButtonRef.current?.blur();
searchToastRef.current = null;
}
};
@@ -329,6 +343,9 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
className={`lyric-search-input ${inputStatus === "error" ? "has-error" : ""} ${inputStatus === "ready" ? "has-ready" : ""}`}
aria-invalid={inputStatus === "error"}
aria-label={`Lyric search input. ${statusTitle}`}
inputRef={(el) => {
autoCompleteInputRef.current = el;
}}
aria-controls="lyric-search-input"
/>
<span
@@ -343,14 +360,18 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
{statusTitle}
</span>
</div>
<Button onClick={() => handleSearch()} className="btn">
<Button
onClick={() => handleSearch()}
className="btn"
ref={searchButtonRef}
>
Search
</Button>
<div className="mt-4">
Exclude:<br />
<div id="exclude-checkboxes">
<UICheckbox id="excl-Genius" label="Genius" onToggle={toggleExclusion} />
<UICheckbox id="excl-LRCLib" label="LRCLib" onToggle={toggleExclusion} />
<UICheckbox id="excl-LRCLib-Cache" label="LRCLib-Cache" onToggle={toggleExclusion} />
<UICheckbox id="excl-Cache" label="Cache" onToggle={toggleExclusion} />
</div>
</div>
@@ -373,6 +394,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
type="button"
className={`text-size-btn text-size-large ${textSize === "large" ? "active" : ""}`}
onClick={() => setTextSize("large")}
title="Larger text"
>
Aa
</button>
@@ -380,6 +402,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
type="button"
className={`text-size-btn ${textSize === "normal" ? "active" : ""}`}
onClick={() => setTextSize("normal")}
title="Default text"
>
Aa
</button>
@@ -389,6 +412,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
variant="plain"
color="neutral"
aria-label="Copy lyrics"
title="Copy lyrics"
className="lyrics-action-button"
onClick={handleCopyLyrics}
>
@@ -399,6 +423,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
variant="plain"
color="neutral"
aria-label="Copy lyric link"
title="Copy shareable link"
className="lyrics-action-button"
onClick={handleCopyLink}
>

View File

@@ -46,20 +46,8 @@ export default function RandomMsg() {
className="inline-block"
>
<path
d="M21 12a9 9 0 1 1-2.64-6.36"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<polyline
points="21 3 21 9 15 9"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
d="M17.65 6.35a7.95 7.95 0 0 0-5.65-2.35 8 8 0 1 0 7.75 9.94h-2.08a6 6 0 1 1-5.67-7.94 5.94 5.94 0 0 1 4.22 1.78L13 11h7V4z"
fill="currentColor"
/>
</svg>