| 
1 |  | -import { useRef, useState, type ReactElement } from "react";  | 
2 |  | -import { useEffect } from "react";  | 
 | 1 | +import {  | 
 | 2 | +  useRef,  | 
 | 3 | +  useState,  | 
 | 4 | +  forwardRef,  | 
 | 5 | +  type ForwardedRef,  | 
 | 6 | +  type ForwardRefExoticComponent,  | 
 | 7 | +  type RefAttributes,  | 
 | 8 | +  useImperativeHandle,  | 
 | 9 | +  useEffect,  | 
 | 10 | +} from "react";  | 
3 | 11 | import { type dataPoint } from "./types";  | 
4 | 12 | import { calculateBarData, draw } from "./utils";  | 
5 | 13 | 
 
  | 
@@ -45,93 +53,118 @@ interface Props {  | 
45 | 53 |    * Custome styles that can be passed to the visualization canvas  | 
46 | 54 |    */  | 
47 | 55 |   style?: React.CSSProperties;  | 
 | 56 | +  /**  | 
 | 57 | +   * A `ForwardedRef` for the `HTMLCanvasElement`  | 
 | 58 | +   */  | 
 | 59 | +  ref?: React.ForwardedRef<HTMLCanvasElement>;  | 
48 | 60 | }  | 
49 | 61 | 
 
  | 
