React 一次播放不同的音频文件 - 使用不同的 refs

React play different audio files at once - working with different refs

我正在创建一个播放音频文件并具有一些功能(循环、停止、静音)的小应用程序。我的目标是添加更多的音频文件,所有这些文件都应该同时播放和停止(一个按钮控制所有文件),但每个文件都有一个静音按钮,我不确定这样做的最佳做法是什么。我使用了 useRef 并认为我可能需要设置一个 refs 数组,但我如何能够一次 start/stop 它们,但仍然能够单独控制静音?

到目前为止,这是我的代码。我想我应该拆分并为音频声音设置不同的组件。感谢您的帮助!

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

如果您想要 mute/unmute 单独的声音,但 play/pause 所有声音在一起,那么您需要为每个声音创建一个 mute/unmute 滑块。我可以想出很多方法来做到这一点。 “最佳选择”可能取决于应用程序其余部分的标准、您要导入的声音数量以及它们是否可能发生变化。

方法 1: 一种方法是创建一个包含每个声音的 isOnMute 值的数组和另一个包含所有引用的数组,然后 map(...)isOnMute 数组的每个元素上创建滑块。

方法 2:另一种方法是让一个对象数组包含所有声音,然后可以将 ref 和 isOnMute 值存储在每个对象中.您也可以 map(...) 创建滑块。

方法三:你也可以像你说的那样为每个声音创建单独的子组件,然后在父AudioPlayer和子AudioChannel之间传递静音属性组件。

然后只要单击 play/pause 按钮,您就需要更新数组中的每个引用(通过 forEach 或通过切换单个 [=16] 来更新每个子组件=] 属性).

不管你选哪个,我也不妨推荐一下use-sound npm package。在我看来,它使管理多个声音及其属性变得不那么麻烦,包括通过单个方法调用播放和暂停的能力。

这是给你的一个片段/ 也不要忘记使用 ids 而不是 idxidx2

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>
  );
};

我更改了以下内容:

我添加了 Audios.js 包含:

const audios = () => {
    return [
        {
            color: 'lightgreen',
            isOnMute: false,
            audio: drums,
            title: 'Drums'
        }, ...

AudioList.js:

const AudioList = ({isPlaying, isLooping}) => {
    const [audioToPlay, setAudioToPlay] = useState();
    useEffect(()=> {
        setAudioToPlay(audios())
    },[]) ....//and mapped through <AudioItem>:

AudioItem.js:

  const AudioItem = ({audio, isPlaying, isLooping}) => {
  const [isOnMute, setIsOnMute] = useState(false);
  const audioRef = useRef(null);
  useEffect(() => {
    if (isLooping) {
        audioRef.current.loop = true;
    } else {.... //other functionality

添加了一个progressBar.js:

const ProgressBar = ({isPlaying}) => {
const [completed, setCompleted] = useState({
    count: 0
});const intervalId = useRef(null)

useEffect(() => {...
    

ControlPanel.js:

const ControlPanel = ({
setIsLooping, isLooping, isPlaying, setIsPlaying}) => {
      return ( 
        <div> 
        <PlayButton> //....

和Home.js包含控制面板、AudioList、ProgressBar:

const Home = () => {
  const [isPlaying, setIsPlaying] = useState(false);
  const [isLooping, setIsLooping] = useState(false);
  
  return (
    <div>
        <ControlPanel
         setIsLooping={setIsLooping} //....