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! :)
Top comments (1)
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 :)