import React, { useEffect, useMemo, useState, useCallback, useRef, } from 'react'; import Dialog from '@mui/material/Dialog'; import DialogTitle from '@mui/material/DialogTitle'; import DialogContent from '@mui/material/DialogContent'; import DialogActions from '@mui/material/DialogActions'; import Slider from '@mui/material/Slider'; import ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import { useWavesurfer } from '@wavesurfer/react'; import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js'; import ZoomPlugin from 'wavesurfer.js/dist/plugins/zoom.esm.js'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import PauseIcon from '@mui/icons-material/Pause'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import DeleteIcon from '@mui/icons-material/Delete'; import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { ClipMetadata, PlaybackType } from '../../redux/types'; import { useAppSelector } from '../hooks'; import PlayStopIcon from './playStopIcon'; import PlayOverlapIcon from './playOverlapIcon'; export interface AudioTrimmerProps { metadata: ClipMetadata; onSave?: (metadata: ClipMetadata) => void; onDelete?: (metadata: ClipMetadata) => void; onMove?: (newCollection: string, metadata: ClipMetadata) => void; } export default function AudioTrimmer({ metadata, onSave, onDelete, onMove, }: AudioTrimmerProps) { const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: metadata.filename }); // Dialog state for editing name const [editDialogOpen, setEditDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [dropdownOpen, setDropdownOpen] = useState(false); const [nameInput, setNameInput] = useState(metadata.name); const [volumeInput, setVolumeInput] = useState(metadata.volume ?? 1); const collectionNames = useAppSelector((state) => state.collections.map((col) => col.name), ); useEffect(() => { setNameInput(metadata.name); }, [metadata.name]); const openEditDialog = () => setEditDialogOpen(true); const closeEditDialog = () => setEditDialogOpen(false); const handleDialogSave = () => { if (nameInput.trim() && nameInput !== metadata.name) { const updated = { ...metadata, name: nameInput.trim() }; if (onSave) onSave(updated); } closeEditDialog(); }; const [blobUrl, setBlobUrl] = useState(undefined); const containerRef = useRef(null); // const [clipStart, setClipStart] = useState(undefined); // const [clipEnd, setClipEnd] = useState(undefined); const plugins = useMemo( () => [ RegionsPlugin.create(), ZoomPlugin.create({ scale: 0.25, }), ], [], ); const fileBaseName = metadata.filename.split('\\').pop()?.split('/').pop() || 'Unknown'; const { wavesurfer, isReady, isPlaying } = useWavesurfer({ container: containerRef, height: 100, waveColor: '#ccb1ff', progressColor: '#6e44ba', hideScrollbar: true, url: blobUrl, plugins, }); // Add this ref to always have the latest metadata const metadataRef = useRef(metadata); useEffect(() => { metadataRef.current = metadata; }, [metadata]); const onRegionCreated = useCallback( (newRegion: any) => { if (wavesurfer === null) return; const allRegions = (plugins[0] as RegionsPlugin).getRegions(); let isNew = metadataRef.current.startTime === undefined; allRegions.forEach((region) => { if (region.id !== newRegion.id) { if ( region.start === newRegion.start && region.end === newRegion.end ) { newRegion.remove(); return; } region.remove(); isNew = !(region.start === 0 && region.end === 0); // console.log('Region replace:', newRegion, region); } }); if (isNew) { console.log('Region created:', metadataRef.current); const updated = { ...metadataRef.current, startTime: newRegion.start, endTime: newRegion.end, }; if (onSave) { onSave(updated); } } }, [plugins, wavesurfer, onSave], ); const onRegionUpdated = useCallback( (newRegion: any) => { if (wavesurfer === null) return; const updated = { ...metadataRef.current, startTime: newRegion.start, endTime: newRegion.end, }; if (onSave) { onSave(updated); } }, [onSave, wavesurfer], ); useEffect(() => { const plugin = plugins[0] as RegionsPlugin; if (!isReady) return; // console.log('ready, setting up regions plugin', plugin, isReady); if ( metadataRef.current.startTime !== undefined && metadataRef.current.endTime !== undefined ) { // setClipStart(metadata.startTime); // setClipEnd(metadata.endTime); // console.log('Adding region from metadata:', metadata);= const allRegions = plugin.getRegions(); // console.log('Existing regions:', allRegions); if ( allRegions.length === 0 || (allRegions.length === 1 && allRegions[0].start === 0 && allRegions[0].end === 0) ) { // console.log('adding region from metadata:', metadataRef.current); plugin.addRegion({ start: metadataRef.current.startTime, end: metadataRef.current.endTime, color: 'rgba(132, 81, 224, 0.3)', drag: false, resize: true, }); } } else { // setClipStart(0); // setClipEnd(wavesurfer ? wavesurfer.getDuration() : 0); } }, [isReady, plugins]); useEffect(() => { const plugin = plugins[0] as RegionsPlugin; plugin.unAll(); plugin.on('region-created', onRegionCreated); plugin.on('region-updated', onRegionUpdated); plugin.enableDragSelection({ color: 'rgba(132, 81, 224, 0.3)', }); }, [onRegionCreated, onRegionUpdated, plugins]); useEffect(() => { let url: string | null = null; async function fetchAudio() { // console.log('Loading audio buffer for file:', filename); const buffer = await window.audio.loadAudioBuffer(metadata.filename); // console.log('Received buffer:', buffer.buffer); if (buffer.buffer && !buffer.error) { const audioData = buffer.buffer ? new Uint8Array(buffer.buffer) : buffer; url = URL.createObjectURL(new Blob([audioData])); // console.log('Created blob URL:', url); setBlobUrl(url); } } fetchAudio(); return () => { if (url) URL.revokeObjectURL(url); }; }, [metadata.filename]); const onPlayPause = () => { if (wavesurfer === null) return; if (isPlaying) { wavesurfer.pause(); } else { const allRegions = (plugins[0] as RegionsPlugin).getRegions(); if (allRegions.length > 0) { wavesurfer.setVolume(metadata.volume ?? 1); wavesurfer.play(allRegions[0].start, allRegions[0].end); } else { wavesurfer.play(); } } }; const formatTime = (seconds: number) => { const minutes = Math.floor(seconds / 60); const secs = (seconds % 60).toFixed(0); return `${minutes}:${secs.padStart(2, '0')}`; }; return (
{/*
*/}
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openEditDialog(); } }} title="Click to edit name" tabIndex={0} role="button" style={{ outline: 'none' }} > {metadata.name} {fileBaseName}
Edit Clip Name