Files
rehearshalhub/web/src/components/MiniPlayer.tsx
Mistral Vibe d4c0e9d776 refactor(audio): Phase 2 — simplify AudioService to thin WaveSurfer wrapper
audioService.ts rewritten from ~850 lines to ~130:
- Remove custom logging system with throttle that suppressed ERROR logs
- Remove AudioContext management entirely (initializeAudioContext,
  handleAudioContextResume, setupAudioContext, shareAudioContextWithWaveSurfer,
  ensureAudioContext). WaveSurfer v7 owns its AudioContext; fighting it caused
  prod/dev divergence and silent failures.
- Replace 5-state InitializationState machine + split promise with a single
  isReady boolean set in the 'ready' event handler
- Remove retry/debounce logic from play() — these are UI concerns
- Remove dead methods: canPlayAudio (always returned true), getWaveSurferVersion,
  updatePlayerState, getAudioContextState, setLogLevel
- Extract destroyWaveSurfer() helper so cleanup is one place
- MiniPlayer now passes songId/bandId to play() (was calling with no args)
- SongPage spacebar handler simplified: just checks isReady from hook
- SongPage no longer imports audioService directly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:47:10 +02:00

125 lines
3.2 KiB
TypeScript
Executable File

import { usePlayerStore } from "../stores/playerStore";
import { useNavigate } from "react-router-dom";
import { audioService } from "../services/audioService";
export function MiniPlayer() {
const { currentSongId, currentBandId, isPlaying, currentTime, duration } = usePlayerStore();
const navigate = useNavigate();
if (!currentSongId || !currentBandId) {
return null;
}
const formatTime = (seconds: number) => {
const m = Math.floor(seconds / 60);
const s = Math.floor(seconds % 60);
return `${m}:${String(s).padStart(2, "0")}`;
};
const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
return (
<div
style={
{
position: "fixed",
bottom: 0,
left: 0,
right: 0,
background: "#18181e",
borderTop: "1px solid rgba(255,255,255,0.06)",
padding: "8px 16px",
zIndex: 999,
display: "flex",
alignItems: "center",
gap: 12,
}
}
>
<button
onClick={() => navigate(`/bands/${currentBandId}/songs/${currentSongId}`)}
style={
{
background: "transparent",
border: "none",
color: "white",
cursor: "pointer",
display: "flex",
alignItems: "center",
gap: 8,
padding: "4px 8px",
borderRadius: 4,
}
}
title="Go to song"
>
<svg width="16" height="16" viewBox="0 0 14 14" fill="currentColor">
<path d="M3 2l9 5-9 5V2z" />
</svg>
<span style={{ fontSize: 12, color: "rgba(255,255,255,0.8)" }}>
Now Playing
</span>
</button>
<div
style={
{
flex: 1,
height: 4,
background: "rgba(255,255,255,0.1)",
borderRadius: 2,
overflow: "hidden",
cursor: "pointer",
}
}
>
<div
style={
{
width: `${progress}%`,
height: "100%",
background: "#e8a22a",
transition: "width 0.1s linear",
}
}
/>
</div>
<div style={{ fontSize: 11, color: "rgba(255,255,255,0.6)", minWidth: 60, textAlign: "right" }}>
{formatTime(currentTime)} / {formatTime(duration)}
</div>
<button
onClick={() => {
if (isPlaying) {
audioService.pause();
} else {
audioService.play(currentSongId, currentBandId).catch(err => {
console.warn('MiniPlayer playback failed:', err);
});
}
}}
style={
{
background: "transparent",
border: "none",
color: "white",
cursor: "pointer",
padding: "4px",
}
}
title={isPlaying ? "Pause" : "Play"}
>
{isPlaying ? (
<svg width="16" height="16" viewBox="0 0 14 14" fill="currentColor">
<path d="M4 2h2v10H4zm4 0h2v10h-2z" />
</svg>
) : (
<svg width="16" height="16" viewBox="0 0 14 14" fill="currentColor">
<path d="M3 2l9 5-9 5V2z" />
</svg>
)}
</button>
</div>
);
}