Let's upgrade our little audio player in React
Tags: react, audio, javascript, html
Published: 2019-01-19 13:05:53

---
title: Let's upgrade our little audio player in React
published: true
description: Using the audio element, logic and styling to upgrade our music player.
tags: react, audio, javascript, html
series: 💥 React Treats
---
_This is a bit of a part two. So if you missed part one, have a gander!_

{% link https://dev.to/ma5ly/lets-make-a-little-audio-player-in-react-p4p %}

In this session we're going to add a skip and back button to our little player. Also we're going to make a visual time indicator and a way to jump to a position in the track. This will require a bit of styling. I tried to keep it to a minimum so we can focus on the Javascript, but yes, we'll do some CSS.

I chose to just use a regular stylesheet and no specific library for the styling. Personally, I love CSS-in-JS solutions – they just feel right – but they _are_ heavily contested. Since we'll just use barely enough styling to get by, let's not get into those discussions and just use whatever Create React App gave us.

In part one we did most of the wiring up. In this sequel we'll do a bit more logic. Let's start with the skip and back buttons. First we'll add the buttons to our JSX and clean up a bit of the logic.

```javascript
{this.state.player !== "stopped" && (
    <div className="buttons">
      <button onClick={() => this.handleSkip("previous")}>Previous</button>
      {this.state.player === "paused" && (
        <button onClick={() => this.setState({ player: "playing" })}>
          Play
        </button>
      )}
      {this.state.player === "playing" && (
        <button onClick={() => this.setState({ player: "paused" })}>
          Pause
        </button>
      )}
      <button onClick={() => this.setState({ player: "stopped" })}>Stop</button>
      <button onClick={() => this.handleSkip("next")}>Skip</button>
    </div>
  );
}
```

I've also added an `onClick` handler to the new buttons. You can see it calls the `handleSkip` function and expects an argument. In this case the direction: 'forward' or 'back'. Next we'll add the `handleSkip` function itself. Before we write it though, I've moved the array with our tracks to a constant at the top of our file. This is just static data that doesn't change. 

```javascript
const TRACKS = [
  { id: 1, title: "Campfire Story" },
  { id: 2, title: "Booting Up" }
];
```

For the function itself we have to figure out a few things:

- the index of the track that's currently playing
- the next or previous track based on the argument we received
- what we're going to do when there is no previous or next track!

Let's start with the easiest thing. We have our array with all the tracks that could possibly be playing at this moment, and we also have a piece of state which should contain the title of the track that is currently playing. That's enough to make use of the `findIndex` method.

When using this method you get a number in return. Either it will be the index of the song we need, or it will be -1 aka "we didn't find what you we're looking for, sorry". Before we 'calculate' the next or previous track, we should really check how many tracks there are.

We can do that by grabbing the length of our `TRACKS` array and subtracting 1. Why subtract 1? Because the length number is the exact amount of items in the array, while the index actually starts at zero. So 2 is 1, and 1 is 0. Hope that makes sense.

We've got two values now: the `currentTrackIndex` and the `tracksAmount`. That's enough to write the following logic.

```javascript
handleSkip = direction => {
   const currentTrackIndex = TRACKS.findIndex(
     track => track.title === this.state.selectedTrack
   );
   const tracksAmount = TRACKS.length - 1;
   if (direction === "previous") {
     const previousTrack =
       currentTrackIndex === 0 ? tracksAmount : currentTrackIndex - 1;
     const trackToPlay = TRACKS[previousTrack];
     this.setState({ selectedTrack: trackToPlay.title });
   } else if (direction === "next") {
     const nextTrack =
       currentTrackIndex === tracksAmount ? 0 : currentTrackIndex + 1;
     const trackToPlay = TRACKS[nextTrack];
     this.setState({ selectedTrack: trackToPlay.title, duration: null });
   }
};
```

If you've read or tried the code, you might have deduced how we handle the third thing we had to figure out: what we're going to do when there is no previous or next track. We're actually just going to play the first track when there is no next track, and play the last track when there is no previous track. That was a personal choice by me. We could've disabled the skip buttons too. Or hide them. But I didn't feel like that.


Anyway. With our buttons working, we can now focus on creating a time indicator. We're going to set up a new component for this, which we'll call `TimeBar`.

```javascript
<TimeBar
  setTime={this.setTime}
  currentTime={this.state.currentTime}
  duration={this.state.duration}
/>
```

As you might see it takes three props: a function to set the time of the track, the current time of the track and the full duration of the track. The current time and duration we already set up in the previous article, so we only have to make a function for setting the time of a track to a specific point. This is actually not too difficult.

```javascript
  setTime = time => {
    this.player.currentTime = time;
  };
```
The function receives a value I called `time`. Then we access our player using the `ref` we placed in our earlier session. The player has value called `currentTime` we can mutate to the value we received into our function. That is all really.

Now for the component itself. Let's think a bit about what it should do. It needs to visually show how long the track is. It then needs to visually show where the track is. And finally you should be able to click anywhere on the bar to jump to that position in the song.

We're going to do this all in pure React, we're going to use my favorite Javascript method `.map`, and a lovely oldskool `while` loop. Let me show you the first bit, so we can talk about it.

```javascript
function TimeBar({ currentTime, duration, setTime }) {
  const sBits = [];
  let count = 0;
  while (count < duration) {
    sBits.push(count);
    count++;
  }

  return <div className="timebar" />;
}
```
In React you can just use a function as a component. The props you've passed it enter the scene as arguments you can destructure. It's quite neat. Now for the first bit of logic. Why is it there, and what does it do?

Well, let's put on our thinking hats again for a minute and try to visualize in our mind's eye how we can display the duration (which is just a number) as a graphical bar consisting of bits of time. We can only do that if we turn the number into bits first. Luckily someone came up with the concept of seconds and even more lucky: the duration is also in seconds.

That means we can create an empty array and then start throwing our bits into it. We do that by starting a loop that will keep looping as long as our count is smaller than the duration. With each loop we add a number to our count, so we can be sure that at some point the count will become higher than the duration, effectively stopping our loop.

If all went correctly the loop should execute code for exactly the amount of full seconds there are in the duration. So we can fill our array with one bit on each turnover.

That's good. But how can we display those bits to become a bar. And let's just get ambitious. How can we make it so that we see the actual time of the track visualized on that bar?

```javascript
const seconds = sBits.map(second => {
  return (
    <div
      key={second}
      style={{
        float: "left",
        cursor: "pointer",
        height: "30px",
        width: `${300 / Math.round(duration)}px`,
        background:
          currentTime && Math.round(currentTime) === second ? "white" : "black",
        transition: "all 0.5s ease-in-out"
      }}
    />
  );
});
```

There's a lot going on here. Let's digest it. The `.map` method allows us to take our array with seconds, and for each of those seconds return something of our liking. In our case, we're going to return a `div`, so that when placed next to each other, they form a nice horizontal bar.

The `div` has a `key`, which let's React keep track of which `div` is which. For that reason it has to be unique. All our seconds are different, so it's fine to use that for a `key`. Next we have our styling. The `float` makes sure the seconds are rendered side by side, but more interesting are the `width` and `background`.

Now you can't see this here, but in our CSS file I have decided that our timebar will be 300 pixels wide. This was an arbitrary choice, but one that affects the width of our seconds; we want all our seconds to be evenly spread across the bar. We can make that happen by calculating the width via a simple division. The `Math.round` is there to round the duration up to a whole number of seconds.

So we have a bar with `divs` evenly spread across its container each representing a second within the duration of the track. We also happen to have passed the current time of the track into our component. This piece of data is in seconds as well. Which is perfect. That means if we round it to whole seconds, we can match it to the seconds in our timebar.

Using a ternary operator we can compare our two values, and decide whether to render our `div` as white or black. With black being no match, and white being a match.

The result of our `.map` is saved to a variable I have called seconds. We only have to place that variable in the `return` of our component to make it render on the screen. As you might notice I have also added the `formattedCurrentTime` and `formattedDuration` to our `return`. These are the values we calculated in the first article.

```javascript
return (
    <div className="timebar">
      <div className="bar">{seconds}</div>
      {currentTime ? (
        <div className="time">
          {formattedCurrentTime} / {formattedDuration}
        </div>
      ) : (
        ""
      )}
    </div>
  );
```

There a bunch of `classNames` in this snippet. And there are more in the full code. They refer to CSS. I won't lay them out here, instead you can check out the `styles.css` in this [CodeSandBox ](https://codesandbox.io/s/j7krz6r4r9). They help put everything in the correct place.

Depending on your creativity, determination and skill you can make something really nice out of it. Or at least something that's a bit more spectacular then what I have created. My excuse is of course that I wanted to focus on the Javascript 😅.

Anyway, that concludes the upgrade of our little player. Now let's play some music and enjoy our day.

![Little Audio Player](https://thepracticaldev.s3.amazonaws.com/i/08odfgxgjg790fx4u0km.gif)

*Final addendum!* What if our track is nicely playing, but it ends? What happens then? Surely it should play the next track right, if there is still one? I think so too. So let's quickly set it up, before I really let you go.

```javascript
if (
      this.state.duration &&
      !isNaN(this.state.duration) &&
      this.state.duration === this.state.currentTime
    ) {
      const currentTrackIndex = TRACKS.findIndex(
        track => track.title === this.state.selectedTrack
      );
      const tracksAmount = TRACKS.length - 1;
      if (currentTrackIndex === tracksAmount) {
        this.setState({
          selectedTrack: null,
          player: "stopped",
          currentTime: null,
          duration: null
        });
      } else {
        this.handleSkip("next");
      }
    }
```
The above code is part of the `componentDidUpdate` lifecycle. It check to see if there is currently a duration and if that duration is equal to the current time of the track. 

If that is the case it would mean the track has ended. We then do a quick check to see if this was the last track of our playlist, or if that there is still another one to be played. If this was really it, then we set all our state back to default. If there is still a next track, then we just simply call the `handleSkip` function we created earlier.

That's really it for now!