|
|
|
|
@@ -133,6 +133,24 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
|
|
|
|
|
|
|
|
|
|
cumulativeTime = step;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Draw end marker for the last segment (at startTime + duration)
|
|
|
|
|
const endTime = startTime + duration;
|
|
|
|
|
const endPx = ((endTime - visibleStart) / visibleDuration) * width;
|
|
|
|
|
if (endPx >= 0 && endPx <= width) {
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
ctx.moveTo(endPx, 0);
|
|
|
|
|
ctx.lineTo(endPx, height);
|
|
|
|
|
ctx.stroke();
|
|
|
|
|
|
|
|
|
|
// Draw "End" label
|
|
|
|
|
ctx.setLineDash([]);
|
|
|
|
|
ctx.fillStyle = '#ef4444';
|
|
|
|
|
ctx.font = 'bold 12px sans-serif';
|
|
|
|
|
ctx.fillText('End', endPx + 3, 15);
|
|
|
|
|
ctx.setLineDash([5, 5]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx.setLineDash([]);
|
|
|
|
|
|
|
|
|
|
// Draw hover preview (semi-transparent)
|
|
|
|
|
@@ -287,30 +305,38 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
|
|
|
|
|
const handlePlayFull = () => {
|
|
|
|
|
if (!audioBuffer || !audioContextRef.current) return;
|
|
|
|
|
|
|
|
|
|
if (isPlaying) {
|
|
|
|
|
// If full playback is already playing, stop it
|
|
|
|
|
if (isPlaying && playingSegment === null) {
|
|
|
|
|
stopPlayback();
|
|
|
|
|
} else {
|
|
|
|
|
const source = audioContextRef.current.createBufferSource();
|
|
|
|
|
source.buffer = audioBuffer;
|
|
|
|
|
source.connect(audioContextRef.current.destination);
|
|
|
|
|
|
|
|
|
|
playbackStartTimeRef.current = audioContextRef.current.currentTime;
|
|
|
|
|
playbackOffsetRef.current = startTime;
|
|
|
|
|
|
|
|
|
|
source.start(0, startTime, duration);
|
|
|
|
|
sourceRef.current = source;
|
|
|
|
|
setIsPlaying(true);
|
|
|
|
|
setPlaybackPosition(startTime);
|
|
|
|
|
|
|
|
|
|
source.onended = () => {
|
|
|
|
|
setIsPlaying(false);
|
|
|
|
|
setPlaybackPosition(null);
|
|
|
|
|
if (animationFrameRef.current) {
|
|
|
|
|
cancelAnimationFrame(animationFrameRef.current);
|
|
|
|
|
animationFrameRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stop any current playback (segment or full)
|
|
|
|
|
stopPlayback();
|
|
|
|
|
|
|
|
|
|
// Start full playback
|
|
|
|
|
const source = audioContextRef.current.createBufferSource();
|
|
|
|
|
source.buffer = audioBuffer;
|
|
|
|
|
source.connect(audioContextRef.current.destination);
|
|
|
|
|
|
|
|
|
|
playbackStartTimeRef.current = audioContextRef.current.currentTime;
|
|
|
|
|
playbackOffsetRef.current = startTime;
|
|
|
|
|
|
|
|
|
|
source.start(0, startTime, duration);
|
|
|
|
|
sourceRef.current = source;
|
|
|
|
|
setIsPlaying(true);
|
|
|
|
|
setPlayingSegment(null); // Ensure playingSegment is null for full playback
|
|
|
|
|
setPlaybackPosition(startTime);
|
|
|
|
|
|
|
|
|
|
source.onended = () => {
|
|
|
|
|
setIsPlaying(false);
|
|
|
|
|
setPlayingSegment(null);
|
|
|
|
|
setPlaybackPosition(null);
|
|
|
|
|
if (animationFrameRef.current) {
|
|
|
|
|
cancelAnimationFrame(animationFrameRef.current);
|
|
|
|
|
animationFrameRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleZoomIn = () => setZoom(prev => Math.min(prev * 1.5, 10));
|
|
|
|
|
|