loading...
Cover image for Build a custom audio player with accessibility

Build a custom audio player with accessibility

snowleo208 profile image Yuki Cheung Originally published at blog.atrera.com on ・4 min read

This article was originally posted in my personal blog.

This is not my first time to create a custom audio player.

In order to cater for my lovely designer's needs, I need to create a custom audio player. After learnt HTML5 Audio attributes and viewed more tutorials of making custom audio player, most of the tutorial did not mention accessibility.

This time I use React for audio player, but you can always view my last custom audio player for vanilla JavaScript version (although did not care much about accessibility at that moment).

I am not an expert of accessibility at all, feel free to let me know your thoughts!


The markup of player

First, the outer container of the audio player, it should have role="region" and aria-label="Audio Player". Role tells screen reader this div represents something, and from aria-label, the screen reader knows it is an audio player.

Outer container

<div className="c-audio" aria-label="Audio Player" role="region">
  // ...
</div>

Play button

The following is the play button inside:

<button
  title={!isPlay || isPlay === null ? 'Play' : 'Pause'}
  className={
    !isPlay || isPlay === null
      ? 'c-audio u-btn l-play l-play__play'
      : 'c-audio u-btn l-play l-play__pause'
  }
  aria-controls="audio1"
  onClick={this.controlAudio}
  aria-label={!isPlay || isPlay === null ? 'Play' : 'Pause'}
/>

The aria-controls links to the id of the audio tag at the bottom (e.g. <audio id="audio1" ... >) and aria-label changes while playing or pausing.

Slider

For the audio control, actually I want to use range (e.g. <input type="range" ...>), however, it is hard to maintain same styles in all browsers, so I decided to use div with svg, plus aria labels. Also, I used tabIndex="0" here for keyboard to focus into this element.

In this slider, user can:

  • use mouse or keyboard to change current time of audio
  • can focus the slider
  • can use mouse to change position
  • can use left or right key in keyboard to change position
<div
className="c-audio__slider"
onKeyDown={this.onKeyDown}
onClick={this.onClick}
tabIndex="0"
aria-valuetext="seek audio bar"
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow={Math.round(percentage)}
role="slider"
ref={this.audioSeekBar}
>

It needs lots of work to reinvent the slider, but it is worth it. After these implementations, you can create slider with different styles, also with accessibility! View here for example from WAI-ARIA Authoring Practices.

Manipulate the slider

How to change the percentage of slider when it detects click or key down? We can use onClick and onKeyDown function. For the click function, it calculates the percentage of click position. (Note: seekBar.getBoundingClientRect().left is for IE11, as it doesn’t support x/y values)

onClick(e) {
    const seekBar = this.audioSeekBar.current;
    const audio = this.audioFile.current;

    const pos =
    (e.pageX -
        (seekBar.getBoundingClientRect().x ||
        seekBar.getBoundingClientRect().left)) /
        seekBar.getClientRects()[0].width;

        this.setState({
            percentage: pos * 100
        });

        audio.currentTime = audio.duration * pos;
}

For keyboard version, it add or decrease percentages based on different keys.

Keybindings for slider:

  • top: to 100 (max)
  • bottom: to 0 (min)
  • left: -1 step
  • right: +1 step
  • top: +10 steps
  • bottom: -10 steps
onKeyDown(e) {
    // when user focus in audio slider and 
    // clicks keys inside key list, will change current time of audio
    const audio = this.audioFile.current;
    const isLeft = 37;
    const isRight = 39;
    const isTop = 38;
    const isBottom = 40;
    const isHome = 36;
    const isEnd = 35;
    const keyList = [isLeft,isRight,isTop,isBottom,isHome,isEnd];

    if (keyList.indexOf(e.keyCode) >= 0) {
        let percentage;
        switch(e.keyCode) {
            case isLeft:
            percentage = parseFloat(this.state.percentage) - 1
            break;
            case isRight:
            percentage = parseFloat(this.state.percentage) + 1
            break;
            case isTop:
            percentage = parseFloat(this.state.percentage) + 10
            break;
            case isBottom:
            percentage = parseFloat(this.state.percentage) - 10
            break;
            case isHome:
            percentage = 0
            break;
            case isEnd:
            percentage = 99.9 // 100 would trigger onEnd, so only 99.9
            break;
            default:
            break;
        }

        // add boundary for percentage, cannot be bigger than 100 or smaller than zero
        if(percentage > 100) {
            percentage = 100
        } else if(percentage < 0) {
            percentage = 0
        }

        this.setState({
            percentage
        });

        audio.currentTime = audio.duration * (percentage / 100);
    }
}

Audio Tag

The main thing here is the audio tag. From the audio tag, we need to use onTimeUpdate and onEnded to control the slider. When the audio is running, it calls the function of onTimeUpdate and update the slider.

When the audio ends, it changes the current time of audio to zero and change slider’s percentage to zero too. For the <track kind="captions" />, it is for audio or video which has subtitles, we don't have one here, so skip it right now.

<audio
  className="c-audio__sound"
  id="audio1"
  src={path}
  onTimeUpdate={this.getCurrDuration}
  onEnded={() => {
    this.audioFile.current.currentTime = 0;
    this.setState({
      isPlay: false,
      currentTime: 0,
      percentage: 0
    });
  }}
  ref={this.audioFile}
>
  <track kind="captions" />
</audio>

Focus Styles

Also, don’t forget to create custom focus styles for play button and slider!

.l-play:focus {
  outline: none;
  box-shadow: 1px 1px 1px 0px rgba(25, 25, 25, 0.2);
}

Result

View my result in the following or click here to view on Codepen!

Welcome to drop me a line or let me know your thoughts! :)


Read More

Posted on by:

snowleo208 profile

Yuki Cheung

@snowleo208

I am a self-taught front-end web developer! Jump into this field using freeCodeCamp since 2017. Now mostly focus on front-end technologies, e.g. SASS / ReactJS and some backend like PHP/Laravel.

Discussion

pic
Editor guide
 

It’s great that you’re considering accessibility in your design.

Accessibility is not something I thought was important but after reading several blog posts that explained accessibility in excruciating detail, I realised how ignorant I’ve been about its importance.

 

Thank you! I think accessibility is important when I try to browse the web with keyboard only few years ago (my mouse was broken at that time lol). It is hard to navigate, even in some famous sites :(

That's why I think it is good to do something for people with different disabilities, or keyboard users. Accessibility is not just for the others, but also for myself :)