-
+
+
+
+
@@ -303,9 +320,19 @@ import html from '../utils/html.js';
const markedPointDisplay = document.getElementById('marked-point-display');
const submitButton = document.getElementById('submit-lift-point-btn');
+ // Waveform elements
+ const waveformCanvas = document.getElementById('waveform-canvas');
+ const playbackIndicator = document.getElementById('playback-indicator');
+ const ctx = waveformCanvas.getContext('2d');
+
let audioDuration = 0;
let liftPointTime = null;
let isPlaying = false;
+ let waveformData = null;
+ let canvasWidth = 0;
+ let canvasHeight = 0;
+ let isDragging = false;
+ let wasPlayingBeforeDrag = false;
// Format time as MM:SS
const formatTime = (seconds) => {
@@ -321,13 +348,100 @@ import html from '../utils/html.js';
return `${minutes}:${secs.padStart(5, '0')}s`;
};
+ // Setup canvas dimensions
+ const setupCanvas = () => {
+ const rect = waveformCanvas.getBoundingClientRect();
+ const dpr = window.devicePixelRatio || 1;
+ canvasWidth = rect.width * dpr;
+ canvasHeight = rect.height * dpr;
+
+ waveformCanvas.width = canvasWidth;
+ waveformCanvas.height = canvasHeight;
+ ctx.scale(dpr, dpr);
+ };
+
+ // Generate waveform data from audio
+ const generateWaveform = async () => {
+ try {
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
+ const arrayBuffer = await audioBlob.arrayBuffer();
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
+
+ const rawData = audioBuffer.getChannelData(0);
+ const samples = Math.floor(canvasWidth / 2); // Sample every 2 pixels
+ const blockSize = Math.floor(rawData.length / samples);
+ const filteredData = [];
+
+ for (let i = 0; i < samples; i++) {
+ let blockStart = blockSize * i;
+ let sum = 0;
+ for (let j = 0; j < blockSize; j++) {
+ sum += Math.abs(rawData[blockStart + j]);
+ }
+ filteredData.push(sum / blockSize);
+ }
+
+ waveformData = filteredData;
+ drawWaveform();
+ } catch (error) {
+ console.error('Error generating waveform:', error);
+ // Fallback to progress bar if waveform fails
+ document.getElementById('fallback-progress').style.display = 'block';
+ waveformCanvas.parentElement.style.display = 'none';
+ }
+ };
+
+ // Draw waveform on canvas
+ const drawWaveform = () => {
+ if (!waveformData || !ctx) return;
+
+ const rect = waveformCanvas.getBoundingClientRect();
+ const width = rect.width;
+ const height = rect.height;
+
+ ctx.clearRect(0, 0, width, height);
+ ctx.fillStyle = '#e0e0e0';
+ ctx.fillRect(0, 0, width, height);
+
+ const barWidth = width / waveformData.length;
+ const maxAmplitude = Math.max(...waveformData);
+
+ ctx.fillStyle = '#007cba';
+ for (let i = 0; i < waveformData.length; i++) {
+ const barHeight = (waveformData[i] / maxAmplitude) * height * 0.8;
+ const x = i * barWidth;
+ const y = (height - barHeight) / 2;
+
+ ctx.fillRect(x, y, Math.max(1, barWidth - 1), barHeight);
+ }
+ };
+
+ // Seek to specific time
+ const seekTo = (seconds) => {
+ if (audioDuration > 0) {
+ audio.currentTime = Math.max(0, Math.min(seconds, audioDuration));
+ updateDisplay();
+ }
+ };
+
// Update display function
const updateDisplay = () => {
const currentTime = audio.currentTime;
const progress = audioDuration > 0 ? (currentTime / audioDuration * 100) : 0;
currentTimeDisplay.textContent = formatTime(currentTime);
- progressBar.style.width = `${progress}%`;
+
+ // Update progress bar (fallback)
+ if (progressBar) {
+ progressBar.style.width = `${progress}%`;
+ }
+
+ // Update waveform playback indicator (only if not dragging)
+ if (playbackIndicator && waveformCanvas && !isDragging && audioDuration > 0) {
+ const rect = waveformCanvas.getBoundingClientRect();
+ const indicatorPosition = (progress / 100) * rect.width;
+ playbackIndicator.style.left = `${Math.max(0, Math.min(indicatorPosition, rect.width - 2))}px`;
+ }
};
// Update submit button state
@@ -413,6 +527,9 @@ import html from '../utils/html.js';
endTimeDisplay.textContent = formatTime(audioDuration);
updateDisplay();
updateSubmitButton();
+ // Initialize waveform
+ setupCanvas();
+ generateWaveform();
console.log('Using stored duration:', audioDuration);
return;
}
@@ -424,6 +541,9 @@ import html from '../utils/html.js';
endTimeDisplay.textContent = formatTime(audioDuration);
updateDisplay();
updateSubmitButton();
+ // Initialize waveform
+ setupCanvas();
+ generateWaveform();
console.log('Audio controls setup complete, duration:', audioDuration);
} else if (retryCount < maxRetries) {
// Retry after a short delay if duration is not available
@@ -438,6 +558,9 @@ import html from '../utils/html.js';
endTimeDisplay.textContent = formatTime(audioDuration);
updateDisplay();
updateSubmitButton();
+ // Initialize waveform even with estimated duration
+ setupCanvas();
+ generateWaveform();
console.log('Using estimated duration:', audioDuration);
}
};
@@ -466,6 +589,74 @@ import html from '../utils/html.js';
// Force load the audio
audio.load();
+ // Handle window resize to redraw waveform
+ window.addEventListener('resize', () => {
+ if (waveformData) {
+ setupCanvas();
+ drawWaveform();
+ }
+ });
+
+ // Mouse event handlers for dragging the playback indicator
+ const handleMouseDown = (e) => {
+ isDragging = true;
+ wasPlayingBeforeDrag = isPlaying;
+ if (isPlaying) {
+ audio.pause();
+ }
+ playbackIndicator.style.cursor = 'grabbing';
+
+ e.preventDefault();
+ handleMouseMove(e); // Immediately update position
+ };
+
+ const handleMouseMove = (e) => {
+ if (!isDragging || !waveformCanvas || audioDuration === 0) return;
+
+ const rect = waveformCanvas.getBoundingClientRect();
+ const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width - 2));
+ const progress = x / rect.width;
+ const newTime = progress * audioDuration;
+
+ // Update indicator position immediately
+ playbackIndicator.style.left = `${x}px`;
+
+ // Update time display
+ currentTimeDisplay.textContent = formatTime(newTime);
+
+ // Update audio position
+ seekTo(newTime);
+ };
+
+ const handleMouseUp = () => {
+ if (!isDragging) return;
+
+ isDragging = false;
+ playbackIndicator.style.cursor = 'grab';
+
+ // Resume playback if it was playing before drag
+ if (wasPlayingBeforeDrag) {
+ audio.play();
+ }
+ };
+
+ // Add event listeners for dragging
+ playbackIndicator.addEventListener('mousedown', handleMouseDown);
+ document.addEventListener('mousemove', handleMouseMove);
+ document.addEventListener('mouseup', handleMouseUp);
+
+ // Also allow clicking anywhere on the waveform to seek
+ waveformCanvas.addEventListener('click', (e) => {
+ if (isDragging) return; // Don't handle click if we're dragging
+
+ const rect = waveformCanvas.getBoundingClientRect();
+ const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width - 2));
+ const progress = x / rect.width;
+ const newTime = progress * audioDuration;
+
+ seekTo(newTime);
+ });
+
// Speed control buttons (commented out - using fixed 0.5x speed)
/*
const speedButtons = document.querySelectorAll('.speed-btn');