单击另一个声音(按钮)时禁用声音

问题描述 投票:0回答:1

我刚刚开始学习 React.js,我正在尝试解决问题。我正在创建一个简单的冥想应用程序,其中每个按钮都有一个与之链接的声音。我创建了 4 个按钮(针对每个可用的声音),当单击它时,声音就会播放。但显然,当单击其他按钮时,之前的声音会在后台继续播放。我已经安装了react-player来更好地控制它,并且我尝试使用useState找到解决方案。

这是代码:

import PropTypes from "prop-types";
import { useState } from "react";
import ReactPlayer from "react-player";

function Buttons({ soundsColor }) {
  const [alternativeText, setAlternativeText] = useState(null);
  const [playing, setPlaying] = useState(true);

  function playSound(soundUrl) {
    if (!playing) {
      soundUrl.play();
    } else {
      setPlaying(!playing);
    }

    console.log("Playing sound:", soundUrl);
  }

  function handleText(id) {
    setAlternativeText(id);
  }

  return (
    <>
      {/* Destructuring per rilevare le properties dall'oggetto */}
      {soundsColor.map(({ name, color, description, sound, id }, index) => (
        <button
          id={`btn-${index}`}
          className="p-20 relative rounded-[10px] active:scale-[0.98] transition-all 150ms ease-in "
          style={{ backgroundColor: color }}
          key={id}
          onClick={() => {
            playSound(sound);
            handleText(id);
          }}
        >
          <ReactPlayer
            url={sound}
            playing={playing}
            width="100%"
            height="100%"
          />
          <div className="absolute top-20 w-3/4 left-3 text-left">
            <p className="text-[#000] font-bold font-sourceSans">{name}</p>
            <small className="font-sourceSans text-secondSubtext font-light ">
              {alternativeText === id ? "Playing..." : description}
            </small>
          </div>
        </button>
      ))}
    </>
  );
}

Buttons.propTypes = {
  soundsColor: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.number,
      name: PropTypes.string,
      description: PropTypes.string,
      color: PropTypes.string,
      sound: PropTypes.object,
    })
  ),
};

export function SoundScapes() {
  const soundsColor = [
    {
      id: 0,
      name: "Suikinkutsu",
      color: "#daff6f",
      description: "Water Drip Resonance",
      sound: new Audio("assets/Suikinkutsu.mp3"),
    },
    {
      id: 1,
      name: "Cicadas",
      color: "#A8AEEF",
      description: "Insect Chorus",
      sound: new Audio("./assets/Cicadas.mp3"),
    },
    {
      id: 2,
      name: "Temple Bells",
      color: "#A8AEEF",
      description: "Waterfall Roar",
      sound: new Audio("./assets/TempleBellSound.mp3"),
    },
    {
      id: 3,
      name: "Shomyo Falls",
      color: "#daff6f",
      description: "Bell Resonance",
      sound: new Audio("./assets/Waterfall.mp3"),
    },
  ];

  return (
    <div className="grid grid-cols-2 grid-rows-2 gap-3 p-6 mt-8">
      <Buttons soundsColor={soundsColor} />
    </div>
  );
}

我想创建一个:

const [playing,setPlaying] = useState(true)
但我想我必须将它“附加”到 playSound() 函数,这使得单击按钮时播放声音,并添加一段代码来在按下另一个按钮时更改状态,但这就是我的位置我被卡住了。

如果我的方法是错误的,我将不胜感激您帮助理解这一点。非常感谢你

reactjs react-hooks
1个回答
0
投票

首先,我真的建议你使用 TypeScript,而不是修改

PropTypes
。学习曲线并没有那么陡峭,它会在未来为你省去很多麻烦。 其次,我建议您在可以避免的情况下不要修改现有原型。几乎(?)总是有其他选择,否则事情就会变得混乱。

好吧,就以你的例子为例。您使用

useState

的预感是正确的,但您需要

memoize
声音,这样它们就不会一直重新创建。以下是有关如何创建、播放和停止声音的草图。我省略了按钮逻辑,因为您的问题看起来您知道如何处理这些问题。
export const Buttons: React.FC = () => {
  // There are many ways to get a structure from id to sound. You could even use a plain object of type { [key:number]: ReturnType<typeof getSoundScapes>[number]>}
  // The important thing is that you create this list with useMemo and an empty dependency array, so on subsequent renderings new sounds will be created
  const soundScapes = useMemo(() => getSoundScapes().reduce(
    (prev, curr) => prev.set(curr.id, curr), new Map<number, ReturnType<typeof getSoundScapes>[number]>,
  ), []);
  // Here we store whether (and which) sound is playing currently
  const [playing, setPlaying] = React.useState<number|null>(null);
  // onClick handler is for your button to use. It will stop the currently playing sound (if any) and start the new one.
  // Additionally, the new sound will be stored as the one currently being played.
  const onClick = (idToPlay:number)=>{
    if (playing != null){
      const currentSound = soundScapes.get(playing);
      if (currentSound != null){
        // this only works b/c the songs are memoized - otherwise you would try to pause a new Sound instance which isn't even running
        currentSound.sound.pause();
        currentSound.sound.currentTime=0;
      }
    }
    const toPlay =soundScapes.get(idToPlay);
    if (toPlay != null){
      toPlay.sound.play();
      setPlaying(idToPlay);
    }
  }

  return Array.of(soundScapes.values()).map( ... create the buttons similar to your example)
};

我使用了 TypeScript b/c,这对我来说更容易 - 你只需删除纯 JavaScript 代码的类型声明即可。

因此,有两个要点:

    memoize
  1. 不应在每个渲染周期重新创建的对象。就像声音一样。如果您在重新渲染时将点映射到新的
    Sound
    对象,您甚至无法停止当前正在运行的对象。
    只需使用 id 来引用播放的声音 - 更容易调试(b/c 您可以通过检查 
  2. number
  3. 来查看当前设置的歌曲),并且如果您开始喜欢的话,也不容易出错。
    
        
© www.soinside.com 2019 - 2024. All rights reserved.