web: add error surfacing

This commit is contained in:
2025-11-15 17:58:29 -08:00
parent 04d23bec1e
commit bc54735d1f
5 changed files with 74 additions and 13 deletions

View File

@@ -28,6 +28,7 @@ export interface PlaylistItem {
id: number;
playing: boolean | null;
metadata?: Metadata;
playbackError?: string;
}
export const getDisplayTitle = (item: PlaylistItem): string => {
@@ -62,6 +63,7 @@ export enum ServerEvent {
FavoritesUpdate = "favorites_update",
MetadataUpdate = "metadata_update",
MPDUpdate = "mpd_update",
PlaybackError = "playback_error",
ScreenShare = "screen_share",
}

View File

@@ -199,6 +199,7 @@ const App: React.FC = () => {
case ServerEvent.NowPlayingUpdate:
case ServerEvent.MetadataUpdate:
case ServerEvent.MPDUpdate:
case ServerEvent.PlaybackError:
fetchPlaylist();
fetchNowPlaying();
break;

View File

@@ -1,6 +1,6 @@
import classNames from 'classnames';
import React, { useState, useRef, useEffect, ReactNode } from 'react';
import { FaPlay, FaVolumeUp, FaVolumeOff } from 'react-icons/fa';
import { FaPlay, FaVolumeUp, FaVolumeOff, FaExclamationTriangle } from 'react-icons/fa';
import { getDisplayTitle, PlaylistItem } from '../api/player';
export enum PlayState {
@@ -19,6 +19,7 @@ export interface SongRowProps {
const SongRow: React.FC<SongRowProps> = ({ song, auxControl, playState, onDelete, onPlay }) => {
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [showErrorDetails, setShowErrorDetails] = useState(false);
const buttonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
@@ -38,6 +39,7 @@ const SongRow: React.FC<SongRowProps> = ({ song, auxControl, playState, onDelete
}, [showDeleteConfirm]);
const displayTitle = getDisplayTitle(song);
const hasError = !!song.playbackError;
return (
<div className={classNames(
@@ -46,16 +48,46 @@ const SongRow: React.FC<SongRowProps> = ({ song, auxControl, playState, onDelete
"bg-black/30": playState === PlayState.NotPlaying,
})}>
<div className="flex flex-row gap-2">
<button
className="text-white/40 hover:text-white transition-colors px-3 py-1 rounded"
onClick={onPlay}
>
{
playState === PlayState.Playing ? <FaVolumeUp size={12} />
: playState === PlayState.Paused ? <FaVolumeOff size={12} />
: <FaPlay size={12} />
}
</button>
<div className="relative">
<button
className={classNames(
"transition-colors px-3 py-1 rounded",
hasError ? "text-amber-300 hover:text-amber-100" : "text-white/40 hover:text-white"
)}
onClick={() => {
if (hasError) {
setShowErrorDetails((prev) => !prev);
} else {
onPlay();
}
}}
onMouseEnter={() => {
if (hasError) {
setShowErrorDetails(true);
}
}}
onMouseLeave={() => {
if (hasError) {
setShowErrorDetails(false);
}
}}
title={hasError ? song.playbackError : undefined}
>
{
hasError ? <FaExclamationTriangle size={12} /> :
playState === PlayState.Playing ? <FaVolumeUp size={12} />
: playState === PlayState.Paused ? <FaVolumeOff size={12} />
: <FaPlay size={12} />
}
</button>
{hasError && showErrorDetails && (
<div className="absolute z-10 top-full left-0 mt-1 w-64 p-2 text-xs text-white bg-red-600/90 rounded shadow-lg">
<div className="font-semibold mb-1">Playback error</div>
<div className="break-words">{song.playbackError}</div>
</div>
)}
</div>
</div>
<div className="flex-grow min-w-0">