Matt
Matt

Reputation: 8942

React.js components synchronization with html5 media current time

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?

jsfiddle

  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

Answers (1)

bennygenel
bennygenel

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

Related Questions