Search code examples
javascriptreactjstypescripthtml5-audiomedia

HTMLAudioElement pause not triggering in ReactJS


I am able to fetch and display audio information, and also to trigger playback but could not pause the media item by running the pause function. At least, it is not receiving an event in "onpause" and the audio keeps running.

Using ref should work as expected. The tested audio is served by https://arweave.net/, if that makes a difference.

Can you spot the issue?

export const Player = (): JSX.Element => {
  const [playingAudio, setPlayingAudio] = useState<boolean>(false);

  const { fetchMetadata, metadata } = useContract();

  const theme: Theme = useTheme();

  const audio = useRef<HTMLAudioElement | null>(null);

  useEffect(() => {
    const updateMetadata = async () => await fetchMetadata();
    updateMetadata();
    if (metadata) {
      audio.current = new Audio(metadata.animation_url);
      if (audio.current) {
        audio.current.onpause = () => setPlayingAudio(false);
        audio.current.onended = () => setPlayingAudio(false);
        audio.current.onplay = () => setPlayingAudio(true);
      }
    }
  }, [fetchMetadata, metadata]);

  const playAudio = async (): Promise<void> => await audio?.current?.play();

  const pauseAudio = (): void => audio?.current?.pause();

  return (
    <Card sx={{ width: '100%', maxWidth: '600px', display: 'flex', justifyContent: 'space-between', marginY: 3 }}>
      <Box sx={{ display: 'flex', flexDirection: 'column' }}>
        <CardContent sx={{ flex: '1 0 auto' }}>
          <Typography component="div" variant="h5">
            {metadata?.name}
          </Typography>
          <Typography variant="subtitle1" color="text.secondary" component="div">
            {metadata?.description}
          </Typography>
        </CardContent>
        <Box sx={{ display: 'flex', alignItems: 'center', pl: 1, pb: 1 }}>
          <IconButton aria-label="previous">
            {theme.direction === 'rtl' ? <SkipNextIcon /> : <SkipPreviousIcon />}
          </IconButton>
          {playingAudio ? (
            <IconButton aria-label="play/pause" onClick={pauseAudio}>
              <PauseCircle sx={{ height: 38, width: 38 }} />
            </IconButton>
          ) : (
            <IconButton aria-label="play/pause" onClick={playAudio}>
              <PlayArrowIcon sx={{ height: 38, width: 38 }} />
            </IconButton>
          )}
          <IconButton aria-label="next">
            {theme.direction === 'rtl' ? <SkipPreviousIcon /> : <SkipNextIcon />}
          </IconButton>
        </Box>
      </Box>
      <CardMedia
        component="img"
        sx={{ width: 200 }}
        image={metadata?.image}
        alt={metadata?.name}
      />
    </Card>
  );
};


Solution

  • As @morganney hinted, I should check the presence of the metadata, before updating it again (as it was in a loop), so now no unnecessary re-rendering is happening, and I can pause the audio.

    useEffect(() => {
        const updateMetadata = async () => await fetchMetadata();
        if (!metadata) updateMetadata();
        if (metadata) {
          audio.current = new Audio(metadata.animation_url);
          if (audio.current) {
            audio.current.onpause = () => setPlayingAudio(false);
            audio.current.onended = () => setPlayingAudio(false);
            audio.current.onplay = () => setPlayingAudio(true);
          }
        }
      }, [fetchMetadata, metadata]);
    

    (the rest of the code stays the same)