Home > Software design >  React play different audio files at once - working with different refs
React play different audio files at once - working with different refs

Time:02-14

I'm creating a small app that plays an audio file and have some functionalities (loop, stop, mute). My goal is to add some more audio files that all should be played and stopped at once (one button to control all), but each will have a mute button, and I'm not sure what is the best practice to do so. I used useRef and thought maybe I need to set a refs array but how will I be able to start/stop them all at once, but still have the ability to control the mute separately?

This is my code so far. I guess I should split and have a different component for the audio sounds. Thanks for helping!

import React, {useState, useRef, useEffect} from 'react'
import {ImPlay2} from "react-icons/im"
import {ImStop} from "react-icons/im"
import styled from "styled-components"
import drums from '../loopfiles/DRUMS.mp3'
//import other audio files//

const AudioPlayer = () => {
    const [isPlaying, setIsPlaying] = useState(false);
    const [isLooping, setIsLooping] = useState(false);
    const [isOnMute, setIsOnMute] = useState(false);
    const audioRef = useRef(new Audio(drums));
  
    useEffect(() => {
        if (isOnMute) {
            audioRef.current.volume=0;
        } 
        else {
            audioRef.current.volume=1;
        }
      }, [isOnMute]);
    useEffect(() => {
        if (isPlaying) {
            audioRef.current.play();
        } else {
            audioRef.current.pause();
            audioRef.current.load();
        }
      }, [isPlaying]);
      useEffect(() => {
        if (isLooping) {
            audioRef.current.loop = true;
        } else {
            audioRef.current.loop = false;
        }
      }, [isLooping]);
  return (
    <div> 
        {!isPlaying ? (
        <button type="button" 
        className="play" 
        onClick={() => setIsPlaying(true)}> 
        <ImPlay2></ImPlay2> Play 
        </button>
        ) : (
        <button type="button"
        className="pause"
        onClick={() => setIsPlaying(false)}> 
        <ImStop></ImStop> Stop 
        </button> 
        )}
        <Flex>
            <Switcher selected={isLooping} />
            <Text
            onClick={() => setIsLooping(true)}>
            Loop
            </Text>
            <Text
            onClick={() => setIsLooping(false)}>
            Unloop
            </Text>
        </Flex>
        <Flex>
            <Switcher selected={isOnMute} />
            <Text
            onClick={() => setIsOnMute(true)}>
            Mute
            </Text>
            <Text
            onClick={() => setIsOnMute(false)}>
            UnMute
            </Text>
        </Flex>
    
  

      
            
    </div>
  )
}
const Flex = styled.div`
  margin-top: 5px;
  display: flex;
  align-items: center;
  border-radius: 2px;
  background: grey;
  height: 20px;
  width: 120px;
  position: relative;
  margin-bottom: 5px;
`;
const Switcher = styled.div`
  background: black;
  border-radius: 2px;
  height: 20px;
  line-height: 41px;
  width: 50%;
  cursor: pointer;
  position: absolute;
  transition: 0.5s;
  -webkit-transition: 0.5s;
  -moz-transition: 0.5s;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
  z-index: 1;
  left: ${({ selected }) =>
    selected === true ? "0px" : "60px"};
`;

const Text = styled.div`
  color: ${({ selected }) => (selected ? "black" : "white")};
  font-size: 13px;
  font-weight: 20;
  line-height: 4px;
  padding: 30;
  width: 50%;
  text-align: center;
  cursor: pointer;
`;
export default AudioPlayer

CodePudding user response:

If you would like to mute/unmute individual sounds, but play/pause all sounds together, then you will need to create a mute/unmute slider for each sound. I can think of a number of ways to do this. The "best choice" might depend upon the standards in the rest of the application, how many sounds you're importing, and whether they're likely to change.

Method 1: One way to do this would be creating one array containing isOnMute values for each sound and another array containing all refs, and then map(...) over each of the elements of the isOnMute array to create your sliders.

Method 2: Another way would be to have one array of objects containing all sounds, and then the ref and the isOnMute values could be stored within each object. You could map(...) over that to create your sliders as well.

Method 3: You could also create separate child components for each sound like you said, and then pass the mute property between the parent AudioPlayer and the child AudioChannel components.

Then anytime the play/pause button is clicked, you would need to update each of the refs in the array (via a forEach or each of the child components via toggling a single isPlaying property).

Regardless of which you choose, I also might like to recommend the use-sound npm package. It makes managing multiple sounds and their properties a little bit less cumbersome in my opinion, including the ability to play and pause with a single method call.

CodePudding user response:

Here is a snippet for you/ Also do not forget to use according ids instead of idx and idx2

const AudioList = () => {
  /* here populate the array in format: array of objects
  {
    drums: mp3file,
    isPlaying: boolean,
    setIsPlaying: boolean,
    isLooping: boolean,
    setIsLooping: boolean,
    isOnMute: boolean,
    setIsOnMute: boolean,
  }[]
   */

  const [audios, setAudios] = useState([
    { isPlaying: true, isOnMute: false, isLooping: true, drums: "Your mpr" },
  ]); // use initial audios

  return (
    <div>
      <button
        onClick={() => {
          // similar to start all, mute all, you have full controll logic over all elements
          // also you could implement add new audiofile, or delete, similar logic :)
          setAudios((audios) =>
            audios.map((audio) => ({ ...audio, isPlaying: false }))
          );
        }}
      >
        Stop all
      </button>
      <div>
        {audios.map((audio, idx) => (
          <AudioPlayer
            key={idx}
            {...audio}
            setIsPlaying={(val) =>
              setAudios((audios) =>
                audios.map((audio, idx2) =>
                  idx === idx2 ? { ...audio, isPlaying: val } : audio
                )
              )
            }
            // similar for setMute and setLopping function,
            // i think you can figure it out, it is just a snippet:)
          />
        ))}
      </div>
    </div>
  );
};

const AudioPlayer = ({
  drums,
  isPlaying,
  setIsPlaying,
  isLooping,
  setIsLooping,
  isOnMute,
  setIsOnMute,
}) => {
  const audioRef = useRef(new Audio(drums));

  // also you have full controll of element inside component
  useEffect(() => {
    if (isOnMute) {
      audioRef.current.volume = 0;
    } else {
      audioRef.current.volume = 1;
    }
  }, [isOnMute]);
  useEffect(() => {
    if (isPlaying) {
      audioRef.current.play();
    } else {
      audioRef.current.pause();
      audioRef.current.load();
    }
  }, [isPlaying]);
  useEffect(() => {
    if (isLooping) {
      audioRef.current.loop = true;
    } else {
      audioRef.current.loop = false;
    }
  }, [isLooping]);
  return (
    <div>
      {!isPlaying ? (
        <button
          type="button"
          className="play"
          onClick={() => setIsPlaying(true)}
        >
          <ImPlay2></ImPlay2> Play
        </button>
      ) : (
        <button
          type="button"
          className="pause"
          onClick={() => setIsPlaying(false)}
        >
          <ImStop></ImStop> Stop
        </button>
      )}
      <Flex>
        <Switcher selected={isLooping} />
        <Text onClick={() => setIsLooping(true)}>Loop</Text>
        <Text onClick={() => setIsLooping(false)}>Unloop</Text>
      </Flex>
      <Flex>
        <Switcher selected={isOnMute} />
        <Text onClick={() => setIsOnMute(true)}>Mute</Text>
        <Text onClick={() => setIsOnMute(false)}>UnMute</Text>
      </Flex>
    </div>
  );
};
  • Related