Search code examples
scalaaudioreactjsscala.jsscalajs-react

Looping on an interval of audio element with scalajs-react


I want to build a little component containing an audio element, which capable of looping on an interval. The two ends of the interval would be a defined as properties of the component. As the timeUpdate event don't have the necessarily precision (I want at least 33Hz guaranteed), I decided to use a backend with TimerSupport, and just set the currentTime back to the starting point once it passes the end of the interval.

val AudioRef = Ref[Audio]("audio")
class PlayerBackend extends TimerSupport
val AudioPlayer = ReactComponentB[String]("AudioPlayer")
  .initialState(0L)
    .backend(i => new PlayerBackend())
  .render_P(url => {
    <.audio(
       ^.ref := AudioRef,
      ^.autoPlay  := true,
      ^.controls  := true,
      <.source(^.src := "http://www.stephaniequinn.com/Music/Allegro%20from%20Duet%20in%20C%20Major.mp3"),
      "Your browser does not support the audio element."
    )
  })
  .componentDidMount({ c =>
    c.backend.setInterval(Callback.log({
      if (AudioRef(c).isDefined) ({
        AudioRef(c).get.currentTime
      }) else "nothing"
    }), 1000 millisecond)
  }).configure(TimerSupport.install)
  .build

It this little example I just want to print the current position of the player, but for some reason (the callback closes over a copy of the backend context at the time when the component mounts?) the AudioRef(c) points to an old version of the audio element. Any idea how to fix this? I'm also interested in other designs, as I'm not really experienced neither with ScalaJS nor React.


Solution

  • The issue is with the log call that evaluates its parameter only once, resulting in a single value that is then logged over and over again. The correct code would be something like this:

    .componentDidMount({ c =>
      c.backend.setInterval(CallbackTo[Double] {
        if (AudioRef(c).isDefined) ({
          AudioRef(c).get.currentTime
        }) else 0
      } >>= Callback.log, 1000 millisecond)
    })
    

    It creates a callback that extracts the currentTime value (or nothing) and then flatMaps to another callback that logs that value.