Welcome to a quick tutorial and example on how to create an HTML custom audio player. So you want to create a custom audio player? Well, the default <audio>
cannot be styled using CSS at the time of writing. The only way is to recreate an audio player – Here’s a simple one that I have made, read on!
TABLE OF CONTENTS
CUSTOM AUDIO PLAYER
All right, let us now get into more details about the custom audio player work. Take note, there is no CSS involved here, only the pure mechanics.
HOW TO USE
<!-- (A) CSS & JS -->
<!-- https://fonts.google.com/icons -->
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="aud-player.css">
<script defer src="aud-player.js"></script>
<!-- (B) AUDIO PLAYERS -->
<div class="aWrap" data-src="S1.mp3"></div>
<div class="aWrap" data-src="S2.mp3"></div>
If you just want to use this as a “plugin”:
- Load Google Material Icons, the audio player CSS and JS.
- Create as many
<div class="aWrap" data-src="AUDIO-FILE">
as required.
PART 1) TIME FORMAT HELPER FUNCTION
// (A) SUPPORT FUNCTION - FORMAT HH:MM:SS
var timeString = secs => {
// (A1) HOURS, MINUTES, SECONDS
let ss = Math.floor(secs),
hh = Math.floor(ss / 3600),
mm = Math.floor((ss - (hh * 3600)) / 60);
ss = ss - (hh * 3600) - (mm * 60);
// (A2) RETURN FORMATTED TIME
if (hh>0) { mm = mm<10 ? "0"+mm : mm; }
ss = ss<10 ? "0"+ss : ss;
return hh>0 ? `${hh}:${mm}:${ss}` : `${mm}:${ss}` ;
};
Right at the top of the Javascript, we have a support function to format the “track time” into a “nice format” of HH:MM:SS
.
PART 2) HTML LAYOUT
window.addEventListener("load", () => { for (let i of document.querySelectorAll(".aWrap")) {
// (B) AUDIO + HTML SETUP + FLAGS
i.audio = new Audio(encodeURI(i.dataset.src));
i.innerHTML =
`<button class="aPlay" disabled><span class="aPlayIco material-icons">
play_arrow
</span></button>
<div class="aCron">
<span class="aNow"></span> / <span class="aTime"></span>
</div>
<input class="aSeek" type="range" min="0" value="0" step="1" disabled>
<span class="aVolIco material-icons">volume_up</span>
<input class="aVolume" type="range" min="0" max="1" value="1" step="0.1" disabled>`;
i.aPlay = i.querySelector(".aPlay"),
i.aPlayIco = i.querySelector(".aPlayIco"),
i.aNow = i.querySelector(".aNow"),
i.aTime = i.querySelector(".aTime"),
i.aSeek = i.querySelector(".aSeek"),
i.aVolume = i.querySelector(".aVolume"),
i.aVolIco = i.querySelector(".aVolIco");
i.seeking = false; // user is dragging the seek bar
// ...
}});
On window load, we grab all .aWrap
and insert the necessary HTML.
<button class="aPlay">
The play/pause button.<span class="aNow">
is the current playtime of the track,<span class="aTime">
is the total time of the track.<input class="aSeek">
Time seek slider, the value is in seconds.<span class="aVolIco>
Volume icon and mute button.<input class="aVolume">
Volume slider.
PART 3) PLAY/PAUSE BUTTON
// (C) PLAY & PAUSE
// (C1) CLICK TO PLAY/PAUSE
i.aPlay.onclick = () => {
if (i.audio.paused) { i.audio.play(); }
else { i.audio.pause(); }
};
// (C2) SET PLAY/PAUSE ICON
i.audio.onplay = () => i.aPlayIco.innerHTML = "pause";
i.audio.onpause = () => i.aPlayIco.innerHTML = "play_arrow";
- (C1) Don’t think this needs any explanation… If the audio is paused, we click to play. If the audio is playing, we click to pause.
- (C2) Automatically set the play/pause icon on the button.
PART 4) TRACK TIME
// (D) TRACK PROGRESS & SEEK TIME
// (D1) TRACK LOADING
i.audio.onloadstart = () => {
i.aNow.innerHTML = "Loading";
i.aTime.innerHTML = "";
};
// (D2) ON META LOADED
i.audio.onloadedmetadata = () => {
// (D2-1) INIT SET TRACK TIME
i.aNow.innerHTML = timeString(0);
i.aTime.innerHTML = timeString(i.audio.duration);
// (D2-2) SET SEEK BAR MAX TIME
i.aSeek.max = Math.floor(i.audio.duration);
// (D2-3) USER CHANGE SEEK BAR TIME
i.aSeek.oninput = () => i.seeking = true; // prevents clash with (d2-4)
i.aSeek.onchange = () => {
i.audio.currentTime = i.aSeek.value;
if (!i.audio.paused) { i.audio.play(); }
i.seeking = false;
};
// (D2-4) UPDATE SEEK BAR ON PLAYING
i.audio.ontimeupdate = () => {
if (!i.seeking) { i.aSeek.value = Math.floor(i.audio.currentTime); }
i.aNow.innerHTML = timeString(i.audio.currentTime);
};
};
- (D1) Set a “now loading” message in the track time while the track is loading.
- (D2) On metadata loaded (information on the track), a whole load of things needs to be done. Not going to explain line-by-line, but basically:
- (D2-1) Set the HTML track time.
- (D2-2) Also set the track time on the seek bar.
- (D2-3) Update the track when the seek bar is being dragged. Take note of how we use the
seeking
flag here – We do not want the manual seek to clash with the auto-update as the track is playing. - (D2-4) Automatically update the HTML seek bar as the track plays.
PART 5) VOLUME CONTROL
// (E) VOLUME
i.aVolIco.onclick = () => {
i.audio.volume = i.audio.volume==0 ? 1 : 0 ;
i.aVolume.value = i.audio.volume;
i.aVolIco.innerHTML = (i.aVolume.value==0 ? "volume_mute" : "volume_up");
};
aVolume.onchange = () => {
i.audio.volume = i.aVolume.value;
i.aVolIco.innerHTML = (i.aVolume.value==0 ? "volume_mute" : "volume_up");
};
Well… Mute/unmute and update the volume as the user moves the slider.
PART 6) ENABLE/DISABLE CONTROLS
// (F) ENABLE/DISABLE CONTROLS
i.audio.oncanplaythrough = () => {
i.aPlay.disabled = false;
i.aVolume.disabled = false;
i.aSeek.disabled = false;
};
i.audio.onwaiting = () => {
i.aPlay.disabled = true;
i.aVolume.disabled = true;
i.aSeek.disabled = true;
};
canplaythrough
When the browser has sufficient audio buffer to play through, we enable the controls.waiting
When there is an insufficient buffer, we disable the controls.
DOWNLOAD & NOTES
Here is the download link to the example code, so you don’t have to copy-paste everything.
SORRY FOR THE ADS...
But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.
Buy Me A Coffee Code Boxx eBooks
EXAMPLE CODE DOWNLOAD
Click here to download. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
EXTRA BITS & LINKS
That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.
ADDING FILE/TRACK NAME
- Very simple solution –
<div>MY TRACK NAME</div><div class="aWrap"></div>
. - Not simple solution – Add
<div class="aWrap" data-name="MY TRACK NAME">
, change the HTML in part (B) to adapt the name.
CHANGING THE AUDIO SOURCE PROGRAMMATICALLY
<div class="aWrap" id="demo">
document.getElementById("demo").audio.src = "ANOTHER.MP3";
COMPATIBILITY CHECKS
This audio player is obviously not backward compatible with the ancient browsers. But it should work well across all “Grade A” modern browsers.
LINKS & REFERENCES
- HTML Media Element– MDN
- HTML Audio – WhatWG
- Audio Player With Playlist – Code Boxx
- PHP Audio Player With Playlist – Code Boxx
THE END
Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!
Hi,
Overall you deserve a big compliment, as your other posts are sure a big help
for anyone who trying to learn to code and you using “Hands-On” examples, what
makes it fun to look up your posts !
But this one, is not worth it to be posted… as in 2023 ALL what anyone is
building MUST be 100% Cellphone friendly and most of those have still pretty
small screens.
– Your Player is impossible to be used on a Cellphone…
UNLESS you move by ONE Player the Volume Control into a “Row” below
(you provide 2 in your Zip-File)
TRY it out… on a Cellphone just useless, as you can’t easy control the Track
or adjust the Volume.
I write this as my friend ask ME to FIX your code for HIM… Ha ha ha!
You coded it in 2022… Well, Okay
but you modified it in March 2023 and didn’t FIX that issue…
Now that is sad 🙁
* Completely missed the point. Are people going to complain this is “broken” on smartwatches and Raspberry Pi displays next? 😆 CSS is excluded for a reason.
* You are free to build the interface however you see fit – Add/remove/arrange buttons, change theme, add responsive support.
* If you want to use it as-it-is, just add
.aWrap { flex-wrap:wrap }
to allow the elements “break into another row”.There’s no need to be critical just because you had to do cosmetic changes in 2023. Such mental diarrhea and passive-aggressive attacks (intentional or unintentional) are uncalled for. Cheers!
sir can you help with our project of web development
https://code-boxx.com/faq/#hire