50 |  | -const AudioVisualizer: (props: Props) => ReactElement = ({  | 
51 |  | -  blob,  | 
52 |  | -  width,  | 
53 |  | -  height,  | 
54 |  | -  barWidth = 2,  | 
55 |  | -  gap = 1,  | 
56 |  | -  currentTime,  | 
57 |  | -  style,  | 
58 |  | -  backgroundColor = "transparent",  | 
59 |  | -  barColor = "rgb(184, 184, 184)",  | 
60 |  | -  barPlayedColor = "rgb(160, 198, 255)",  | 
61 |  | -}: Props) => {  | 
62 |  | -  const canvasRef = useRef<HTMLCanvasElement>(null);  | 
63 |  | -  const [data, setData] = useState<dataPoint[]>([]);  | 
64 |  | -  const [duration, setDuration] = useState<number>(0);  | 
65 |  | - | 
66 |  | -  useEffect(() => {  | 
67 |  | -    const processBlob = async (): Promise<void> => {  | 
68 |  | -      if (!canvasRef.current) return;  | 
 | 62 | +const AudioVisualizer: ForwardRefExoticComponent<  | 
 | 63 | +  Props & RefAttributes<HTMLCanvasElement>  | 
 | 64 | +> = forwardRef(  | 
 | 65 | +  (  | 
 | 66 | +    {  | 
 | 67 | +      blob,  | 
 | 68 | +      width,  | 
 | 69 | +      height,  | 
 | 70 | +      barWidth = 2,  | 
 | 71 | +      gap = 1,  | 
 | 72 | +      currentTime,  | 
 | 73 | +      style,  | 
 | 74 | +      backgroundColor = "transparent",  | 
 | 75 | +      barColor = "rgb(184, 184, 184)",  | 
 | 76 | +      barPlayedColor = "rgb(160, 198, 255)",  | 
 | 77 | +    }: Props,  | 
 | 78 | +    ref?: ForwardedRef<HTMLCanvasElement>  | 
 | 79 | +  ) => {  | 
 | 80 | +    const canvasRef = useRef<HTMLCanvasElement>(null);  | 
 | 81 | +    const [data, setData] = useState<dataPoint[]>([]);  | 
 | 82 | +    const [duration, setDuration] = useState<number>(0);  | 
69 | 83 | 
 
  | 
70 |  | -      if (!blob) {  | 
71 |  | -        const barsData = Array.from({ length: 100 }, () => ({  | 
72 |  | -          max: 0,  | 
73 |  | -          min: 0,  | 
74 |  | -        }));  | 
75 |  | -        draw(  | 
76 |  | -          barsData,  | 
77 |  | -          canvasRef.current,  | 
78 |  | -          barWidth,  | 
79 |  | -          gap,  | 
80 |  | -          backgroundColor,  | 
81 |  | -          barColor,  | 
82 |  | -          barPlayedColor  | 
83 |  | -        );  | 
84 |  | -        return;  | 
85 |  | -      }  | 
 | 84 | +    useImperativeHandle<HTMLCanvasElement | null, HTMLCanvasElement | null>(  | 
 | 85 | +      ref,  | 
 | 86 | +      () => canvasRef.current,  | 
 | 87 | +      []  | 
 | 88 | +    );  | 
86 | 89 | 
 
  | 
87 |  | -      const audioBuffer = await blob.arrayBuffer();  | 
88 |  | -      const audioContext = new AudioContext();  | 
89 |  | -      await audioContext.decodeAudioData(audioBuffer, (buffer) => {  | 
 | 90 | +    useEffect(() => {  | 
 | 91 | +      const processBlob = async (): Promise<void> => {  | 
90 | 92 |         if (!canvasRef.current) return;  | 
91 |  | -        setDuration(buffer.duration);  | 
92 |  | -        const barsData = calculateBarData(buffer, height, width, barWidth, gap);  | 
93 |  | -        setData(barsData);  | 
94 |  | -        draw(  | 
95 |  | -          barsData,  | 
96 |  | -          canvasRef.current,  | 
97 |  | -          barWidth,  | 
98 |  | -          gap,  | 
99 |  | -          backgroundColor,  | 
100 |  | -          barColor,  | 
101 |  | -          barPlayedColor  | 
102 |  | -        );  | 
103 |  | -      });  | 
104 |  | -    };  | 
105 | 93 | 
 
  | 
106 |  | -    processBlob();  | 
107 |  | -  }, [blob, canvasRef.current]);  | 
 | 94 | +        if (!blob) {  | 
 | 95 | +          const barsData = Array.from({ length: 100 }, () => ({  | 
 | 96 | +            max: 0,  | 
 | 97 | +            min: 0,  | 
 | 98 | +          }));  | 
 | 99 | +          draw(  | 
 | 100 | +            barsData,  | 
 | 101 | +            canvasRef.current,  | 
 | 102 | +            barWidth,  | 
 | 103 | +            gap,  | 
 | 104 | +            backgroundColor,  | 
 | 105 | +            barColor,  | 
 | 106 | +            barPlayedColor  | 
 | 107 | +          );  | 
 | 108 | +          return;  | 
 | 109 | +        }  | 
108 | 110 | 
 
  | 
109 |  | -  useEffect(() => {  | 
110 |  | -    if (!canvasRef.current) return;  | 
 | 111 | +        const audioBuffer = await blob.arrayBuffer();  | 
 | 112 | +        const audioContext = new AudioContext();  | 
 | 113 | +        await audioContext.decodeAudioData(audioBuffer, (buffer) => {  | 
 | 114 | +          if (!canvasRef.current) return;  | 
 | 115 | +          setDuration(buffer.duration);  | 
 | 116 | +          const barsData = calculateBarData(  | 
 | 117 | +            buffer,  | 
 | 118 | +            height,  | 
 | 119 | +            width,  | 
 | 120 | +            barWidth,  | 
 | 121 | +            gap  | 
 | 122 | +          );  | 
 | 123 | +          setData(barsData);  | 
 | 124 | +          draw(  | 
 | 125 | +            barsData,  | 
 | 126 | +            canvasRef.current,  | 
 | 127 | +            barWidth,  | 
 | 128 | +            gap,  | 
 | 129 | +            backgroundColor,  | 
 | 130 | +            barColor,  | 
 | 131 | +            barPlayedColor  | 
 | 132 | +          );  | 
 | 133 | +        });  | 
 | 134 | +      };  | 
111 | 135 | 
 
  | 
112 |  | -    draw(  | 
113 |  | -      data,  | 
114 |  | -      canvasRef.current,  | 
115 |  | -      barWidth,  | 
116 |  | -      gap,  | 
117 |  | -      backgroundColor,  | 
118 |  | -      barColor,  | 
119 |  | -      barPlayedColor,  | 
120 |  | -      currentTime,  | 
121 |  | -      duration  | 
 | 136 | +      processBlob();  | 
 | 137 | +    }, [blob, canvasRef.current]);  | 
 | 138 | + | 
 | 139 | +    useEffect(() => {  | 
 | 140 | +      if (!canvasRef.current) return;  | 
 | 141 | + | 
 | 142 | +      draw(  | 
 | 143 | +        data,  | 
 | 144 | +        canvasRef.current,  | 
 | 145 | +        barWidth,  | 
 | 146 | +        gap,  | 
 | 147 | +        backgroundColor,  | 
 | 148 | +        barColor,  | 
 | 149 | +        barPlayedColor,  | 
 | 150 | +        currentTime,  | 
 | 151 | +        duration  | 
 | 152 | +      );  | 
 | 153 | +    }, [currentTime, duration]);  | 
 | 154 | + | 
 | 155 | +    return (  | 
 | 156 | +      <canvas  | 
 | 157 | +        ref={canvasRef}  | 
 | 158 | +        width={width}  | 
 | 159 | +        height={height}  | 
 | 160 | +        style={{  | 
 | 161 | +          ...style,  | 
 | 162 | +        }}  | 
 | 163 | +      />  | 
122 | 164 |     );  | 
123 |  | -  }, [currentTime, duration]);  | 
 | 165 | +  }  | 
 | 166 | +);  | 
124 | 167 | 
 
  | 
125 |  | -  return (  | 
126 |  | -    <canvas  | 
127 |  | -      ref={canvasRef}  | 
128 |  | -      width={width}  | 
129 |  | -      height={height}  | 
130 |  | -      style={{  | 
131 |  | -        ...style,  | 
132 |  | -      }}  | 
133 |  | -    />  | 
134 |  | -  );  | 
135 |  | -};  | 
 | 168 | +AudioVisualizer.displayName = "AudioVisualizer";  | 
136 | 169 | 
 
  | 
137 | 170 | export { AudioVisualizer };  | 
0 commit comments