Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# **react-audio-visualize**
# React-audio-visualize

An audio visualizer for React. Provides separate components to visualize both live audio and audio blobs.

## Installation

```sh
npm install react-audio-visualize
```
Expand Down Expand Up @@ -96,6 +98,3 @@ const Visualizer = () => {
| **`maxDecibels`** | A double, representing the maximum decibel value for scaling the FFT analysis data. For more [details](https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/maxDecibels) | `-10` | Yes |
| **`minDecibels`** | A double, representing the minimum decibel value for scaling the FFT analysis data, where 0 dB is the loudest possible sound. For more [details](https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/minDecibels) | `-90` | Yes |
| **`smoothingTimeConstant`** | A double within the range 0 to 1 (0 meaning no time averaging). For more [details](https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/smoothingTimeConstant) | `0.4` | Yes |



49 changes: 49 additions & 0 deletions dist/AudioVisualizer/AudioVisualizer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
interface Props {
/**
* Audio blob to visualize
*/
blob: Blob;
/**
* Width of the visualizer
*/
width: number;
/**
* Height of the visualizer
*/
height: number;
/**
* Width of each individual bar in the visualization. Default: `2`
*/
barWidth?: number;
/**
* Gap between each bar in the visualization. Default: `1`
*/
gap?: number;
/**
* BackgroundColor for the visualization: Default: `"transparent"`
*/
backgroundColor?: string;
/**
* Color for the bars that have not yet been played: Default: `"rgb(184, 184, 184)""`
*/
barColor?: string;
/**
* Color for the bars that have been played: Default: `"rgb(160, 198, 255)""`
*/
barPlayedColor?: string;
/**
* Current time stamp till which the audio blob has been played.
* Visualized bars that fall before the current time will have `barPlayerColor`, while that ones that fall after will have `barColor`
*/
currentTime?: number;
/**
* Custome styles that can be passed to the visualization canvas
*/
style?: React.CSSProperties;
/**
* A `ForwardedRef` for the `HTMLCanvasElement`
*/
ref?: React.ForwardedRef<HTMLCanvasElement>;
}
declare const AudioVisualizer: import("react").ForwardRefExoticComponent<Omit<Props, "ref"> & import("react").RefAttributes<HTMLCanvasElement>>;
export { AudioVisualizer };
1 change: 1 addition & 0 deletions dist/AudioVisualizer/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AudioVisualizer } from "./AudioVisualizer";
4 changes: 4 additions & 0 deletions dist/AudioVisualizer/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface dataPoint {
max: number;
min: number;
}
3 changes: 3 additions & 0 deletions dist/AudioVisualizer/utils.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { type dataPoint } from "./types";
export declare const calculateBarData: (buffer: AudioBuffer, height: number, width: number, barWidth: number, gap: number) => dataPoint[];
export declare const draw: (data: dataPoint[], canvas: HTMLCanvasElement, barWidth: number, gap: number, backgroundColor: string, barColor: string, barPlayedColor?: string, currentTime?: number, duration?: number) => void;
60 changes: 60 additions & 0 deletions dist/LiveAudioVisualizer/LiveAudioVisualizer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { type ReactElement } from "react";
export interface Props {
/**
* Media recorder who's stream needs to visualized
*/
mediaRecorder: MediaRecorder;
/**
* Width of the visualization. Default" "100%"
*/
width?: number | string;
/**
* Height of the visualization. Default" "100%"
*/
height?: number | string;
/**
* Width of each individual bar in the visualization. Default: `2`
*/
barWidth?: number;
/**
* Gap between each bar in the visualization. Default `1`
*/
gap?: number;
/**
* BackgroundColor for the visualization: Default `transparent`
*/
backgroundColor?: string;
/**
* Color of the bars drawn in the visualization. Default: `"rgb(160, 198, 255)"`
*/
barColor?: string;
/**
* An unsigned integer, representing the window size of the FFT, given in number of samples.
* A higher value will result in more details in the frequency domain but fewer details in the amplitude domain.
* For more details {@link https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/fftSize MDN AnalyserNode: fftSize property}
* Default: `1024`
*/
fftSize?: 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384 | 32768;
/**
* A double, representing the maximum decibel value for scaling the FFT analysis data
* For more details {@link https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/maxDecibels MDN AnalyserNode: maxDecibels property}
* Default: `-10`
*/
maxDecibels?: number;
/**
* A double, representing the minimum decibel value for scaling the FFT analysis data, where 0 dB is the loudest possible sound
* For more details {@link https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/minDecibels MDN AnalyserNode: minDecibels property}
* Default: `-90`
*/
minDecibels?: number;
/**
* A double within the range 0 to 1 (0 meaning no time averaging). The default value is 0.8.
* If 0 is set, there is no averaging done, whereas a value of 1 means "overlap the previous and current buffer quite a lot while computing the value",
* which essentially smooths the changes across
* For more details {@link https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/smoothingTimeConstant MDN AnalyserNode: smoothingTimeConstant property}
* Default: `0.4`
*/
smoothingTimeConstant?: number;
}
declare const LiveAudioVisualizer: (props: Props) => ReactElement;
export { LiveAudioVisualizer };
1 change: 1 addition & 0 deletions dist/LiveAudioVisualizer/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { LiveAudioVisualizer } from "./LiveAudioVisualizer";
2 changes: 2 additions & 0 deletions dist/LiveAudioVisualizer/utils.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export declare const calculateBarData: (frequencyData: Uint8Array, width: number, barWidth: number, gap: number) => number[];
export declare const draw: (data: number[], canvas: HTMLCanvasElement, barWidth: number, gap: number, backgroundColor: string, barColor: string) => void;
2 changes: 2 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { LiveAudioVisualizer } from "./LiveAudioVisualizer";
export { AudioVisualizer } from "./AudioVisualizer";
210 changes: 210 additions & 0 deletions dist/react-audio-visualize.es.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { jsx as j } from "react/jsx-runtime";
import { useState as B, useRef as z, useEffect as R, useCallback as C, forwardRef as F, useImperativeHandle as P } from "react";
const E = (n, o, x, m) => {
let i = o / (x + m), l = Math.floor(n.length / i);
i > n.length && (i = n.length, l = 1);
const p = [];
for (let e = 0; e < i; e++) {
let f = 0;
for (let r = 0; r < l && e * l + r < n.length; r++)
f += n[e * l + r];
p.push(f / l);
}
return p;
}, V = (n, o, x, m, i, l) => {
const p = o.height / 2, e = o.getContext("2d");
e && (e.clearRect(0, 0, o.width, o.height), i !== "transparent" && (e.fillStyle = i, e.fillRect(0, 0, o.width, o.height)), n.forEach((f, r) => {
e.fillStyle = l;
const s = r * (x + m), t = p - f / 2, h = x, d = f || 1;
e.beginPath(), e.roundRect ? (e.roundRect(s, t, h, d, 50), e.fill()) : e.fillRect(s, t, h, d);
}));
}, L = ({
mediaRecorder: n,
width: o = "100%",
height: x = "100%",
barWidth: m = 2,
gap: i = 1,
backgroundColor: l = "transparent",
barColor: p = "rgb(160, 198, 255)",
fftSize: e = 1024,
maxDecibels: f = -10,
minDecibels: r = -90,
smoothingTimeConstant: s = 0.4
}) => {
const [t, h] = B(), [d, w] = B(), [a, D] = B(), A = z(null);
R(() => {
if (!n.stream)
return;
const c = new AudioContext(), y = c.createAnalyser();
D(y), y.fftSize = e, y.minDecibels = r, y.maxDecibels = f, y.smoothingTimeConstant = s;
const S = c.createMediaStreamSource(n.stream);
return S.connect(y), h(c), w(S), () => {
S.disconnect(), y.disconnect(), c.state !== "closed" && c.close();
};
}, [n.stream]), R(() => {
a && n.state === "recording" && g();
}, [a, n.state]);
const g = C(() => {
if (!a || !t)
return;
const c = new Uint8Array(a == null ? void 0 : a.frequencyBinCount);
n.state === "recording" ? (a == null || a.getByteFrequencyData(c), u(c), requestAnimationFrame(g)) : n.state === "paused" ? u(c) : n.state === "inactive" && t.state !== "closed" && t.close();
}, [a, t == null ? void 0 : t.state]);
R(() => () => {
t && t.state !== "closed" && t.close(), d == null || d.disconnect(), a == null || a.disconnect();
}, []);
const u = (c) => {
if (!A.current)
return;
const y = E(
c,
A.current.width,
m,
i
);
V(
y,
A.current,
m,
i,
l,
p
);
};
return /* @__PURE__ */ j(
"canvas",
{
ref: A,
width: o,
height: x,
style: {
aspectRatio: "unset"
}
}
);
}, N = (n, o, x, m, i) => {
const l = n.getChannelData(0), p = x / (m + i), e = Math.floor(l.length / p), f = o / 2;
let r = [], s = 0;
for (let t = 0; t < p; t++) {
const h = [];
let d = 0;
const w = [];
let a = 0;
for (let u = 0; u < e && t * e + u < n.length; u++) {
const c = l[t * e + u];
c <= 0 && (h.push(c), d++), c > 0 && (w.push(c), a++);
}
const D = h.reduce((u, c) => u + c, 0) / d, g = { max: w.reduce((u, c) => u + c, 0) / a, min: D };
g.max > s && (s = g.max), Math.abs(g.min) > s && (s = Math.abs(g.min)), r.push(g);
}
if (f * 0.8 > s * f) {
const t = f * 0.8 / s;
r = r.map((h) => ({
max: h.max * t,
min: h.min * t
}));
}
return r;
}, M = (n, o, x, m, i, l, p, e = 0, f = 1) => {
const r = o.height / 2, s = o.getContext("2d");
if (!s)
return;
s.clearRect(0, 0, o.width, o.height), i !== "transparent" && (s.fillStyle = i, s.fillRect(0, 0, o.width, o.height));
const t = (e || 0) / f;
n.forEach((h, d) => {
const w = d / n.length, a = t > w;
s.fillStyle = a && p ? p : l;
const D = d * (x + m), A = r + h.min, g = x, u = r + h.max - A;
s.beginPath(), s.roundRect ? (s.roundRect(D, A, g, u, 50), s.fill()) : s.fillRect(D, A, g, u);
});
}, $ = F(
({
blob: n,
width: o,
height: x,
barWidth: m = 2,
gap: i = 1,
currentTime: l,
style: p,
backgroundColor: e = "transparent",
barColor: f = "rgb(184, 184, 184)",
barPlayedColor: r = "rgb(160, 198, 255)"
}, s) => {
const t = z(null), [h, d] = B([]), [w, a] = B(0);
return P(
s,
() => t.current,
[]
), R(() => {
(async () => {
if (!t.current)
return;
if (!n) {
const u = Array.from({ length: 100 }, () => ({
max: 0,
min: 0
}));
M(
u,
t.current,
m,
i,
e,
f,
r
);
return;
}
const A = await n.arrayBuffer();
await new AudioContext().decodeAudioData(A, (u) => {
if (!t.current)
return;
a(u.duration);
const c = N(
u,
x,
o,
m,
i
);
d(c), M(
c,
t.current,
m,
i,
e,
f,
r
);
});
})();
}, [n, t.current]), R(() => {
t.current && M(
h,
t.current,
m,
i,
e,
f,
r,
l,
w
);
}, [l, w]), /* @__PURE__ */ j(
"canvas",
{
ref: t,
width: o,
height: x,
style: {
...p
}
}
);
}
);
$.displayName = "AudioVisualizer";
export {
$ as AudioVisualizer,
L as LiveAudioVisualizer
};
1 change: 1 addition & 0 deletions dist/react-audio-visualize.umd.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@
"eslint-plugin-react-refresh": "^0.3.4",
"path": "^0.12.7",
"prettier": "^2.8.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"typescript": "^5.0.2",
"vite": "^4.5.3",
"vite-plugin-dts": "^2.3.0"
},
"peerDependencies": {
"react": ">=16.2.0",
"react-dom": ">=16.2.0"
"react": ">=19.0.0",
"react-dom": ">=19.0.0"
}
}
Loading