Skip to content

Commit ebdd952

Browse files
committed
fix: prevent memory leak in Visualizer component
1 parent 9c45fb9 commit ebdd952

File tree

2 files changed

+47
-26
lines changed

2 files changed

+47
-26
lines changed

src/components/Main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const Main = props => {
1515
{isBrowser && (
1616
<>
1717
<div className='animation saron' id='container' />
18-
<Visualizer player={props.player} playing={props.playing} />
18+
{props.playing && <Visualizer player={props.player} />}
1919
<details>
2020
<summary>Keyboard Controls</summary>
2121
<dl>

src/components/Visualizer.js

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33

4-
export default class Visualizer extends React.Component {
4+
export default class Visualizer extends React.PureComponent {
5+
rafId = null;
6+
timerId = null;
7+
58
constructor(props) {
69
super(props);
710
this.state = {
@@ -14,16 +17,17 @@ export default class Visualizer extends React.Component {
1417
};
1518
}
1619

17-
// In order to get around some mobile browser limitations,
18-
// we can only generate a lot
19-
// of the audio context stuff AFTER the audio has been triggered.
20-
// We can't see it until
21-
// then anyway so it makes no difference to desktop.
22-
componentWillReceiveProps(nextProps) {
23-
if (nextProps.playing && !this.state.eq.context) {
24-
this.initiateEQ();
25-
this.createVisualizer();
26-
}
20+
componentDidMount() {
21+
this.initiateEQ();
22+
this.createVisualizer();
23+
this.startDrawing();
24+
}
25+
26+
componentWillUnmount() {
27+
this.stopDrawing();
28+
this.state.eq.analyser.disconnect();
29+
this.state.eq.src.disconnect();
30+
this.state.eq.context.close();
2731
}
2832

2933
initiateEQ() {
@@ -47,7 +51,6 @@ export default class Visualizer extends React.Component {
4751
eq.bands = new Uint8Array(eq.analyser.frequencyBinCount - 32);
4852

4953
this.setState({ eq });
50-
this.updateEQBands();
5154
}
5255

5356
/** *
@@ -56,11 +59,11 @@ export default class Visualizer extends React.Component {
5659
* visualizer is up to date.
5760
*/
5861
updateEQBands() {
62+
const newEQ = this.state.eq;
5963
// Populate the buffer with the audio source’s current data
60-
this.state.eq.analyser.getByteFrequencyData(this.state.eq.bands);
64+
newEQ.analyser.getByteFrequencyData(newEQ.bands);
6165

62-
// Can’t stop, won’t stop
63-
requestAnimationFrame(() => this.updateEQBands());
66+
this.setState({ eq: { ...newEQ } });
6467
}
6568

6669
/** *
@@ -77,22 +80,41 @@ export default class Visualizer extends React.Component {
7780
width: this._canvas.width,
7881
barWidth: this._canvas.width / this.state.eq.bands.length
7982
};
83+
}
8084

85+
startDrawing = () => {
86+
if (!this.rafId) {
87+
this.rafId = window.requestAnimationFrame(this.drawingLoop);
88+
}
89+
};
90+
91+
stopDrawing = () => {
92+
window.cancelAnimationFrame(this.rafId);
93+
clearTimeout(this.timerId);
94+
};
95+
96+
drawingLoop = () => {
97+
const haveWaveform = this.state.eq.bands.reduce((a, b) => a + b, 0) !== 0;
98+
99+
this.updateEQBands();
81100
this.drawVisualizer();
82-
}
101+
102+
// Because timeupdate events are not triggered at browser speed,
103+
// we use requestanimationframe for higher framerates
104+
if (haveWaveform) {
105+
this.rafId = window.requestAnimationFrame(this.drawingLoop);
106+
}
107+
// If there is no music or audio in the song, then reduce the FPS
108+
else {
109+
this.timerId = setTimeout(this.drawingLoop, 250);
110+
}
111+
};
83112

84113
/** *
85114
* As a base visualizer, the equalizer bands are drawn using
86115
* canvas in the window directly above the song into.
87116
*/
88117
drawVisualizer() {
89-
if (this.state.eq.bands.reduce((a, b) => a + b, 0) !== 0)
90-
requestAnimationFrame(() => this.drawVisualizer());
91-
// Because timeupdate events are not triggered at browser speed,
92-
// we use requestanimationframe for higher framerates
93-
// eslint-disable-next-line no-inline-comments
94-
// If there is no music or audio in the song, then reduce the FPS
95-
else setTimeout(() => this.drawVisualizer(), 250);
96118
// Intial bar x coordinate
97119
let y,
98120
x = 0;
@@ -139,6 +161,5 @@ export default class Visualizer extends React.Component {
139161
}
140162

141163
Visualizer.propTypes = {
142-
player: PropTypes.object,
143-
playing: PropTypes.bool
164+
player: PropTypes.object
144165
};

0 commit comments

Comments
 (0)