- import * as React from 'react';
- import {WaveSurferOptions} from 'wavesurfer.js';
- import clsx from 'clsx';
- import {getFormValues} from '@theoryofnekomata/formxtra';
-
- export type WaveformCanvasDerivedElement = HTMLDivElement;
-
- export interface WaveformCanvasProps
- extends React.HTMLProps<WaveformCanvasDerivedElement>,
- Omit<WaveSurferOptions, 'plugins' | 'height' | 'media' | 'container' | 'fillParent' | 'url' | 'autoplay' | 'renderFunction'> {
- audioRef?: React.Ref<HTMLAudioElement>;
- }
-
- export const WaveformCanvas = React.forwardRef<WaveformCanvasDerivedElement, WaveformCanvasProps>(({
- className,
- children,
- controls,
- waveColor,
- progressColor,
- cursorColor,
- cursorWidth,
- barWidth,
- barGap,
- barRadius,
- barHeight,
- barAlign,
- minPxPerSec,
- peaks,
- duration,
- autoPlay,
- interact,
- hideScrollbar,
- audioRate,
- autoScroll,
- autoCenter,
- sampleRate,
- splitChannels,
- normalize,
- audioRef,
- ...etcProps
- }, forwardedRef) => {
- const [isPlaying, setIsPlaying] = React.useState(false);
- const defaultRef = React.useRef<WaveformCanvasDerivedElement>(null);
- const containerRef = forwardedRef ?? defaultRef;
- const waveSurferRef = React.useRef<any>(null);
-
- const handleAction: React.FormEventHandler<HTMLFormElement> = (e) => {
- e.preventDefault();
- const nativeEvent = e.nativeEvent as unknown as { submitter: HTMLElement };
- const formData = getFormValues(
- e.currentTarget,
- {
- submitter: nativeEvent.submitter,
- }
- );
- const actionName = formData['action'] as string;
- switch (actionName) {
- case 'togglePlayback':
- setIsPlaying((prev) => !prev);
- break;
- default:
- break;
- }
- };
-
- React.useEffect(() => {
- if (!(typeof audioRef === 'object' && audioRef)) {
- return;
- }
- const { current: media } = audioRef;
- if (!media) {
- return;
- }
-
- if (!(typeof containerRef === 'object' && containerRef)) {
- return;
- }
- const { current: container } = containerRef;
- if (!container) {
- return;
- }
-
- const load = async (media: HTMLAudioElement, container: HTMLElement) => {
- const {default: WaveSurfer} = await import('wavesurfer.js');
- const waveSurferInstance = WaveSurfer.create({
- container,
- height: 'auto',
- autoplay: autoPlay,
- fillParent: true,
- waveColor,
- progressColor,
- cursorColor,
- barWidth,
- barGap,
- barRadius,
- barHeight,
- barAlign,
- minPxPerSec,
- peaks,
- duration,
- interact,
- hideScrollbar,
- audioRate,
- autoScroll,
- autoCenter,
- sampleRate,
- splitChannels,
- normalize,
- cursorWidth,
- plugins: [],
- media,
- });
- waveSurferInstance.on('ready', () => {
- if (!container) {
- return;
- }
- while (container.children.length > 1) {
- container.removeChild(container.children[0]);
- }
- });
- await waveSurferInstance.load(media.currentSrc);
- waveSurferInstance.setTime(media.currentTime);
- return waveSurferInstance;
- };
-
- const { current: waveSurferCurrent } = waveSurferRef;
- load(media, container)
- .then((i) => {
- waveSurferRef.current = i;
- })
- .catch((error) => {
- console.log(error);
- });
-
- return () => {
- if (waveSurferCurrent) {
- (waveSurferCurrent as unknown as Record<string, Function>).destroy();
- }
- if (container) {
- container.innerHTML = '';
- }
- };
- }, [
- audioRef,
- autoPlay,
- waveColor,
- progressColor,
- cursorColor,
- barWidth,
- barGap,
- barRadius,
- barHeight,
- barAlign,
- minPxPerSec,
- peaks,
- duration,
- interact,
- hideScrollbar,
- audioRate,
- autoScroll,
- autoCenter,
- sampleRate,
- splitChannels,
- normalize,
- cursorWidth,
- containerRef,
- ]);
-
- return (
- <div
- className={clsx(
- 'flex flex-col',
- className,
- )}
- >
- <div
- className="flex-auto relative aspect-video sm:aspect-auto"
- >
- <div className="absolute top-0 left-0 w-full h-full">
- <div className="w-full h-full relative"
- ref={containerRef}
- />
- </div>
- </div>
- {controls && (
- <form
- onSubmit={handleAction}
- >
- <button
- type="submit"
- name="action"
- value="togglePlayback"
- >
- {isPlaying ? '⏸' : '▶'}
- </button>
- </form>
- )}
- </div>
- );
- });
-
- WaveformCanvas.displayName = 'WavesurferCanvas';
|