optional waveform

This commit is contained in:
2025-07-25 15:16:46 +02:00
parent 3eec087640
commit 776f71dac0

View File

@@ -73,6 +73,9 @@ import html from '../utils/html.js';
return;
}
// Check if waveform should be shown
const showWaveform = import.meta.env.VITE_SHOW_WAVEFORM === 'true';
display_element.innerHTML = `
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@@ -125,6 +128,10 @@ import html from '../utils/html.js';
<ul class="list-inside list-disc ml-5 mt-1 space-y-1 text-gray-600">
<li><span class="key-icon space">SPACE</span> Play/Pause</li>
<li><span class="key-icon">ENTER</span> Mark lift point</li>
${showWaveform ? `
<li><span class="key-icon"><i class="fas fa-arrow-left"></i></span> Skip back 0.1s</li>
<li><span class="key-icon"><i class="fas fa-arrow-right"></i></span> Skip forward 0.1s</li>
` : ''}
</ul>
@@ -183,6 +190,7 @@ import html from '../utils/html.js';
-->
</div>
${showWaveform ? `
<div style="margin: 20px 0; display: flex; gap: 10px; align-items: center; justify-content: center; flex-wrap: wrap;">
<button id="skip-back-btn" style="
padding: 8px 16px;
@@ -204,6 +212,7 @@ import html from '../utils/html.js';
cursor: pointer;
">Skip Forward 0.1s <span class="key-icon"><i class="fas fa-arrow-right"></i></span> ⏩</button>
</div>
` : ''}
<!--
<div id="current-time-display" style="
@@ -222,6 +231,7 @@ import html from '../utils/html.js';
-->
<div style="margin: 20px 0;">
${showWaveform ? `
<!-- Waveform Display -->
<div style="margin-bottom: 20px;">
<div style="display: flex; justify-content: space-between; font-size: 12px; color: #666; margin-bottom: 5px;">
@@ -243,9 +253,43 @@ import html from '../utils/html.js';
"></div>
</div>
</div>
` : `
<!-- Simple time display with invisible scrubbing area when waveform is disabled -->
<div style="margin-bottom: 20px;">
<div style="display: flex; justify-content: center; font-size: 14px; color: #666; margin-bottom: 10px;">
<span>Current time: <span id="current-time">0:00</span> / <span id="end-time">0:00</span></span>
</div>
<!-- Invisible scrubbing area -->
<div id="scrub-area" style="
position: relative;
width: 100%;
height: 40px;
background: transparent;
border: 2px dashed #ccc;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 12px;
">
Click anywhere to seek
<div id="scrub-indicator" style="
position: absolute;
top: 0;
left: 0;
width: 2px;
height: 100%;
background-color: #ff4444;
z-index: 10;
"></div>
</div>
</div>
`}
<!-- Progress Bar (kept as fallback) -->
<div style="margin-bottom: 20px; display: none;" id="fallback-progress">
<div style="margin-bottom: 20px; display: ${showWaveform ? 'none' : 'block'};" 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>
@@ -294,10 +338,10 @@ import html from '../utils/html.js';
console.log('Audio source set to blob URL:', audioData);
console.log('Audio blob size:', audioBlob.size, 'bytes');
this.setupMarkingEvents(recordingData, audioBlob);
this.setupMarkingEvents(recordingData, audioBlob, showWaveform);
}
setupMarkingEvents(recordingData, audioBlob) {
setupMarkingEvents(recordingData, audioBlob, showWaveform = true) {
const audio = document.getElementById('playback-audio');
const playPauseBtn = document.getElementById('play-pause-btn');
// Progress bar elements
@@ -314,7 +358,11 @@ import html from '../utils/html.js';
// Waveform elements
const waveformCanvas = document.getElementById('waveform-canvas');
const playbackIndicator = document.getElementById('playback-indicator');
const ctx = waveformCanvas.getContext('2d');
const ctx = waveformCanvas ? waveformCanvas.getContext('2d') : null;
// Scrubbing area elements (for when waveform is disabled)
const scrubArea = document.getElementById('scrub-area');
const scrubIndicator = document.getElementById('scrub-indicator');
let audioDuration = 0;
let liftPointTime = null;
@@ -451,7 +499,9 @@ import html from '../utils/html.js';
const currentTime = audio.currentTime;
const progress = audioDuration > 0 ? (currentTime / audioDuration * 100) : 0;
currentTimeDisplay.textContent = formatTime(currentTime);
if (currentTimeDisplay) {
currentTimeDisplay.textContent = formatTime(currentTime);
}
// Update progress bar (fallback)
if (progressBar) {
@@ -459,11 +509,18 @@ import html from '../utils/html.js';
}
// Update waveform playback indicator (only if not dragging)
if (playbackIndicator && waveformCanvas && !isDragging && audioDuration > 0) {
if (showWaveform && 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 scrub area indicator when waveform is disabled
if (!showWaveform && scrubIndicator && scrubArea && !isDragging && audioDuration > 0) {
const rect = scrubArea.getBoundingClientRect();
const indicatorPosition = (progress / 100) * rect.width;
scrubIndicator.style.left = `${Math.max(0, Math.min(indicatorPosition, rect.width - 2))}px`;
}
};
// Update submit button state
@@ -546,12 +603,14 @@ import html from '../utils/html.js';
// Use stored duration if available and valid
if (storedDuration && isFinite(storedDuration) && storedDuration > 0) {
audioDuration = storedDuration;
startTimeDisplay.textContent = '0:00';
endTimeDisplay.textContent = formatTime(audioDuration);
if (startTimeDisplay) startTimeDisplay.textContent = '0:00';
if (endTimeDisplay) endTimeDisplay.textContent = formatTime(audioDuration);
updateDisplay();
updateSubmitButton();
// Initialize waveform
generateWaveform();
// Initialize waveform only if enabled
if (showWaveform) {
generateWaveform();
}
console.log('Using stored duration:', audioDuration);
return;
}
@@ -559,12 +618,14 @@ import html from '../utils/html.js';
// Otherwise try to get duration from audio element (reject Infinity)
if (audio.duration && isFinite(audio.duration) && audio.duration > 0 && audio.duration !== Infinity) {
audioDuration = audio.duration;
startTimeDisplay.textContent = '0:00';
endTimeDisplay.textContent = formatTime(audioDuration);
if (startTimeDisplay) startTimeDisplay.textContent = '0:00';
if (endTimeDisplay) endTimeDisplay.textContent = formatTime(audioDuration);
updateDisplay();
updateSubmitButton();
// Initialize waveform
generateWaveform();
// Initialize waveform only if enabled
if (showWaveform) {
generateWaveform();
}
console.log('Audio controls setup complete, duration:', audioDuration);
} else if (retryCount < maxRetries) {
// Retry after a short delay if duration is not available
@@ -575,12 +636,14 @@ import html from '../utils/html.js';
// Fallback: estimate duration based on blob size (rough approximation)
const estimatedDuration = Math.max(1, audioBlob.size / 8000); // ~8KB per second rough estimate
audioDuration = estimatedDuration;
startTimeDisplay.textContent = '0:00';
endTimeDisplay.textContent = formatTime(audioDuration);
if (startTimeDisplay) startTimeDisplay.textContent = '0:00';
if (endTimeDisplay) endTimeDisplay.textContent = formatTime(audioDuration);
updateDisplay();
updateSubmitButton();
// Initialize waveform even with estimated duration
generateWaveform();
// Initialize waveform even with estimated duration (only if enabled)
if (showWaveform) {
generateWaveform();
}
console.log('Using estimated duration:', audioDuration);
}
};
@@ -609,14 +672,16 @@ import html from '../utils/html.js';
// Force load the audio
audio.load();
// Handle window resize to redraw waveform
window.addEventListener('resize', () => {
if (waveformData) {
if (setupCanvas()) {
drawWaveform();
// Handle window resize to redraw waveform (only if waveform is enabled)
if (showWaveform) {
window.addEventListener('resize', () => {
if (waveformData) {
if (setupCanvas()) {
drawWaveform();
}
}
}
});
});
}
// Mouse event handlers for dragging the playback indicator
const handleMouseDown = (e) => {
@@ -625,25 +690,41 @@ import html from '../utils/html.js';
if (isPlaying) {
audio.pause();
}
playbackIndicator.style.cursor = 'grabbing';
if (showWaveform && playbackIndicator) {
playbackIndicator.style.cursor = 'grabbing';
}
e.preventDefault();
handleMouseMove(e); // Immediately update position
};
const handleMouseMove = (e) => {
if (!isDragging || !waveformCanvas || audioDuration === 0) return;
if (!isDragging || audioDuration === 0) return;
let rect, indicator;
if (showWaveform && waveformCanvas) {
rect = waveformCanvas.getBoundingClientRect();
indicator = playbackIndicator;
} else if (!showWaveform && scrubArea) {
rect = scrubArea.getBoundingClientRect();
indicator = scrubIndicator;
} else {
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`;
if (indicator) {
indicator.style.left = `${x}px`;
}
// Update time display
currentTimeDisplay.textContent = formatTime(newTime);
if (currentTimeDisplay) {
currentTimeDisplay.textContent = formatTime(newTime);
}
// Update audio position
seekTo(newTime);
@@ -653,7 +734,9 @@ import html from '../utils/html.js';
if (!isDragging) return;
isDragging = false;
playbackIndicator.style.cursor = 'grab';
if (showWaveform && playbackIndicator) {
playbackIndicator.style.cursor = 'grab';
}
// Resume playback if it was playing before drag
if (wasPlayingBeforeDrag) {
@@ -662,21 +745,40 @@ import html from '../utils/html.js';
};
// Add event listeners for dragging
playbackIndicator.addEventListener('mousedown', handleMouseDown);
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
if (showWaveform && playbackIndicator) {
playbackIndicator.addEventListener('mousedown', handleMouseDown);
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
} else if (!showWaveform && scrubIndicator) {
scrubIndicator.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);
});
// Also allow clicking anywhere to seek
if (showWaveform && waveformCanvas) {
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);
});
} else if (!showWaveform && scrubArea) {
scrubArea.addEventListener('click', (e) => {
if (isDragging) return; // Don't handle click if we're dragging
const rect = scrubArea.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)
/*
@@ -699,18 +801,22 @@ import html from '../utils/html.js';
});
*/
// Skip buttons (0.1 second increments)
skipBackBtn.addEventListener('click', () => {
const skipAmount = 0.1; // 0.1 seconds
audio.currentTime = Math.max(0, audio.currentTime - skipAmount);
updateDisplay();
});
// Skip buttons (0.1 second increments) - only if waveform is enabled
if (showWaveform && skipBackBtn) {
skipBackBtn.addEventListener('click', () => {
const skipAmount = 0.1; // 0.1 seconds
audio.currentTime = Math.max(0, audio.currentTime - skipAmount);
updateDisplay();
});
}
skipForwardBtn.addEventListener('click', () => {
const skipAmount = 0.1; // 0.1 seconds
audio.currentTime = Math.min(audioDuration, audio.currentTime + skipAmount);
updateDisplay();
});
if (showWaveform && skipForwardBtn) {
skipForwardBtn.addEventListener('click', () => {
const skipAmount = 0.1; // 0.1 seconds
audio.currentTime = Math.min(audioDuration, audio.currentTime + skipAmount);
updateDisplay();
});
}
// Play/Pause button
playPauseBtn.addEventListener('click', () => {
@@ -757,19 +863,23 @@ import html from '../utils/html.js';
break;
case 'ArrowLeft':
e.preventDefault();
// Skip back with left arrow (0.1 seconds)
const skipBackAmount = 0.1;
audio.currentTime = Math.max(0, audio.currentTime - skipBackAmount);
updateDisplay();
if (showWaveform) {
e.preventDefault();
// Skip back with left arrow (0.1 seconds)
const skipBackAmount = 0.1;
audio.currentTime = Math.max(0, audio.currentTime - skipBackAmount);
updateDisplay();
}
break;
case 'ArrowRight':
e.preventDefault();
// Skip forward with right arrow (0.1 seconds)
const skipForwardAmount = 0.1;
audio.currentTime = Math.min(audioDuration, audio.currentTime + skipForwardAmount);
updateDisplay();
if (showWaveform) {
e.preventDefault();
// Skip forward with right arrow (0.1 seconds)
const skipForwardAmount = 0.1;
audio.currentTime = Math.min(audioDuration, audio.currentTime + skipForwardAmount);
updateDisplay();
}
break;
case 'Enter':