I have a React app using NodeJS and Express hosted on Heroku. We use use-sound.js, which is a 3rd party hook built on howler.js to play ambient sound as well as sound effects. On all 4 of our local machines, the sound will work fine.
Intended behaviour: When the site is loaded, an icon on the top left allows the user to unmute the ambient sound which will also enable SFX when the game is started.
Current behaviour: When the site is loaded, the icon indicates the sound is unmuted. When the button is clicked, the icon toggles and the audio menu button appears. However, no sound is played and nothing is displayed in the console logs which would indicate any problems at all.
Code
GitHub: https://github.com/AlecWGreene/MSU-Project3-Four-Horsemen
Deployed Site: https://ashen-void.herokuapp.com/
Build Packs:
https://github.com/timanovsky/subdir-heroku-buildpack.git
NodeJS
Relevant Code Snippets
SoundSuite Component
import React, { useState, useContext, createContext } from 'react';
import useSound from 'use-sound';
import soundEnums from '../../../game/SpriteEnums.js';
import SfxOptions from '../SfxOptions/index';
import ButtonGroup from 'react-bootstrap/ButtonGroup';
import './style.css'
import SfxButton from '../SfxButton/index.js';
function SoundSuite({ children }) {
const sfx = useProvideSfx();
return (
<>
<sfxContext.Provider value={sfx}>
<SfxButton />
{children}
</sfxContext.Provider>
</>
)
};
// access context provider state and methods
export function useSfx() {
return useContext(sfxContext);
}
export default SoundSuite;
// create new context instance of sfxContext to keep track of sound effect states accross the site
const sfxContext = createContext();
// context provider state and methods
function useProvideSfx() {
const [soundEnabled, setSoundEnabled] = useState(false);
const [playbackRate, setPlaybackRate] = useState(1);
const [masterVol, setMasterVol] = useState(1.0);
const [nextFile, setNextFile] = useState('');
const [ambientFile, setAmbientFile] = useState('Sound_background_0');
const [sfxFile, setSfxFile] = useState('Sound_pop_0');
const [ambientVol, setAmbientVol] = useState(0.5);
const [sfxVol, setSfxVol] = useState(0.5);
const [auto, setAuto] = useState(false);
const [sfxAuto, setSFXAuto] = useState(false);
const [play, { pause, stop, isPlaying } ] = useSound(
soundEnums[ambientFile].src,
{
autoplay: auto,
preload: true,
volume: masterVol*ambientVol,
loop: true,
playbackRate,
id: ambientFile
},
);
const [playSfx, { sound }] = useSound(
soundEnums[sfxFile].src,
{
autoplay: sfxAuto,
preload: true,
loop: false,
volume: masterVol*sfxVol,
id: sfxFile,
soundEnabled
},
);
const mute = (enable) => {
if(enable) {
setSFXAuto(false);
setAuto(true);
setSoundEnabled(true);
play(ambientFile);
}else{
setAuto(false);
setSFXAuto(false);
setSoundEnabled(false);
pause();
};
};
// rate may be adjusted from 0.5x -> 4x the original speed
const playbackSpeed = (rate) => {
setPlaybackRate(rate);
};
// ambient sound volume may be adjusted from 0 (muted) -> 1 (max)
const masterVolume = (vol) => {
setMasterVol(vol);
};
// play next after song ends - requires a void/while loop or event listener to execute the if statement logic with isPlaying.
// could also attempt to set up an event listenr with ambient sound and playNext values to begin testing for isPlaying in order to render next mp3 file.
const playNext = (file) => {
if(isPlaying) {
setNextFile(file);
}
};
// force ambient sound to play new file
const ambientSound = (file) => {
if(ambientFile!==file) {
setAmbientFile(file);
if(!soundEnabled){
return;
}
setAuto(true);
pause();
}else{
if(!soundEnabled){
return;
}
setAuto(true);
setAmbientFile(file);
stop();
play();
}
};
// force sfx to paly new sound
const sfxSound = (file) => {
if(!soundEnabled){
return;
}
setSFXAuto(true); // keeps buttons from triggering upon first click while muted when set to false
setAuto(true);
// if file is different than current sfxfile, unmount current sound
if(sfxFile!==file) {
sound.unload(sfxFile)
}
// play next sound
setSfxFile(file, nextOne(file));
};
// ambient sound volume may be adjusted from 0 (muted) -> 1 (max)
const ambientVolume = (vol) => {
setAmbientVol(vol);
};
// volume of sound effects may be adjusted from 0 (muted) -> 1 (max)
const sfxVolume = (vol) => {
setSfxVol(vol);
};
const nextOne = (file) => {
playSfx(file);
};
return { mute, playbackSpeed, masterVolume, playNext, ambientSound, sfxSound, ambientVolume, sfxVolume, soundEnabled, playbackRate, masterVol, nextFile, ambientFile, sfxFile, ambientVol, sfxVol };
};
SFX Button component
import React from 'react';
import SfxOptions from '../SfxOptions/index';
import ButtonGroup from 'react-bootstrap/ButtonGroup';
import { useSfx } from "../../components/SoundSuite/index";
import "./style.css"
export default function SfxButton() {
const sfx = useSfx();
const toggle = (event) => {
event.preventDefault();
event.stopPropagation();
if(event.target.id==='play'){
sfx.mute(true);
}else if(event.target.id==='mute'){
sfx.mute(false);
}
};
return (
<div className="sound-bar" value={sfx}>
{
!sfx.soundEnabled
?
(
<div className="row align-items-center phone-row">
<div className="col-sm-2 soundwave">
<button id='play' onClick={toggle}>
<svg width="1.5em" height="1.5em" viewBox="0 0 16 16" class="bi bi-soundwave sound-icon" fill="white" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M8.5 2a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-1 0v-11a.5.5 0 0 1 .5-.5zm-2 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5zm4 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5zm-6 1.5A.5.5 0 0 1 5 6v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm8 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm-10 1A.5.5 0 0 1 3 7v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5zm12 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5z"/>
</svg>
</button>
</div>
<div className="col-sm-10 game-title-phone">
AsheN Void
</div>
</div>
)
:
(
<ButtonGroup className="mr-2" aria-label="First group">
<button id='mute' onClick={toggle}>
<svg width="1.5em" height="1.5em" viewBox="0 0 16 16" class="bi bi-soundwave sound-icon" fill="white" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M8.5 2a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-1 0v-11a.5.5 0 0 1 .5-.5zm-2 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5zm4 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5zm-6 1.5A.5.5 0 0 1 5 6v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm8 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm-10 1A.5.5 0 0 1 3 7v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5zm12 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5z"/>
</svg>
</button>
<SfxOptions />
</ButtonGroup>
)
}
</div>
)
}
App component
import React from "react";
import { Router, Route, Switch} from "react-router-dom";
import UserAuth from "./userInterface/components/UserAuth/index"
import PrivateRoute from "./userInterface/components/PrivateRoute/index"
import PreventReverse from "./userInterface/components/PreventReverse/index";
import history from "./utils/history";
import Nav from "./userInterface/components/Nav";
import Wrapper from "./userInterface/components/Wrapper";
import Rules from "./userInterface/pages/Rules";
import SignUp from "./userInterface/pages/SignUp";
import LogIn from "./userInterface/pages/LogIn";
import GamePage from "./userInterface/pages/GamePage";
// import NoMatch from "./userInterface/pages/NoMatch";
import ContentWrapper from "./userInterface/components/Wrapper/ContentWrapper";
import GameContainer from "./userInterface/components/GameContainer/index"
import HomePage from "./userInterface/pages/HomePage";
import LoadingPage from "./userInterface/pages/LoadingPage"
import SoundSuite from "./userInterface/components/SoundSuite/index"
function App() {
return (
<UserAuth>
<SoundSuite>
<Router history={history}>
<div>
<Wrapper>
<Switch>
<PreventReverse path="/" exact>
<ContentWrapper>
<HomePage />
</ContentWrapper>
</PreventReverse>
<PreventReverse path="/rules">
<ContentWrapper>
<Rules />
</ContentWrapper>
</PreventReverse>
<PreventReverse path="/login">
<ContentWrapper>
<LogIn />
</ContentWrapper>
</PreventReverse>
<PreventReverse path="/signup">
<ContentWrapper>
<SignUp />
</ContentWrapper>
</PreventReverse>
<PrivateRoute path="/game">
<GamePage />
</PrivateRoute>
<PreventReverse path="/loading">
<LoadingPage />
</PreventReverse>
</Switch>
</Wrapper>
</div>
</Router>
</SoundSuite>
</UserAuth>
);
}
export default App;