Play Sounds with Stimulus JS
How to load sounds in a Stimulus Controller in Rails 7.1
I'm working on a game using Rails 7.1 and Stimulus JS. In the game, when the user scores a point I want to play a sound. I created an sfx_controller.js
in my javascript/controllers
and wrote:
import { Controller } from "@hotwired/stimulus"
import correctSound from "./assets/correct.mp3";
// Connects to data-controller="sfx"
export default class extends Controller {
static values = {
correct: Boolean
}
connect() {
console.log("connected")
console.log(this.correctValue)
if (this.correctValue) {
this.playCorrectSound();
}
}
playCorrectSound() {
console.log("playing correct sound");
const audio = new Audio(correctSound);
audio.play();
}
}
And in my view I have:
<div data-controller="sfx" data-sfx-correct-value="<%= !!@correct %>">
...
</div>
Unfortunately when I loaded my page I got the following error in my console.
GET http://localhost:3000/assets/controllers/assets/correct.mp3 net::ERR_ABORTED 404 (Not Found)
I inspected my <div data-controller="sfx">
tag to ensure that the !!@correct
value was being set to the expected boolean value. This confirmed my suspicion that import correctSound from "./assets/correct.mp3"
was at issue.
Solution
First, I added an audio tag to my view. You can render a little player by setting controls: true
. When I did so, I clicked the play button to verify that the correct.mp3
was indeed where I thought it was in my assets
directory.
<div data-controller="sfx" data-sfx-correct-value="<%= !!@correct %>">
...
<%= audio_tag "correct.mp3", controls: true %>
</div>
When I loaded the page, I got the following extremely helpful error:
Asset `correct.mp3` was not declared to be precompiled in production.
Declare links to your assets in `app/assets/config/manifest.js`.
//= link correct.mp3
I followed these instructions and added //= link_tree ../sounds
right under the existing //= link_tree ../images
following to my app/assets/config/manifest.js
:
//= link_tree ../images
//= link_tree ../sounds
//= link_directory ../stylesheets .css
//= link_tree ../../javascript .js
//= link_tree ../../../vendor/javascript .js
//= link_tree ../builds
I believe this makes everything in my assets/sounds
directory "compilable" for production.
When I reloaded my page I could inspect the audio tag and see the following:
<audio data-sfx-target="sound" src="/assets/correct-37...1b1df46.mp3"></audio>
This led me to the understanding that the correct.mp3
file in my assets/sounds
directory was being renamed by Rails. My hypothesis was that if I could grab the src
value of this audio tag in my Stimulus sfx_controller.js
I'd be able to then play it.
I updated my view to add a data target attribute to my audio tag to make it accessible in my sfx_controller.js
:
<div data-controller="sfx" data-sfx-correct-value="<%= !!@correct %>">
...
<%= audio_tag "correct.mp3", controls: false, data: { sfx_target: "sound" } %>
</div>
I updated my sfx_controller.js
to be:
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="sfx"
export default class extends Controller {
// The sound target points to the audio tag in the view
static targets = ["sound"]
static values = {
correct: Boolean
}
connect() {
if (this.correctValue) {
this.playCorrectSound();
}
}
playCorrectSound() {
try {
// I pull the src value from the audio tag
const url = this.soundTarget.src;
const audio = new Audio(url);
audio.play();
} catch (error) {
console.error("Failed to play sound:", error);
}
}
}
I am not asserting that this is the right way to do this, but it does work. And no amount of Googling or ChatGPTing could quickly answer this problem for me. I hope this helps the next person trying to play an audio file in their Stimulus controller.
For reference, you can take a look at my commit on github that includes the changes described in this post.
Member discussion