murmuraba
Version:
Real-time audio noise reduction with advanced chunked processing for web applications
64 lines (63 loc) • 7.4 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback } from 'react';
import { ChunkHeader } from '../chunk-results/chunk-header/chunk-header';
import { ProcessingMetrics } from '../chunk-results/processing-metrics/processing-metrics';
import { FileInfo } from '../chunk-results/file-info/file-info';
import { VadTimeline } from '../chunk-results/vad-timeline/vad-timeline';
import { AudioControls } from '../chunk-results/audio-controls/audio-controls';
import { formatDuration, formatPercentage, calculateChunkStats } from '../chunk-results/formatters';
import './chunk-processing-results.css';
/**
* Professional chunk processing results component that displays processed audio chunks
* with playback controls, export options, and detailed metrics.
*/
export function ChunkProcessingResults({ chunks, averageNoiseReduction, selectedChunk, onTogglePlayback, onToggleExpansion, onClearAll, onDownloadChunk, className = '', }) {
const chunkStats = calculateChunkStats(chunks);
// Handle clear all with confirmation
const handleClearAll = useCallback(() => {
if (chunks.length === 0)
return;
const confirmed = window.confirm(`Are you sure you want to delete all ${chunks.length} recorded chunks? This action cannot be undone.`);
if (confirmed) {
onClearAll();
}
}, [chunks.length, onClearAll]);
// Handle download with error handling
const handleDownload = useCallback(async (chunkId, format, audioType) => {
try {
await onDownloadChunk(chunkId, format, audioType);
}
catch (error) {
console.error(`Failed to download ${format.toUpperCase()}:`, error);
// Could add toast notification here
}
}, [onDownloadChunk]);
// Handle playback toggle with error handling
const handlePlaybackToggle = useCallback(async (chunkId, audioType) => {
try {
await onTogglePlayback(chunkId, audioType);
}
catch (error) {
console.error('Failed to toggle playback:', error);
// Could add toast notification here
}
}, [onTogglePlayback]);
// Handle keyboard events for accessibility
const handleKeyDown = useCallback((event, action) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
action();
}
}, []);
// Render empty state
if (chunks.length === 0) {
return (_jsx("section", { className: `chunk-results chunk-results--empty ${className}`.trim(), children: _jsxs("div", { className: "chunk-results__empty-state", children: [_jsx("div", { className: "empty-state__icon", "aria-hidden": "true", children: "\uD83C\uDFB5" }), _jsx("h2", { className: "empty-state__title", children: "No recordings yet" }), _jsx("p", { className: "empty-state__subtitle", children: "Start recording to see processed chunks here. Each chunk will show detailed metrics and allow you to play, compare, and export the audio." })] }) }));
}
return (_jsxs("section", { className: `chunk-results ${className}`.trim(), role: "region", "aria-label": "Processing Results", children: [_jsxs("div", { className: "chunk-results__header", children: [_jsxs("div", { className: "header__info", children: [_jsx("h2", { className: "header__title", children: "\uD83C\uDFAF Processing Results" }), _jsxs("div", { className: "header__stats", children: [_jsxs("span", { className: "stat-badge", children: [_jsx("strong", { children: chunks.length }), " chunks"] }), _jsxs("span", { className: "stat-badge", children: [_jsx("strong", { children: formatDuration(chunkStats?.totalDuration || 0) }), " total"] }), _jsxs("span", { className: "stat-badge stat-badge--highlight", children: [_jsx("strong", { children: formatPercentage(averageNoiseReduction) }), " avg noise reduction"] })] })] }), chunks.length > 0 && (_jsxs("button", { className: "btn btn-ghost btn--destructive", onClick: handleClearAll, onKeyDown: (e) => handleKeyDown(e, handleClearAll), "aria-label": `Clear all ${chunks.length} chunks`, type: "button", children: [_jsx("span", { className: "btn__icon", "aria-hidden": "true", children: "\uD83D\uDDD1\uFE0F" }), _jsx("span", { children: "Clear All" })] }))] }), _jsx("div", { className: "chunk-results__list", role: "list", children: chunks.map((chunk, index) => {
const isSelected = selectedChunk === chunk.id;
const hasProcessedAudio = Boolean(chunk.processedAudioUrl);
const hasOriginalAudio = Boolean(chunk.originalAudioUrl);
const isValid = chunk.isValid !== false;
return (_jsxs("div", { "data-chunk-id": chunk.id, "data-testid": `chunk-${chunk.id}`, className: `chunk ${isSelected ? 'chunk--selected' : ''} ${!isValid ? 'chunk--invalid' : ''}`.trim(), role: "listitem", children: [_jsx(ChunkHeader, { index: index, duration: chunk.duration, noiseReduction: chunk.metrics.noiseReductionLevel, processingLatency: chunk.metrics.processingLatency, averageVad: chunk.averageVad, vadData: chunk.vadData, isValid: isValid, isPlaying: chunk.isPlaying, isExpanded: chunk.isExpanded, hasProcessedAudio: hasProcessedAudio, onTogglePlayback: () => handlePlaybackToggle(chunk.id, 'processed'), onToggleExpansion: () => onToggleExpansion(chunk.id), onKeyDown: handleKeyDown, formatTime: formatDuration, formatPercentage: formatPercentage }), !isValid && chunk.errorMessage && (_jsxs("div", { className: "chunk__error", role: "alert", children: [_jsx("span", { className: "error__icon", "aria-hidden": "true", children: "\u26A0\uFE0F" }), _jsx("span", { className: "error__message", children: chunk.errorMessage })] })), _jsxs("div", { className: "chunk__details", "aria-label": "Chunk details", style: { display: chunk.isExpanded ? 'block' : 'none' }, children: [_jsx(ProcessingMetrics, { inputLevel: chunk.metrics.inputLevel, outputLevel: chunk.metrics.outputLevel, frameCount: chunk.metrics.frameCount, droppedFrames: chunk.metrics.droppedFrames }), _jsx(FileInfo, { originalSize: chunk.originalSize, processedSize: chunk.processedSize, noiseRemoved: chunk.noiseRemoved }), chunk.vadData && chunk.vadData.length > 0 ? (_jsx(VadTimeline, { vadData: chunk.vadData, chunkId: chunk.id }, `vad-${chunk.id}-${chunk.vadData.length}`)) : (_jsxs("div", { className: "details__section", children: [_jsx("h4", { className: "section__title", children: "\uD83D\uDCC8 Voice Activity Detection (VAD) Timeline" }), _jsxs("div", { style: { padding: '2rem', textAlign: 'center', color: '#a0a0a0' }, children: [_jsx("span", { style: { fontSize: '2rem' }, children: "\u26A0\uFE0F" }), _jsx("p", { children: "No VAD data available for this chunk" }), !chunk.isValid && _jsx("p", { style: { fontSize: '0.875rem', marginTop: '0.5rem' }, children: "Chunk processing failed" })] })] })), _jsx(AudioControls, { chunkId: chunk.id, index: index, isPlaying: chunk.isPlaying, hasProcessedAudio: hasProcessedAudio, hasOriginalAudio: hasOriginalAudio, isValid: isValid, onTogglePlayback: (audioType) => handlePlaybackToggle(chunk.id, audioType), onDownload: (format, audioType) => handleDownload(chunk.id, format, audioType), processedAudioUrl: chunk.processedAudioUrl, originalAudioUrl: chunk.originalAudioUrl, currentlyPlayingType: chunk.currentlyPlayingType })] })] }, chunk.id));
}) })] }));
}