feat: added spacebar navigation to audioplayer

This commit is contained in:
averel10
2026-04-03 10:23:19 +02:00
parent 6986e88560
commit 2812689e59
2 changed files with 121 additions and 44 deletions

View File

@@ -25,11 +25,12 @@ export default function WaveformPlayer({
showWaveform = true,
playMode = 'pause',
}: WaveformPlayerProps) {
const { currentAudioId, setCurrentAudio } = useAudio();
const { currentAudioId, setCurrentAudio, playerCount, registerPlayer, unregisterPlayer } = useAudio();
// Generate unique ID for this player instance
const playerId = useRef(Math.random().toString(36).slice(2)).current;
const isActive = currentAudioId === playerId;
const isOnlyPlayer = playerCount === 1;
const audioRef = useRef<HTMLAudioElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
@@ -41,6 +42,14 @@ export default function WaveformPlayer({
const [duration, setDuration] = useState(0);
const [peaks, setPeaks] = useState<number[]>([]);
// Register/unregister player on mount/unmount
useEffect(() => {
registerPlayer(playerId);
return () => {
unregisterPlayer(playerId);
};
}, [playerId, registerPlayer, unregisterPlayer]);
// Stop if another player became active
useEffect(() => {
if (!isActive && isPlaying) {
@@ -112,10 +121,37 @@ export default function WaveformPlayer({
}
}, [onFullyPlayed]);
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.code === 'Space' && isOnlyPlayer) {
e.preventDefault();
if (isPlaying) {
if (playMode === 'stop') {
handleStop();
} else {
handlePause();
}
} else {
handlePlay();
}
}
},
[isOnlyPlayer, isPlaying, playMode, handlePlay, handlePause, handleStop]
);
const handleLoadedMetadata = useCallback(() => {
if (audioRef.current) setDuration(audioRef.current.duration);
}, []);
// Add spacebar listener when only one player exists
useEffect(() => {
if (!isOnlyPlayer) return;
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [isOnlyPlayer, handleKeyDown]);
// Decode audio and generate waveform peaks (only if showWaveform is true)
useEffect(() => {
if (!showWaveform) return;
@@ -188,38 +224,50 @@ export default function WaveformPlayer({
// Simple player button (no waveform)
if (!showWaveform) {
return (
<div className="flex items-center gap-2">
<audio
ref={audioRef}
src={src}
onEnded={handleEnded}
/>
<button
onClick={isPlaying ? (playMode === 'stop' ? handleStop : handlePause) : handlePlay}
className={`inline-flex items-center justify-center w-8 h-8 rounded-full transition-colors ${
isPlaying
? 'bg-red-500 hover:bg-red-600 text-white'
: 'bg-blue-500 hover:bg-blue-600 text-white'
}`}
title={isPlaying ? (playMode === 'stop' ? 'Stop' : 'Pause') : 'Play'}
>
{isPlaying ? (
playMode === 'stop' ? (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<rect x="5" y="5" width="10" height="10" />
</svg>
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<audio
ref={audioRef}
src={src}
onEnded={handleEnded}
/>
<div className="flex items-center gap-1">
<button
onClick={isPlaying ? (playMode === 'stop' ? handleStop : handlePause) : handlePlay}
className={`inline-flex items-center justify-center w-8 h-8 rounded-full transition-colors ${
isPlaying
? 'bg-red-500 hover:bg-red-600 text-white'
: 'bg-blue-500 hover:bg-blue-600 text-white'
}`}
title={`${isPlaying ? (playMode === 'stop' ? 'Stop' : 'Pause') : 'Play'}`}
>
{isPlaying ? (
playMode === 'stop' ? (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<rect x="5" y="5" width="10" height="10" />
</svg>
) : (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<rect x="6" y="4" width="2" height="12" />
<rect x="12" y="4" width="2" height="12" />
</svg>
)
) : (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<rect x="6" y="4" width="2" height="12" />
<rect x="12" y="4" width="2" height="12" />
<path d="M6.3 2.841A1.5 1.5 0 004 4.11v11.78a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z" />
</svg>
)
) : (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path d="M6.3 2.841A1.5 1.5 0 004 4.11v11.78a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z" />
</svg>
)}
</button>
)}
</button>
{isOnlyPlayer && (
<div
className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-gray-300 text-gray-700 text-xs font-bold cursor-help"
title="Leertaste zum Abspielen/Pausieren verwenden"
>
?
</div>
)}
</div>
</div>
</div>
);
}
@@ -235,16 +283,17 @@ export default function WaveformPlayer({
preload="metadata"
/>
<div className="flex items-center gap-3">
{/* Play / Pause button */}
<button
onClick={isPlaying ? (playMode === 'stop' ? handleStop : handlePause) : handlePlay}
className={`flex-shrink-0 inline-flex items-center justify-center w-9 h-9 rounded-full transition-colors ${
isPlaying
? 'bg-red-500 hover:bg-red-600 text-white'
: 'bg-blue-500 hover:bg-blue-600 text-white'
}`}
title={isPlaying ? (playMode === 'stop' ? 'Stop' : 'Pause') : 'Abspielen'}
>
{/* Play / Pause button with optional info icon */}
<div className="flex items-center gap-1">
<button
onClick={isPlaying ? (playMode === 'stop' ? handleStop : handlePause) : handlePlay}
className={`flex-shrink-0 inline-flex items-center justify-center w-9 h-9 rounded-full transition-colors ${
isPlaying
? 'bg-red-500 hover:bg-red-600 text-white'
: 'bg-blue-500 hover:bg-blue-600 text-white'
}`}
title={isPlaying ? (playMode === 'stop' ? 'Stop' : 'Pause') : 'Abspielen'}
>
{isPlaying ? (
playMode === 'stop' ? (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
@@ -261,8 +310,16 @@ export default function WaveformPlayer({
<path d="M6.3 2.841A1.5 1.5 0 004 4.11v11.78a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z" />
</svg>
)}
</button>
</button>
{isOnlyPlayer && (
<div
className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-gray-300 text-gray-700 text-xs font-bold cursor-help"
title="Leertaste zum Abspielen/Pausieren verwenden"
>
?
</div>
)}
</div>
{/* Waveform canvas - hidden on small screens */}
<canvas
ref={canvasRef}