首页 > 解决方案 > React HTMLAudioElement 不会暂停

问题描述

我在 React 中有一个组件,它接受一个 URL 并显示一个让用户停止/暂停音频文件的按钮。该组件在页面上多次使用。

该按钮可以很好地启动音频,但它audio.pause()不会停止流。

我正在使用 Next.js,但是我正在像这样导入组件:

const HistoryRow = dynamic(() => import('../components/HistoryRow'), { ssr: false });

有人知道为什么这不会停止流吗?我可以确认它正在输入 if 语句就好了。

import { useState } from 'react';
import m from 'moment';
import path from 'path';

function HistoryRow({ data }) {
  const [playing, setPlaying] = useState(false);
  const audio = new Audio(`${window.location.origin}/audio-responses/${data.timestamp}.wav`);

  audio.onended = function () {
    setPlaying(false);
  };

  audio.onpause = function () {
    setPlaying(false);
  };

  audio.onplay = function () {
    setPlaying(true);
  };

  audio.onerror = function () {
    setPlaying(false);
  };

  function execute() {
    if (playing) {
      audio.pause();
    } else {
      audio.play();
    }
  }

  const stopSvg = (
    <svg> /** svg data ** / </svg>
  );

  const playSvg = (
    <svg> /** svg data ** / </svg>
  );

  return (
      <div className="text-base flex leading-5 font-medium text-blue-600 truncate" onClick={() => execute()}>
        {data.type === 'command' ? (playing ? stopSvg : playSvg) : null}
      </div>  
  );
}

export default HistoryRow;

标签: javascriptreactjsaudionext.js

解决方案


每当您声明更改时,都会重新渲染整个组件。这意味着组件中的每个变量都被重写。因此,您创建一个Audio实例,然后开始播放,组件重新渲染并Audio创建一个新实例,因此您失去了与前一个实例的绑定。

您可以使用该useRef钩子创建对Audio在组件的整个生命周期内持续存在的实例的引用。所以它永远不会改变,除非你明确告诉它。current您可以使用返回useRef值的属性访问实例。

由于 React 是状态驱动的,我建议使用useEffect钩子来监听状态的变化,playing并根据状态的值开始或停止播放playing,这是你目前拥有的另一种方式。

function HistoryRow({ data }) {
  const [playing, setPlaying] = useState(false);
  const [hasError, setHasError] = useState(false);
  const audio = useRef(new Audio(`${window.location.origin}/audio-responses/${data.timestamp}.wav`));

  audio.current.onended = function () {
    setPlaying(false);
  };

  audio.current.onplay = function () {
    setHasError(false);
  };

  const handleClick = () => {
    setPlaying(playing => !playing);
  });

  useEffect(() => {
    if (playing) {
      audio.current.play().then(() => {
        // Audio is playing.
      }).catch(error => {
        setHasError(true);
      });
    } else if (!hasError) {
      audio.current.pause();
    }
  }, [playing, hasError]);

  const stopSvg = (
    <svg> /* svg data */ </svg>
  );

  const playSvg = (
    <svg> /* svg data */ </svg>
  );

  return (
      <div 
        className="text-base flex leading-5 font-medium text-blue-600 truncate" 
        onClick={handleClick}>
        {data.type === 'command' ? (playing ? stopSvg : playSvg) : null}
      </div>  
  );
}

推荐阅读