-
-
Notifications
You must be signed in to change notification settings - Fork 51
fix: prevent memory leak in Visualizer component #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,13 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import PageVisibility from 'react-page-visibility'; | ||
|
||
const DELAY = 500; | ||
|
||
export default class Visualizer extends React.PureComponent { | ||
rafId = null; | ||
timerId = null; | ||
|
||
export default class Visualizer extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
|
@@ -10,7 +16,8 @@ export default class Visualizer extends React.Component { | |
baseColour: 'rgb(10, 10, 35)', | ||
translucent: 'rgba(10, 10, 35, 0.6)', | ||
multiplier: 0.7529 | ||
} | ||
}, | ||
isTabVisible: true, | ||
}; | ||
} | ||
|
||
|
@@ -19,10 +26,31 @@ export default class Visualizer extends React.Component { | |
// of the audio context stuff AFTER the audio has been triggered. | ||
// We can't see it until | ||
// then anyway so it makes no difference to desktop. | ||
componentWillReceiveProps(nextProps) { | ||
if (nextProps.playing && !this.state.eq.context) { | ||
this.initiateEQ(); | ||
componentDidUpdate(prevProps, prevState) { | ||
if (prevProps.playing === this.props.playing && prevState.isTabVisible === this.state.isTabVisible) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we do (prevProps.playing !== this.props.playing && prevState.isTabVisible !== this.state.isTabVisible) so all of the conditions can stay in one block. |
||
return | ||
} | ||
|
||
// If the player is playing and the tab is being active, | ||
// draw the visualization | ||
if (this.props.playing && this.state.isTabVisible) { | ||
// Create a new audio context if there isn't one available | ||
if (!this.state.eq.context) { | ||
this.initiateEQ(); | ||
} | ||
this.createVisualizer(); | ||
this.startDrawing(); | ||
} | ||
// If the player is not playing or the tab is running in the background, | ||
// stop the animation | ||
else { | ||
// Workaround for componentWillUnmount to delay the clean up and achieve fadeout animation | ||
setTimeout(() => { | ||
// Note: Order matters. | ||
// Stop the drawing loop first (using this.rafId), then set the ID to null | ||
this.stopDrawing(); | ||
this.reset(); | ||
}, DELAY); | ||
} | ||
} | ||
|
||
|
@@ -47,7 +75,10 @@ export default class Visualizer extends React.Component { | |
eq.bands = new Uint8Array(eq.analyser.frequencyBinCount - 32); | ||
|
||
this.setState({ eq }); | ||
this.updateEQBands(); | ||
} | ||
|
||
reset = () => { | ||
this.rafId = null; | ||
} | ||
|
||
/** * | ||
|
@@ -56,11 +87,11 @@ export default class Visualizer extends React.Component { | |
* visualizer is up to date. | ||
*/ | ||
updateEQBands() { | ||
const newEQ = this.state.eq; | ||
// Populate the buffer with the audio source’s current data | ||
this.state.eq.analyser.getByteFrequencyData(this.state.eq.bands); | ||
newEQ.analyser.getByteFrequencyData(newEQ.bands); | ||
|
||
// Can’t stop, won’t stop | ||
requestAnimationFrame(() => this.updateEQBands()); | ||
this.setState({ eq: { ...newEQ } }); | ||
} | ||
|
||
/** * | ||
|
@@ -77,22 +108,41 @@ export default class Visualizer extends React.Component { | |
width: this._canvas.width, | ||
barWidth: this._canvas.width / this.state.eq.bands.length | ||
}; | ||
} | ||
|
||
startDrawing = () => { | ||
if (!this.rafId) { | ||
this.rafId = window.requestAnimationFrame(this.drawingLoop); | ||
} | ||
}; | ||
|
||
stopDrawing = () => { | ||
window.cancelAnimationFrame(this.rafId); | ||
clearTimeout(this.timerId); | ||
}; | ||
|
||
drawingLoop = () => { | ||
const haveWaveform = this.state.eq.bands.reduce((a, b) => a + b, 0) !== 0; | ||
|
||
this.updateEQBands(); | ||
this.drawVisualizer(); | ||
} | ||
|
||
// Because timeupdate events are not triggered at browser speed, | ||
// we use requestanimationframe for higher framerates | ||
if (haveWaveform) { | ||
this.rafId = window.requestAnimationFrame(this.drawingLoop); | ||
} | ||
// If there is no music or audio in the song, then reduce the FPS | ||
else { | ||
this.timerId = setTimeout(this.drawingLoop, 250); | ||
} | ||
}; | ||
|
||
/** * | ||
* As a base visualizer, the equalizer bands are drawn using | ||
* canvas in the window directly above the song into. | ||
*/ | ||
drawVisualizer() { | ||
if (this.state.eq.bands.reduce((a, b) => a + b, 0) !== 0) | ||
requestAnimationFrame(() => this.drawVisualizer()); | ||
// Because timeupdate events are not triggered at browser speed, | ||
// we use requestanimationframe for higher framerates | ||
// eslint-disable-next-line no-inline-comments | ||
// If there is no music or audio in the song, then reduce the FPS | ||
else setTimeout(() => this.drawVisualizer(), 250); | ||
// Intial bar x coordinate | ||
let y, | ||
x = 0; | ||
|
@@ -129,11 +179,17 @@ export default class Visualizer extends React.Component { | |
this.visualizer.ctx.fill(); | ||
} | ||
|
||
handleVisibilityChange = isTabVisible => { | ||
this.setState({ isTabVisible }); | ||
} | ||
|
||
render() { | ||
return ( | ||
<div id='visualizer'> | ||
<canvas aria-label='visualizer' ref={a => (this._canvas = a)} /> | ||
</div> | ||
<PageVisibility onChange={this.handleVisibilityChange}> | ||
<div id='visualizer'> | ||
<canvas aria-label='visualizer' ref={a => (this._canvas = a)} /> | ||
</div> | ||
</PageVisibility> | ||
); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since they are changing, should we set the to states?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what rafId stands for.