useState() вызывает неожиданное поведение с аудиообъектом

#reactjs #react-hooks

#reactjs #реагирование-перехваты

Вопрос:

Я пытаюсь создать базовый аудиоплеер и отслеживать состояние проигрывателя с помощью перехвата состояния.

Следующий код создает компонент со следующим поведением:

  1. При первом нажатии кнопки воспроизводится звук
  2. При каждом нажатии кнопки текст переключается правильно
  3. Когда состояние воспроизводится, вызов player.pause() ничего не делает
  4. Когда состояние не воспроизводится, звук продолжается, и вызов player.play() приводит к запуску второго уровня звука сверху
     import React, {useState} from 'react'
    
    function InlinePlayer ({audio}) {
      const [playing, setPlaying] = useState(false)
    
      const player = new Audio(audio.asset.url)
    
      function togglePlay () {
        playing ? player.pause() : player.play()
        setPlaying(!playing)
      }
    
      return <>
        <button onClick={() => togglePlay()}>
          {playing ? 'Stop' : 'Play' }
        </button>
      </>
    }
    
    export default InlinePlayer
 

Если я вообще не использую перехват состояния, я могу остановить и запустить звук без проблем.

Одна странная вещь заключается в том, что даже если я вызываю play() безоговорочно, а затем вызываю перехват состояния, последующие вызовы pause() также больше не работают. Это как если бы вызов setPlaying() разрушал соединение с объектом player. Если я закомментирую строку setPlaying, это сработает.

   function play () {
    player.play()
    setPlaying(true)
  }

  function pause () {
    player.pause()
    setPlaying(false)
  }
 

Сначала я думал, что проблема заключалась в том, что состояние устанавливалось асинхронно, поэтому виновником было условное воспроизведение. Что, кажется, здесь?

Ответ №1:

player.pause() и player.play() похожи на побочные эффекты. Используйте useEffect с правильной функцией очистки, чтобы вы могли переключать play/pause :

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

function InlinePlayer({ audio }) {
  const [playing, setPlaying] = useState(false);

  const player = new Audio(
    "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
  );

  useEffect(() => {
    playing ? player.play() : player.pause();

    // This is cleanup of the effect
    return () => player.pause();

  }, [playing]);
   // ^ Run the effect every time the `playing` is changed

  function togglePlay() {
    // Using the callback version of `setState` so you always
    // toggle based on the latest state
    setPlaying(s => !s);
  }

  return (
    <>
      <button onClick={() => togglePlay()}>{playing ? "Stop" : "Play"}</button>
    </>
  );
}
 

Комментарии:

1. Замечательно, у меня было ощущение, что я должен использовать useEffect, но не мог понять, как. Спасибо!