My-MC
@My-MC

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

要素をクリックしている間のみ再生しているようにしたい

解決したいこと

現在ある要素をクリックしている(mousedown)しているときのみWeb Audio APIを使って音を流すといった仕組みを作っています。そのなかでうまくoscillator がstopしないよう(コンソールのエラーから推測するにstartされてないと認識されてる感じがします)で音が出続けてしまいます。解決方法をご教授ください。

発生している問題・エラー

コンソールのエラーログ.
Uncaught DOMException: AudioScheduledSourceNode.stop: Can't call stop() without calling start()
    mouseUp page.tsx:23
    callCallback react-dom.development.js:19449
    invokeGuardedCallbackImpl react-dom.development.js:19498
    invokeGuardedCallback react-dom.development.js:19573
    invokeGuardedCallbackAndCatchFirstError react-dom.development.js:19587
    executeDispatch react-dom.development.js:30622
    processDispatchQueueItemsInOrder react-dom.development.js:30654
    processDispatchQueue react-dom.development.js:30667
    dispatchEventsForPlugins react-dom.development.js:30678
    dispatchEventForPluginEventSystem react-dom.development.js:30868
    batchedUpdates$1 react-dom.development.js:23765
    batchedUpdates react-dom.development.js:27584
    dispatchEventForPluginEventSystem react-dom.development.js:30867
    dispatchEvent react-dom.development.js:28640
    dispatchDiscreteEvent react-dom.development.js:28611
    addEventBubbleListener react-dom.development.js:28860
    addTrappedEventListener react-dom.development.js:30771
    listenToNativeEvent react-dom.development.js:30710
    listenToAllSupportedEvents react-dom.development.js:30721
    listenToAllSupportedEvents react-dom.development.js:30716
    createRoot$1 react-dom.development.js:36127
    createRoot react-dom.development.js:36582
    createRoot client.js:12
    hydrate app-index.js:239
    <anonymous> app-next-dev.js:9
    appBootstrap app-bootstrap.js:58
    loadScriptsInSequence app-bootstrap.js:23
    appBootstrap app-bootstrap.js:57
    <anonymous> app-next-dev.js:7
    NextJS 7
page.tsx:72

該当するソースコード

page.tsx
"use client";

import { useRef, useState } from "react";
import styles from "./Home.module.css";

const Home = () => {
  const audioContext = useRef(new AudioContext());

  const oscillator = audioContext.current.createOscillator();
  oscillator.type = "sine";
  oscillator.frequency.setValueAtTime(700, audioContext.current.currentTime);
  oscillator.connect(audioContext.current.destination);

  const [isTouching, setIsTouching] = useState(0);

  const mouseDown = () => {
    oscillator.start();
    console.debug("start");
    setIsTouching(1);
  };

  const mouseUp = () => {
    oscillator.stop();
    console.debug("stop");
    setIsTouching(0);
  };

  return (
    <main className={styles.main}>
      <div>
        <p>touching: {isTouching}</p>
      </div>
      <div
        onMouseDown={mouseDown}
        onMouseUp={mouseUp}
        className="box-border h-32 w-32 p-4 border-4"
        id="touching-box"
      />
    </main>
  );
};

export default Home;
Home.module.css
.main {
  user-select: none;
}

* 実際の配信されるCSSにはAutoprefixerが適用されています。

自分で試したこと

いっかいuseEffectで、eventlistnerも試しましたが、そちらでもエラーが同様にエラーが発生しました。

0

1Answer

ちょっとこんな感じではないかと思いますので、参考までに。
Web Audio APIのオシレーターは、一度 stop() が呼ばれるとそのオシレーターは使い終わったものとみなされ、再利用することはできません。したがって、mousedownstart() を呼び出し、mouseupstop() を呼び出すという処理を複数回行いたい場合、毎回新しいオシレーターを生成する必要があります。

参考コードはこんな感じ

import { useRef, useState } from "react";
import styles from "./Home.module.css";

const Home = () => {
  const audioContext = useRef(new AudioContext());

  // オシレーターの作成を関数に抽出します
  const createOscillator = () => {
    const oscillator = audioContext.current.createOscillator();
    oscillator.type = "sine";
    oscillator.frequency.setValueAtTime(700, audioContext.current.currentTime);
    oscillator.connect(audioContext.current.destination);
    return oscillator;
  }

  // 初期状態ではnullとしておき、mousedownイベントで作成します
  const [oscillator, setOscillator] = useState(null);

  const [isTouching, setIsTouching] = useState(0);

  const mouseDown = () => {
    const newOscillator = createOscillator();
    newOscillator.start();
    console.debug("start");
    setOscillator(newOscillator);  // 作成したオシレーターを状態として保持します
    setIsTouching(1);
  };

  const mouseUp = () => {
    if(oscillator) { // 万が一nullの場合を考慮
      oscillator.stop();
      console.debug("stop");
      setOscillator(null);  // オシレーターを停止したらnullにリセットします
      setIsTouching(0);
    }
  };

  return (
    <main className={styles.main}>
      <div>
        <p>touching: {isTouching}</p>
      </div>
      <div
        onMouseDown={mouseDown}
        onMouseUp={mouseUp}
        className="box-border h-32 w-32 p-4 border-4"
        id="touching-box"
      />
    </main>
  );
};

export default Home;

mousedown イベントが発生する度に新しいオシレーターが生成され、mouseup イベントでそのオシレーターが停止する、という流れを実現できるかと。

1Like

Comments

  1. @My-MC

    Questioner

    オシレータを再生成するようにしたところ動作するようになりました。オシレータをそのまま使えると思っていました。ありがとうございました。

  2. 良かったです。いいねもらえると嬉しがります!

Your answer might help someone💌