diff --git a/components/WaveformEditor.tsx b/components/WaveformEditor.tsx index 51a83e6..0165b9f 100644 --- a/components/WaveformEditor.tsx +++ b/components/WaveformEditor.tsx @@ -17,6 +17,8 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt const [isPlaying, setIsPlaying] = useState(false); const [playingSegment, setPlayingSegment] = useState(null); const [isPlayingFullTitle, setIsPlayingFullTitle] = useState(false); + const [pausedPosition, setPausedPosition] = useState(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(null); // Current playback position in seconds @@ -234,12 +236,22 @@ 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); + } else { + // Clear paused position if stopping completely + setPausedPosition(null); + setPausedType(null); + } sourceRef.current?.stop(); setIsPlaying(false); setPlayingSegment(null); setIsPlayingFullTitle(false); - setPlaybackPosition(null); + if (!savePosition) { + setPlaybackPosition(null); + } if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); animationFrameRef.current = null; @@ -307,35 +319,55 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt const handlePlayFull = () => { if (!audioBuffer || !audioContextRef.current) return; - // If full selection playback is already playing, stop it + // If full selection playback is already playing, pause it if (isPlaying && playingSegment === null && !isPlayingFullTitle) { - stopPlayback(); + stopPlayback(true); // Save position + setPausedType('selection'); 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 === '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); setIsPlayingFullTitle(false); - setPlaybackPosition(startTime); + 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; @@ -346,35 +378,55 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt const handlePlayFullTitle = () => { if (!audioBuffer || !audioContextRef.current) return; - // If full title playback is already playing, stop it + // If full title playback is already playing, pause it if (isPlaying && isPlayingFullTitle) { - stopPlayback(); + stopPlayback(true); // Save position + setPausedType('title'); return; } // Stop any current playback (segment, full selection, or full title) stopPlayback(); - // Start full title playback (from 0 to audioDuration) + // 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 = 0; + playbackOffsetRef.current = resumePosition; - source.start(0, 0, audioDuration); + source.start(0, resumePosition, remainingDuration); sourceRef.current = source; setIsPlaying(true); setPlayingSegment(null); setIsPlayingFullTitle(true); - setPlaybackPosition(0); + 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; @@ -470,7 +522,11 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt fontWeight: 'bold' }} > - {isPlaying && playingSegment === null && !isPlayingFullTitle ? '⏸ Pause' : '▶ Play Full Selection'} + {isPlaying && playingSegment === null && !isPlayingFullTitle + ? '⏸ Pause' + : (pausedType === 'selection' && pausedPosition !== null + ? '▶ Resume' + : '▶ Play Full Selection')}