added waveform
This commit is contained in:
@@ -231,13 +231,30 @@ import html from '../utils/html.js';
|
||||
-->
|
||||
|
||||
<div style="margin: 20px 0;">
|
||||
<!-- Progress Bar -->
|
||||
<!-- Waveform Display -->
|
||||
<div style="margin-bottom: 20px;">
|
||||
<div style="display: flex; justify-content: space-between; font-size: 12px; color: #666; margin-bottom: 5px;">
|
||||
<span id="start-time">0:00</span>
|
||||
<span id="current-time">0:00</span>
|
||||
<span id="end-time">0:00</span>
|
||||
</div>
|
||||
<div style="position: relative; width: 100%; height: 120px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 8px; overflow: hidden;">
|
||||
<canvas id="waveform-canvas" style="width: 100%; height: 100%; display: block;"></canvas>
|
||||
<div id="playback-indicator" style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background-color: #ff4444;
|
||||
z-index: 10;
|
||||
cursor: grab;
|
||||
"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar (kept as fallback) -->
|
||||
<div style="margin-bottom: 20px; display: none;" id="fallback-progress">
|
||||
<div style="width: 100%; background-color: #ddd; border-radius: 10px; height: 8px; position: relative;">
|
||||
<div id="progress-bar" style="background-color: #007cba; height: 100%; border-radius: 10px; width: 0%; transition: width 0.1s ease;"></div>
|
||||
</div>
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user