- "frontend/src/components/AudioWaveform.tsx" - "frontend/src/hooks/useMediaSync.ts" - "frontend/src/pages/WatchPage.tsx" - "frontend/src/App.css" - "frontend/package.json" GSD-Task: S05/T02
58 lines
1.6 KiB
TypeScript
58 lines
1.6 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|
import WaveSurfer from "wavesurfer.js";
|
|
import type { MediaSyncState } from "../hooks/useMediaSync";
|
|
|
|
interface AudioWaveformProps {
|
|
mediaSync: MediaSyncState;
|
|
src: string;
|
|
}
|
|
|
|
/**
|
|
* Audio-only waveform visualiser powered by wavesurfer.js.
|
|
* Renders a hidden <audio> element owned by useMediaSync and an
|
|
* interactive waveform. Used when no video URL is available.
|
|
*/
|
|
export default function AudioWaveform({ mediaSync, src }: AudioWaveformProps) {
|
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
const wsRef = useRef<WaveSurfer | null>(null);
|
|
|
|
useEffect(() => {
|
|
const container = containerRef.current;
|
|
const audio = mediaSync.videoRef.current as HTMLAudioElement | null;
|
|
if (!container || !audio) return;
|
|
|
|
const ws = WaveSurfer.create({
|
|
container,
|
|
media: audio,
|
|
height: 128,
|
|
waveColor: "rgba(34, 211, 238, 0.4)",
|
|
progressColor: "rgba(34, 211, 238, 0.8)",
|
|
cursorColor: "#22d3ee",
|
|
barWidth: 2,
|
|
barGap: 1,
|
|
barRadius: 2,
|
|
backend: "MediaElement",
|
|
});
|
|
|
|
wsRef.current = ws;
|
|
|
|
return () => {
|
|
ws.destroy();
|
|
wsRef.current = null;
|
|
};
|
|
// Re-create when src changes
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [src]);
|
|
|
|
return (
|
|
<div className="audio-waveform">
|
|
<audio
|
|
ref={mediaSync.videoRef as React.RefObject<HTMLAudioElement>}
|
|
src={src}
|
|
preload="metadata"
|
|
style={{ display: "none" }}
|
|
/>
|
|
<div ref={containerRef} className="audio-waveform__canvas" />
|
|
</div>
|
|
);
|
|
}
|