Why would my React app not play sound when deployed on Heroku?

587 Views Asked by At

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;
0

There are 0 best solutions below