Reputation: 8942
I have a React.js component, which is an audio
file and a child component, where is a slider with play/pause button. I want arrow position in slider synchronized with audio current time.
When audio is playing, arrow animation is delayed, sometimes more and sometimes less. When audio is paused arrow animation occurs immediately. How can I achieve the same effect when audio is playing without delay?
class Player extends React.Component {
constructor(props) {
super(props);
this.state = {
};
this.playClick = this.playClick.bind(this);
this.changePosition = this.changePosition.bind(this);
}
componentWillReceiveProps(nextProps) {
const that = this;
if (this.props.currentTime !== nextProps.currentTime) {
const horizontalOffset = nextProps.currentTime * 500 / this.props.duration;
console.log('horOffset', horizontalOffset);
$('.arrow').animate(
{ left: `${horizontalOffset - 20}px` },
{
duration: 'slow',
easing: 'easeInOutExpo',
},
);
}
}
changePosition(e){
const newCurrTime = this.props.duration * e.clientX / 500;
console.log('changed time', newCurrTime);
this.props.onChangeAudioTime(newCurrTime);
}
playClick() {
this.props.onTogglePlay();
}
render() {
return <div>
<button onClick={this.playClick}>play</button>
<div className="slider-content" onClick={this.changePosition}>
<div className="arrow">
</div>
</div>
</div>;
}
}
class Audio extends React.Component {
constructor(props) {
super(props);
this.state = {
play: false,
current: 0,
duration: 2024.496,
seeked: false
};
this.toggle = this.toggle.bind(this);
this.play = this.play.bind(this);
this.pause = this.pause.bind(this);
this.setCurrTime = this.setCurrTime.bind(this);
}
componentDidMount() {
const that = this;
this.currentTimeInterval = null;
this.audio.onplay = () => {
this.currentTimeInterval = setInterval(() => {
if (that.state.current !== this.audio.currentTime) {
that.setState({ current: this.audio.currentTime, seeked: false });
}
}, 500);
};
this.audio.onseeked = () => {
this.currentTimeInterval = setInterval(() => {
if (that.state.current !== this.audio.currentTime) {
that.setState({ current: this.audio.currentTime, seeked: false });
}
}, 500);
};
this.audio.onpause = () => {
clearInterval(this.currentTimeInterval);
};
}
play() {
this.setState({ play: true }, () => {
this.audio.play();
});
}
pause() {
this.setState({ play: false }, () => {
this.audio.pause();
});
}
toggle() {
this.state.play ? this.pause() : this.play();
}
setCurrTime(newCurrTime) {
let that = this;
clearInterval(this.currentTimeInterval);
this.setState({ current: newCurrTime, seeked: true }, () => {
this.audio.currentTime = newCurrTime;
});
}
render() {
return <div>
<audio src="https://dts.podtrac.com/redirect.mp3/www.kqed.org/.stream/mp3splice/radio/theleap/2017/04/HennyMastered.mp3"
id="audio"
ref={el => (this.audio = el)}
/>
<Player
currentTime={this.state.current}
duration={this.state.duration}
onTogglePlay={this.toggle}
onChangeAudioTime={this.setCurrTime}
play={this.state.play}
ref={el => (this.player = el)}
/>
</div>;
}
}
ReactDOM.render(
<Audio name="World" />,
document.getElementById('container')
);
Upvotes: 2
Views: 1299
Reputation: 24660
My guess is that its because the $.animate
is not being finished when the new animation started. According to jQuery docs slow
duration of animate is 600ms.
It isn't a good practice to combine jQuery and react. Because of this I would suggest you to use CSS transitions to achieve desired behavior.
Example
// in css file
div.arrow {
left: 0; /* desired initial left value */
transition: left 500ms;
-moz-transition: left 500ms;
-webkit-transition: left 500ms;
-o-transition: left 500ms;
}
// in component class
componentWillReceiveProps(nextProps) {
const that = this;
if (this.props.currentTime !== nextProps.currentTime) {
const horizontalOffset = nextProps.currentTime * 500 / this.props.duration;
console.log('horOffset', horizontalOffset);
this.setState({ leftValue: (horizontalOffset - 20) });
}
}
render() {
return (
<div>
<button onClick={this.playClick}>play</button>
<div className="slider-content" onClick={this.changePosition}>
<div className="arrow" style={{left: this.state.leftValue}}></div>
</div>
</div>
);
}
Upvotes: 2