joshwcomeau / use-sound

A React Hook for playing sound effects

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Sound not playing when in useEffect()

DevinParentice opened this issue · comments

Hello, I am using this with socket.io and my code looks like this:

import moveAudioFile from "../assets/sounds/Move.mp3";
import captureAudioFile from "../assets/sounds/Capture.mp3";
import gameStartAudioFile from "../assets/sounds/GameStart.mp3";

export default function Example() {
        const [playMoveSound] = useSound(moveAudioFile);
	const [playCaptureSound] = useSound(captureAudioFile);
	const [playGameStartSound] = useSound(gameStartAudioFile);

        useEffect(() => {
                socket.on("opponentMoved", (moveMade) => {
			let move = null;
	                safeGameMutate((game) => {
				move = game.move({
				from: moveMade.move.sourceSquare,
				to: moveMade.move.targetSquare,
				promotion: "q", // always promote to a queen for example simplicity
			 });
			if (move && move.captured !== undefined) {
				 console.log("capture sound");
				 playCaptureSound();
			} else {
	                        console.log("move sound");
				playMoveSound();
			}
		  });
		setFen(game.fen());
		});
	}, []);
}

It is properly printing out "capture sound" and "move sound" to the console but no sound plays on either. Do you have any suggestions? Thanks!

Did you got any solution though?
Stuck at same place

UPDATE:
https://codesandbox.io/s/elegant-black-c0jr8?file=/src/PlayAudio.js

Check this code more easier

Stuck also here.

Same here.
Minimal code to reproduce:

const [playSound] = useSound('path/to/file.mp3')
useEffect(() => {
    playSound()
}, [])

Very weird issue: In a dev environment (next dev with HMR), sound doesn't play when the page loads initially, but on each subsequent refresh triggered by HMR, the sound plays.

useEffect(() => {
    playSound()
})

Works fine..

--Update--

I snapped and changed the code to new Audio('/path/to/file.mp3').play(). Works flawlessly 😄

facing same issue. apparently i doesnt work on mount. but below cases do work.

useEffect(() => { if (count === 3) play(); });
useEffect(() => { if (count === 2) play(); }, [count]);

useEffect(() => { if (!loading) play(); }, [loading]);

@CodingMeSwiftly OMG the new Audio thing worked. Thank you so much!

OP's problem is identical to mine. I would love to see a fix that would allow me to use use-sound. But this works!

That's because we don't know is our sound loaded or not. We need to get a flag that will show that. Something like:

const [play, isReady] = useSound('/sounds/notifications/dashboard-donation-notification.mp3');

if (isReady === false) {
  return 'We are loading now';
}

return 'Play this!';

Example I wrote in 30 seconds:

<button id="btn">play</button>
const button = document.getElementById('btn');

let isLoaded = false;

const audio = new Audio('https://notificationsounds.com/storage/sounds/file-sounds-1217-relax.mp3');

audio.addEventListener('canplaythrough', () => {
   console.log('Audio is loaded');
  
  isLoaded = true;
});

button.onclick = () => {
  if (isLoaded === false) {
    return alert('Please wait');
  }
  
  audio.play();
};

That's easy. Why didn't it implemented yet?

Damnnn. So now I gotta write my own implementation due to this.

I spent 10 minutes writing it, so everyone who will use it must pay me $30. I'm waiting dude. Yes you!

import { useEffect, useState, useCallback } from 'react';

export const useSound = (soundPath: string) => {
  const [audio, setAudio] = useState<null | HTMLAudioElement>(null);
  const [isReady, setIsReady] = useState(false);

  const play = useCallback(() => {
    audio?.play();
  }, [audio]);

  // Due to no window
  useEffect(() => {
    setAudio(new Audio(soundPath));
  }, [soundPath]);

  useEffect(() => {
    if (audio === null) {
      return;
    }

    audio.addEventListener('canplaythrough', () => {
      setIsReady(true);
    });

    return () => {
      setIsReady(false);
    };
  }, [audio]);

  return { play, isReady };
};
  const { isReady, play } = useSound(pathToSoundFile)
  
  useEffect(() => {
    if (isReady === false) {
      return;
    }
    console.log('isReady', isReady);
    

    // Don't forget to interact with the document first https://goo.gl/xX8pDD
    setTimeout(() => {
      play();
      console.log('playing');
    }, 1000);
  }, [isReady, play]);

Any news on how this could be fixed with this library? While @IvanAdmaers approach seems to work in general, but the sound file will apparently always be loaded again and also has a delay before playing. So this isn't feasible for something where the sound has to be immediate, like a game. I have to trigger the sound via useEffect, because my game state determines if and when the sound should be played, so that it is in sync between multiple clients.
After the first hot reload of my dev server this library works and the sounds are snappy and immediate.
So I would really like to know if there is a solution making it work inside useEffect :/

Hi, @on3iro. In my example your sound loads only once. It will be reloaded only if you change a song url. Of course you cannot play your sound immediately either in my example or using this library because loading takes some time anyways.

Hi, @on3iro. In my example your sound loads only once. It will be reloaded only if you change a song url. Of course you cannot play your sound immediately either in my example or using this library because loading takes some time anyways.

Thanks for the response. Hm, weird - maybe I am still doing something wrong, then. However there seems definitely to be a difference between the libraries approach and your approach (or its my fault...), because even after the file has been loaded it will only play with a slight delay. When use-sound kicks in after I trigger a HMR-reload the sound playback feels way snappier and on time. (every single time the sound effect is triggered afterwards) - to be clear I am not mixing these two approaches in my code, just comparing them to each other one after another.

Ok, tried again and I must've done something wrong the first time - the audio file is now only loaded once, exactly as you said @IvanAdmaers :)
However the slight delay on each playback still remains.
Another issue I currently have is, that I need to be able to play that sound multiple times in quick succession, without knowing how many times exactly 🤔

Actually I can't say anything about delay because in my case there is no need in playing songs it immediately. So, the only one thing that will help us is practice. Good luck with that, @on3iro)

Actually I can't say anything about delay because in my case there is no need in playing songs it immediately. So, the only one thing that will help us is practice. Good luck with that, @on3iro)

Thanks :)

commented

i'm facing this issue as well :(

I used duration from the exposedData object

const [play, { duration }] = useSound("/sounds/beep.mp3", { volume: 0.8 });

  useEffect(() => {
    if (duration) {
      play();
    }
  }, [duration]);

In the README you will find:

duration is the length of the sample, in milliseconds. It will be null until the sample has been loaded