Search code examples
htmlreactjshtml5-audiouse-state

HTML audio src for absolute URL does not work in React Component


I have a simple music web app that changes the music and image cover onClick, the problem is when I click on a song, the cover image change correctly but the audio source does not play (the src attribute set correctly but it is not playable)

import React, {useState, useEffect} from "react";

const App = () => {
  const [songs, setSongs] = useState([]);
  const [playing, setPlaying] = useState({});
  const server_url = `http://localhost:8765`;

  useEffect(() => {
    fetch(server_url)
    .then(res => res.json())
    .then(jsonRes => {
      
      setPlaying(jsonRes.songs[0]);
      setSongs(jsonRes.songs);
    });
    
  }, []);
  
  return (
    <div>
      <div>
        <img src={`${server_url}/music/imgs/${playing.img}`} width="200" height="200" />
        <audio controls>
          <source src={`${server_url}/music/mp3/${playing.mp3}`} type="audio/mpeg" />
          Your browser does not support the audio element.
        </audio>
      </div>
      <div>
        <ul>
        {
        songs.map(song => {
          return (
            <li key={song.id} onClick={() => setPlaying(song)}>{song.name}</li>
          )
        })
        }
        </ul>
      </div>
    </div>
  );
}

export default App;

The response from the localhost:8765 is like this:

   {"songs":
      [
{"id":1,"name":"Hello","singer":"Adele","img":"adele.png","type":"pop","mp3":"Adele.mp3"},
{"id":2,"name":"de una vez","singer":"Selena gomez","img":"selena.png","type":"pop","mp3":"Selena.mp3"},
{"id":3,"name":"Bayda","singer":"Navid","img":"navid.png","type":"pop","mp3":"Navid.mp3"},
{"id":4,"name":"Takin' Back My Love ","singer":"Enrique Iglesias","img":"enrique.png","type":"Pop","mp3":"Enrique.mp3"}
]
}

Solution

  • Your React container component will initially render with a broken image and audio player. The audio player doesn't recover so the second audio source element doesn't load correctly

    1. When the fetch data call takes time, you should prevent the undefined song from rendering or set a default song to play. Detailed answer: Add a new second return statement after the useEffect. When songs is an empty array (which you set as default on line 4) then return a spinner or return null (React will not render the component, it's basically skipped). OR secondary solution is to set better defaults on line 4 to be an image and song that has a valid path so it will load before the long running fetch data call.
    2. In the existing render, set the song to automatically play so the user doesn't need to press play. Detailed answer: Use the autoplay HTML attribute on the audio JSX element (ie <audio controls autoplay>)
    3. Add defensive code for your fetch data call. Add a try / catch inside your useEffect so server errors are caught and handled. To prevent your component from crashing