use wavesurefer and some styling

This commit is contained in:
michalcourson
2026-02-06 19:46:33 -05:00
parent 17bace5eaf
commit c292350b25
10 changed files with 883 additions and 261 deletions

View File

@ -1,11 +1,12 @@
import React, { useEffect, useMemo, useState } from 'react';
// import WaveSurfer from 'wavesurfer.js';
import React, {
useEffect,
useMemo,
useState,
useCallback,
useRef,
} from 'react';
import Regions from 'wavesurfer.js/dist/plugins/regions.esm.js';
// import { useWavesurfer } from '@wavesurfer/react';
import { BlockList } from 'net';
import WavesurferPlayer from '@wavesurfer/react';
// import { IpcRenderer } from 'electron';
import { useWavesurfer } from '@wavesurfer/react';
export interface AudioTrimmerProps {
filePath: string;
@ -17,18 +18,59 @@ export interface AudioTrimmerProps {
onDelete?: () => void;
}
function getBaseName(filePath: string) {
return filePath.split(/[\\/]/).pop() || filePath;
}
export default function AudioTrimmer({ filePath }: { filePath: string }) {
export default function AudioTrimmer({
filePath,
section,
title,
trimStart,
trimEnd,
onSave,
onDelete,
}: AudioTrimmerProps) {
const [blobUrl, setBlobUrl] = useState<string | undefined>(undefined);
const [wavesurfer, setWavesurfer] = useState(null);
const [isPlaying, setIsPlaying] = useState(false);
const containerRef = useRef(null);
const plugins = useMemo(() => {
return [Regions.create()];
}, []);
const plugins = useMemo(() => [Regions.create()], []);
const { wavesurfer, isReady, isPlaying } = useWavesurfer({
container: containerRef,
height: 100,
waveColor: '#ccb1ff',
progressColor: '#6e44ba',
url: blobUrl,
plugins,
});
const onRegionCreated = useCallback(
(newRegion: any) => {
if (wavesurfer === null) return;
const allRegions = plugins[0].getRegions();
allRegions.forEach((region) => {
if (region.id !== newRegion.id) {
region.remove();
}
});
},
[plugins, wavesurfer],
);
useEffect(() => {
console.log('ready, setting up regions plugin', wavesurfer);
if (trimStart !== undefined && trimEnd !== undefined) {
plugins[0].addRegion({
start: trimStart,
end: trimEnd,
color: 'rgba(132, 81, 224, 0.3)',
drag: false,
resize: true,
});
}
plugins[0].enableDragSelection({
color: 'rgba(132, 81, 224, 0.3)',
});
plugins[0].on('region-created', onRegionCreated);
}, [isReady, plugins, wavesurfer, onRegionCreated, trimStart, trimEnd]);
useEffect(() => {
let url: string | null = null;
@ -40,7 +82,6 @@ export default function AudioTrimmer({ filePath }: { filePath: string }) {
const audioData = buffer.data ? new Uint8Array(buffer.data) : buffer;
url = URL.createObjectURL(new Blob([audioData]));
setBlobUrl(url);
console.log('Audio blob URL created:', url);
}
}
fetchAudio();
@ -49,187 +90,32 @@ export default function AudioTrimmer({ filePath }: { filePath: string }) {
};
}, [filePath]);
const onReady = (ws) => {
setWavesurfer(ws);
setIsPlaying(false);
// setDuration(ws.getDuration());
// console.log('Wavesurfer ready, duration:', ws.getDuration());
// console.log('Wavesurfer regions plugin:', ws.plugins[0]);
ws.plugins[0].addRegion?.({
start: 0,
end: ws.getDuration(),
color: 'rgba(132, 81, 224, 0.3)',
drag: false,
resize: true,
});
};
const onPlayPause = () => {
if (wavesurfer === null) return;
wavesurfer.playPause();
if (isPlaying) {
wavesurfer.pause();
} else {
const allRegions = plugins[0].getRegions();
if (allRegions.length > 0) {
wavesurfer.play(allRegions[0].start, allRegions[0].end);
} else {
wavesurfer.play();
}
}
};
// useEffect(() => {
// if (!containerRef.current || !blobUrl) return;
// const ws = WaveSurfer.create({
// container: containerRef.current,
// waveColor: 'purple',
// url: blobUrl,
// height: 100,
// width: 600,
// });
// return () => ws.destroy();
// }, [blobUrl]);
return (
<div>
{/* <div ref={containerRef} /> */}
<WavesurferPlayer
height={100}
width={600}
url={blobUrl}
onReady={onReady}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
plugins={plugins}
/>
<button type="button" onClick={onPlayPause}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<div className="shadow-[0_4px_6px_rgba(0,0,0,0.5)] m-2 p-4 rounded-lg bg-darkDrop">
<div>
<text className="m-2 font-bold text-lg">{title}</text>
</div>
<div className="w-[100%] m-2 ">
<div ref={containerRef} />
<button type="button" onClick={onPlayPause}>
{isPlaying ? 'Pause' : 'Play'}
</button>
</div>
</div>
);
}
// export default function AudioTrimmer({
// filePath,
// section,
// title,
// trimStart = 0,
// trimEnd,
// onSave,
// onDelete,
// }: AudioTrimmerProps) {
// const [wavesurfer, setWavesurfer] = useState(null);
// const waveformRef = useRef<HTMLDivElement>(null);
// const wavesurferRef = useRef<WaveSurfer | null>(null);
// const [region, setRegion] = useState<{ start: number; end: number }>({
// start: trimStart,
// end: trimEnd || 0,
// });
// // eslint-disable-next-line @typescript-eslint/no-unused-vars
// const [duration, setDuration] = useState<number>(0);
// useEffect(() => {
// if (!waveformRef.current) return;
// // const regions = Regions.create({
// // color: 'rgba(132, 81, 224, 0.3)',
// // drag: false,
// // resize: true,
// // });
// const regions = Regions.create();
// const ws = WaveSurfer.create({
// container: waveformRef.current,
// waveColor: '#ccb1ff',
// progressColor: '#6e44ba',
// // responsive: true,
// height: 100,
// hideScrollbar: true,
// backend: 'WebAudio',
// plugins: [regions],
// });
// wavesurferRef.current = ws;
// ws.load(`file://${filePath}`);
// ws.on('ready', () => {
// setDuration(ws.getDuration());
// regions.clearRegions();
// // ws.clearRegions();
// regions.addRegion({
// start: trimStart,
// end: trimEnd || ws.getDuration(),
// color: 'rgba(132, 81, 224, 0.3)',
// drag: false,
// resize: true,
// });
// });
// regions.on('region-updated', (updatedRegion: any) => {
// setRegion({
// start: Math.max(0, updatedRegion.start),
// end: Math.min(ws.getDuration(), updatedRegion.end),
// });
// });
// // eslint-disable-next-line consistent-return
// return () => {
// ws.destroy();
// };
// }, [filePath, trimStart, trimEnd]);
// const formatTime = (seconds: number) => {
// const minutes = Math.floor(seconds / 60);
// const remainingSeconds = Math.floor(seconds % 60);
// return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
// };
// return (
// <div className="audio-trimmer-item" data-filepath={filePath}>
// <div className="audio-trimmer-header">
// <div className="audio-trimmer-title-container">
// <div className="audio-trimmer-title">
// {title || getBaseName(filePath)}
// </div>
// <div className="audio-trimmer-filename">
// {title ? getBaseName(filePath) : 'hidden'}
// </div>
// <div className="audio-trimmer-section">{section}</div>
// </div>
// <div className="audio-trimmer-controls">
// <button
// type="button"
// className="play-pause-btn"
// onClick={() => {
// const ws = wavesurferRef.current;
// if (!ws) return;
// if (ws.isPlaying()) {
// ws.pause();
// } else {
// ws.play(region.start, region.end);
// }
// }}
// >
// ▶️
// </button>
// <button
// type="button"
// className="save-trim"
// onClick={() => onSave?.(region.start, region.end, title)}
// >
// 💾
// </button>
// <button type="button" className="delete-btn" onClick={onDelete}>
// 🗑️
// </button>
// </div>
// </div>
// <div className="waveform-container">
// <div ref={waveformRef} className="waveform" />
// </div>
// <div className="trim-info">
// <div className="trim-time">
// <span>Start: </span>
// <span className="trim-start-time">{formatTime(region.start)}</span>
// </div>
// <div className="trim-time">
// <span>End: </span>
// <span className="trim-end-time">{formatTime(region.end)}</span>
// </div>
// </div>
// </div>
// );
// }
// export default AudioTrimmer;