3 Commits

Author SHA1 Message Date
Hördle Bot
9bf7e72a6c Fix: Properly handle async play() and remove autoPlay conflict 2025-11-25 15:28:22 +01:00
Hördle Bot
f8b5dcf300 Fix: Start button now actually starts audio playback 2025-11-25 15:26:20 +01:00
Hördle Bot
072158f4ed Feature: Skip button becomes Start button on first attempt if audio not played 2025-11-25 15:18:25 +01:00
2 changed files with 62 additions and 6 deletions

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState, useRef, useEffect } from 'react'; import { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
interface AudioPlayerProps { interface AudioPlayerProps {
src: string; src: string;
@@ -9,9 +9,14 @@ interface AudioPlayerProps {
onPlay?: () => void; onPlay?: () => void;
onReplay?: () => void; onReplay?: () => void;
autoPlay?: boolean; autoPlay?: boolean;
onHasPlayedChange?: (hasPlayed: boolean) => void;
} }
export default function AudioPlayer({ src, unlockedSeconds, startTime = 0, onPlay, onReplay, autoPlay = false }: AudioPlayerProps) { export interface AudioPlayerRef {
play: () => void;
}
const AudioPlayer = forwardRef<AudioPlayerRef, AudioPlayerProps>(({ src, unlockedSeconds, startTime = 0, onPlay, onReplay, autoPlay = false, onHasPlayedChange }, ref) => {
const audioRef = useRef<HTMLAudioElement>(null); const audioRef = useRef<HTMLAudioElement>(null);
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const [progress, setProgress] = useState(0); const [progress, setProgress] = useState(0);
@@ -24,6 +29,7 @@ export default function AudioPlayer({ src, unlockedSeconds, startTime = 0, onPla
setIsPlaying(false); setIsPlaying(false);
setProgress(0); setProgress(0);
setHasPlayedOnce(false); // Reset for new segment setHasPlayedOnce(false); // Reset for new segment
onHasPlayedChange?.(false); // Notify parent
if (autoPlay) { if (autoPlay) {
const playPromise = audioRef.current.play(); const playPromise = audioRef.current.play();
@@ -33,6 +39,7 @@ export default function AudioPlayer({ src, unlockedSeconds, startTime = 0, onPla
setIsPlaying(true); setIsPlaying(true);
onPlay?.(); onPlay?.();
setHasPlayedOnce(true); setHasPlayedOnce(true);
onHasPlayedChange?.(true); // Notify parent
}) })
.catch(error => { .catch(error => {
console.log("Autoplay prevented:", error); console.log("Autoplay prevented:", error);
@@ -43,6 +50,30 @@ export default function AudioPlayer({ src, unlockedSeconds, startTime = 0, onPla
} }
}, [src, unlockedSeconds, startTime, autoPlay]); }, [src, unlockedSeconds, startTime, autoPlay]);
// Expose play method to parent component
useImperativeHandle(ref, () => ({
play: () => {
if (!audioRef.current) return;
const playPromise = audioRef.current.play();
if (playPromise !== undefined) {
playPromise
.then(() => {
setIsPlaying(true);
onPlay?.();
if (!hasPlayedOnce) {
setHasPlayedOnce(true);
onHasPlayedChange?.(true);
}
})
.catch(error => {
console.error("Play failed:", error);
setIsPlaying(false);
});
}
}
}));
const togglePlay = () => { const togglePlay = () => {
if (!audioRef.current) return; if (!audioRef.current) return;
@@ -56,6 +87,7 @@ export default function AudioPlayer({ src, unlockedSeconds, startTime = 0, onPla
onReplay?.(); onReplay?.();
} else { } else {
setHasPlayedOnce(true); setHasPlayedOnce(true);
onHasPlayedChange?.(true); // Notify parent
} }
} }
setIsPlaying(!isPlaying); setIsPlaying(!isPlaying);
@@ -112,4 +144,8 @@ export default function AudioPlayer({ src, unlockedSeconds, startTime = 0, onPla
</div> </div>
</div> </div>
); );
} });
AudioPlayer.displayName = 'AudioPlayer';
export default AudioPlayer;

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useState, useRef } from 'react';
import AudioPlayer from './AudioPlayer'; import AudioPlayer, { AudioPlayerRef } from './AudioPlayer';
import GuessInput from './GuessInput'; import GuessInput from './GuessInput';
import Statistics from './Statistics'; import Statistics from './Statistics';
import { useGameState } from '../lib/gameState'; import { useGameState } from '../lib/gameState';
@@ -37,6 +37,8 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
const [timeUntilNext, setTimeUntilNext] = useState(''); const [timeUntilNext, setTimeUntilNext] = useState('');
const [hasRated, setHasRated] = useState(false); const [hasRated, setHasRated] = useState(false);
const [showYearModal, setShowYearModal] = useState(false); const [showYearModal, setShowYearModal] = useState(false);
const [hasPlayedAudio, setHasPlayedAudio] = useState(false);
const audioPlayerRef = useRef<AudioPlayerRef>(null);
useEffect(() => { useEffect(() => {
const updateCountdown = () => { const updateCountdown = () => {
@@ -116,7 +118,20 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
setTimeout(() => setIsProcessingGuess(false), 500); setTimeout(() => setIsProcessingGuess(false), 500);
}; };
const handleStartAudio = () => {
// This will be called when user clicks "Start" button on first attempt
// Trigger the audio player to start playing
audioPlayerRef.current?.play();
setHasPlayedAudio(true);
};
const handleSkip = () => { const handleSkip = () => {
// If user hasn't played audio yet on first attempt, start it instead of skipping
if (gameState.guesses.length === 0 && !hasPlayedAudio) {
handleStartAudio();
return;
}
setLastAction('SKIP'); setLastAction('SKIP');
addGuess("SKIPPED", false); addGuess("SKIPPED", false);
@@ -252,11 +267,13 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
<ScoreDisplay score={gameState.score} breakdown={gameState.scoreBreakdown} /> <ScoreDisplay score={gameState.score} breakdown={gameState.scoreBreakdown} />
<AudioPlayer <AudioPlayer
ref={audioPlayerRef}
src={dailyPuzzle.audioUrl} src={dailyPuzzle.audioUrl}
unlockedSeconds={unlockedSeconds} unlockedSeconds={unlockedSeconds}
startTime={dailyPuzzle.startTime} startTime={dailyPuzzle.startTime}
autoPlay={lastAction === 'SKIP' || (lastAction === 'GUESS' && !hasWon && !hasLost)} autoPlay={lastAction === 'SKIP' || (lastAction === 'GUESS' && !hasWon && !hasLost)}
onReplay={addReplay} onReplay={addReplay}
onHasPlayedChange={setHasPlayedAudio}
/> />
</div> </div>
@@ -282,7 +299,10 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
onClick={handleSkip} onClick={handleSkip}
className="skip-button" className="skip-button"
> >
Skip (+{unlockSteps[Math.min(gameState.guesses.length + 1, unlockSteps.length - 1)] - unlockedSeconds}s) {gameState.guesses.length === 0 && !hasPlayedAudio
? 'Start'
: `Skip (+${unlockSteps[Math.min(gameState.guesses.length + 1, unlockSteps.length - 1)] - unlockedSeconds}s)`
}
</button> </button>
) : ( ) : (
<button <button