Cerner, the company I’m currently employed at, holds an annual hackathon called 2^5, where every day for 32 days, you can make 1 submission of code that is 32 lines long or less. I’ve blogged about this previously on dev.to, and you can check out the post here.
I’ve been a participant for 4 years, and it always surprises me how much folks are able to do using only 32 lines of code. This year was one of my favorite years to participate because I themed all of my submissions around Animal Crossing: New Horizons. Like millions of other Nintendo Switch gamers out there, ACNH became daily therapy as I caught bugs and fish, dug up fossils, and built an island full of really awesome villagers (the best overall being Cherry, as I’m sure everyone who has ever had her as a villager would agree).
What excited me even more was to find a community-supported REST API for the new game that would allow you to query for a lot of in-game items and get information about those items in the form of a JSON body, and almost every submission for 2^5 that I made this year revolved around this free API.
There’s one submission that I made though that… Bothers me. A lot. I’ve thought about it non-stop since I submitted it, not for any reason other than the fact that it is visually one of the ugliest things I’ve ever concocted. That submission is the radio that plays K.K. Slider songs. For brevity, I’ve deployed this file to Vercel here so that you can see what it does.
As you can see, this is really really not visually appealing in any sense of the word. The JavaScript that was needed to make this work correctly made the file so large that there wasn’t really enough time leftover to make it pretty…
I’m working on making it prettier right now, so for now, I’ll focus on what the audio
tag in HTML5 can do for you, and how I used it to make a little mvp of a radio!
First thing we need to discuss is: how are we going to be retrieving the music we want to play? Well first I needed to go retrieve a song to play. The ACNH API has a Songs endpoint that makes this easy. Since I’m buildling a radio, I just want to go get random songs to play, so I’ll first start by generating a random integer in JavaScript between 1 and 95:
Math.floor(Math.random() * 94) + 1
This is one of those quirky things about JavaScript that I really dislike: unlike other modern languages that can actually generate a random integer, us JavaScript developers have to use a combination of Math.random
(which generates a random decimal number between 0.0 and 1.0) and Math.floor
(which will round a number down to it’s nearest whole number value, so numbers like 3.9, 3.7, and 3.0000001 all become 3). My intention here is to generate a random number between 1 and 95, because the ANCH API uses positive integers for the IDs, and currently the only way to get songs from the API is to use ID numbers within that range as query parameters:
const req = new XMLHttpRequest();
req.open('GET', `https://acnhapi.com/v1/songs/${Math.floor(Math.random() * 94) + 1}`);
req.onload = function () { setSong(req.response); };
req.send();
I’m not using NPM for this project, so just using XMLHttpRequest
works for my needs. Now… What is that setSong
and what does it do? Well, I use that to set some attributes on an HTML5 audio
tag. Using this tag will help me answer the next conundrum:
The audio
tag, when provided with a music source
, can play audio media straight from a browser once the browser has loaded the audio! So we can take our response from the API call, extract the URL of the MP3 from it, and begin to play it:
<audio autoplay>
<source type="audio/mpeg"/>Looks like your browser doesn't support audio tags :(
</audio>
function setSong(resp) {
const json = JSON.parse(resp);
document.querySelector('#text').textContent = json["name"]["name-USen"];
document.querySelector('source').setAttribute('src', json["music_uri"]);
document.querySelector('audio').load();
}
This code will set the src
of the source
tag, which will then allow the audio
tag to start playing music, once it’s told to start playing. Before I go any further, I want to address how this code actually plays music, and why if you’re on a Chrome-based browser, the autoplay
attribute likely isn’t causing the song to auto-play like you would expect:
From the Mozilla docs I linked above, the audio
tag has many attributes. The two main ones I want to focus on are autoplay
and controls
. Many browsers will honor autoplay
if the controls
attribute is included here, the reason being that controls
asks the browser to supply music controls for the media. For this project, I don’t want to render the default controls, and I want it to still autoplay
where available, which is why I left it in.
However, Chrome refuses to autoplay
any media without controls being present to play/pause said media. Because of this, I had to introduce something into the UI that would allow it to play the media based on a user-action, so I just added an onClick
handler on the body
tag, the entirety of which ends up looking like this:
<body onclick="document.querySelector('audio').load();" onload="loadNextSong()">
<div class="content">
<h2 id="text"></h2>
<h2>Click here to start playing music.</h2>
</div>
<audio autoplay>
<source type="audio/mpeg"/>Looks like your browser doesn't support audio tags :(
</audio>
</body>
Now, when the page loads, the user is prompted to click somewhere in order to start playing music. Now, we’re building a radio, so when the first song we loaded stops, how do we load the next one?
The audio
tag comes with very convenient event handlers that we can take advantage of. The main one that we’ll be able to take advantage of for our radio is the ended
event. So far, the JavaScript we’ve written looks like this:
function setSong(resp) {
const json = JSON.parse(resp);
document.querySelector('#text').textContent = json["name"]["name-USen"];
document.querySelector('source').setAttribute('src', json["music_uri"]);
document.querySelector('audio').load();
}
function loadNextSong() {
document.querySelector('#text').textContent = "Loading...";
const req = new XMLHttpRequest();
req.open('GET', `https://acnhapi.com/v1/songs/${Math.floor(Math.random() * 94) + 1}`);
req.onload = function () { setSong(req.response); };
req.send();
}
We have functions for retrieving the next song, and loading it into our web page. However, this code will stop playing after the first randomly selected song. We can’t have it stop. What would K.K. Slider think of us if that happened?
Luckily, we can just load the next song when the audio tag has ended the playing of media. We can do this by adding an event handler for the ended
event:
document.querySelector('audio').addEventListener('ended', loadNextSong);
When the song stops playing, we’ll query up another song, set all the appropriate tags, and then document.querySelector('audio').load()
will play the next song for us! Because we added autoplay
to the audio tag, and the user had to click on the body of the page in order to start playing, songs will continue to play even in Chrome until the browser is closed!
This is not a bad start for an ACNH radio, but the internet deserves better! So far, I don’t have a way to turn the radio off once it’s on, and it… Really doesn’t look so much like a radio.
Stay tuned for an upcoming post where I use some CSS magic to turn this blob of text on a screen into something we can all be proud of. Until then, I hope you liked learning a little bit about how you could use the audio
tag! If you have any questions or feedback for me, links to my social media can be found on the footer of my website. Thanks for reading, and have a good one!