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

@@ -1,19 +1,39 @@
'use client'; 'use client';
import React, { createContext, useContext, useState, ReactNode } from 'react'; import React, { createContext, useContext, useState, ReactNode, useCallback } from 'react';
interface AudioContextType { interface AudioContextType {
currentAudioId: string | null; currentAudioId: string | null;
setCurrentAudio: (id: string | null) => void; setCurrentAudio: (id: string | null) => void;
playerCount: number;
registerPlayer: (id: string) => void;
unregisterPlayer: (id: string) => void;
} }
const AudioContext = createContext<AudioContextType | undefined>(undefined); const AudioContext = createContext<AudioContextType | undefined>(undefined);
export function AudioProvider({ children }: { children: ReactNode }) { export function AudioProvider({ children }: { children: ReactNode }) {
const [currentAudioId, setCurrentAudioId] = useState<string | null>(null); const [currentAudioId, setCurrentAudioId] = useState<string | null>(null);
const [playerCount, setPlayerCount] = useState(0);
const registerPlayer = useCallback((id: string) => {
setPlayerCount((prev) => prev + 1);
}, []);
const unregisterPlayer = useCallback((id: string) => {
setPlayerCount((prev) => Math.max(0, prev - 1));
}, []);
return ( return (
<AudioContext.Provider value={{ currentAudioId, setCurrentAudio: setCurrentAudioId }}> <AudioContext.Provider
value={{
currentAudioId,
setCurrentAudio: setCurrentAudioId,
playerCount,
registerPlayer,
unregisterPlayer,
}}
>
{children} {children}
</AudioContext.Provider> </AudioContext.Provider>
); );

View File

@@ -25,11 +25,12 @@ export default function WaveformPlayer({
showWaveform = true, showWaveform = true,
playMode = 'pause', playMode = 'pause',
}: WaveformPlayerProps) { }: WaveformPlayerProps) {
const { currentAudioId, setCurrentAudio } = useAudio(); const { currentAudioId, setCurrentAudio, playerCount, registerPlayer, unregisterPlayer } = useAudio();
// Generate unique ID for this player instance // Generate unique ID for this player instance
const playerId = useRef(Math.random().toString(36).slice(2)).current; const playerId = useRef(Math.random().toString(36).slice(2)).current;
const isActive = currentAudioId === playerId; const isActive = currentAudioId === playerId;
const isOnlyPlayer = playerCount === 1;
const audioRef = useRef<HTMLAudioElement>(null); const audioRef = useRef<HTMLAudioElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
@@ -41,6 +42,14 @@ export default function WaveformPlayer({
const [duration, setDuration] = useState(0); const [duration, setDuration] = useState(0);
const [peaks, setPeaks] = useState<number[]>([]); 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 // Stop if another player became active
useEffect(() => { useEffect(() => {
if (!isActive && isPlaying) { if (!isActive && isPlaying) {
@@ -112,10 +121,37 @@ export default function WaveformPlayer({
} }
}, [onFullyPlayed]); }, [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(() => { const handleLoadedMetadata = useCallback(() => {
if (audioRef.current) setDuration(audioRef.current.duration); 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) // Decode audio and generate waveform peaks (only if showWaveform is true)
useEffect(() => { useEffect(() => {
if (!showWaveform) return; if (!showWaveform) return;
@@ -188,12 +224,14 @@ export default function WaveformPlayer({
// Simple player button (no waveform) // Simple player button (no waveform)
if (!showWaveform) { if (!showWaveform) {
return ( return (
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<audio <audio
ref={audioRef} ref={audioRef}
src={src} src={src}
onEnded={handleEnded} onEnded={handleEnded}
/> />
<div className="flex items-center gap-1">
<button <button
onClick={isPlaying ? (playMode === 'stop' ? handleStop : handlePause) : handlePlay} onClick={isPlaying ? (playMode === 'stop' ? handleStop : handlePause) : handlePlay}
className={`inline-flex items-center justify-center w-8 h-8 rounded-full transition-colors ${ className={`inline-flex items-center justify-center w-8 h-8 rounded-full transition-colors ${
@@ -201,7 +239,7 @@ export default function WaveformPlayer({
? 'bg-red-500 hover:bg-red-600 text-white' ? 'bg-red-500 hover:bg-red-600 text-white'
: 'bg-blue-500 hover:bg-blue-600 text-white' : 'bg-blue-500 hover:bg-blue-600 text-white'
}`} }`}
title={isPlaying ? (playMode === 'stop' ? 'Stop' : 'Pause') : 'Play'} title={`${isPlaying ? (playMode === 'stop' ? 'Stop' : 'Pause') : 'Play'}`}
> >
{isPlaying ? ( {isPlaying ? (
playMode === 'stop' ? ( playMode === 'stop' ? (
@@ -220,6 +258,16 @@ export default function WaveformPlayer({
</svg> </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> </div>
); );
} }
@@ -235,7 +283,8 @@ export default function WaveformPlayer({
preload="metadata" preload="metadata"
/> />
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* Play / Pause button */} {/* Play / Pause button with optional info icon */}
<div className="flex items-center gap-1">
<button <button
onClick={isPlaying ? (playMode === 'stop' ? handleStop : handlePause) : handlePlay} 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 ${ className={`flex-shrink-0 inline-flex items-center justify-center w-9 h-9 rounded-full transition-colors ${
@@ -262,7 +311,15 @@ export default function WaveformPlayer({
</svg> </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 */} {/* Waveform canvas - hidden on small screens */}
<canvas <canvas
ref={canvasRef} ref={canvasRef}