use wavesurefer and some styling
This commit is contained in:
@ -1,36 +1,23 @@
|
||||
|
||||
@import "tailwindcss";
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
/*
|
||||
* @NOTE: Prepend a `~` to css file paths that are in your node_modules
|
||||
* See https://github.com/webpack-contrib/sass-loader#imports
|
||||
*/
|
||||
body {
|
||||
position: relative;
|
||||
color: white;
|
||||
height: 100vh;
|
||||
background: linear-gradient(
|
||||
200.96deg,
|
||||
#fedc2a -29.09%,
|
||||
#dd5789 51.77%,
|
||||
#7a2c9e 129.35%
|
||||
);
|
||||
font-family: sans-serif;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@theme {
|
||||
--color-midnight: #1E1E1E;
|
||||
--color-plum: #4f3186;
|
||||
--color-offwhite: #d4d4d4;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: white;
|
||||
background-color: #4f3186;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
appearance: none;
|
||||
font-size: 1.3rem;
|
||||
box-shadow: 0px 8px 28px -6px rgba(24, 39, 75, 0.12),
|
||||
0px 18px 88px -4px rgba(24, 39, 75, 0.14);
|
||||
transition: all ease-in 0.1s;
|
||||
cursor: pointer;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
// import 'tailwindcss/tailwind.css';
|
||||
import icon from '../../assets/icon.svg';
|
||||
import './App.css';
|
||||
import AudioTrimmer from './components/AudioTrimer';
|
||||
|
||||
function Hello() {
|
||||
return (
|
||||
<div>
|
||||
<div className="min-h-screen min-w-screen flex flex-col items-center justify-center bg-midnight text-offwhite">
|
||||
{/* <div className="Hello">
|
||||
<img width="200" alt="icon" src={icon} />
|
||||
</div>
|
||||
@ -36,8 +37,9 @@ function Hello() {
|
||||
</button>
|
||||
</a>
|
||||
</div> */}
|
||||
<div>
|
||||
<div className="bg-midnight min-w-screen">
|
||||
<AudioTrimmer
|
||||
title="audio_capture_20251206_123108.wav"
|
||||
filePath="C:\\Users\\mickl\\Music\\clips\\original\\audio_capture_20250118_000351.wav"
|
||||
// section="Section 1"
|
||||
/>
|
||||
|
||||
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user