Compare commits

...

8 Commits

Author SHA1 Message Date
Hördle Bot
aed300b1bb Bump version to 0.1.6.23 2025-12-05 23:55:24 +01:00
Hördle Bot
e93b3b9096 Keep playback cursor visible when pausing in waveform editor 2025-12-05 23:55:21 +01:00
Hördle Bot
cdd2ff15d5 Bump version to 0.1.6.22 2025-12-05 22:25:41 +01:00
Hördle Bot
adcfbfa811 Fix pause functionality for waveform editor playback buttons 2025-12-05 22:25:39 +01:00
Hördle Bot
0cdfe90476 Bump version to 0.1.6.21 2025-12-05 22:06:44 +01:00
Hördle Bot
1715ca02ed Remove duplicate back button from curator special editor 2025-12-05 22:06:42 +01:00
Hördle Bot
ece3991d37 Bump version to 0.1.6.20 2025-12-05 21:57:36 +01:00
Hördle Bot
fa3b64f490 Add 'Play Full Title' button to waveform editor 2025-12-05 21:57:34 +01:00
3 changed files with 129 additions and 26 deletions

View File

@@ -98,19 +98,6 @@ export default function CurateSpecialEditor({
return (
<div style={{ padding: '2rem', maxWidth: '1200px', margin: '0 auto' }}>
<div style={{ marginBottom: '2rem' }}>
<button
onClick={onBack}
style={{
padding: '0.5rem 1rem',
background: '#e5e7eb',
border: 'none',
borderRadius: '0.5rem',
cursor: 'pointer',
marginBottom: '1rem'
}}
>
{backLabel}
</button>
<h1 style={{ fontSize: '2rem', fontWeight: 'bold' }}>
{headerPrefix} {specialName}
</h1>

View File

@@ -16,6 +16,9 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
const [audioDuration, setAudioDuration] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
const [playingSegment, setPlayingSegment] = useState<number | null>(null);
const [isPlayingFullTitle, setIsPlayingFullTitle] = useState(false);
const [pausedPosition, setPausedPosition] = useState<number | null>(null); // Position when paused
const [pausedType, setPausedType] = useState<'selection' | 'title' | null>(null); // Type of playback that was paused
const [zoom, setZoom] = useState(1); // 1 = full view, higher = zoomed in
const [viewOffset, setViewOffset] = useState(0); // Offset in seconds for panning
const [playbackPosition, setPlaybackPosition] = useState<number | null>(null); // Current playback position in seconds
@@ -233,11 +236,21 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
setHoverPreviewTime(null);
};
const stopPlayback = () => {
const stopPlayback = (savePosition = false) => {
if (savePosition && playbackPosition !== null) {
// Save current position for resume
setPausedPosition(playbackPosition);
// Keep playbackPosition visible (don't set to null) so cursor stays visible
} else {
// Clear paused position if stopping completely
setPausedPosition(null);
setPausedType(null);
setPlaybackPosition(null);
}
sourceRef.current?.stop();
setIsPlaying(false);
setPlayingSegment(null);
setPlaybackPosition(null);
setIsPlayingFullTitle(false);
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
animationFrameRef.current = null;
@@ -305,33 +318,114 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
const handlePlayFull = () => {
if (!audioBuffer || !audioContextRef.current) return;
// If full playback is already playing, stop it
if (isPlaying && playingSegment === null) {
stopPlayback();
// If full selection playback is already playing, pause it
if (isPlaying && playingSegment === null && !isPlayingFullTitle) {
stopPlayback(true); // Save position
setPausedType('selection');
return;
}
// Stop any current playback (segment or full)
// Stop any current playback (segment, full selection, or full title)
stopPlayback();
// Start full playback
// Determine start position (resume from pause or start from beginning)
const resumePosition = pausedType === 'selection' && pausedPosition !== null
? pausedPosition
: startTime;
const remainingDuration = resumePosition >= startTime + duration
? 0
: (startTime + duration) - resumePosition;
if (remainingDuration <= 0) {
// Already finished, reset
setPausedPosition(null);
setPausedType(null);
return;
}
// Start full selection playback
const source = audioContextRef.current.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContextRef.current.destination);
playbackStartTimeRef.current = audioContextRef.current.currentTime;
playbackOffsetRef.current = startTime;
playbackOffsetRef.current = resumePosition;
source.start(0, startTime, duration);
source.start(0, resumePosition, remainingDuration);
sourceRef.current = source;
setIsPlaying(true);
setPlayingSegment(null); // Ensure playingSegment is null for full playback
setPlaybackPosition(startTime);
setPlayingSegment(null);
setIsPlayingFullTitle(false);
setPausedPosition(null);
setPausedType(null);
setPlaybackPosition(resumePosition);
source.onended = () => {
setIsPlaying(false);
setPlayingSegment(null);
setIsPlayingFullTitle(false);
setPlaybackPosition(null);
setPausedPosition(null);
setPausedType(null);
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
animationFrameRef.current = null;
}
};
};
const handlePlayFullTitle = () => {
if (!audioBuffer || !audioContextRef.current) return;
// If full title playback is already playing, pause it
if (isPlaying && isPlayingFullTitle) {
stopPlayback(true); // Save position
setPausedType('title');
return;
}
// Stop any current playback (segment, full selection, or full title)
stopPlayback();
// Determine start position (resume from pause or start from beginning)
const resumePosition = pausedType === 'title' && pausedPosition !== null
? pausedPosition
: 0;
const remainingDuration = resumePosition >= audioDuration
? 0
: audioDuration - resumePosition;
if (remainingDuration <= 0) {
// Already finished, reset
setPausedPosition(null);
setPausedType(null);
return;
}
// Start full title playback (from resumePosition to audioDuration)
const source = audioContextRef.current.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContextRef.current.destination);
playbackStartTimeRef.current = audioContextRef.current.currentTime;
playbackOffsetRef.current = resumePosition;
source.start(0, resumePosition, remainingDuration);
sourceRef.current = source;
setIsPlaying(true);
setPlayingSegment(null);
setIsPlayingFullTitle(true);
setPausedPosition(null);
setPausedType(null);
setPlaybackPosition(resumePosition);
source.onended = () => {
setIsPlaying(false);
setPlayingSegment(null);
setIsPlayingFullTitle(false);
setPlaybackPosition(null);
setPausedPosition(null);
setPausedType(null);
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
animationFrameRef.current = null;
@@ -427,7 +521,29 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
fontWeight: 'bold'
}}
>
{isPlaying && playingSegment === null ? '⏸ Pause' : '▶ Play Full Selection'}
{isPlaying && playingSegment === null && !isPlayingFullTitle
? '⏸ Pause'
: (pausedType === 'selection' && pausedPosition !== null
? '▶ Resume'
: '▶ Play Full Selection')}
</button>
<button
onClick={handlePlayFullTitle}
style={{
padding: '0.5rem 1rem',
background: '#10b981',
color: 'white',
border: 'none',
borderRadius: '0.5rem',
cursor: 'pointer',
fontWeight: 'bold'
}}
>
{isPlaying && isPlayingFullTitle
? '⏸ Pause'
: (pausedType === 'title' && pausedPosition !== null
? '▶ Resume'
: '▶ Play Full Title')}
</button>
<div style={{ fontSize: '0.875rem', color: '#666' }}>

View File

@@ -1,6 +1,6 @@
{
"name": "hoerdle",
"version": "0.1.6.19",
"version": "0.1.6.23",
"private": true,
"scripts": {
"dev": "next dev",